From 7f5ca536da4599e4ad2d22ba05af0e33bf107fee Mon Sep 17 00:00:00 2001 From: Clay Downs Date: Thu, 23 Jun 2022 07:53:14 -0700 Subject: [PATCH 001/369] Adds default roles for Snapshot Management plugin (#1897) Signed-off-by: Clay Downs --- securityconfig/roles.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/securityconfig/roles.yml b/securityconfig/roles.yml index 09c943dace..721349c086 100644 --- a/securityconfig/roles.yml +++ b/securityconfig/roles.yml @@ -227,3 +227,22 @@ notifications_read_access: - 'cluster:admin/opensearch/notifications/configs/get' - 'cluster:admin/opensearch/notifications/features' - 'cluster:admin/opensearch/notifications/channels/get' + +# Allows users to use all snapshot management functionality +snapshot_management_full_access: + reserved: true + cluster_permissions: + - 'cluster:admin/opensearch/snapshot_management/*' + - 'cluster:admin/opensearch/notifications/feature/publish' + - 'cluster:admin/repository/*' + - 'cluster:admin/snapshot/*' + +# Allows users to see snapshots, repositories, and snapshot management policies +snapshot_management_read_access: + reserved: true + cluster_permissions: + - 'cluster:admin/opensearch/snapshot_management/policy/get' + - 'cluster:admin/opensearch/snapshot_management/policy/search' + - 'cluster:admin/opensearch/snapshot_management/policy/explain' + - 'cluster:admin/repository/get' + - 'cluster:admin/snapshot/get' From 03a224d16045a8f561e2d7d84b8765c95309524c Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 24 Jun 2022 12:22:44 -0500 Subject: [PATCH 002/369] Switch to standard OpenSearch gradle build (#1888) * Switch to standard OpenSearch gradle build * Rewrote build.gradle to follow OpenSearch plugin standards * Enabled license headers check on the repository * Did not enable several new repository checks * Added maven publishing for security * Removed excess forced dependency resolutions * Rebuilt projects dependencies, release plugin binary has the exact same dependencies and versions * Converted dependencies into runtime dependencies to avoid use during coding * Converted dependencies from project wide to test only dependencies * jackson-databind version comes from OpenSearch * Replaced handmade build manifest with git properties to automated version used by OpenSearch * Added license headers to files that were missing them * Checkstyle improvements * Disable checkstyle checks that are not errors * Moved checkstyle file into directory off of the project root * Moved standard configuration directory * Using default test running from OpenSearch * Switched to RandomizedTest as the base test class * Parameterized test runner cannot be used with RandomizedTest converted test functionality * Fixed tests resource issues, added new fields to make this consistent in the codebase * Fixed issue with leaky environment variable for security root directory, refactored usage * Removed unneeded setDefaultUncaughtExceptionHandler in tests * Fixed issues with deprecated internal reflection from Mockito * Disabled ThreadLeak detection as it is catching real issues that are mitigated by retries * Initial pass on non-inclusive terminology, commented out exclusions * Removed test dependency on scala * Removed test dependency on legacy xmlsecurity library Signed-off-by: Peter Nied --- .github/workflows/ci.yml | 10 +- build.gradle | 630 +++++++------- .../checkstyle => checkstyle}/sun_checks.xml | 12 +- {securityconfig => config}/action_groups.yml | 0 {securityconfig => config}/allowlist.yml | 0 {securityconfig => config}/audit.yml | 0 {securityconfig => config}/config.yml | 0 {securityconfig => config}/internal_users.yml | 0 {securityconfig => config}/nodes_dn.yml | 0 .../opensearch.yml.example | 0 {securityconfig => config}/roles.yml | 0 {securityconfig => config}/roles_mapping.yml | 0 {securityconfig => config}/tenants.yml | 0 {securityconfig => config}/whitelist.yml | 0 .../dlic/rest/api/MigrateApiAction.java | 2 +- .../dlic/rest/api/WhitelistApiAction.java | 1 - .../securityconf/DynamicConfigFactory.java | 1 - .../security/ssl/util/CertFileProps.java | 11 + .../security/ssl/util/CertFromFile.java | 11 + .../security/ssl/util/CertFromKeystore.java | 11 + .../security/ssl/util/CertFromTruststore.java | 11 + .../ssl/util/CertificateValidator.java | 11 + .../security/ssl/util/KeystoreProps.java | 11 + .../security/tools/SecurityAdmin.java | 2 +- .../http/jwt/HTTPJwtAuthenticatorTest.java | 6 - .../auth/http/saml/MockSamlIdpServer.java | 4 +- .../AdvancedSecurityMigrationTests.java | 5 +- .../org/opensearch/security/ConfigTests.java | 19 +- .../InitializationIntegrationTests.java | 12 +- .../opensearch/security/IntegrationTests.java | 13 +- .../security/PrivilegesEvaluationTest.java | 11 + .../security/RolesValidationIntegTest.java | 11 + .../security/SecurityAdminTests.java | 55 +- .../security/SnapshotRestoreTests.java | 19 +- .../org/opensearch/security/TracingTests.java | 22 - .../TransportUserInjectorIntegTest.java | 11 + .../compliance/ComplianceAuditlogTest.java | 11 +- .../security/auditlog/impl/TracingTests.java | 24 - .../security/auditlog/sink/KafkaSinkTest.java | 81 +- .../security/auth/UserInjectorTest.java | 11 + ...ossClusterMinimalRoundtripSearchTests.java | 17 + .../ccstest/CrossClusterSearchTests.java | 131 ++- ...ossClusterMinimalRoundtripSearchTests.java | 17 + .../dlsfls/DlsFlsCrossClusterSearchTest.java | 20 +- .../dlic/rest/api/AccountApiTest.java | 25 +- .../dlic/rest/api/ActionGroupsApiTest.java | 21 +- .../dlic/rest/api/AuditApiActionTest.java | 24 +- .../rest/api/DashboardsInfoActionTest.java | 21 +- .../dlic/rest/api/FlushCacheApiTest.java | 96 +-- .../rest/api/GetConfigurationApiTest.java | 128 ++- .../dlic/rest/api/IndexMissingTest.java | 158 ++-- .../dlic/rest/api/NodesDnApiTest.java | 22 +- .../dlic/rest/api/RoleBasedAccessTest.java | 481 ++++++----- .../security/dlic/rest/api/RolesApiTest.java | 30 +- .../dlic/rest/api/RolesMappingApiTest.java | 793 +++++++++--------- .../dlic/rest/api/SecurityApiAccessTest.java | 77 +- .../dlic/rest/api/SecurityConfigApiTest.java | 21 +- .../rest/api/SecurityHealthActionTest.java | 20 +- .../dlic/rest/api/SecurityInfoActionTest.java | 20 +- .../dlic/rest/api/TenantInfoActionTest.java | 36 +- .../security/dlic/rest/api/UserApiTest.java | 21 +- .../dlic/rest/api/WhitelistApiTest.java | 22 +- .../api/legacy/LegacyAccountApiTests.java | 23 + .../legacy/LegacyActionGroupsApiTests.java | 23 + .../api/legacy/LegacyAuditApiActionTests.java | 23 + .../LegacyDashboardsInfoActionTests.java | 23 + .../api/legacy/LegacyFlushCacheApiTests.java | 23 + .../LegacyGetConfigurationApiTests.java | 23 + .../api/legacy/LegacyIndexMissingTests.java | 23 + .../api/legacy/LegacyNodesDnApiTests.java | 23 + .../legacy/LegacyRoleBasedAccessTests.java | 23 + .../rest/api/legacy/LegacyRolesApiTests.java | 23 + .../legacy/LegacyRolesMappingApiTests.java | 23 + .../legacy/LegacySecurityApiAccessTests.java | 23 + .../legacy/LegacySecurityConfigApiTests.java | 23 + .../LegacySecurityHealthActionTests.java | 23 + .../legacy/LegacySecurityInfoActionTests.java | 23 + .../legacy/LegacyTenantInfoActionTests.java | 23 + .../rest/api/legacy/LegacyUserApiTests.java | 23 + .../api/legacy/LegacyWhitelistApiTests.java | 23 + .../security/filter/SecurityFilterTest.java | 14 +- .../securityconf/impl/v6/ConfigV6Test.java | 11 + .../securityconf/impl/v7/ConfigV7Test.java | 11 + .../ssl/SecuritySSLCertsInfoActionTests.java | 39 +- .../security/ssl/util/CertFromFileTests.java | 11 + .../ssl/util/CertFromKeystoreTests.java | 11 + .../ssl/util/CertFromTruststoreTests.java | 11 + .../ssl/util/SSLConnectionTestUtilTests.java | 4 +- .../test/AbstractSecurityUnitTest.java | 12 +- .../security/test/SingleClusterTest.java | 5 + .../test/helper/cluster/ClusterHelper.java | 20 +- 91 files changed, 2107 insertions(+), 1696 deletions(-) rename {config/checkstyle => checkstyle}/sun_checks.xml (94%) rename {securityconfig => config}/action_groups.yml (100%) rename {securityconfig => config}/allowlist.yml (100%) rename {securityconfig => config}/audit.yml (100%) rename {securityconfig => config}/config.yml (100%) rename {securityconfig => config}/internal_users.yml (100%) rename {securityconfig => config}/nodes_dn.yml (100%) rename {securityconfig => config}/opensearch.yml.example (100%) rename {securityconfig => config}/roles.yml (100%) rename {securityconfig => config}/roles_mapping.yml (100%) rename {securityconfig => config}/tenants.yml (100%) rename {securityconfig => config}/whitelist.yml (100%) create mode 100644 src/test/java/org/opensearch/security/ccstest/CrossClusterMinimalRoundtripSearchTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterMinimalRoundtripSearchTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAccountApiTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyActionGroupsApiTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAuditApiActionTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyDashboardsInfoActionTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyFlushCacheApiTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyGetConfigurationApiTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyIndexMissingTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyNodesDnApiTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRoleBasedAccessTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesApiTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesMappingApiTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityApiAccessTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityConfigApiTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityHealthActionTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityInfoActionTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyTenantInfoActionTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyUserApiTests.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyWhitelistApiTests.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5993bc311..d8972ac82b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,14 +117,14 @@ jobs: 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 && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip - - run: ./gradlew clean assemble -Dbuild.snapshot=false && test -s ./build/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.jar + - run: ./gradlew clean assemble -Dbuild.snapshot=false && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip - - run: ./gradlew clean assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.jar + - run: ./gradlew clean assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip - - run: ./gradlew clean assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.jar + - run: ./gradlew clean assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip - name: List files in the build directory if there was an error - run: ls -al ./build/ + run: ls -al ./build/distributions/ if: failure() diff --git a/build.gradle b/build.gradle index 5a66d36b6d..7d2526a0ac 100644 --- a/build.gradle +++ b/build.gradle @@ -9,79 +9,221 @@ * GitHub history for details. */ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License 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. - */ +buildscript { + ext { + opensearch_version = System.getProperty("opensearch.version", "2.1.0-SNAPSHOT") + isSnapshot = "true" == System.getProperty("build.snapshot", "true") + buildVersionQualifier = System.getProperty("build.version_qualifier", "") + + // 2.0.0-rc1-SNAPSHOT -> 2.0.0.0-rc1-SNAPSHOT + version_tokens = opensearch_version.tokenize('-') + opensearch_build = version_tokens[0] + '.0' + if (buildVersionQualifier) { + opensearch_build += "-${buildVersionQualifier}" + } + if (isSnapshot) { + opensearch_build += "-SNAPSHOT" + } + } + + repositories { + mavenCentral() + mavenLocal() + maven { url "https://plugins.gradle.org/m2/" } + maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } + maven { url "https://d1nvenhzbhpy0q.cloudfront.net/snapshots/lucene/" } + } + + dependencies { + classpath "org.opensearch.gradle:build-tools:${opensearch_version}" + } +} plugins { id 'java' - id 'maven-publish' + id 'idea' id 'jacoco' + id 'maven-publish' + id 'com.diffplug.spotless' version '5.11.0' id 'checkstyle' - id "com.gorylenko.gradle-git-properties" version "2.3.2" - id 'org.gradle.crypto.checksum' version '1.1.0' + id 'nebula.ospackage' version "8.3.0" + id "org.gradle.test-retry" version "1.3.1" +} - // Plugin prints gradle task graph, use following command: ./gradlew tiTree build - id 'org.barfuin.gradle.taskinfo' version '1.0.5' +allprojects { + group = "org.opensearch" + version = opensearch_build +} - 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.diffplug.spotless" version "6.5.0" +apply plugin: 'opensearch.opensearchplugin' +apply plugin: 'opensearch.pluginzip' + +licenseFile = rootProject.file('LICENSE.txt') +noticeFile = rootProject.file('NOTICE.txt') + +spotless { + java { + // note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports + importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') + } +} + +java.sourceCompatibility = JavaVersion.VERSION_11 +java.targetCompatibility = JavaVersion.VERSION_11 + +licenseHeaders.enabled = true + +// The following check that have never be enabled in security +dependencyLicenses.enabled = false +thirdPartyAudit.enabled = false +loggerUsageCheck.enabled = false +forbiddenApisMain.enabled = false +forbiddenApisTest.enabled = false +filepermissions.enabled = false +forbiddenPatterns.enabled = false +testingConventions.enabled = false +// Conflicts between runtime kafka-clients:3.0.1 & testRuntime kafka-clients:3.0.1:test +jarHell.enabled = false + +test { + include '**/*.class' + maxParallelForks = 8 + jvmArgs += "-Xmx3072m" + if (JavaVersion.current() > JavaVersion.VERSION_1_8) { + jvmArgs += "--add-opens=java.base/java.io=ALL-UNNAMED" + } + retry { + failOnPassedAfterRetry = false + maxRetries = 5 + } + jacoco { + excludes = [ + "com.sun.jndi.dns.*", + "com.sun.security.sasl.gsskerb.*", + "java.sql.*", + "javax.script.*", + "org.jcp.xml.dsig.internal.dom.*", + "sun.nio.cs.ext.*", + "sun.security.ec.*", + "sun.security.jgss.*", + "sun.security.pkcs11.*", + "sun.security.smartcardio.*", + "sun.util.resources.provider.*" + ] + } } -import org.gradle.crypto.checksum.Checksum -import java.text.SimpleDateFormat +task copyExtraTestResources(dependsOn: testClasses) { + copy { + from 'src/test/resources' + into 'build/testrun/test/src/test/resources' + } +} +tasks.test.dependsOn(copyExtraTestResources) + +jacoco { + reportsDirectory = file("$buildDir/reports/jacoco") +} + +jacocoTestReport { + reports { + xml.required = true + } +} + +checkstyle { + configFile file("checkstyle/sun_checks.xml") +} + +opensearchplugin { + name 'opensearch-security' + description 'Provide access control related features for OpenSearch' + classname 'org.opensearch.security.OpenSearchSecurityPlugin' +} + +// This requires an additional Jar not published as part of build-tools +loggerUsageCheck.enabled = false + +// No need to validate pom, as we do not upload to maven/sonatype +validateNebulaPom.enabled = false + +publishing { + publications { + pluginZip(MavenPublication) { publication -> + pom { + name = "opensearch-security" + description = "Provide access control related features for OpenSearch" + licenses { + license { + name = "The Apache License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + name = "OpenSearch" + url = "https://github.com/opensearch-project/security" + } + } + } + } + } +} repositories { - mavenLocal() - maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } mavenCentral() + mavenLocal() maven { url "https://plugins.gradle.org/m2/" } + maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } maven { url "https://d1nvenhzbhpy0q.cloudfront.net/snapshots/lucene/" } } -ext { - isSnapshot = "true" == System.getProperty("build.snapshot", "true") - opensearch_version = System.getProperty("opensearch.version", "2.1.0-SNAPSHOT") - buildVersionQualifier = System.getProperty("build.version_qualifier", "") - version_tokens = opensearch_version.tokenize('-') - opensearch_build = version_tokens[0] + '.0' - if (buildVersionQualifier) { - opensearch_build += "-${buildVersionQualifier}" - opensearch_build_nosnapshot = opensearch_build +tasks.withType(Checkstyle) { + showViolations true + reports { + ignoreFailures = false } - if (isSnapshot) { - opensearch_build += "-SNAPSHOT" +} + +tasks.withType(JavaCompile) { + configure(options) { + options.encoding = 'UTF-8' + options.compilerArgs << '-Xlint:removal' << '-Werror' + } +} + +tasks.test.finalizedBy(jacocoTestReport) // report is always generated after tests run +tasks.jacocoTestReport.dependsOn(test) // tests are required to run before generating the report + + +allprojects { + tasks.withType(Javadoc).all { enabled = false } +} + +bundlePlugin { + from('plugin-security.policy') + from('config') { + into 'config' + } + from('tools') { + into 'tools' } } configurations.all { resolutionStrategy { force 'commons-codec:commons-codec:1.14' - force 'org.apache.santuario:xmlsec:2.2.3' - force 'org.cryptacular:cryptacular:1.2.4' - force 'net.minidev:json-smart:2.4.7' - force 'commons-cli:commons-cli:1.3.1' - force 'org.apache.httpcomponents:httpcore:4.4.12' - force "org.apache.commons:commons-lang3:3.4" - force "org.springframework:spring-core:5.3.20" - force "com.google.guava:guava:30.0-jre" + force 'org.slf4j:slf4j-api:1.7.30' + force 'org.scala-lang:scala-library:2.13.8' + force 'commons-io:commons-io:2.11.0' + force "com.fasterxml.jackson:jackson-bom:${versions.jackson}" + force "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" + force "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:${versions.jackson}" + force "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" + force 'io.netty:netty-buffer:4.1.73.Final' + force 'io.netty:netty-common:4.1.73.Final' + force 'io.netty:netty-handler:4.1.73.Final' + force 'io.netty:netty-transport:4.1.73.Final' } } @@ -96,22 +238,76 @@ dependencies { implementation 'org.ldaptive:ldaptive:1.2.3' implementation 'org.apache.httpcomponents:httpclient-cache:4.5.13' implementation 'io.jsonwebtoken:jjwt-api:0.10.8' - implementation("org.apache.cxf:cxf-rt-rs-security-jose:3.4.5") { + implementation('org.apache.cxf:cxf-rt-rs-security-jose:3.4.5') { exclude(group: 'jakarta.activation', module: 'jakarta.activation-api') } implementation 'com.github.wnameless:json-flattener:0.5.0' implementation 'com.flipkart.zjsonpatch:zjsonpatch:0.4.4' - implementation 'org.apache.kafka:kafka-clients:3.0.0' + implementation 'org.apache.kafka:kafka-clients:3.0.1' implementation 'com.onelogin:java-saml:2.5.0' + implementation 'com.onelogin:java-saml-core:2.5.0' + + runtimeOnly 'net.minidev:accessors-smart:2.4.7' + + runtimeOnly 'org.apache.cxf:cxf-core:3.4.5' + implementation 'org.apache.cxf:cxf-rt-rs-json-basic:3.4.5' + runtimeOnly 'org.apache.cxf:cxf-rt-security:3.4.5' + + runtimeOnly 'com.sun.activation:jakarta.activation:1.2.2' + runtimeOnly 'com.eclipsesource.minimal-json:minimal-json:0.9.5' + runtimeOnly 'commons-codec:commons-codec:1.14' + runtimeOnly 'org.cryptacular:cryptacular:1.2.4' + runtimeOnly 'com.google.errorprone:error_prone_annotations:2.3.4' + runtimeOnly 'com.sun.istack:istack-commons-runtime:3.0.12' + runtimeOnly 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.3' + runtimeOnly 'org.ow2.asm:asm:9.1' + + testImplementation 'org.apache.camel:camel-xmlsecurity:3.14.2' + + implementation 'net.shibboleth.utilities:java-support:7.5.1' + implementation 'org.opensaml:opensaml-core:3.4.5' + implementation 'org.opensaml:opensaml-security-impl:3.4.5' + implementation 'org.opensaml:opensaml-security-api:3.4.5' + implementation 'org.opensaml:opensaml-xmlsec-api:3.4.5' + implementation 'org.opensaml:opensaml-xmlsec-impl:3.4.5' + implementation 'org.opensaml:opensaml-saml-api:3.4.5' implementation ('org.opensaml:opensaml-saml-impl:3.4.5') { exclude(group: 'org.apache.velocity', module: 'velocity') } + testImplementation 'org.opensaml:opensaml-messaging-impl:3.4.5' + implementation 'org.opensaml:opensaml-messaging-api:3.4.5' + runtimeOnly 'org.opensaml:opensaml-profile-api:3.4.5' + runtimeOnly 'org.opensaml:opensaml-soap-api:3.4.5' + runtimeOnly 'org.opensaml:opensaml-soap-impl:3.4.5' + implementation 'org.opensaml:opensaml-storage-api:3.4.5' implementation 'commons-lang:commons-lang:2.4' implementation 'commons-collections:commons-collections:3.2.2' implementation 'com.jayway.jsonpath:json-path:2.4.0' implementation 'org.apache.httpcomponents:httpclient:4.5.13' + implementation 'org.apache.httpcomponents:httpclient:4.5.13' + implementation 'net.minidev:json-smart:2.4.7' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.10.8' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.10.8' + runtimeOnly 'com.google.guava:failureaccess:1.0.1' + runtimeOnly 'org.apache.commons:commons-text:1.2' + runtimeOnly 'org.glassfish.jaxb:jaxb-runtime:2.3.4' + runtimeOnly 'com.google.j2objc:j2objc-annotations:1.3' + runtimeOnly 'com.google.code.findbugs:jsr305:3.0.2' + runtimeOnly 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' + runtimeOnly 'org.lz4:lz4-java:1.7.1' + runtimeOnly 'io.dropwizard.metrics:metrics-core:3.1.2' + runtimeOnly 'org.slf4j:slf4j-api:1.7.30' + runtimeOnly 'org.xerial.snappy:snappy-java:1.1.8.1' + runtimeOnly 'org.codehaus.woodstox:stax2-api:4.2.1' + runtimeOnly 'org.glassfish.jaxb:txw2:2.3.4' + runtimeOnly 'com.fasterxml.woodstox:woodstox-core:6.2.6' + runtimeOnly 'org.apache.ws.xmlschema:xmlschema-core:2.2.5' + runtimeOnly 'org.apache.santuario:xmlsec:2.2.3' + runtimeOnly 'com.github.luben:zstd-jni:1.5.0-2' + runtimeOnly 'org.checkerframework:checker-qual:3.5.0' + + + implementation 'org.apache.commons:commons-lang3:3.4' testImplementation "org.opensearch.plugin:reindex-client:${opensearch_version}" testImplementation "org.opensearch:opensearch-ssl-config:${opensearch_version}" testImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}" @@ -120,75 +316,42 @@ dependencies { testImplementation "org.opensearch.plugin:aggs-matrix-stats-client:${opensearch_version}" testImplementation 'org.apache.logging.log4j:log4j-core:2.17.1' testImplementation 'commons-io:commons-io:2.7' - testImplementation 'org.hamcrest:hamcrest-all:1.3' - testImplementation 'junit:junit:4.13.1' - testImplementation 'org.apache.httpcomponents:fluent-hc:4.5.13' - testImplementation 'org.mockito:mockito-core:2.23.0' - testImplementation 'org.springframework.kafka:spring-kafka-test:2.8.6' testImplementation 'javax.servlet:servlet-api:2.5' testImplementation 'com.unboundid:unboundid-ldapsdk:4.0.9' testImplementation 'com.github.stephenc.jcip:jcip-annotations:1.0-1' - testImplementation 'org.apache.kafka:kafka_2.13:2.8.1' - testImplementation 'org.apache.kafka:kafka_2.13:2.8.1:test' - testImplementation 'org.apache.kafka:kafka-clients:2.8.1:test' - compileOnly "org.opensearch:opensearch:${opensearch_version}" -} - -group = 'org.opensearch' -version = opensearch_build - -description = 'OpenSearch Security' - - -java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_11 - -tasks.register('testsJar', Jar) { - archiveClassifier = 'tests' - from(sourceSets.test.output) -} - -publishing { - publications { - maven(MavenPublication) { - from(components.java) - artifact(testsJar) - } + testImplementation 'com.unboundid:unboundid-ldapsdk:4.0.9' + testImplementation 'javax.servlet:servlet-api:2.5' + testImplementation 'org.apache.httpcomponents:fluent-hc:4.5.13' + testImplementation 'org.apache.kafka:kafka_2.13:3.0.1' + testImplementation 'org.apache.kafka:kafka_2.13:3.0.1:test' + testImplementation 'org.apache.kafka:kafka-clients:3.0.1:test' + testImplementation 'org.springframework.kafka:spring-kafka-test:2.8.6' + testImplementation 'org.springframework:spring-beans:5.3.20' + testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + // JUnit build requirement + testCompileOnly 'org.apiguardian:apiguardian-api:1.0.0' + // Kafka test execution + testRuntimeOnly 'org.springframework.retry:spring-retry:1.3.3' + testRuntimeOnly ('org.springframework:spring-core:5.3.21') { + exclude(group:'org.springframework', module: 'spring-jcl' ) } -} + testRuntimeOnly 'org.scala-lang:scala-library:2.13.8' + testRuntimeOnly 'com.yammer.metrics:metrics-core:2.2.0' + testRuntimeOnly 'com.typesafe.scala-logging:scala-logging_3:3.9.5' + testRuntimeOnly 'org.apache.zookeeper:zookeeper:3.6.3' + testRuntimeOnly 'org.apache.kafka:kafka-metadata:3.0.1' + testRuntimeOnly 'org.apache.kafka:kafka-storage:3.0.1' -tasks.withType(JavaCompile) { - options.encoding = 'UTF-8' -} -static def getTimestamp() { - def df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") - df.setTimeZone(TimeZone.getTimeZone("UTC")) - return df.format(new Date()) -} -static def gitCommitId() { - def cmd = "git rev-parse HEAD" - def proc = cmd.execute() - return proc.text.trim() + implementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}" + implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" + + compileOnly "org.opensearch:opensearch:${opensearch_version}" } jar { - manifest { - attributes( - "Manifest-Version": "1.0", - "Created-By": "Gradle ${gradle.gradleVersion}", - "Build-Jdk": "${System.properties['java.version']}", - "Implementation-Title": "OpenSearch Security", - "Implementation-Version": archiveVersion, - "Implementation-Vendor-Id": "org.opensearch", - "Implementation-URL": "https://github.com/opensearch-project/security", - "Build-Time": getTimestamp(), - "Built-By": "OpenSearch Security Plugin", - "git-sha1": gitCommitId() - ) - } - libsDirName = '.' into '', { from 'NOTICE.txt', "THIRD-PARTY.txt", "LICENSE" @@ -196,101 +359,17 @@ jar { processResources { exclude("KEYS") } +} +tasks.register('testsJar', Jar) { + archiveClassifier = 'tests' + from(sourceSets.test.output) } testsJar { - manifest { - attributes( - "Manifest-Version": "1.0", - "Created-By": "Gradle ${gradle.gradleVersion}", - "Build-Jdk": "${System.properties['java.version']}", - "Implementation-Title": "OpenSearch Security", - "Implementation-Version": archiveVersion, - "Implementation-Vendor-Id": "org.opensearch", - "Implementation-URL": "https://github.com/opensearch-project/security", - "Build-Time": getTimestamp(), - "Built-By": "OpenSearch Security Plugin", - "git-sha1": gitCommitId() - ) - } - libsDirName = '.' } - -test { - maxParallelForks = 3 - jvmArgs += "-Xmx3072m" - if (JavaVersion.current() > JavaVersion.VERSION_1_8) { - jvmArgs += "--add-opens=java.base/java.io=ALL-UNNAMED" - } - retry { - failOnPassedAfterRetry = false - maxFailures = 30 - maxRetries = 5 - } - jacoco { - excludes = [ - "com.sun.jndi.dns.*", - "com.sun.security.sasl.gsskerb.*", - "java.sql.*", - "javax.script.*", - "org.jcp.xml.dsig.internal.dom.*", - "sun.nio.cs.ext.*", - "sun.security.ec.*", - "sun.security.jgss.*", - "sun.security.pkcs11.*", - "sun.security.smartcardio.*", - "sun.util.resources.provider.*" - ] - } -} - -gitProperties { - keys = [ - 'git.branch', - 'git.build.version', - 'git.closest.tag.commit.count', - 'git.closest.tag.name', - 'git.commit.id', - 'git.commit.id.abbrev', - 'git.commit.id.describe', - 'git.commit.message.full', - 'git.commit.message.short', - 'git.commit.time', - 'git.dirty', - 'git.remote.origin.url', - 'git.tags', - 'git.total.commit.count' - ] -} - -// copied from: org.opensearch.gradle.dependencies.CompileOnlyResolvePlugin -project.getConfigurations().all { Configuration configuration -> - if (configuration.getName().equals(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME)) { - NamedDomainObjectProvider resolvableCompileOnly = project.getConfigurations().register('resolveableCompileOnly'); - resolvableCompileOnly.configure { c -> - c.setCanBeResolved(true); - c.setCanBeConsumed(false); - c.extendsFrom(configuration); - }; - } -}; - -task bundle(dependsOn: jar, type: Zip) { - from configurations.runtimeClasspath - project.configurations.getByName('resolveableCompileOnly') - from project.jar - from 'plugin-security.policy' - from 'plugin-descriptor.properties' - from('securityconfig') { - into 'config/' - } - from('tools') { - into 'tools/' - } -} - task bundleSecurityAdminStandalone(dependsOn: jar, type: Zip) { archiveClassifier = 'securityadmin-standalone' from(configurations.runtimeClasspath) { @@ -302,10 +381,11 @@ task bundleSecurityAdminStandalone(dependsOn: jar, type: Zip) { from('tools') { into 'tools/' } - from('securityconfig') { + from('config') { into 'deps/securityconfig' } } + task bundleSecurityAdminStandaloneTarGz(dependsOn: jar, type: Tar) { archiveClassifier = 'securityadmin-standalone' archiveExtension = 'tar.gz' @@ -319,110 +399,90 @@ task bundleSecurityAdminStandaloneTarGz(dependsOn: jar, type: Tar) { from('tools') { into 'tools/' } - from('securityconfig') { + from('config') { into 'deps/securityconfig' } } -task createPluginDescriptor() { - List descriptorProperties = [ - "description=Provide access control related features for OpenSearch", - "version=${version}", - "name=opensearch-security", - "classname=org.opensearch.security.OpenSearchSecurityPlugin", - "java.version=${java.targetCompatibility}", - "opensearch.version=${version_tokens[0]}", - ] - - new File("plugin-descriptor.properties").text = descriptorProperties.join ("\n") -} -bundle.doLast() { - new File("plugin-descriptor.properties").delete() -} - -tasks.assemble.dependsOn(bundle) -tasks.bundle.dependsOn(createPluginDescriptor) - -clean { - delete 'data/' +buildRpm { + arch = 'NOARCH' + addParentDirs = false + archiveName "${packageName}-${version}.rpm" } - -task createChecksums(type: Checksum) { - files = bundle.outputs.files - outputDir = new File(project.buildDir, "distributions") - algorithm = Checksum.Algorithm.SHA512 +buildDeb { + arch = 'all' + archiveName "${packageName}-${version}.deb" } -tasks.assemble.finalizedBy(createChecksums) - -jacoco { - reportsDirectory = file("$buildDir/reports/jacoco") -} -jacocoTestReport { - reports { - xml.required = true +publishing { + publications { + maven(MavenPublication) { + from(components.java) + artifact(testsJar) + } } } -tasks.test.finalizedBy(jacocoTestReport) // report is always generated after tests run -tasks.jacocoTestReport.dependsOn(test) // tests are required to run before generating the report +// This is afterEvaluate because the bundlePlugin ZIP task is updated afterEvaluate and changes the ZIP name to match the plugin name +afterEvaluate { + ospackage { + packageName = "${name}" + release = isSnapshot ? "0.1" : '1' + version = "${project.version}" - "-SNAPSHOT" -checkstyle { - configFile file("config/checkstyle/sun_checks.xml") -} + into '/usr/share/opensearch/plugins' + from(zipTree(bundlePlugin.archivePath)) { + into opensearchplugin.name + } -tasks.withType(Checkstyle) { - showViolations true - reports { - ignoreFailures = false + user 'root' + permissionGroup 'root' + fileMode 0644 + dirMode 0755 + + requires('opensearch', versions.opensearch, EQUAL) + packager = 'Amazon' + vendor = 'Amazon' + os = 'LINUX' + prefix '/usr' + + license 'ASL-2.0' + maintainer 'OpenSearch ' + url 'https://opensearch.org/downloads.html' + summary ''' + Security plugin for OpenSearch. + Reference documentation can be found at https://opensearch.org/docs/latest/. + '''.stripIndent().replace('\n', ' ').trim() } -} -tasks.withType(JavaCompile) { - configure(options) { - options.compilerArgs << '-Xlint:removal' << '-Werror' + buildRpm { + arch = 'NOARCH' + dependsOn 'assemble' + finalizedBy 'renameRpm' + task renameRpm(type: Copy) { + from("$buildDir/distributions") + into("$buildDir/distributions") + include archiveName + rename archiveName, "${packageName}-${version}.rpm" + doLast { delete file("$buildDir/distributions/$archiveName") } + } } -} - -spotless { - java { - // note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports - importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') - } -} -buildRpm { - arch = 'NOARCH' - addParentDirs = false - archiveName "${packageName}-${version}.rpm" -} -buildDeb { - arch = 'all' - archiveName "${packageName}-${version}.deb" -} - -allprojects { - // add a collection to track failedTests - ext.failedTests = [] - - // add a testlistener to all tasks of type Test - tasks.withType(Test) { - afterTest { TestDescriptor descriptor, TestResult result -> - if(result.resultType == org.gradle.api.tasks.testing.TestResult.ResultType.FAILURE){ - failedTests << ["${descriptor.className}::${descriptor.name}"] - } + buildDeb { + arch = 'all' + dependsOn 'assemble' + finalizedBy 'renameDeb' + task renameDeb(type: Copy) { + from("$buildDir/distributions") + into("$buildDir/distributions") + include archiveName + rename archiveName, "${packageName}-${version}.deb" + doLast { delete file("$buildDir/distributions/$archiveName") } } } - // print out tracked failed tests when the build has finished - gradle.buildFinished { - if(!failedTests.empty){ - println "Failed tests for ${project.name}:" - failedTests.each { failedTest -> - println failedTest - } - println "" - } + task buildPackages(type: GradleBuild) { + tasks = ['build', 'buildRpm', 'buildDeb'] } } diff --git a/config/checkstyle/sun_checks.xml b/checkstyle/sun_checks.xml similarity index 94% rename from config/checkstyle/sun_checks.xml rename to checkstyle/sun_checks.xml index e4c0e03341..099c8d39a5 100644 --- a/config/checkstyle/sun_checks.xml +++ b/checkstyle/sun_checks.xml @@ -194,22 +194,14 @@ - + - - - - - - - - - + diff --git a/securityconfig/action_groups.yml b/config/action_groups.yml similarity index 100% rename from securityconfig/action_groups.yml rename to config/action_groups.yml diff --git a/securityconfig/allowlist.yml b/config/allowlist.yml similarity index 100% rename from securityconfig/allowlist.yml rename to config/allowlist.yml diff --git a/securityconfig/audit.yml b/config/audit.yml similarity index 100% rename from securityconfig/audit.yml rename to config/audit.yml diff --git a/securityconfig/config.yml b/config/config.yml similarity index 100% rename from securityconfig/config.yml rename to config/config.yml diff --git a/securityconfig/internal_users.yml b/config/internal_users.yml similarity index 100% rename from securityconfig/internal_users.yml rename to config/internal_users.yml diff --git a/securityconfig/nodes_dn.yml b/config/nodes_dn.yml similarity index 100% rename from securityconfig/nodes_dn.yml rename to config/nodes_dn.yml diff --git a/securityconfig/opensearch.yml.example b/config/opensearch.yml.example similarity index 100% rename from securityconfig/opensearch.yml.example rename to config/opensearch.yml.example diff --git a/securityconfig/roles.yml b/config/roles.yml similarity index 100% rename from securityconfig/roles.yml rename to config/roles.yml diff --git a/securityconfig/roles_mapping.yml b/config/roles_mapping.yml similarity index 100% rename from securityconfig/roles_mapping.yml rename to config/roles_mapping.yml diff --git a/securityconfig/tenants.yml b/config/tenants.yml similarity index 100% rename from securityconfig/tenants.yml rename to config/tenants.yml diff --git a/securityconfig/whitelist.yml b/config/whitelist.yml similarity index 100% rename from securityconfig/whitelist.yml rename to config/whitelist.yml diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java index 7ea87cba09..6c973f3557 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java @@ -27,7 +27,7 @@ import org.opensearch.action.bulk.BulkResponse; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.support.WriteRequest.RefreshPolicy; -import org.opensearch.action.support.master.AcknowledgedResponse; +import org.opensearch.action.support.clustermanager.AcknowledgedResponse; import org.opensearch.client.Client; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.service.ClusterService; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/WhitelistApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/WhitelistApiAction.java index 1928bbdf7a..6f55a2d762 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/WhitelistApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/WhitelistApiAction.java @@ -9,7 +9,6 @@ * GitHub history for details. */ - package org.opensearch.security.dlic.rest.api; import java.nio.file.Path; diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java index e735c82b45..d812bd7bbb 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java @@ -184,7 +184,6 @@ public void onChange(Map> typeToConfig) { " whitelist " + whitelistingSetting.getImplementingClass() + " with " + whitelistingSetting.getCEntries().size() + " entries\n" + " allowlist " + allowlistingSetting.getImplementingClass() + " with " + allowlistingSetting.getCEntries().size() + " entries\n"; log.debug(logmsg); - } final DynamicConfigModel dcm; diff --git a/src/main/java/org/opensearch/security/ssl/util/CertFileProps.java b/src/main/java/org/opensearch/security/ssl/util/CertFileProps.java index 018d922c6c..3e86ec5ede 100644 --- a/src/main/java/org/opensearch/security/ssl/util/CertFileProps.java +++ b/src/main/java/org/opensearch/security/ssl/util/CertFileProps.java @@ -1,3 +1,14 @@ +/* + * 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.ssl.util; diff --git a/src/main/java/org/opensearch/security/ssl/util/CertFromFile.java b/src/main/java/org/opensearch/security/ssl/util/CertFromFile.java index 7383ae8290..d3cb62efe0 100644 --- a/src/main/java/org/opensearch/security/ssl/util/CertFromFile.java +++ b/src/main/java/org/opensearch/security/ssl/util/CertFromFile.java @@ -1,3 +1,14 @@ +/* + * 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.ssl.util; import java.io.File; diff --git a/src/main/java/org/opensearch/security/ssl/util/CertFromKeystore.java b/src/main/java/org/opensearch/security/ssl/util/CertFromKeystore.java index 4591ace69c..f2395a350a 100644 --- a/src/main/java/org/opensearch/security/ssl/util/CertFromKeystore.java +++ b/src/main/java/org/opensearch/security/ssl/util/CertFromKeystore.java @@ -1,3 +1,14 @@ +/* + * 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.ssl.util; import java.io.IOException; diff --git a/src/main/java/org/opensearch/security/ssl/util/CertFromTruststore.java b/src/main/java/org/opensearch/security/ssl/util/CertFromTruststore.java index 8ca7e500a7..86dae8da50 100644 --- a/src/main/java/org/opensearch/security/ssl/util/CertFromTruststore.java +++ b/src/main/java/org/opensearch/security/ssl/util/CertFromTruststore.java @@ -1,3 +1,14 @@ +/* + * 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.ssl.util; import java.io.IOException; diff --git a/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java b/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java index 4ab0760fc4..81d625126b 100644 --- a/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java +++ b/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java @@ -1,3 +1,14 @@ +/* + * 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.ssl.util; // diff --git a/src/main/java/org/opensearch/security/ssl/util/KeystoreProps.java b/src/main/java/org/opensearch/security/ssl/util/KeystoreProps.java index 9d79f28916..37fa6b791b 100644 --- a/src/main/java/org/opensearch/security/ssl/util/KeystoreProps.java +++ b/src/main/java/org/opensearch/security/ssl/util/KeystoreProps.java @@ -1,3 +1,14 @@ +/* + * 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.ssl.util; import java.io.File; diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 6360f508b3..4839524552 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -89,7 +89,7 @@ import org.opensearch.action.get.GetResponse; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.support.WriteRequest.RefreshPolicy; -import org.opensearch.action.support.master.AcknowledgedResponse; +import org.opensearch.action.support.clustermanager.AcknowledgedResponse; import org.opensearch.client.Request; import org.opensearch.client.RequestOptions; import org.opensearch.client.Response; diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticatorTest.java index f391ee4a8a..2e4b659841 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticatorTest.java @@ -26,15 +26,12 @@ import com.google.common.io.BaseEncoding; import io.jsonwebtoken.JwtBuilder; -import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import org.apache.http.HttpHeaders; import org.junit.Assert; import org.junit.Test; -import org.mockito.Mockito; -import org.mockito.internal.util.reflection.FieldSetter; import org.opensearch.common.settings.Settings; import org.opensearch.security.user.AuthCredentials; @@ -148,15 +145,12 @@ public void testBearerWrongPosition() throws Exception { public void testBasicAuthHeader() throws Exception { Settings settings = Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).build(); HTTPJwtAuthenticator jwtAuth = new HTTPJwtAuthenticator(settings, null); - JwtParser jwtParser = Mockito.spy(JwtParser.class); - FieldSetter.setField(jwtAuth, HTTPJwtAuthenticator.class.getDeclaredField("jwtParser"), jwtParser); String basicAuth = BaseEncoding.base64().encode("user:password".getBytes(StandardCharsets.UTF_8)); Map headers = Collections.singletonMap(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth); AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, Collections.emptyMap()), null); Assert.assertNull(credentials); - Mockito.verifyZeroInteractions(jwtParser); } @Test diff --git a/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java b/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java index 8ceb44aeab..9b2a2f1854 100644 --- a/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java +++ b/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java @@ -87,7 +87,6 @@ import org.apache.http.message.BasicHttpRequest; import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpRequestHandler; -import org.apache.xml.security.utils.EncryptionConstants; import org.joda.time.DateTime; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilderFactory; @@ -516,7 +515,8 @@ private String createSamlAuthResponse(AuthnRequest authnRequest) { private Encrypter getEncrypter() { KeyEncryptionParameters kek = new KeyEncryptionParameters(); - kek.setAlgorithm(EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSA15); + // Algorithm from https://santuario.apache.org/Java/api/constant-values.html#org.apache.xml.security.utils.EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSA15 + kek.setAlgorithm("http://www.w3.org/2001/04/xmlenc#rsa-1_5"); kek.setEncryptionCredential(new BasicX509Credential(spSignatureCertificate)); Encrypter encrypter = new Encrypter( new DataEncryptionParameters(),kek); encrypter.setKeyPlacement(Encrypter.KeyPlacement.INLINE); diff --git a/src/test/java/org/opensearch/security/AdvancedSecurityMigrationTests.java b/src/test/java/org/opensearch/security/AdvancedSecurityMigrationTests.java index d9f2bd607a..e4711bb504 100644 --- a/src/test/java/org/opensearch/security/AdvancedSecurityMigrationTests.java +++ b/src/test/java/org/opensearch/security/AdvancedSecurityMigrationTests.java @@ -25,18 +25,19 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.security.test.helper.cluster.ClusterConfiguration; +import org.opensearch.security.test.helper.cluster.ClusterHelper; import org.opensearch.security.test.helper.rest.RestHelper; public class AdvancedSecurityMigrationTests extends SingleClusterTest { @Before public void setupBefore() { - System.setProperty("security.default_init.dir", new File("./src/test/resources/security_passive").getAbsolutePath()); + ClusterHelper.updateDefaultDirectory(new File(TEST_RESOURCE_RELATIVE_PATH + "security_passive").getAbsolutePath()); } @After public void cleanupAfter() { - System.setProperty("security.default_init.dir", new File("./securityconfig").getAbsolutePath()); + ClusterHelper.resetSystemProperties(); } /** diff --git a/src/test/java/org/opensearch/security/ConfigTests.java b/src/test/java/org/opensearch/security/ConfigTests.java index cd7c21b892..8d7ebf8003 100644 --- a/src/test/java/org/opensearch/security/ConfigTests.java +++ b/src/test/java/org/opensearch/security/ConfigTests.java @@ -42,6 +42,7 @@ import org.opensearch.security.securityconf.impl.v7.RoleMappingsV7; import org.opensearch.security.securityconf.impl.v7.RoleV7; import org.opensearch.security.securityconf.impl.v7.TenantV7; +import org.opensearch.security.test.SingleClusterTest; public class ConfigTests { @@ -76,26 +77,27 @@ public void testMigrate() throws Exception { public void testParseSg67Config() throws Exception { check("./legacy/securityconfig_v6/action_groups.yml", CType.ACTIONGROUPS); - check("./securityconfig/action_groups.yml", CType.ACTIONGROUPS); + check("./action_groups.yml", CType.ACTIONGROUPS); check("./legacy/securityconfig_v6/config.yml", CType.CONFIG); - check("./securityconfig/config.yml", CType.CONFIG); + check("./config.yml", CType.CONFIG); check("./legacy/securityconfig_v6/roles.yml", CType.ROLES); - check("./securityconfig/roles.yml", CType.ROLES); + check("./roles.yml", CType.ROLES); check("./legacy/securityconfig_v6/internal_users.yml", CType.INTERNALUSERS); - check("./securityconfig/internal_users.yml", CType.INTERNALUSERS); + check("./internal_users.yml", CType.INTERNALUSERS); check("./legacy/securityconfig_v6/roles_mapping.yml", CType.ROLESMAPPING); - check("./securityconfig/roles_mapping.yml", CType.ROLESMAPPING); + check("./roles_mapping.yml", CType.ROLESMAPPING); - check("./securityconfig/tenants.yml", CType.TENANTS); + check("./tenants.yml", CType.TENANTS); } private void check(String file, CType cType) throws Exception { - JsonNode jsonNode = YAML.readTree(FileUtils.readFileToString(new File(file), "UTF-8")); + final String adjustedFilePath = SingleClusterTest.TEST_RESOURCE_RELATIVE_PATH + file; + JsonNode jsonNode = YAML.readTree(FileUtils.readFileToString(new File(adjustedFilePath), "UTF-8")); int configVersion = 1; System.out.println("%%%%%%%% THIS IS A LINE OF INTEREST %%%%%%%"); if(jsonNode.get("_meta") != null) { @@ -116,7 +118,8 @@ private void check(String file, CType cType) throws Exception { } private SecurityDynamicConfiguration load(String file, CType cType) throws Exception { - JsonNode jsonNode = YAML.readTree(FileUtils.readFileToString(new File(file), "UTF-8")); + final String adjustedFilePath = SingleClusterTest.TEST_RESOURCE_RELATIVE_PATH + file; + JsonNode jsonNode = YAML.readTree(FileUtils.readFileToString(new File(adjustedFilePath), "UTF-8")); int configVersion = 1; System.out.println("%%%%%%%% THIS IS A LINE OF INTEREST LOAD: CONFIG VERSION: %%%%%%%"); diff --git a/src/test/java/org/opensearch/security/InitializationIntegrationTests.java b/src/test/java/org/opensearch/security/InitializationIntegrationTests.java index f5285bec22..090b1ece75 100644 --- a/src/test/java/org/opensearch/security/InitializationIntegrationTests.java +++ b/src/test/java/org/opensearch/security/InitializationIntegrationTests.java @@ -54,6 +54,7 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.SingleClusterTest; +import org.opensearch.security.test.helper.cluster.ClusterHelper; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; @@ -194,9 +195,8 @@ public void testDefaultConfig() throws Exception { @Test public void testInvalidDefaultConfig() throws Exception { - String defaultInitDirectory = System.getProperty("security.default_init.dir"); try { - System.setProperty("security.default_init.dir", new File("./src/test/resources/invalid_config").getAbsolutePath()); + final String defaultInitDirectory = ClusterHelper.updateDefaultDirectory(new File(TEST_RESOURCE_RELATIVE_PATH + "invalid_config").getAbsolutePath()); final Settings settings = Settings.builder() .put(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, true) .build(); @@ -205,17 +205,13 @@ public void testInvalidDefaultConfig() throws Exception { Thread.sleep(10000); Assert.assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, rh.executeGetRequest("", encodeBasicHeader("admin", "admin")).getStatusCode()); - System.setProperty("security.default_init.dir", defaultInitDirectory); + ClusterHelper.updateDefaultDirectory(defaultInitDirectory); restart(Settings.EMPTY, null, settings, false); rh = nonSslRestHelper(); Thread.sleep(10000); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("admin", "admin")).getStatusCode()); } finally { - if (defaultInitDirectory != null) { - System.setProperty("security.default_init.dir", defaultInitDirectory); - } else { - System.clearProperty("security.default_init.dir"); - } + ClusterHelper.resetSystemProperties(); } } diff --git a/src/test/java/org/opensearch/security/IntegrationTests.java b/src/test/java/org/opensearch/security/IntegrationTests.java index 8e74e033be..985ea826b6 100644 --- a/src/test/java/org/opensearch/security/IntegrationTests.java +++ b/src/test/java/org/opensearch/security/IntegrationTests.java @@ -26,7 +26,6 @@ package org.opensearch.security; -import java.lang.Thread.UncaughtExceptionHandler; import java.util.TreeSet; import com.fasterxml.jackson.databind.JsonNode; @@ -63,17 +62,7 @@ public class IntegrationTests extends SingleClusterTest { @Test - public void testSearchScroll() throws Exception { - - Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { - - @Override - public void uncaughtException(Thread t, Throwable e) { - e.printStackTrace(); - - } - }); - + public void testSearchScroll() throws Exception { final Settings settings = Settings.builder() .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".worf", "knuddel","nonexists") .build(); diff --git a/src/test/java/org/opensearch/security/PrivilegesEvaluationTest.java b/src/test/java/org/opensearch/security/PrivilegesEvaluationTest.java index 9ddff6414c..1f9668c641 100644 --- a/src/test/java/org/opensearch/security/PrivilegesEvaluationTest.java +++ b/src/test/java/org/opensearch/security/PrivilegesEvaluationTest.java @@ -1,3 +1,14 @@ +/* + * 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; import com.google.common.collect.ImmutableMap; diff --git a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java index ba36653fe8..86168c0c14 100644 --- a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java @@ -1,3 +1,14 @@ +/* + * 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; import java.nio.file.Path; diff --git a/src/test/java/org/opensearch/security/SecurityAdminTests.java b/src/test/java/org/opensearch/security/SecurityAdminTests.java index 18e6397ad7..0de30943de 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminTests.java @@ -56,8 +56,7 @@ public void testSecurityAdmin() throws Exception { argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); - argsAsList.add("-cd"); - argsAsList.add(new File("src/test/resources/").getAbsolutePath()); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-nhnv"); @@ -89,8 +88,7 @@ public void testSecurityAdminInvalidCert() throws Exception { argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); - argsAsList.add("-cd"); - argsAsList.add(new File("src/test/resources/").getAbsolutePath()); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-nhnv"); int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); @@ -109,8 +107,7 @@ public void testSecurityAdminInvalidCert() throws Exception { argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); - argsAsList.add("-cd"); - argsAsList.add(new File("src/test/resources/").getAbsolutePath()); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("--diagnose"); argsAsList.add("-nhnv"); @@ -129,8 +126,7 @@ public void testSecurityAdminInvalidCert() throws Exception { argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); - argsAsList.add("-cd"); - argsAsList.add(new File("src/test/resources/").getAbsolutePath()); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-nhnv"); returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); @@ -159,8 +155,7 @@ public void testSecurityAdminV6Update() throws Exception { argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); - argsAsList.add("-cd"); - argsAsList.add(new File("./legacy/securityconfig_v6").getAbsolutePath()); + addDirectoryPath(argsAsList, new File("./legacy/securityconfig_v6").getAbsolutePath()); argsAsList.add("-nhnv"); @@ -196,8 +191,7 @@ public void testSecurityAdminRegularUpdate() throws Exception { argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); - argsAsList.add("-cd"); - argsAsList.add(new File("src/test/resources/").getAbsolutePath()); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-nhnv"); @@ -235,7 +229,7 @@ public void testSecurityAdminSingularV7Updates() throws Exception { argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-f"); - argsAsList.add(new File("./securityconfig/config.yml").getAbsolutePath()); + argsAsList.add(new File(TEST_RESOURCE_RELATIVE_PATH + "config.yml").getAbsolutePath()); argsAsList.add("-t"); argsAsList.add("config"); argsAsList.add("-nhnv"); @@ -254,7 +248,7 @@ public void testSecurityAdminSingularV7Updates() throws Exception { argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-f"); - argsAsList.add(new File("./securityconfig/roles_mapping.yml").getAbsolutePath()); + argsAsList.add(new File(TEST_RESOURCE_RELATIVE_PATH + "roles_mapping.yml").getAbsolutePath()); argsAsList.add("-t"); argsAsList.add("rolesmapping"); argsAsList.add("-nhnv"); @@ -273,7 +267,7 @@ public void testSecurityAdminSingularV7Updates() throws Exception { argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-f"); - argsAsList.add(new File("./securityconfig/tenants.yml").getAbsolutePath()); + argsAsList.add(new File(TEST_RESOURCE_RELATIVE_PATH + "tenants.yml").getAbsolutePath()); argsAsList.add("-t"); argsAsList.add("tenants"); argsAsList.add("-nhnv"); @@ -313,7 +307,7 @@ public void testSecurityAdminSingularV6Updates() throws Exception { argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-f"); - argsAsList.add(new File("./legacy/securityconfig_v6/config.yml").getAbsolutePath()); + argsAsList.add(new File(TEST_RESOURCE_RELATIVE_PATH + "legacy/securityconfig_v6/config.yml").getAbsolutePath()); argsAsList.add("-t"); argsAsList.add("config"); argsAsList.add("-nhnv"); @@ -420,8 +414,7 @@ public void testSecurityAdminReloadInvalidConfig() throws Exception { @Test public void testSecurityAdminValidateConfig() throws Exception { List argsAsList = new ArrayList<>(); - argsAsList.add("-cd"); - argsAsList.add(new File("src/test/resources/").getAbsolutePath()); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-vc"); int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); @@ -429,7 +422,7 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList = new ArrayList<>(); argsAsList.add("-f"); - argsAsList.add(new File("src/test/resources/roles.yml").getAbsolutePath()); + argsAsList.add(new File(PROJECT_ROOT_RELATIVE_PATH + "src/test/resources/roles.yml").getAbsolutePath()); argsAsList.add("-vc"); returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); @@ -437,7 +430,7 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList = new ArrayList<>(); argsAsList.add("-f"); - argsAsList.add(new File("./src/main/resources/static_config/static_roles.yml").getAbsolutePath()); + argsAsList.add(new File(PROJECT_ROOT_RELATIVE_PATH + "src/main/resources/static_config/static_roles.yml").getAbsolutePath()); argsAsList.add("-vc"); returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); @@ -445,7 +438,7 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList = new ArrayList<>(); argsAsList.add("-f"); - argsAsList.add(new File("./src/main/resources/static_config/static_action_groups.yml").getAbsolutePath()); + argsAsList.add(new File(PROJECT_ROOT_RELATIVE_PATH + "src/main/resources/static_config/static_action_groups.yml").getAbsolutePath()); argsAsList.add("-vc"); returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); @@ -453,7 +446,7 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList = new ArrayList<>(); argsAsList.add("-f"); - argsAsList.add(new File("./src/main/resources/static_config/static_tenants.yml").getAbsolutePath()); + argsAsList.add(new File(PROJECT_ROOT_RELATIVE_PATH + "src/main/resources/static_config/static_tenants.yml").getAbsolutePath()); argsAsList.add("-vc"); returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); @@ -461,7 +454,7 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList = new ArrayList<>(); argsAsList.add("-f"); - argsAsList.add(new File("src/test/resources/roles.yml").getAbsolutePath()); + argsAsList.add(TEST_RESOURCE_ABSOLUTE_PATH + "roles.yml"); argsAsList.add("-vc"); argsAsList.add("-t"); argsAsList.add("config"); @@ -471,23 +464,21 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList = new ArrayList<>(); argsAsList.add("-ks"); - argsAsList.add(new File("src/test/resources/").getAbsolutePath()); + argsAsList.add(TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-vc"); returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); argsAsList = new ArrayList<>(); - argsAsList.add("-cd"); - argsAsList.add(new File("./legacy/securityconfig_v6").getAbsolutePath()); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH + "legacy/securityconfig_v6"); argsAsList.add("-vc"); returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); argsAsList = new ArrayList<>(); - argsAsList.add("-cd"); - argsAsList.add(new File("./legacy/securityconfig_v6").getAbsolutePath()); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH + "legacy/securityconfig_v6"); argsAsList.add("-vc"); argsAsList.add("6"); @@ -495,12 +486,16 @@ public void testSecurityAdminValidateConfig() throws Exception { Assert.assertEquals(0, returnCode); argsAsList = new ArrayList<>(); - argsAsList.add("-cd"); - argsAsList.add(new File("src/test/resources/").getAbsolutePath()); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-vc"); argsAsList.add("8"); returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); } + + private void addDirectoryPath(final List args, final String path) { + args.add("-cd"); + args.add(path); + } } diff --git a/src/test/java/org/opensearch/security/SnapshotRestoreTests.java b/src/test/java/org/opensearch/security/SnapshotRestoreTests.java index 6c92788748..03d1128bfe 100644 --- a/src/test/java/org/opensearch/security/SnapshotRestoreTests.java +++ b/src/test/java/org/opensearch/security/SnapshotRestoreTests.java @@ -26,16 +26,9 @@ package org.opensearch.security; -import java.util.Arrays; -import java.util.Collection; - import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; import org.opensearch.action.admin.cluster.repositories.put.PutRepositoryRequest; import org.opensearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; @@ -52,18 +45,8 @@ import org.opensearch.security.test.helper.cluster.ClusterConfiguration; import org.opensearch.security.test.helper.rest.RestHelper; -@RunWith(Parameterized.class) public class SnapshotRestoreTests extends SingleClusterTest { - - @Parameters - public static Collection data() { - return Arrays.asList(new ClusterConfiguration[] { - ClusterConfiguration.DEFAULT - }); - } - - @Parameter - public ClusterConfiguration currentClusterConfig; + private ClusterConfiguration currentClusterConfig = ClusterConfiguration.DEFAULT; @Test public void testSnapshotEnableSecurityIndexRestore() throws Exception { diff --git a/src/test/java/org/opensearch/security/TracingTests.java b/src/test/java/org/opensearch/security/TracingTests.java index 6021bb6241..4c7e0472ce 100644 --- a/src/test/java/org/opensearch/security/TracingTests.java +++ b/src/test/java/org/opensearch/security/TracingTests.java @@ -26,8 +26,6 @@ package org.opensearch.security; -import java.lang.Thread.UncaughtExceptionHandler; - import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Ignore; @@ -239,16 +237,6 @@ public void testHTTPTraceNoSource() throws Exception { @Test public void testHTTPSingle() throws Exception { - - Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { - - @Override - public void uncaughtException(Thread t, Throwable e) { - e.printStackTrace(); - - } - }); - final Settings settings = Settings.builder() .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".worf", "knuddel","nonexists") .build(); @@ -305,16 +293,6 @@ public void uncaughtException(Thread t, Throwable e) { @Test public void testSearchScroll() throws Exception { - - Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { - - @Override - public void uncaughtException(Thread t, Throwable e) { - e.printStackTrace(); - - } - }); - final Settings settings = Settings.builder() .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".worf", "knuddel","nonexists") .build(); diff --git a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java index 6a96c02926..065b99146b 100644 --- a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java @@ -1,3 +1,14 @@ +/* + * 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; import java.nio.file.Path; 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 79f9108518..a88baef90f 100644 --- a/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java @@ -183,13 +183,12 @@ 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); + TestAuditlogImpl.doThenWaitForMessages(() -> { + HttpResponse response = rh.executePostRequest("_msearch?pretty", search, encodeBasicHeader("admin", "admin")); + assertNotContains(response, "*exception*"); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + }, 2); 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")); diff --git a/src/test/java/org/opensearch/security/auditlog/impl/TracingTests.java b/src/test/java/org/opensearch/security/auditlog/impl/TracingTests.java index 7c38bc952c..49dd3b38b2 100644 --- a/src/test/java/org/opensearch/security/auditlog/impl/TracingTests.java +++ b/src/test/java/org/opensearch/security/auditlog/impl/TracingTests.java @@ -11,9 +11,6 @@ package org.opensearch.security.auditlog.impl; -import java.lang.Thread.UncaughtExceptionHandler; - -import net.jcip.annotations.NotThreadSafe; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; @@ -35,7 +32,6 @@ import org.opensearch.security.test.helper.rest.RestHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -@NotThreadSafe public class TracingTests extends SingleClusterTest { @Override @@ -215,16 +211,6 @@ public void testHTTPTrace() throws Exception { @Test public void testHTTPSingle() throws Exception { - - Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { - - @Override - public void uncaughtException(Thread t, Throwable e) { - e.printStackTrace(); - - } - }); - final Settings settings = Settings.builder() .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".worf", "knuddel","nonexists") .build(); @@ -281,16 +267,6 @@ public void uncaughtException(Thread t, Throwable e) { @Test public void testSearchScroll() throws Exception { - - Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { - - @Override - public void uncaughtException(Thread t, Throwable e) { - e.printStackTrace(); - - } - }); - final Settings settings = Settings.builder() .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".worf", "knuddel","nonexists") .build(); diff --git a/src/test/java/org/opensearch/security/auditlog/sink/KafkaSinkTest.java b/src/test/java/org/opensearch/security/auditlog/sink/KafkaSinkTest.java index 03ee1b16b5..ea9ce18e3d 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/KafkaSinkTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/KafkaSinkTest.java @@ -11,9 +11,11 @@ package org.opensearch.security.auditlog.sink; +import java.lang.Thread.UncaughtExceptionHandler; import java.time.Duration; import java.util.Arrays; import java.util.Properties; +import java.util.Random; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; @@ -21,7 +23,6 @@ import org.junit.ClassRule; import org.junit.Test; import org.springframework.kafka.test.rule.EmbeddedKafkaRule; -import scala.util.Random; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.yaml.YamlXContent; @@ -32,40 +33,54 @@ public class KafkaSinkTest extends AbstractAuditlogiUnitTest { - @ClassRule - public static EmbeddedKafkaRule embeddedKafka = new EmbeddedKafkaRule(1, true, 1, "compliance"); + @ClassRule + public static EmbeddedKafkaRule embeddedKafka = new EmbeddedKafkaRule(1, true, 1, "compliance") { + // Prevents test exceptions from randomized runner, see https://bit.ly/3y17IkI + private UncaughtExceptionHandler currentHandler; + @Override + public void before() { + currentHandler = Thread.getDefaultUncaughtExceptionHandler(); + super.before(); + } - @Test - public void testKafka() throws Exception { - String configYml = FileHelper.loadFile("auditlog/endpoints/sink/configuration_kafka.yml"); - configYml = configYml.replace("_RPLC_BOOTSTRAP_SERVERS_",embeddedKafka.getEmbeddedKafka().getBrokersAsString()); - Settings.Builder settingsBuilder = Settings.builder().loadFromSource(configYml, YamlXContent.yamlXContent.type()); - try(KafkaConsumer consumer = createConsumer()) { - consumer.subscribe(Arrays.asList("compliance")); + @Override + public void after() { + super.after(); + Thread.setDefaultUncaughtExceptionHandler(currentHandler); + } + }; - Settings settings = settingsBuilder.put("path.home", ".").build(); - SinkProvider provider = new SinkProvider(settings, null, null, null); - AuditLogSink sink = provider.getDefaultSink(); - try { - Assert.assertEquals(KafkaSink.class, sink.getClass()); - boolean success = sink.doStore(MockAuditMessageFactory.validAuditMessage(AuditCategory.MISSING_PRIVILEGES)); - Assert.assertTrue(success); - ConsumerRecords records = consumer.poll(Duration.ofSeconds(10)); - Assert.assertEquals(1, records.count()); - } finally { - sink.close(); - } - } + @Test + public void testKafka() throws Exception { + String configYml = FileHelper.loadFile("auditlog/endpoints/sink/configuration_kafka.yml"); + configYml = configYml.replace("_RPLC_BOOTSTRAP_SERVERS_",embeddedKafka.getEmbeddedKafka().getBrokersAsString()); + Settings.Builder settingsBuilder = Settings.builder().loadFromSource(configYml, YamlXContent.yamlXContent.type()); + try(KafkaConsumer consumer = createConsumer()) { + consumer.subscribe(Arrays.asList("compliance")); - } + Settings settings = settingsBuilder.put("path.home", ".").build(); + SinkProvider provider = new SinkProvider(settings, null, null, null); + AuditLogSink sink = provider.getDefaultSink(); + try { + Assert.assertEquals(KafkaSink.class, sink.getClass()); + boolean success = sink.doStore(MockAuditMessageFactory.validAuditMessage(AuditCategory.MISSING_PRIVILEGES)); + Assert.assertTrue(success); + ConsumerRecords records = consumer.poll(Duration.ofSeconds(10)); + Assert.assertEquals(1, records.count()); + } finally { + sink.close(); + } + } - private KafkaConsumer createConsumer() { - Properties props = new Properties(); - props.put("bootstrap.servers", embeddedKafka.getEmbeddedKafka().getBrokersAsString()); - props.put("auto.offset.reset", "earliest"); - props.put("group.id", "mygroup"+System.currentTimeMillis()+"_"+new Random().nextDouble()); - props.put("key.deserializer", "org.apache.kafka.common.serialization.LongDeserializer"); - props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); - return new KafkaConsumer<>(props); - } + } + + private KafkaConsumer createConsumer() { + Properties props = new Properties(); + props.put("bootstrap.servers", embeddedKafka.getEmbeddedKafka().getBrokersAsString()); + props.put("auto.offset.reset", "earliest"); + props.put("group.id", "mygroup"+System.currentTimeMillis()+"_"+new Random().nextDouble()); + props.put("key.deserializer", "org.apache.kafka.common.serialization.LongDeserializer"); + props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + return new KafkaConsumer<>(props); + } } diff --git a/src/test/java/org/opensearch/security/auth/UserInjectorTest.java b/src/test/java/org/opensearch/security/auth/UserInjectorTest.java index cfde2c89de..09bc1653a4 100644 --- a/src/test/java/org/opensearch/security/auth/UserInjectorTest.java +++ b/src/test/java/org/opensearch/security/auth/UserInjectorTest.java @@ -1,3 +1,14 @@ +/* + * 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.auth; import java.util.Arrays; diff --git a/src/test/java/org/opensearch/security/ccstest/CrossClusterMinimalRoundtripSearchTests.java b/src/test/java/org/opensearch/security/ccstest/CrossClusterMinimalRoundtripSearchTests.java new file mode 100644 index 0000000000..292a0d38d8 --- /dev/null +++ b/src/test/java/org/opensearch/security/ccstest/CrossClusterMinimalRoundtripSearchTests.java @@ -0,0 +1,17 @@ +/* + * 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.ccstest; + +public class CrossClusterMinimalRoundtripSearchTests extends CrossClusterSearchTests { + @Override + protected boolean ccsMinimizeRoundtrips() { return true; } +} diff --git a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java index 13f0629d7e..246e159c5a 100644 --- a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java +++ b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java @@ -30,10 +30,6 @@ import org.junit.After; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; import org.opensearch.OpenSearchSecurityException; import org.opensearch.action.admin.cluster.health.ClusterHealthRequest; @@ -70,7 +66,6 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; -@RunWith(Parameterized.class) public class CrossClusterSearchTests extends AbstractSecurityUnitTest { private final ClusterHelper cl1 = new ClusterHelper("crl1_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); @@ -80,9 +75,7 @@ public class CrossClusterSearchTests extends AbstractSecurityUnitTest { private RestHelper rh1; private RestHelper rh2; - //default is true - @Parameter - public boolean ccsMinimizeRoundtrips; + protected boolean ccsMinimizeRoundtrips() { return false; }; private static class ClusterTransportClientSettings extends Tuple { @@ -103,12 +96,6 @@ public Settings transportClientSettings() { } } - - @Parameters - public static Object[] parameters() { - return new Object[] { Boolean.FALSE, Boolean.TRUE }; - } - private void setupCcs() throws Exception { setupCcs(new DynamicSecurityConfig()); } @@ -188,7 +175,7 @@ public void testCcs() throws Exception { HttpResponse ccs = null; System.out.println("###################### query 1"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("nagilum","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("crl1")); @@ -197,20 +184,20 @@ public void testCcs() throws Exception { System.out.println("###################### query 4"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:xx,xx/xx/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:xx,xx/xx/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("nagilum","nagilum")); System.out.println(ccs.getBody()); //TODO fix exception nesting //Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, ccs.getStatusCode()); //Assert.assertTrue(ccs.getBody().contains("Can not filter indices; index cross_cluster_two:xx exists but there is also a remote cluster named: cross_cluster_two")); System.out.println("###################### query 5"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:abcnonext/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:abcnonext/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("nagilum","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("index_not_found_exception")); System.out.println("###################### query 6"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twutter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twutter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("nagilum","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); @@ -251,34 +238,34 @@ public void testCcsNonadmin() throws Exception { HttpResponse ccs = null; System.out.println("###################### query 1"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); System.out.println("###################### query 2"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twit*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twit*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); System.out.println("###################### query 3"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter,twutter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter,twutter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); System.out.println("###################### query 4"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("crl1_")); Assert.assertTrue(ccs.getBody().contains("crl2_")); System.out.println("###################### query 5"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twutter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twutter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); @@ -287,7 +274,7 @@ public void testCcsNonadmin() throws Exception { "{}"+System.lineSeparator()+ "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:twitter,twitter/_msearch?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, msearchBody, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:twitter,twitter/_msearch?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), msearchBody, encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); @@ -296,35 +283,35 @@ public void testCcsNonadmin() throws Exception { "{}"+System.lineSeparator()+ "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:twitter/_msearch?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, msearchBody, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:twitter/_msearch?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), msearchBody, encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("_all/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("_all/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("hfghgtdhfhuth/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("hfghgtdhfhuth/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); @@ -338,54 +325,54 @@ public void testCcsNonadmin() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("\"hits\":[]")); //TODO: Change for 25.0 to be forbidden (Indices options) - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*:/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*:/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E,%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E,%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); System.out.println("#### Alias both"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("notexist,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("notexist,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); //TODO Fix for 25.0 to resolve coordalias (Indices options) - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("crusherw","crusherw")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("crusherw","crusherw")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); @@ -422,33 +409,33 @@ public void testCcsNonadminDnfof() throws Exception { HttpResponse ccs = null; System.out.println("###################### query 1"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("crl1_")); Assert.assertTrue(ccs.getBody().contains("crl2_")); System.out.println("###################### query 2"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twit*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twit*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); System.out.println("###################### query 3"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter,twutter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter,twutter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("twutter")); System.out.println("###################### query 4"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("crl1_")); Assert.assertTrue(ccs.getBody().contains("crl2_")); System.out.println("###################### query 5"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twutter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twutter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); @@ -470,47 +457,47 @@ public void testCcsNonadminDnfof() throws Exception { System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("_all/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("_all/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); System.out.println("#####*"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*,*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*,*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("crl1_")); Assert.assertTrue(ccs.getBody().contains("crl2_")); //wildcard in remote cluster names - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*cross*:*twit*,*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*cross*:*twit*,*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,t*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,t*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("hfghgtdhfhuth/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("hfghgtdhfhuth/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); @@ -524,47 +511,47 @@ public void testCcsNonadminDnfof() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("\"hits\":[]")); //TODO: Change for 25.0 to be forbidden (Indices options) - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*:/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*:/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E,%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E,%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("crusherw","crusherw")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("crusherw","crusherw")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); } @@ -587,7 +574,7 @@ public void testCcsEmptyCoord() throws Exception { HttpResponse ccs = null; System.out.println("###################### query 1"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterMinimalRoundtripSearchTests.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterMinimalRoundtripSearchTests.java new file mode 100644 index 0000000000..86b97c1afe --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterMinimalRoundtripSearchTests.java @@ -0,0 +1,17 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.dlsfls; + +public class DlsFlsCrossClusterMinimalRoundtripSearchTests extends DlsFlsCrossClusterSearchTest { + @Override + protected boolean ccsMinimizeRoundtrips() { return true; } +} diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java index eeee724f6d..d44d5b4f6e 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java @@ -15,10 +15,6 @@ import org.junit.After; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.support.WriteRequest.RefreshPolicy; @@ -33,7 +29,6 @@ import org.opensearch.security.test.helper.rest.RestHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -@RunWith(Parameterized.class) public class DlsFlsCrossClusterSearchTest extends AbstractSecurityUnitTest { private final ClusterHelper cl1 = new ClusterHelper("crl1_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); @@ -41,14 +36,7 @@ public class DlsFlsCrossClusterSearchTest extends AbstractSecurityUnitTest { private ClusterInfo cl1Info; private ClusterInfo cl2Info; - //default is true - @Parameter - public boolean ccsMinimizeRoundtrips; - - @Parameters - public static Object[] parameters() { - return new Object[] { Boolean.FALSE, Boolean.TRUE }; - } + protected boolean ccsMinimizeRoundtrips() { return false; }; @Override protected String getResourceFolder() { @@ -122,7 +110,7 @@ public void testCcs() throws Exception { System.out.println("###################### query 1"); //on coordinating cluster - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:humanresources/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("human_resources_trainee", "password")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:humanresources/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("human_resources_trainee", "password")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("crl1")); @@ -179,7 +167,7 @@ public void testCcsDifferentConfig() throws Exception { System.out.println("###################### query 1"); //on coordinating cluster - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:humanresources/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("human_resources_trainee", "password")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:humanresources/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("human_resources_trainee", "password")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("crl1")); @@ -259,7 +247,7 @@ public void testCcsDifferentConfigBoth() throws Exception { System.out.println("###################### query 1"); //on coordinating cluster - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:humanresources,humanresources/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips, encodeBasicHeader("human_resources_trainee", "password")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:humanresources,humanresources/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("human_resources_trainee", "password")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("crl1")); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java index 05bdbc1f66..c1840524c9 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java @@ -11,13 +11,10 @@ package org.opensearch.security.dlic.rest.api; -import com.google.common.collect.ImmutableList; import org.apache.http.Header; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; @@ -29,26 +26,18 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class AccountApiTest extends AbstractRestApiUnitTest { - private final String BASE_ENDPOINT; - private final String ENDPOINT; - - - public AccountApiTest(String endpoint){ - BASE_ENDPOINT = endpoint; - ENDPOINT = BASE_ENDPOINT + "account"; + private final String BASE_ENDPOINT; + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; } - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api/", - PLUGINS_PREFIX + "/api/" - ); + public AccountApiTest(){ + BASE_ENDPOINT = getEndpointPrefix() + "/api/"; + ENDPOINT = getEndpointPrefix() + "/api/account"; } @Test diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java index b4c3fdf2ae..09efae9fbe 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java @@ -13,13 +13,10 @@ import java.util.List; -import com.google.common.collect.ImmutableList; import org.apache.http.Header; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; @@ -27,24 +24,16 @@ import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class ActionGroupsApiTest extends AbstractRestApiUnitTest { - - private final String ENDPOINT; - - public ActionGroupsApiTest(String endpoint){ - ENDPOINT = endpoint; + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; } - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api/actiongroups", - PLUGINS_PREFIX + "/api/actiongroups" - ); + public ActionGroupsApiTest(){ + ENDPOINT = getEndpointPrefix() + "/api/actiongroups"; } @Test diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java index c5949515e5..c5e0a61d2f 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java @@ -30,8 +30,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.security.DefaultObjectMapper; @@ -45,31 +43,23 @@ import static org.junit.Assert.assertTrue; import static org.opensearch.security.DefaultObjectMapper.readTree; import static org.opensearch.security.DefaultObjectMapper.writeValueAsString; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class AuditApiActionTest extends AbstractRestApiUnitTest { - - private final String ENDPOINT; - private final String CONFIG_ENDPOINT; - // admin cred with roles in test yml files final Header adminCredsHeader = encodeBasicHeader("sarek", "sarek"); // non-admin final Header nonAdminCredsHeader = encodeBasicHeader("random", "random"); - public AuditApiActionTest(String endpoint){ - ENDPOINT = endpoint; - CONFIG_ENDPOINT = ENDPOINT + "/config"; + private final String ENDPOINT; + private final String CONFIG_ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; } - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api/audit", - PLUGINS_PREFIX + "/api/audit" - ); + public AuditApiActionTest(){ + ENDPOINT = getEndpointPrefix() + "/api/audit"; + CONFIG_ENDPOINT = ENDPOINT + "/config"; } @Rule diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java index a5841c42c0..c6af253f95 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java @@ -11,35 +11,24 @@ package org.opensearch.security.dlic.rest.api; -import com.google.common.collect.ImmutableList; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.rest.RestHelper; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class DashboardsInfoActionTest extends AbstractRestApiUnitTest { - - private final String ENDPOINT; - - public DashboardsInfoActionTest(String endpoint) { - ENDPOINT = endpoint; + private final String ENDPOINT; + protected String getEndpoint() { + return PLUGINS_PREFIX + "/dashboardsinfo"; } - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/kibanainfo", - PLUGINS_PREFIX + "/dashboardsinfo" - ); + public DashboardsInfoActionTest(){ + ENDPOINT = getEndpoint(); } @Test diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java index 3c42f020c6..ad0a4eea14 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java @@ -11,70 +11,58 @@ package org.opensearch.security.dlic.rest.api; -import com.google.common.collect.ImmutableList; import org.apache.http.Header; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class FlushCacheApiTest extends AbstractRestApiUnitTest { - - private final String ENDPOINT; - - public FlushCacheApiTest(String endpoint){ - ENDPOINT = endpoint; - } - - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api/cache", - PLUGINS_PREFIX + "/api/cache" - ); - } - - @Test - public void testFlushCache() throws Exception { - - setup(); - - // Only DELETE is allowed for flush cache - rh.keystore = "restapi/kirk-keystore.jks"; - rh.sendAdminCertificate = true; - - // GET - HttpResponse response = rh.executeGetRequest(ENDPOINT); - Assert.assertEquals(HttpStatus.SC_NOT_IMPLEMENTED, response.getStatusCode()); - Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(settings.get("message"), "Method GET not supported for this action."); - - // PUT - response = rh.executePutRequest(ENDPOINT, "{}", new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_IMPLEMENTED, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(settings.get("message"), "Method PUT not supported for this action."); - - // POST - response = rh.executePostRequest(ENDPOINT, "{}", new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_IMPLEMENTED, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(settings.get("message"), "Method POST not supported for this action."); - - // DELETE - response = rh.executeDeleteRequest(ENDPOINT, new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(settings.get("message"), "Cache flushed successfully."); - - } + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; + } + public FlushCacheApiTest(){ + ENDPOINT = getEndpointPrefix() + "/api/cache"; + } + + @Test + public void testFlushCache() throws Exception { + + setup(); + + // Only DELETE is allowed for flush cache + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendAdminCertificate = true; + + // GET + HttpResponse response = rh.executeGetRequest(ENDPOINT); + Assert.assertEquals(HttpStatus.SC_NOT_IMPLEMENTED, response.getStatusCode()); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(settings.get("message"), "Method GET not supported for this action."); + + // PUT + response = rh.executePutRequest(ENDPOINT, "{}", new Header[0]); + Assert.assertEquals(HttpStatus.SC_NOT_IMPLEMENTED, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(settings.get("message"), "Method PUT not supported for this action."); + + // POST + response = rh.executePostRequest(ENDPOINT, "{}", new Header[0]); + Assert.assertEquals(HttpStatus.SC_NOT_IMPLEMENTED, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(settings.get("message"), "Method POST not supported for this action."); + + // DELETE + response = rh.executeDeleteRequest(ENDPOINT, new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(settings.get("message"), "Cache flushed successfully."); + + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java index abdc9d143d..237e75a79a 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java @@ -12,86 +12,76 @@ package org.opensearch.security.dlic.rest.api; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.ImmutableList; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class GetConfigurationApiTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; - - public GetConfigurationApiTest(String endpoint){ - ENDPOINT = endpoint; - } - - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api", - PLUGINS_PREFIX + "/api" - ); - } - - @Test - public void testGetConfiguration() throws Exception { - - setup(); - rh.keystore = "restapi/kirk-keystore.jks"; - rh.sendAdminCertificate = true; - - // wrong config name -> bad request - HttpResponse response = null; - - // test that every config is accessible - // config - response = rh.executeGetRequest(ENDPOINT + "/securityconfig"); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals( - settings.getAsBoolean("config.dynamic.authc.authentication_domain_basic_internal.http_enabled", false), - true); - Assert.assertNull(settings.get("_opendistro_security_meta.type")); - - // internalusers - response = rh.executeGetRequest(ENDPOINT + "/internalusers"); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals("", settings.get("admin.hash")); - Assert.assertEquals("", settings.get("other.hash")); - Assert.assertNull(settings.get("_opendistro_security_meta.type")); - - // roles - response = rh.executeGetRequest(ENDPOINT + "/roles"); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - JsonNode jnode = DefaultObjectMapper.readTree(response.getBody()); - Assert.assertEquals(jnode.get("opendistro_security_all_access").get("cluster_permissions").get(0).asText(), "cluster:*"); - Assert.assertNull(settings.get("_opendistro_security_meta.type")); - - // roles - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping"); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(settings.getAsList("opendistro_security_role_starfleet.backend_roles").get(0), "starfleet"); - Assert.assertNull(settings.get("_opendistro_security_meta.type")); - - // action groups - response = rh.executeGetRequest(ENDPOINT + "/actiongroups"); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(settings.getAsList("ALL.allowed_actions").get(0), "indices:*"); - Assert.assertTrue(settings.hasValue("INTERNAL.allowed_actions")); - Assert.assertNull(settings.get("_opendistro_security_meta.type")); - } + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; + } + + public GetConfigurationApiTest(){ + ENDPOINT = getEndpointPrefix() + "/api"; + } + + @Test + public void testGetConfiguration() throws Exception { + + setup(); + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendAdminCertificate = true; + + // wrong config name -> bad request + HttpResponse response = null; + + // test that every config is accessible + // config + response = rh.executeGetRequest(ENDPOINT + "/securityconfig"); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals( + settings.getAsBoolean("config.dynamic.authc.authentication_domain_basic_internal.http_enabled", false), + true); + Assert.assertNull(settings.get("_opendistro_security_meta.type")); + + // internalusers + response = rh.executeGetRequest(ENDPOINT + "/internalusers"); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals("", settings.get("admin.hash")); + Assert.assertEquals("", settings.get("other.hash")); + Assert.assertNull(settings.get("_opendistro_security_meta.type")); + + // roles + response = rh.executeGetRequest(ENDPOINT + "/roles"); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + JsonNode jnode = DefaultObjectMapper.readTree(response.getBody()); + Assert.assertEquals(jnode.get("opendistro_security_all_access").get("cluster_permissions").get(0).asText(), "cluster:*"); + Assert.assertNull(settings.get("_opendistro_security_meta.type")); + + // roles + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping"); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(settings.getAsList("opendistro_security_role_starfleet.backend_roles").get(0), "starfleet"); + Assert.assertNull(settings.get("_opendistro_security_meta.type")); + + // action groups + response = rh.executeGetRequest(ENDPOINT + "/actiongroups"); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(settings.getAsList("ALL.allowed_actions").get(0), "indices:*"); + Assert.assertTrue(settings.hasValue("INTERNAL.allowed_actions")); + Assert.assertNull(settings.get("_opendistro_security_meta.type")); + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java index d0c00612d3..257732f129 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java @@ -11,101 +11,91 @@ package org.opensearch.security.dlic.rest.api; -import com.google.common.collect.ImmutableList; import org.apache.http.Header; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.support.SecurityJsonNode; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class IndexMissingTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; - - public IndexMissingTest(String endpoint){ - ENDPOINT = endpoint; - } - - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api", - PLUGINS_PREFIX + "/api" - ); - } - - @Test - public void testGetConfiguration() throws Exception { - // don't setup index for this test - init = false; - setup(); - - // test with no Security index at all - testHttpOperations(); - - } - - protected void testHttpOperations() throws Exception { - - rh.keystore = "restapi/kirk-keystore.jks"; - rh.sendAdminCertificate = true; - - // GET configuration - HttpResponse response = rh.executeGetRequest(ENDPOINT + "/roles"); - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); - String errorString = response.getBody(); - System.out.println(errorString); - Assert.assertEquals("{\"status\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Security index not initialized\"}", errorString); - - // GET roles - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", new Header[0]); - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); - errorString = response.getBody(); - Assert.assertEquals("{\"status\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Security index not initialized\"}", errorString); - - // GET rolesmapping - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", new Header[0]); - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); - errorString = response.getBody(); - Assert.assertEquals("{\"status\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Security index not initialized\"}", errorString); - - // GET actiongroups - response = rh.executeGetRequest(ENDPOINT + "/actiongroups/READ"); - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); - errorString = response.getBody(); - Assert.assertEquals("{\"status\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Security index not initialized\"}", errorString); - - // GET internalusers - response = rh.executeGetRequest(ENDPOINT + "/internalusers/picard"); - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); - errorString = response.getBody(); - Assert.assertEquals("{\"status\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Security index not initialized\"}", errorString); - - // PUT request - response = rh.executePutRequest(ENDPOINT + "/actiongroups/READ", FileHelper.loadFile("restapi/actiongroup_read.json"), new Header[0]); - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); - - // DELETE request - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", new Header[0]); - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); - - // setup index now - initialize(this.clusterHelper, this.clusterInfo); - - // GET configuration - response = rh.executeGetRequest(ENDPOINT + "/roles"); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - SecurityJsonNode securityJsonNode = new SecurityJsonNode(DefaultObjectMapper.readTree(response.getBody())); - Assert.assertEquals("OPENDISTRO_SECURITY_CLUSTER_ALL", securityJsonNode.get("opendistro_security_admin").get("cluster_permissions").get(0).asString()); - - } + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; + } + + public IndexMissingTest(){ + ENDPOINT = getEndpointPrefix() + "/api"; + } + + @Test + public void testGetConfiguration() throws Exception { + // don't setup index for this test + init = false; + setup(); + + // test with no Security index at all + testHttpOperations(); + + } + + protected void testHttpOperations() throws Exception { + + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendAdminCertificate = true; + + // GET configuration + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/roles"); + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); + String errorString = response.getBody(); + System.out.println(errorString); + Assert.assertEquals("{\"status\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Security index not initialized\"}", errorString); + + // GET roles + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", new Header[0]); + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); + errorString = response.getBody(); + Assert.assertEquals("{\"status\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Security index not initialized\"}", errorString); + + // GET rolesmapping + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", new Header[0]); + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); + errorString = response.getBody(); + Assert.assertEquals("{\"status\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Security index not initialized\"}", errorString); + + // GET actiongroups + response = rh.executeGetRequest(ENDPOINT + "/actiongroups/READ"); + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); + errorString = response.getBody(); + Assert.assertEquals("{\"status\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Security index not initialized\"}", errorString); + + // GET internalusers + response = rh.executeGetRequest(ENDPOINT + "/internalusers/picard"); + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); + errorString = response.getBody(); + Assert.assertEquals("{\"status\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Security index not initialized\"}", errorString); + + // PUT request + response = rh.executePutRequest(ENDPOINT + "/actiongroups/READ", FileHelper.loadFile("restapi/actiongroup_read.json"), new Header[0]); + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); + + // DELETE request + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", new Header[0]); + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); + + // setup index now + initialize(this.clusterHelper, this.clusterInfo); + + // GET configuration + response = rh.executeGetRequest(ENDPOINT + "/roles"); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + SecurityJsonNode securityJsonNode = new SecurityJsonNode(DefaultObjectMapper.readTree(response.getBody())); + Assert.assertEquals("OPENDISTRO_SECURITY_CLUSTER_ALL", securityJsonNode.get("opendistro_security_admin").get("cluster_permissions").get(0).asString()); + + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java index 6296bb4e6b..f72375600c 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java @@ -18,14 +18,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.apache.http.Header; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; @@ -39,27 +36,18 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; - -@RunWith(Parameterized.class) public class NodesDnApiTest extends AbstractRestApiUnitTest { private HttpResponse response; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - private final String ENDPOINT; - - public NodesDnApiTest(String endpoint){ - ENDPOINT = endpoint; + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; } - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api", - PLUGINS_PREFIX + "/api" - ); + public NodesDnApiTest(){ + ENDPOINT = getEndpointPrefix() + "/api"; } private JsonNode asJsonNode(T t) throws Exception { diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java index 9ca0f4378b..96027e6f8d 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java @@ -11,12 +11,9 @@ package org.opensearch.security.dlic.rest.api; -import com.google.common.collect.ImmutableList; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; @@ -25,254 +22,246 @@ import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class RoleBasedAccessTest extends AbstractRestApiUnitTest { + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; + } + + public RoleBasedAccessTest(){ + ENDPOINT = getEndpointPrefix() + "/api"; + } + + @Test + public void testActionGroupsApi() throws Exception { + + setupWithRestRoles(); + + rh.sendAdminCertificate = false; + + // worf and sarek have access, worf has some endpoints disabled + + // ------ GET ------ + + // --- Allowed Access --- + + // legacy user API, accessible for worf, single user + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertTrue(settings.get("admin.hash") != null); + Assert.assertEquals("", settings.get("admin.hash")); + + // new user API, accessible for worf, single user + response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertTrue(settings.get("admin.hash") != null); + + // legacy user API, accessible for worf, get complete config + response = rh.executeGetRequest(ENDPOINT + "/internalusers/", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals("", settings.get("admin.hash")); + Assert.assertEquals("", settings.get("sarek.hash")); + Assert.assertEquals("", settings.get("worf.hash")); + + // new user API, accessible for worf + response = rh.executeGetRequest(ENDPOINT + "/internalusers/", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals("", settings.get("admin.hash")); + Assert.assertEquals("", settings.get("sarek.hash")); + Assert.assertEquals("", settings.get("worf.hash")); + + // legacy user API, accessible for worf, get complete config, no trailing slash + response = rh.executeGetRequest(ENDPOINT + "/internalusers", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals("", settings.get("admin.hash")); + Assert.assertEquals("", settings.get("sarek.hash")); + Assert.assertEquals("", settings.get("worf.hash")); + + // new user API, accessible for worf, get complete config, no trailing slash + response = rh.executeGetRequest(ENDPOINT + "/internalusers", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals("", settings.get("admin.hash")); + Assert.assertEquals("", settings.get("sarek.hash")); + Assert.assertEquals("", settings.get("worf.hash")); + + // roles API, GET accessible for worf + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals("", settings.getAsList("opendistro_security_all_access.users").get(0), "nagilum"); + Assert.assertEquals("", settings.getAsList("opendistro_security_role_starfleet_library.backend_roles").get(0), "starfleet*"); + Assert.assertEquals("", settings.getAsList("opendistro_security_zdummy_all.users").get(0), "bug108"); + + + // Deprecated get configuration API, acessible for sarek + // response = rh.executeGetRequest("_opendistro/_security/api/configuration/internalusers", encodeBasicHeader("sarek", "sarek")); + // settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + // Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + // Assert.assertEquals("", settings.get("admin.hash")); + // Assert.assertEquals("", settings.get("sarek.hash")); + // Assert.assertEquals("", settings.get("worf.hash")); + + // Deprecated get configuration API, acessible for sarek + // response = rh.executeGetRequest("_opendistro/_security/api/configuration/actiongroups", encodeBasicHeader("sarek", "sarek")); + // settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + // Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + // Assert.assertEquals("", settings.getAsList("ALL").get(0), "indices:*"); + // Assert.assertEquals("", settings.getAsList("OPENDISTRO_SECURITY_CLUSTER_MONITOR").get(0), "cluster:monitor/*"); + // new format for action groups + // Assert.assertEquals("", settings.getAsList("CRUD.permissions").get(0), "READ_UT"); + + // configuration API, not accessible for worf +// response = rh.executeGetRequest("_opendistro/_security/api/configuration/actiongroups", encodeBasicHeader("worf", "worf")); +// Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); +// Assert.assertTrue(response.getBody().contains("does not have any access to endpoint CONFIGURATION")); + + // cache API, not accessible for worf since it's disabled globally + response = rh.executeDeleteRequest("_opendistro/_security/api/cache", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("does not have any access to endpoint CACHE")); + + // cache API, not accessible for sarek since it's disabled globally + response = rh.executeDeleteRequest("_opendistro/_security/api/cache", encodeBasicHeader("sarek", "sarek")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("does not have any access to endpoint CACHE")); + + // Admin user has no eligible role at all + response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("does not have any role privileged for admin access")); + + // Admin user has no eligible role at all + response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("does not have any role privileged for admin access")); + + // Admin user has no eligible role at all + response = rh.executeGetRequest(ENDPOINT + "/internalusers", encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("does not have any role privileged for admin access")); + + // Admin user has no eligible role at all + response = rh.executeGetRequest(ENDPOINT + "/roles", encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("does not have any role privileged for admin access")); + + // --- DELETE --- + + // Admin user has no eligible role at all + response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/admin", encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("does not have any role privileged for admin access")); + + // Worf, has access to internalusers API, able to delete + response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/other", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("'other' deleted")); + + // Worf, has access to internalusers API, user "other" deleted now + response = rh.executeGetRequest(ENDPOINT + "/internalusers/other", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("'other' not found")); + + // Worf, has access to roles API, get captains role + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertEquals(new SecurityJsonNode(DefaultObjectMapper.readTree(response.getBody())).getDotted("opendistro_security_role_starfleet_captains.cluster_permissions").get(0).asString(), "cluster:monitor*"); + + // Worf, has access to roles API, able to delete + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("'opendistro_security_role_starfleet_captains' deleted")); + + // Worf, has access to roles API, captains role deleted now + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("'opendistro_security_role_starfleet_captains' not found")); + + // Worf, has no DELETE access to rolemappings API + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_unittest_1", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // Worf, has no DELETE access to rolemappings API, legacy endpoint + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_unittest_1", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // --- PUT --- + + // admin, no access + response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_captains_tenants.json"), encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // worf, restore role starfleet captains + response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_captains_different_content.json"), encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + + // starfleet role present again + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertEquals(new SecurityJsonNode(DefaultObjectMapper.readTree(response.getBody())).getDotted("opendistro_security_role_starfleet_captains.index_permissions").get(0).get("allowed_actions").get(0).asString(), "blafasel"); + + // Try the same, but now with admin certificate + rh.sendAdminCertificate = true; + + // admin + response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", encodeBasicHeader("la", "lu")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertTrue(settings.get("admin.hash") != null); + Assert.assertEquals("", settings.get("admin.hash")); + + // worf and config + // response = rh.executeGetRequest("_opendistro/_security/api/configuration/actiongroups", encodeBasicHeader("bla", "fasel")); + // Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + + // cache + response = rh.executeDeleteRequest("_opendistro/_security/api/cache", encodeBasicHeader("wrong", "wrong")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + + // -- test user, does not have any endpoints disabled, but has access to API, i.e. full access + + rh.sendAdminCertificate = false; + + // GET actiongroups + // response = rh.executeGetRequest("_opendistro/_security/api/configuration/actiongroups", encodeBasicHeader("test", "test")); + // Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + + response = rh.executeGetRequest("_opendistro/_security/api/actiongroups", encodeBasicHeader("test", "test")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + + // clear cache - globally disabled, has to fail + response = rh.executeDeleteRequest("_opendistro/_security/api/cache", encodeBasicHeader("test", "test")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // PUT roles + response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_captains_different_content.json"), encodeBasicHeader("test", "test")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + + // GET captions role + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("test", "test")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - private final String ENDPOINT; - - public RoleBasedAccessTest(String endpoint){ - ENDPOINT = endpoint; - } - - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api", - PLUGINS_PREFIX + "/api" - ); - } - - @Test - public void testActionGroupsApi() throws Exception { - - setupWithRestRoles(); - - rh.sendAdminCertificate = false; - - // worf and sarek have access, worf has some endpoints disabled - - // ------ GET ------ - - // --- Allowed Access --- - - // legacy user API, accessible for worf, single user - HttpResponse response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertTrue(settings.get("admin.hash") != null); - Assert.assertEquals("", settings.get("admin.hash")); - - // new user API, accessible for worf, single user - response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertTrue(settings.get("admin.hash") != null); - - // legacy user API, accessible for worf, get complete config - response = rh.executeGetRequest(ENDPOINT + "/internalusers/", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals("", settings.get("admin.hash")); - Assert.assertEquals("", settings.get("sarek.hash")); - Assert.assertEquals("", settings.get("worf.hash")); - - // new user API, accessible for worf - response = rh.executeGetRequest(ENDPOINT + "/internalusers/", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals("", settings.get("admin.hash")); - Assert.assertEquals("", settings.get("sarek.hash")); - Assert.assertEquals("", settings.get("worf.hash")); - - // legacy user API, accessible for worf, get complete config, no trailing slash - response = rh.executeGetRequest(ENDPOINT + "/internalusers", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals("", settings.get("admin.hash")); - Assert.assertEquals("", settings.get("sarek.hash")); - Assert.assertEquals("", settings.get("worf.hash")); - - // new user API, accessible for worf, get complete config, no trailing slash - response = rh.executeGetRequest(ENDPOINT + "/internalusers", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals("", settings.get("admin.hash")); - Assert.assertEquals("", settings.get("sarek.hash")); - Assert.assertEquals("", settings.get("worf.hash")); - - // roles API, GET accessible for worf - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals("", settings.getAsList("opendistro_security_all_access.users").get(0), "nagilum"); - Assert.assertEquals("", settings.getAsList("opendistro_security_role_starfleet_library.backend_roles").get(0), "starfleet*"); - Assert.assertEquals("", settings.getAsList("opendistro_security_zdummy_all.users").get(0), "bug108"); - - - // Deprecated get configuration API, acessible for sarek - // response = rh.executeGetRequest("_opendistro/_security/api/configuration/internalusers", encodeBasicHeader("sarek", "sarek")); - // settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - // Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - // Assert.assertEquals("", settings.get("admin.hash")); - // Assert.assertEquals("", settings.get("sarek.hash")); - // Assert.assertEquals("", settings.get("worf.hash")); - - // Deprecated get configuration API, acessible for sarek - // response = rh.executeGetRequest("_opendistro/_security/api/configuration/actiongroups", encodeBasicHeader("sarek", "sarek")); - // settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - // Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - // Assert.assertEquals("", settings.getAsList("ALL").get(0), "indices:*"); - // Assert.assertEquals("", settings.getAsList("OPENDISTRO_SECURITY_CLUSTER_MONITOR").get(0), "cluster:monitor/*"); - // new format for action groups - // Assert.assertEquals("", settings.getAsList("CRUD.permissions").get(0), "READ_UT"); - - // configuration API, not accessible for worf -// response = rh.executeGetRequest("_opendistro/_security/api/configuration/actiongroups", encodeBasicHeader("worf", "worf")); -// Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); -// Assert.assertTrue(response.getBody().contains("does not have any access to endpoint CONFIGURATION")); - - // cache API, not accessible for worf since it's disabled globally - response = rh.executeDeleteRequest("_opendistro/_security/api/cache", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("does not have any access to endpoint CACHE")); - - // cache API, not accessible for sarek since it's disabled globally - response = rh.executeDeleteRequest("_opendistro/_security/api/cache", encodeBasicHeader("sarek", "sarek")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("does not have any access to endpoint CACHE")); - - // Admin user has no eligible role at all - response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("does not have any role privileged for admin access")); - - // Admin user has no eligible role at all - response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("does not have any role privileged for admin access")); - - // Admin user has no eligible role at all - response = rh.executeGetRequest(ENDPOINT + "/internalusers", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("does not have any role privileged for admin access")); - - // Admin user has no eligible role at all - response = rh.executeGetRequest(ENDPOINT + "/roles", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("does not have any role privileged for admin access")); - - // --- DELETE --- - - // Admin user has no eligible role at all - response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/admin", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("does not have any role privileged for admin access")); - - // Worf, has access to internalusers API, able to delete - response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/other", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("'other' deleted")); - - // Worf, has access to internalusers API, user "other" deleted now - response = rh.executeGetRequest(ENDPOINT + "/internalusers/other", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("'other' not found")); - - // Worf, has access to roles API, get captains role - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertEquals(new SecurityJsonNode(DefaultObjectMapper.readTree(response.getBody())).getDotted("opendistro_security_role_starfleet_captains.cluster_permissions").get(0).asString(), "cluster:monitor*"); - - // Worf, has access to roles API, able to delete - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("'opendistro_security_role_starfleet_captains' deleted")); - - // Worf, has access to roles API, captains role deleted now - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("'opendistro_security_role_starfleet_captains' not found")); - - // Worf, has no DELETE access to rolemappings API - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_unittest_1", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - - // Worf, has no DELETE access to rolemappings API, legacy endpoint - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_unittest_1", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - - // --- PUT --- - - // admin, no access - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_tenants.json"), encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - - // worf, restore role starfleet captains - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_different_content.json"), encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - - // starfleet role present again - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertEquals(new SecurityJsonNode(DefaultObjectMapper.readTree(response.getBody())).getDotted("opendistro_security_role_starfleet_captains.index_permissions").get(0).get("allowed_actions").get(0).asString(), "blafasel"); - - // Try the same, but now with admin certificate - rh.sendAdminCertificate = true; - - // admin - response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", encodeBasicHeader("la", "lu")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertTrue(settings.get("admin.hash") != null); - Assert.assertEquals("", settings.get("admin.hash")); - - // worf and config - // response = rh.executeGetRequest("_opendistro/_security/api/configuration/actiongroups", encodeBasicHeader("bla", "fasel")); - // Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - - // cache - response = rh.executeDeleteRequest("_opendistro/_security/api/cache", encodeBasicHeader("wrong", "wrong")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - - // -- test user, does not have any endpoints disabled, but has access to API, i.e. full access - - rh.sendAdminCertificate = false; - - // GET actiongroups - // response = rh.executeGetRequest("_opendistro/_security/api/configuration/actiongroups", encodeBasicHeader("test", "test")); - // Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - - response = rh.executeGetRequest("_opendistro/_security/api/actiongroups", encodeBasicHeader("test", "test")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - - // clear cache - globally disabled, has to fail - response = rh.executeDeleteRequest("_opendistro/_security/api/cache", encodeBasicHeader("test", "test")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - - // PUT roles - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_different_content.json"), encodeBasicHeader("test", "test")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - - // GET captions role - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("test", "test")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + // Delete captions role + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("test", "test")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("'opendistro_security_role_starfleet_captains' deleted")); - // Delete captions role - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("test", "test")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("'opendistro_security_role_starfleet_captains' deleted")); + // GET captions role + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("test", "test")); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - // GET captions role - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("test", "test")); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - } + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java index 016ba4903a..8dc18f5043 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java @@ -14,13 +14,10 @@ import java.util.List; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.ImmutableList; import org.apache.http.Header; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; @@ -30,27 +27,18 @@ import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class RolesApiTest extends AbstractRestApiUnitTest { - - private final String ENDPOINT; - - public RolesApiTest(String endpoint){ - ENDPOINT = endpoint; + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; } - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api", - PLUGINS_PREFIX + "/api" - ); + public RolesApiTest(){ + ENDPOINT = getEndpointPrefix() + "/api"; } - @Test public void testPutRole() throws Exception { @@ -285,10 +273,10 @@ public void testRolesApi() throws Exception { FileHelper.loadFile("restapi/roles_complete_invalid.json"), new Header[0]); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); -// rh.sendAdminCertificate = true; -// response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", -// FileHelper.loadFile("restapi/roles_multiple.json"), new Header[0]); -// Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); +// rh.sendAdminCertificate = true; +// response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", +// FileHelper.loadFile("restapi/roles_multiple.json"), new Header[0]); +// Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", FileHelper.loadFile("restapi/roles_multiple_2.json"), new Header[0]); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java index f09d7284ee..2d1f10736d 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java @@ -13,13 +13,10 @@ import java.util.List; -import com.google.common.collect.ImmutableList; import org.apache.http.Header; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; @@ -27,412 +24,404 @@ import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class RolesMappingApiTest extends AbstractRestApiUnitTest { + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; + } - private final String ENDPOINT; + public RolesMappingApiTest(){ + ENDPOINT = getEndpointPrefix() + "/api"; + } - public RolesMappingApiTest(String endpoint){ - ENDPOINT = endpoint; - } + @Test + public void testRolesMappingApi() throws Exception { + + setup(); - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api", - PLUGINS_PREFIX + "/api" - ); - } + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendAdminCertificate = true; - @Test - public void testRolesMappingApi() throws Exception { + // check rolesmapping exists, old config api + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/rolesmapping"); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - setup(); + // check rolesmapping exists, new API + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping"); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getContentType(), response.isJsonContentType()); - rh.keystore = "restapi/kirk-keystore.jks"; - rh.sendAdminCertificate = true; - - // check rolesmapping exists, old config api - HttpResponse response = rh.executeGetRequest(ENDPOINT + "/rolesmapping"); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - - // check rolesmapping exists, new API - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping"); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getContentType(), response.isJsonContentType()); - - // Superadmin should be able to see hidden rolesmapping - Assert.assertTrue(response.getBody().contains("opendistro_security_hidden")); - - // Superadmin should be able to see reserved rolesmapping - Assert.assertTrue(response.getBody().contains("opendistro_security_reserved")); - - - // -- GET - - // GET opendistro_security_role_starfleet, exists - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getContentType(), response.isJsonContentType()); - Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals("starfleet", settings.getAsList("opendistro_security_role_starfleet.backend_roles").get(0)); - Assert.assertEquals("captains", settings.getAsList("opendistro_security_role_starfleet.backend_roles").get(1)); - Assert.assertEquals("*.starfleetintranet.com", settings.getAsList("opendistro_security_role_starfleet.hosts").get(0)); - Assert.assertEquals("nagilum", settings.getAsList("opendistro_security_role_starfleet.users").get(0)); - - // GET, rolesmapping does not exist - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/nothinghthere", new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // GET, new URL endpoint in security - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getContentType(), response.isJsonContentType()); - - // GET, new URL endpoint in security - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getContentType(), response.isJsonContentType()); - - // Super admin should be able to describe particular hidden rolemapping - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("\"hidden\":true")); - - // create index - setupStarfleetIndex(); - - // add user picard, role captains initially maps to - // opendistro_security_role_starfleet_captains and opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[] { "captains" }, HttpStatus.SC_CREATED); - checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); - - // TODO: only one doctype allowed for ES6 - //checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); - - // --- DELETE - - rh.sendAdminCertificate = true; - - // Non-existing role - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/idonotexist", new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // read only role - // SuperAdmin can delete read only role - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - - // hidden role - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("'opendistro_security_internal' deleted.")); - - // remove complete role mapping for opendistro_security_role_starfleet_captains - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/configuration/rolesmapping"); - rh.sendAdminCertificate = false; - - // now picard is only in opendistro_security_role_starfleet, which has write access to - // public, but not to _doc - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); - - // TODO: only one doctype allowed for ES6 - // checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 1); - - // remove also opendistro_security_role_starfleet, poor picard has no mapping left - rh.sendAdminCertificate = true; - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - rh.sendAdminCertificate = false; - checkAllSfForbidden(); - - rh.sendAdminCertificate = true; - - // --- PUT - - // put with empty mapping, must fail - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", "", new Header[0]); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason")); - - // put new configuration with invalid payload, must fail - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_not_parseable.json"), new Header[0]); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason")); - - // put new configuration with invalid keys, must fail - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_invalid_keys.json"), new Header[0]); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage(), settings.get("reason")); - Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("theusers")); - Assert.assertTrue( - settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("thebackendroles")); - Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("thehosts")); - - // wrong datatypes - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_backendroles_captains_single_wrong_datatype.json"), new Header[0]); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); - Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); - Assert.assertTrue(settings.get("hosts") == null); - Assert.assertTrue(settings.get("users") == null); - - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_hosts_single_wrong_datatype.json"), new Header[0]); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); - Assert.assertTrue(settings.get("hosts").equals("Array expected")); - Assert.assertTrue(settings.get("backend_roles") == null); - Assert.assertTrue(settings.get("users") == null); - - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_users_picard_single_wrong_datatype.json"), new Header[0]); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); - Assert.assertTrue(settings.get("hosts").equals("Array expected")); - Assert.assertTrue(settings.get("users").equals("Array expected")); - Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); - - // Read only role mapping - // SuperAdmin can add read only roles - mappings - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - - // hidden role, allowed for super admin - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - - // -- PATCH - // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // PATCH read only resource, must be forbidden - // SuperAdmin can patch read-only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\"] }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - - // PATCH hidden resource, must be not found, can be found by super admin - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ " + - "\"foo\", \"bar\" ] }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - - // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); - - // PATCH - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", "[{ \"op\": \"add\", \"path\": \"/backend_roles/-\", \"value\": \"spring\" }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - List permissions = settings.getAsList("opendistro_security_role_vulcans.backend_roles"); - Assert.assertNotNull(permissions); - Assert.assertTrue(permissions.contains("spring")); - - // -- PATCH on whole config resource - // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - - // PATCH read only resource, must be forbidden - // SuperAdmin can patch read only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - - // PATCH hidden resource, must be bad request - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - - // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_vulcans/hidden\", \"value\": true }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); - - // PATCH - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"backend_roles\":[\"vulcanadmin\"]} }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - permissions = settings.getAsList("bulknew1.backend_roles"); - Assert.assertNotNull(permissions); - Assert.assertTrue(permissions.contains("vulcanadmin")); - - // PATCH delete - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"remove\", \"path\": \"/bulknew1\"}]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - - // mapping with several backend roles, one of the is captain - deleteAndputNewMapping("rolesmapping_backendroles_captains_list.json"); - checkAllSfAllowed(); - - // mapping with one backend role, captain - deleteAndputNewMapping("rolesmapping_backendroles_captains_single.json"); - checkAllSfAllowed(); - - // mapping with several users, one is picard - deleteAndputNewMapping("rolesmapping_users_picard_list.json"); - checkAllSfAllowed(); - - // just user picard - deleteAndputNewMapping("rolesmapping_users_picard_single.json"); - checkAllSfAllowed(); - - // hosts - deleteAndputNewMapping("rolesmapping_hosts_list.json"); - checkAllSfAllowed(); - - // hosts - deleteAndputNewMapping("rolesmapping_hosts_single.json"); - checkAllSfAllowed(); - - // full settings, access - deleteAndputNewMapping("rolesmapping_all_access.json"); - checkAllSfAllowed(); - - // full settings, no access - deleteAndputNewMapping("rolesmapping_all_noaccess.json"); - checkAllSfForbidden(); - - } - - private void checkAllSfAllowed() throws Exception { - rh.sendAdminCertificate = false; - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 1); - checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 1); - } - - private void checkAllSfForbidden() throws Exception { - rh.sendAdminCertificate = false; - checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); - } - - private HttpResponse deleteAndputNewMapping(String fileName) throws Exception { - rh.sendAdminCertificate = true; - HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/"+fileName), new Header[0]); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - rh.sendAdminCertificate = false; - return response; - } - - @Test - public void testRolesMappingApiForNonSuperAdmin() throws Exception { - - setupWithRestRoles(); - - rh.keystore = "restapi/kirk-keystore.jks"; - rh.sendAdminCertificate = false; - rh.sendHTTPClientCredentials = true; - - HttpResponse response; - - // Delete read only roles mapping - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library" , new Header[0]); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - - // Put read only roles mapping - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - - // Patch single read only roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - - // Patch multiple read only roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - - // GET, rolesmapping is hidden, allowed for super admin - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // Delete hidden roles mapping - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal" , new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // Put hidden roles mapping - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // Patch hidden roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // Patch multiple hidden roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - } - - @Test - public void checkNullElementsInArray() throws Exception{ - setup(); - rh.keystore = "restapi/kirk-keystore.jks"; - rh.sendAdminCertificate = true; - - String body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_users.json"); - HttpResponse response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - body, new Header[0]); - Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); - - body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_backend_roles.json"); - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - body, new Header[0]); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); - - body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_hosts.json"); - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - body, new Header[0]); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); - } + // Superadmin should be able to see hidden rolesmapping + Assert.assertTrue(response.getBody().contains("opendistro_security_hidden")); + + // Superadmin should be able to see reserved rolesmapping + Assert.assertTrue(response.getBody().contains("opendistro_security_reserved")); + + + // -- GET + + // GET opendistro_security_role_starfleet, exists + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getContentType(), response.isJsonContentType()); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals("starfleet", settings.getAsList("opendistro_security_role_starfleet.backend_roles").get(0)); + Assert.assertEquals("captains", settings.getAsList("opendistro_security_role_starfleet.backend_roles").get(1)); + Assert.assertEquals("*.starfleetintranet.com", settings.getAsList("opendistro_security_role_starfleet.hosts").get(0)); + Assert.assertEquals("nagilum", settings.getAsList("opendistro_security_role_starfleet.users").get(0)); + + // GET, rolesmapping does not exist + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/nothinghthere", new Header[0]); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + + // GET, new URL endpoint in security + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getContentType(), response.isJsonContentType()); + + // GET, new URL endpoint in security + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getContentType(), response.isJsonContentType()); + + // Super admin should be able to describe particular hidden rolemapping + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hidden\":true")); + + // create index + setupStarfleetIndex(); + + // add user picard, role captains initially maps to + // opendistro_security_role_starfleet_captains and opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[] { "captains" }, HttpStatus.SC_CREATED); + checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + + // TODO: only one doctype allowed for ES6 + //checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + + // --- DELETE + + rh.sendAdminCertificate = true; + + // Non-existing role + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/idonotexist", new Header[0]); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + + // read only role + // SuperAdmin can delete read only role + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + + // hidden role + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("'opendistro_security_internal' deleted.")); + + // remove complete role mapping for opendistro_security_role_starfleet_captains + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + response = rh.executeGetRequest(ENDPOINT + "/configuration/rolesmapping"); + rh.sendAdminCertificate = false; + + // now picard is only in opendistro_security_role_starfleet, which has write access to + // public, but not to _doc + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); + + // TODO: only one doctype allowed for ES6 + // checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 1); + + // remove also opendistro_security_role_starfleet, poor picard has no mapping left + rh.sendAdminCertificate = true; + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + rh.sendAdminCertificate = false; + checkAllSfForbidden(); + + rh.sendAdminCertificate = true; + + // --- PUT + + // put with empty mapping, must fail + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", "", new Header[0]); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason")); + + // put new configuration with invalid payload, must fail + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/rolesmapping_not_parseable.json"), new Header[0]); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason")); + + // put new configuration with invalid keys, must fail + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/rolesmapping_invalid_keys.json"), new Header[0]); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertEquals(AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage(), settings.get("reason")); + Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("theusers")); + Assert.assertTrue( + settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("thebackendroles")); + Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("thehosts")); + + // wrong datatypes + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/rolesmapping_backendroles_captains_single_wrong_datatype.json"), new Header[0]); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); + Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); + Assert.assertTrue(settings.get("hosts") == null); + Assert.assertTrue(settings.get("users") == null); + + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/rolesmapping_hosts_single_wrong_datatype.json"), new Header[0]); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); + Assert.assertTrue(settings.get("hosts").equals("Array expected")); + Assert.assertTrue(settings.get("backend_roles") == null); + Assert.assertTrue(settings.get("users") == null); + + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/rolesmapping_users_picard_single_wrong_datatype.json"), new Header[0]); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); + Assert.assertTrue(settings.get("hosts").equals("Array expected")); + Assert.assertTrue(settings.get("users").equals("Array expected")); + Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); + + // Read only role mapping + // SuperAdmin can add read only roles - mappings + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + // hidden role, allowed for super admin + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + // -- PATCH + // PATCH on non-existing resource + rh.sendAdminCertificate = true; + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + + // PATCH read only resource, must be forbidden + // SuperAdmin can patch read-only resource + rh.sendAdminCertificate = true; + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\"] }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + + // PATCH hidden resource, must be not found, can be found by super admin + rh.sendAdminCertificate = true; + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ " + + "\"foo\", \"bar\" ] }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + + // PATCH value of hidden flag, must fail with validation error + rh.sendAdminCertificate = true; + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); + + // PATCH + rh.sendAdminCertificate = true; + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", "[{ \"op\": \"add\", \"path\": \"/backend_roles/-\", \"value\": \"spring\" }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + List permissions = settings.getAsList("opendistro_security_role_vulcans.backend_roles"); + Assert.assertNotNull(permissions); + Assert.assertTrue(permissions.contains("spring")); + + // -- PATCH on whole config resource + // PATCH on non-existing resource + rh.sendAdminCertificate = true; + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + + // PATCH read only resource, must be forbidden + // SuperAdmin can patch read only resource + rh.sendAdminCertificate = true; + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + + // PATCH hidden resource, must be bad request + rh.sendAdminCertificate = true; + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + + // PATCH value of hidden flag, must fail with validation error + rh.sendAdminCertificate = true; + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_vulcans/hidden\", \"value\": true }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); + + // PATCH + rh.sendAdminCertificate = true; + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"backend_roles\":[\"vulcanadmin\"]} }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + permissions = settings.getAsList("bulknew1.backend_roles"); + Assert.assertNotNull(permissions); + Assert.assertTrue(permissions.contains("vulcanadmin")); + + // PATCH delete + rh.sendAdminCertificate = true; + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"remove\", \"path\": \"/bulknew1\"}]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", new Header[0]); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + + + // mapping with several backend roles, one of the is captain + deleteAndputNewMapping("rolesmapping_backendroles_captains_list.json"); + checkAllSfAllowed(); + + // mapping with one backend role, captain + deleteAndputNewMapping("rolesmapping_backendroles_captains_single.json"); + checkAllSfAllowed(); + + // mapping with several users, one is picard + deleteAndputNewMapping("rolesmapping_users_picard_list.json"); + checkAllSfAllowed(); + + // just user picard + deleteAndputNewMapping("rolesmapping_users_picard_single.json"); + checkAllSfAllowed(); + + // hosts + deleteAndputNewMapping("rolesmapping_hosts_list.json"); + checkAllSfAllowed(); + + // hosts + deleteAndputNewMapping("rolesmapping_hosts_single.json"); + checkAllSfAllowed(); + + // full settings, access + deleteAndputNewMapping("rolesmapping_all_access.json"); + checkAllSfAllowed(); + + // full settings, no access + deleteAndputNewMapping("rolesmapping_all_noaccess.json"); + checkAllSfForbidden(); + + } + + private void checkAllSfAllowed() throws Exception { + rh.sendAdminCertificate = false; + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 1); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 1); + } + + private void checkAllSfForbidden() throws Exception { + rh.sendAdminCertificate = false; + checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); + } + + private HttpResponse deleteAndputNewMapping(String fileName) throws Exception { + rh.sendAdminCertificate = true; + HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/"+fileName), new Header[0]); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + rh.sendAdminCertificate = false; + return response; + } + + @Test + public void testRolesMappingApiForNonSuperAdmin() throws Exception { + + setupWithRestRoles(); + + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendAdminCertificate = false; + rh.sendHTTPClientCredentials = true; + + HttpResponse response; + + // Delete read only roles mapping + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library" , new Header[0]); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // Put read only roles mapping + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // Patch single read only roles mapping + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // Patch multiple read only roles mapping + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // GET, rolesmapping is hidden, allowed for super admin + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", new Header[0]); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + + // Delete hidden roles mapping + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal" , new Header[0]); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + + // Put hidden roles mapping + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + + // Patch hidden roles mapping + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + + // Patch multiple hidden roles mapping + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", new Header[0]); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + + } + + @Test + public void checkNullElementsInArray() throws Exception{ + setup(); + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendAdminCertificate = true; + + String body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_users.json"); + HttpResponse response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + body, new Header[0]); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + + body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_backend_roles.json"); + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + body, new Header[0]); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + + body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_hosts.json"); + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + body, new Header[0]); + settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java index 9883a74d1d..4e8808e811 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java @@ -11,55 +11,44 @@ package org.opensearch.security.dlic.rest.api; -import com.google.common.collect.ImmutableList; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class SecurityApiAccessTest extends AbstractRestApiUnitTest { - - private final String ENDPOINT; - - public SecurityApiAccessTest(String endpoint){ - ENDPOINT = endpoint; - } - - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api/internalusers", - PLUGINS_PREFIX + "/api/internalusers" - ); - } - - @Test - public void testRestApi() throws Exception { - - setup(); - - // test with no cert, must fail - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, - rh.executeGetRequest(ENDPOINT).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, - rh.executeGetRequest(ENDPOINT, - encodeBasicHeader("admin", "admin")) - .getStatusCode()); - - // test with non-admin cert, must fail - rh.keystore = "restapi/node-0-keystore.jks"; - rh.sendAdminCertificate = true; - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, - rh.executeGetRequest(ENDPOINT).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, - rh.executeGetRequest(ENDPOINT, - encodeBasicHeader("admin", "admin")) - .getStatusCode()); - - } + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; + } + + public SecurityApiAccessTest(){ + ENDPOINT = getEndpointPrefix() + "/api/internalusers"; + } + + @Test + public void testRestApi() throws Exception { + + setup(); + + // test with no cert, must fail + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest(ENDPOINT).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest(ENDPOINT, + encodeBasicHeader("admin", "admin")) + .getStatusCode()); + + // test with non-admin cert, must fail + rh.keystore = "restapi/node-0-keystore.jks"; + rh.sendAdminCertificate = true; + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest(ENDPOINT).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest(ENDPOINT, + encodeBasicHeader("admin", "admin")) + .getStatusCode()); + + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java index 9338e3bc33..f5742cfecd 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java @@ -11,13 +11,10 @@ package org.opensearch.security.dlic.rest.api; -import com.google.common.collect.ImmutableList; import org.apache.http.Header; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.security.DefaultObjectMapper; @@ -25,24 +22,16 @@ import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class SecurityConfigApiTest extends AbstractRestApiUnitTest { - - private final String ENDPOINT; - - public SecurityConfigApiTest(String endpoint){ - ENDPOINT = endpoint; + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; } - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api", - PLUGINS_PREFIX + "/api" - ); + public SecurityConfigApiTest(){ + ENDPOINT = getEndpointPrefix() + "/api"; } @Test diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java index f0145a0cb1..54aeb6d8a1 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java @@ -11,34 +11,24 @@ package org.opensearch.security.dlic.rest.api; -import com.google.common.collect.ImmutableList; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.rest.RestHelper; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class SecurityHealthActionTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; - - public SecurityHealthActionTest(String endpoint){ - ENDPOINT = endpoint; + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; } - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "", - PLUGINS_PREFIX + "" - ); + public SecurityHealthActionTest(){ + ENDPOINT = getEndpointPrefix(); } @Test diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java index 10de3b495a..0743cd4d95 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java @@ -11,34 +11,24 @@ package org.opensearch.security.dlic.rest.api; -import com.google.common.collect.ImmutableList; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.rest.RestHelper; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class SecurityInfoActionTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; - - public SecurityInfoActionTest(String endpoint){ - ENDPOINT = endpoint; + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; } - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/authinfo", - PLUGINS_PREFIX + "/authinfo" - ); + public SecurityInfoActionTest(){ + ENDPOINT = getEndpointPrefix() + "/authinfo"; } @Test diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java index 8986f40fec..01004faba7 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java @@ -11,45 +11,34 @@ package org.opensearch.security.dlic.rest.api; -import com.google.common.collect.ImmutableList; import org.apache.http.Header; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.rest.RestHelper; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class TenantInfoActionTest extends AbstractRestApiUnitTest { private String payload = "{\"hosts\":[],\"users\":[\"sarek\"]," + "\"backend_roles\":[\"starfleet*\",\"ambassador\"],\"and_backend_roles\":[],\"description\":\"Migrated " + "from v6\"}"; - private final String BASE_ENDPOINT; - private final String ENDPOINT; - - - public TenantInfoActionTest(String endpoint){ - BASE_ENDPOINT = endpoint; - ENDPOINT = BASE_ENDPOINT + "/tenantinfo"; + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; } - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX, - PLUGINS_PREFIX - ); + public TenantInfoActionTest(){ + BASE_ENDPOINT = getEndpointPrefix(); + ENDPOINT = getEndpointPrefix() + "/tenantinfo"; } + @Test - public void testTenantInfoAPI() throws Exception { + public void testTenantInfoAPIAccess() throws Exception { Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build(); setup(settings); @@ -65,11 +54,18 @@ public void testTenantInfoAPI() throws Exception { rh.sendHTTPClientCredentials = true; response = rh.executeGetRequest(ENDPOINT); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + @Test + public void testTenantInfoAPIUpdate() throws Exception { + Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build(); + setup(settings); + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendHTTPClientCredentials = true; rh.sendAdminCertificate = true; //update security config - response = rh.executePatchRequest(BASE_ENDPOINT + "/api/securityconfig", "[{\"op\": \"add\",\"path\": \"/config/dynamic/kibana/opendistro_role\"," + + RestHelper.HttpResponse response = rh.executePatchRequest(BASE_ENDPOINT + "/api/securityconfig", "[{\"op\": \"add\",\"path\": \"/config/dynamic/kibana/opendistro_role\"," + "\"value\": \"opendistro_security_internal\"}]", new Header[0]); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index 622b202833..e81e42c25c 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -14,14 +14,11 @@ import java.net.URLEncoder; import java.util.List; -import com.google.common.collect.ImmutableList; import org.apache.http.Header; import org.apache.http.HttpStatus; import org.apache.http.message.BasicHeader; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; @@ -31,24 +28,16 @@ import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class UserApiTest extends AbstractRestApiUnitTest { - - private final String ENDPOINT; - - public UserApiTest(String endpoint){ - ENDPOINT = endpoint; + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; } - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api", - PLUGINS_PREFIX + "/api" - ); + public UserApiTest(){ + ENDPOINT = getEndpointPrefix() + "/api"; } @Test diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java index 84d804e575..b5c0d98fc2 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java @@ -11,19 +11,15 @@ package org.opensearch.security.dlic.rest.api; - import java.util.Map; import java.util.stream.Collectors; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.apache.http.Header; import org.apache.http.HttpStatus; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.security.DefaultObjectMapper; @@ -40,14 +36,12 @@ import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; /** * Testing class to verify that {@link WhitelistApiAction} works correctly. * Check {@link SecurityRestFilter} for extra tests for whitelisting functionality. */ -@RunWith(Parameterized.class) public class WhitelistApiTest extends AbstractRestApiUnitTest { private RestHelper.HttpResponse response; @@ -57,19 +51,13 @@ public class WhitelistApiTest extends AbstractRestApiUnitTest { */ private final Header adminCredsHeader = encodeBasicHeader("admin_all_access", "admin_all_access"); private final Header nonAdminCredsHeader = encodeBasicHeader("sarek", "sarek"); - - private final String ENDPOINT; - - public WhitelistApiTest(String endpoint){ - ENDPOINT = endpoint; + private final String ENDPOINT; + protected String getEndpointPrefix() { + return PLUGINS_PREFIX; } - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api", - PLUGINS_PREFIX + "/api" - ); + public WhitelistApiTest(){ + ENDPOINT = getEndpointPrefix() + "/api"; } /** diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAccountApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAccountApiTests.java new file mode 100644 index 0000000000..a48a7d2e3a --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAccountApiTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.AccountApiTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyAccountApiTests extends AccountApiTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyActionGroupsApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyActionGroupsApiTests.java new file mode 100644 index 0000000000..9aa4b70c77 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyActionGroupsApiTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.ActionGroupsApiTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyActionGroupsApiTests extends ActionGroupsApiTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAuditApiActionTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAuditApiActionTests.java new file mode 100644 index 0000000000..4d97da8bbb --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAuditApiActionTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.AuditApiActionTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyAuditApiActionTests extends AuditApiActionTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyDashboardsInfoActionTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyDashboardsInfoActionTests.java new file mode 100644 index 0000000000..a9baec37bd --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyDashboardsInfoActionTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.DashboardsInfoActionTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyDashboardsInfoActionTests extends DashboardsInfoActionTest { + @Override + protected String getEndpoint() { + return LEGACY_OPENDISTRO_PREFIX + "/kibanainfo"; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyFlushCacheApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyFlushCacheApiTests.java new file mode 100644 index 0000000000..ab09a6e2f2 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyFlushCacheApiTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.FlushCacheApiTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyFlushCacheApiTests extends FlushCacheApiTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyGetConfigurationApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyGetConfigurationApiTests.java new file mode 100644 index 0000000000..cca6739733 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyGetConfigurationApiTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.GetConfigurationApiTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyGetConfigurationApiTests extends GetConfigurationApiTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyIndexMissingTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyIndexMissingTests.java new file mode 100644 index 0000000000..0680aa2c2e --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyIndexMissingTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.IndexMissingTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyIndexMissingTests extends IndexMissingTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyNodesDnApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyNodesDnApiTests.java new file mode 100644 index 0000000000..22237ece3f --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyNodesDnApiTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.NodesDnApiTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyNodesDnApiTests extends NodesDnApiTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRoleBasedAccessTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRoleBasedAccessTests.java new file mode 100644 index 0000000000..c9f421058c --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRoleBasedAccessTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.RoleBasedAccessTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyRoleBasedAccessTests extends RoleBasedAccessTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesApiTests.java new file mode 100644 index 0000000000..b4ec33a2d5 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesApiTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.RolesApiTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyRolesApiTests extends RolesApiTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesMappingApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesMappingApiTests.java new file mode 100644 index 0000000000..c659fb57bc --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesMappingApiTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.RolesMappingApiTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyRolesMappingApiTests extends RolesMappingApiTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityApiAccessTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityApiAccessTests.java new file mode 100644 index 0000000000..72b6086c1e --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityApiAccessTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.SecurityApiAccessTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacySecurityApiAccessTests extends SecurityApiAccessTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityConfigApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityConfigApiTests.java new file mode 100644 index 0000000000..fd03e7248a --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityConfigApiTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.SecurityConfigApiTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacySecurityConfigApiTests extends SecurityConfigApiTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityHealthActionTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityHealthActionTests.java new file mode 100644 index 0000000000..470db0a526 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityHealthActionTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.SecurityHealthActionTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacySecurityHealthActionTests extends SecurityHealthActionTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityInfoActionTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityInfoActionTests.java new file mode 100644 index 0000000000..8480787423 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityInfoActionTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.SecurityInfoActionTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacySecurityInfoActionTests extends SecurityInfoActionTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyTenantInfoActionTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyTenantInfoActionTests.java new file mode 100644 index 0000000000..1f2ac9a77d --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyTenantInfoActionTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.TenantInfoActionTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyTenantInfoActionTests extends TenantInfoActionTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyUserApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyUserApiTests.java new file mode 100644 index 0000000000..5753688097 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyUserApiTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.UserApiTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyUserApiTests extends UserApiTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyWhitelistApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyWhitelistApiTests.java new file mode 100644 index 0000000000..3ae501f9a4 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyWhitelistApiTests.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.WhitelistApiTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacyWhitelistApiTests extends WhitelistApiTest { + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } +} diff --git a/src/test/java/org/opensearch/security/filter/SecurityFilterTest.java b/src/test/java/org/opensearch/security/filter/SecurityFilterTest.java index 98b46a6548..9430450875 100644 --- a/src/test/java/org/opensearch/security/filter/SecurityFilterTest.java +++ b/src/test/java/org/opensearch/security/filter/SecurityFilterTest.java @@ -36,9 +36,11 @@ import org.opensearch.security.support.WildcardMatcher; import org.opensearch.threadpool.ThreadPool; -import static org.junit.Assert.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -84,7 +86,7 @@ public void testImmutableIndicesWildcardMatcher() { mock(IndexResolverReplacer.class), mock(XFFResolver.class) ); - assertEquals(expected, filter.getImmutableIndicesMatcher()); + assertThat(expected, equalTo(filter.getImmutableIndicesMatcher())); } @SuppressWarnings("unchecked") @@ -117,8 +119,8 @@ public void testUnexepectedCausesAreNotSendToCallers() { final ArgumentCaptor cap = ArgumentCaptor.forClass(OpenSearchSecurityException.class); verify(listener).onFailure(cap.capture()); - assertNull(cap.getValue().getCause(), "The cause should never be included as it will leak to callers"); - assertFalse(cap.getValue().getMessage().contains("ABC!"), "Make sure the cause exception wasn't toStringed in the method"); + assertThat("The cause should never be included as it will leak to callers", cap.getValue().getCause(), nullValue()); + assertThat("Make sure the cause exception wasn't toStringed in the method", cap.getValue().getMessage(), not(containsString("ABC!"))); verifyNoMoreInteractions(auditLog, listener); } diff --git a/src/test/java/org/opensearch/security/securityconf/impl/v6/ConfigV6Test.java b/src/test/java/org/opensearch/security/securityconf/impl/v6/ConfigV6Test.java index 2b87974f9f..245127995e 100644 --- a/src/test/java/org/opensearch/security/securityconf/impl/v6/ConfigV6Test.java +++ b/src/test/java/org/opensearch/security/securityconf/impl/v6/ConfigV6Test.java @@ -1,3 +1,14 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + package org.opensearch.security.securityconf.impl.v6; import com.fasterxml.jackson.databind.JsonNode; diff --git a/src/test/java/org/opensearch/security/securityconf/impl/v7/ConfigV7Test.java b/src/test/java/org/opensearch/security/securityconf/impl/v7/ConfigV7Test.java index 0ec6e6a4e6..92af5aeebd 100644 --- a/src/test/java/org/opensearch/security/securityconf/impl/v7/ConfigV7Test.java +++ b/src/test/java/org/opensearch/security/securityconf/impl/v7/ConfigV7Test.java @@ -1,3 +1,14 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + package org.opensearch.security.securityconf.impl.v7; import com.fasterxml.jackson.databind.JsonNode; diff --git a/src/test/java/org/opensearch/security/ssl/SecuritySSLCertsInfoActionTests.java b/src/test/java/org/opensearch/security/ssl/SecuritySSLCertsInfoActionTests.java index 1f1f5e7d41..c9618e6463 100644 --- a/src/test/java/org/opensearch/security/ssl/SecuritySSLCertsInfoActionTests.java +++ b/src/test/java/org/opensearch/security/ssl/SecuritySSLCertsInfoActionTests.java @@ -19,8 +19,6 @@ import net.minidev.json.JSONObject; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.opensearch.common.settings.Settings; import org.opensearch.security.ssl.util.SSLConfigConstants; @@ -32,22 +30,7 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -@RunWith(Parameterized.class) public class SecuritySSLCertsInfoActionTests extends SingleClusterTest { - private final String ENDPOINT; - - public SecuritySSLCertsInfoActionTests(String endpoint){ - ENDPOINT = endpoint; - } - - @Parameterized.Parameters - public static Iterable endpoints() { - return ImmutableList.of( - LEGACY_OPENDISTRO_PREFIX + "/api/ssl/certs", - PLUGINS_PREFIX + "/api/ssl/certs" - ); - } - private final List> NODE_CERT_DETAILS = ImmutableList.of( ImmutableMap.of( "issuer_dn", "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", @@ -57,8 +40,17 @@ public static Iterable endpoints() { "not_after","2028-05-02T14:37:09Z" )); + @Test + public void testCertInfo_Legacy_Pass() throws Exception { + certInfo_Pass(LEGACY_OPENDISTRO_PREFIX + "/api/ssl/certs"); + } + @Test public void testCertInfo_Pass() throws Exception { + certInfo_Pass(PLUGINS_PREFIX + "/api/ssl/certs"); + } + + public void certInfo_Pass(final String endpoint) throws Exception { initTestCluster(); final RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; @@ -66,15 +58,24 @@ public void testCertInfo_Pass() throws Exception { rh.sendAdminCertificate = true; rh.keystore = "kirk-keystore.jks"; - final RestHelper.HttpResponse transportInfoRestResponse = rh.executeGetRequest(ENDPOINT); + final RestHelper.HttpResponse transportInfoRestResponse = rh.executeGetRequest(endpoint); JSONObject expectedJsonResponse = new JSONObject(); expectedJsonResponse.appendField("http_certificates_list", NODE_CERT_DETAILS); expectedJsonResponse.appendField("transport_certificates_list", NODE_CERT_DETAILS); Assert.assertEquals(expectedJsonResponse.toString(), transportInfoRestResponse.getBody()); } + @Test + public void testCertInfoFail_Legacy_NonAdmin() throws Exception { + certInfoFail_NonAdmin(LEGACY_OPENDISTRO_PREFIX + "/api/ssl/certs"); + } + @Test public void testCertInfoFail_NonAdmin() throws Exception { + certInfoFail_NonAdmin(PLUGINS_PREFIX + "/api/ssl/certs"); + } + + public void certInfoFail_NonAdmin(final String endpoint) throws Exception { initTestCluster(); final RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; @@ -82,7 +83,7 @@ public void testCertInfoFail_NonAdmin() throws Exception { rh.sendAdminCertificate = true; rh.keystore = "spock-keystore.jks"; - final RestHelper.HttpResponse transportInfoRestResponse = rh.executeGetRequest(ENDPOINT); + final RestHelper.HttpResponse transportInfoRestResponse = rh.executeGetRequest(endpoint); Assert.assertEquals(401, transportInfoRestResponse.getStatusCode()); // Forbidden for non-admin Assert.assertEquals("Unauthorized", transportInfoRestResponse.getStatusReason()); } diff --git a/src/test/java/org/opensearch/security/ssl/util/CertFromFileTests.java b/src/test/java/org/opensearch/security/ssl/util/CertFromFileTests.java index 825094871a..383c60147c 100644 --- a/src/test/java/org/opensearch/security/ssl/util/CertFromFileTests.java +++ b/src/test/java/org/opensearch/security/ssl/util/CertFromFileTests.java @@ -1,3 +1,14 @@ +/* + * 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.ssl.util; import java.io.FileNotFoundException; diff --git a/src/test/java/org/opensearch/security/ssl/util/CertFromKeystoreTests.java b/src/test/java/org/opensearch/security/ssl/util/CertFromKeystoreTests.java index 354d1bc09d..0a2cac18b5 100644 --- a/src/test/java/org/opensearch/security/ssl/util/CertFromKeystoreTests.java +++ b/src/test/java/org/opensearch/security/ssl/util/CertFromKeystoreTests.java @@ -1,3 +1,14 @@ +/* + * 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.ssl.util; import java.io.IOException; diff --git a/src/test/java/org/opensearch/security/ssl/util/CertFromTruststoreTests.java b/src/test/java/org/opensearch/security/ssl/util/CertFromTruststoreTests.java index 0f0b5e8867..ed0f0ac252 100644 --- a/src/test/java/org/opensearch/security/ssl/util/CertFromTruststoreTests.java +++ b/src/test/java/org/opensearch/security/ssl/util/CertFromTruststoreTests.java @@ -1,3 +1,14 @@ +/* + * 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.ssl.util; import java.io.IOException; diff --git a/src/test/java/org/opensearch/security/ssl/util/SSLConnectionTestUtilTests.java b/src/test/java/org/opensearch/security/ssl/util/SSLConnectionTestUtilTests.java index 8ff27b9e84..1400b0d401 100644 --- a/src/test/java/org/opensearch/security/ssl/util/SSLConnectionTestUtilTests.java +++ b/src/test/java/org/opensearch/security/ssl/util/SSLConnectionTestUtilTests.java @@ -87,7 +87,7 @@ public void testConnectionSSLNotAvailableIOException() throws Exception { SSLConnectionTestResult result = connectionTestUtil.testConnection(); verifyClientHelloSend(); - Mockito.verifyZeroInteractions(inputStreamReader); + Mockito.verifyNoInteractions(inputStreamReader); verifyOpenSearchPingSend(); Mockito.verify(socket, Mockito.times(2)).close(); Assert.assertEquals("Unexpected result for testConnection invocation", SSLConnectionTestResult.SSL_NOT_AVAILABLE, result); @@ -149,7 +149,7 @@ public void testConnectionOpenSearchPingFailedIOException() throws Exception { verifyClientHelloSend(); verifyOpenSearchPingSend(); - Mockito.verifyZeroInteractions(inputStream); + Mockito.verifyNoInteractions(inputStream); Mockito.verify(socket, Mockito.times(2)).close(); Assert.assertEquals("Unexpected result for testConnection invocation", SSLConnectionTestResult.OPENSEARCH_PING_FAILED, result); } diff --git a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java index 3b9b821165..c5c3172855 100644 --- a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java +++ b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java @@ -38,6 +38,9 @@ import javax.net.ssl.SSLContext; +import com.carrotsearch.randomizedtesting.RandomizedTest; +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope.Scope; import io.netty.handler.ssl.OpenSsl; import org.apache.http.Header; import org.apache.http.HttpHost; @@ -78,7 +81,14 @@ import org.opensearch.security.test.helper.rules.SecurityTestWatcher; import org.opensearch.threadpool.ThreadPool; -public abstract class AbstractSecurityUnitTest { +/* + * There are real thread leaks during test execution, not all threads are + * properly waited on or interupted. While this normally doesn't create test + * failures, retries mitigate this. Remove this attribute to explore these + * issues. + */ +@ThreadLeakScope(Scope.NONE) +public abstract class AbstractSecurityUnitTest extends RandomizedTest { protected static final AtomicLong num = new AtomicLong(); protected static boolean withRemoteCluster; diff --git a/src/test/java/org/opensearch/security/test/SingleClusterTest.java b/src/test/java/org/opensearch/security/test/SingleClusterTest.java index 39d0b595fc..0c2f3bfc07 100644 --- a/src/test/java/org/opensearch/security/test/SingleClusterTest.java +++ b/src/test/java/org/opensearch/security/test/SingleClusterTest.java @@ -26,6 +26,7 @@ package org.opensearch.security.test; +import java.io.File; import java.util.List; import org.junit.After; @@ -40,6 +41,10 @@ public abstract class SingleClusterTest extends AbstractSecurityUnitTest { + public static final String TEST_RESOURCE_RELATIVE_PATH = "../../resources/test/"; + public static final String TEST_RESOURCE_ABSOLUTE_PATH = new File(TEST_RESOURCE_RELATIVE_PATH).getAbsolutePath() + "/"; + public static final String PROJECT_ROOT_RELATIVE_PATH = "../../../"; + private static final int DEFAULT_CLUSTER_MANAGER_NODE_NUM = 3; private static final int DEFAULT_FIRST_DATA_NODE_NUM = 2; diff --git a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java index 72360a5712..ab6bc9d236 100644 --- a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java +++ b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java @@ -49,7 +49,7 @@ import org.opensearch.action.admin.cluster.node.info.NodesInfoRequest; import org.opensearch.action.admin.cluster.node.info.NodesInfoResponse; import org.opensearch.action.admin.indices.template.put.PutIndexTemplateRequest; -import org.opensearch.action.support.master.AcknowledgedResponse; +import org.opensearch.action.support.clustermanager.AcknowledgedResponse; import org.opensearch.client.Client; import org.opensearch.cluster.health.ClusterHealthStatus; import org.opensearch.cluster.node.DiscoveryNodeRole; @@ -61,6 +61,7 @@ import org.opensearch.node.Node; import org.opensearch.node.PluginAwareNode; import org.opensearch.security.test.NodeSettingsSupplier; +import org.opensearch.security.test.SingleClusterTest; import org.opensearch.security.test.helper.cluster.ClusterConfiguration.NodeSettings; import org.opensearch.security.test.helper.network.SocketUtils; import org.opensearch.transport.TransportInfo; @@ -68,8 +69,23 @@ public final class ClusterHelper { static { + resetSystemProperties(); + } + + /** Resets all system properties associated with a cluster */ + public static void resetSystemProperties() { System.setProperty("opensearch.enforce.bootstrap.checks", "true"); - System.setProperty("security.default_init.dir", new File("./securityconfig").getAbsolutePath()); + updateDefaultDirectory(new File( SingleClusterTest.PROJECT_ROOT_RELATIVE_PATH + "config").getAbsolutePath()); + } + + /** + * Update the default directory used by the security plugin + * NOTE: this setting is system wide, use ClusterHelper.resetSystemProperties() to restore the original state + * + * @return the previous value if one was set, otherwise null + */ + public static String updateDefaultDirectory(final String newValue) { + return System.setProperty("security.default_init.dir", newValue); } protected final Logger log = LogManager.getLogger(ClusterHelper.class); From 1a63f228932d90011f2519759143de6cf08c7714 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 24 Jun 2022 13:05:17 -0500 Subject: [PATCH 003/369] Fix build break from cluster manager changes (#1911) Signed-off-by: Peter Nied --- .../org/opensearch/security/dlic/rest/api/MigrateApiAction.java | 2 +- src/main/java/org/opensearch/security/tools/SecurityAdmin.java | 2 +- .../opensearch/security/test/helper/cluster/ClusterHelper.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java index 6c973f3557..7ea87cba09 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java @@ -27,7 +27,7 @@ import org.opensearch.action.bulk.BulkResponse; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.support.WriteRequest.RefreshPolicy; -import org.opensearch.action.support.clustermanager.AcknowledgedResponse; +import org.opensearch.action.support.master.AcknowledgedResponse; import org.opensearch.client.Client; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.service.ClusterService; diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 4839524552..6360f508b3 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -89,7 +89,7 @@ import org.opensearch.action.get.GetResponse; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.support.WriteRequest.RefreshPolicy; -import org.opensearch.action.support.clustermanager.AcknowledgedResponse; +import org.opensearch.action.support.master.AcknowledgedResponse; import org.opensearch.client.Request; import org.opensearch.client.RequestOptions; import org.opensearch.client.Response; diff --git a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java index ab6bc9d236..fdbef60d70 100644 --- a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java +++ b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java @@ -49,7 +49,7 @@ import org.opensearch.action.admin.cluster.node.info.NodesInfoRequest; import org.opensearch.action.admin.cluster.node.info.NodesInfoResponse; import org.opensearch.action.admin.indices.template.put.PutIndexTemplateRequest; -import org.opensearch.action.support.clustermanager.AcknowledgedResponse; +import org.opensearch.action.support.master.AcknowledgedResponse; import org.opensearch.client.Client; import org.opensearch.cluster.health.ClusterHealthStatus; import org.opensearch.cluster.node.DiscoveryNodeRole; From 746748b16a2e6db05d47eed5de96a8f38963f88e Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 24 Jun 2022 13:59:48 -0500 Subject: [PATCH 004/369] Update `org.apache.zookeeper:zookeeper` to `3.7.1` (#1912) Fixes CVE-2021-4104, CVE-2020-9488, and more from dependency on log4j-1.2.17.jar Signed-off-by: Peter Nied --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7d2526a0ac..6c3d584350 100644 --- a/build.gradle +++ b/build.gradle @@ -339,7 +339,7 @@ dependencies { testRuntimeOnly 'org.scala-lang:scala-library:2.13.8' testRuntimeOnly 'com.yammer.metrics:metrics-core:2.2.0' testRuntimeOnly 'com.typesafe.scala-logging:scala-logging_3:3.9.5' - testRuntimeOnly 'org.apache.zookeeper:zookeeper:3.6.3' + testRuntimeOnly 'org.apache.zookeeper:zookeeper:3.7.1' testRuntimeOnly 'org.apache.kafka:kafka-metadata:3.0.1' testRuntimeOnly 'org.apache.kafka:kafka-storage:3.0.1' From 00e2a5d146a3e25b36e7c98069ae7f749b13b82f Mon Sep 17 00:00:00 2001 From: Chang Liu Date: Mon, 27 Jun 2022 09:54:03 -0700 Subject: [PATCH 005/369] Bump version to 3.0.0.0 (#1890) * Bump version to 3.0.0.0 * Fix for the breaking changes in OpenSearch 3.0 Signed-off-by: cliu123 --- .github/workflows/ci.yml | 6 +++--- build.gradle | 2 +- bwc-test/build.gradle | 8 ++++---- .../dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java | 2 +- .../auth/http/saml/SamlFilesystemMetadataResolver.java | 2 +- .../org/opensearch/security/OpenSearchSecurityPlugin.java | 4 ++-- .../security/auditlog/impl/AbstractAuditLog.java | 2 +- .../security/configuration/ConfigurationRepository.java | 2 +- .../opensearch/security/ssl/DefaultSecurityKeyStore.java | 6 +++--- .../opensearch/security/ssl/util/SSLRequestHelper.java | 6 +++--- .../org/opensearch/security/support/PemKeyReader.java | 4 ++-- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8972ac82b..3bb24077cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,9 +72,9 @@ jobs: cp -r build/ ./bwc-test/ mkdir ./bwc-test/src/test/resources/security_plugin_version_no_snapshot cp build/distributions/opensearch-security-${security_plugin_version_no_snapshot}.zip ./bwc-test/src/test/resources/${security_plugin_version_no_snapshot} - mkdir bwc-test/src/test/resources/2.0.0.0 - wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.0.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.0.0.0.zip - mv opensearch-security-2.0.0.0.zip bwc-test/src/test/resources/2.0.0.0/ + mkdir bwc-test/src/test/resources/2.1.0.0 + wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.1.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.1.0.0.zip + mv opensearch-security-2.1.0.0.zip bwc-test/src/test/resources/2.1.0.0/ cd bwc-test/ ./gradlew bwcTestSuite -Dtests.security.manager=false diff --git a/build.gradle b/build.gradle index 6c3d584350..aa68041b5d 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { ext { - opensearch_version = System.getProperty("opensearch.version", "2.1.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "3.0.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") diff --git a/bwc-test/build.gradle b/bwc-test/build.gradle index 8ad987b1a7..60270bf7f5 100644 --- a/bwc-test/build.gradle +++ b/bwc-test/build.gradle @@ -47,7 +47,7 @@ ext { buildscript { ext { - opensearch_version = System.getProperty("opensearch.version", "2.1.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "3.0.0-SNAPSHOT") opensearch_group = "org.opensearch" } repositories { @@ -73,16 +73,16 @@ dependencies { testImplementation "org.opensearch.test:framework:${opensearch_version}" } -String bwcVersion = "2.0.0.0"; +String bwcVersion = "2.1.0.0"; String baseName = "securityBwcCluster" String bwcFilePath = "src/test/resources/" -String projectVersion = "2.1.0.0" +String projectVersion = "3.0.0.0" 2.times {i -> testClusters { "${baseName}$i" { testDistribution = "ARCHIVE" - versions = ["2.0.0","2.1.0"] + versions = ["2.1.0","3.0.0"] numberOfNodes = 3 plugin(provider(new Callable() { @Override diff --git a/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java index 812ca4f82f..3603aeb94e 100644 --- a/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java @@ -70,7 +70,7 @@ public class HTTPSpnegoAuthenticator implements HTTPAuthenticator { public HTTPSpnegoAuthenticator(final Settings settings, final Path configPath) { super(); try { - final Path configDir = new Environment(settings, configPath).configFile(); + final Path configDir = new Environment(settings, configPath).configDir(); final String krb5PathSetting = settings.get("plugins.security.kerberos.krb5_filepath"); final SecurityManager sm = System.getSecurityManager(); diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java b/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java index 80f272b43b..302b1f41ea 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java @@ -51,6 +51,6 @@ public byte[] run() throws ResolverException { private static File getMetadataFile(String filePath, Settings settings, Path configPath) { Environment env = new Environment(settings, configPath); - return env.configFile().resolve(filePath).toAbsolutePath().toFile(); + return env.configDir().resolve(filePath).toAbsolutePath().toFile(); } } diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 66530cfaed..69dce00d41 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -326,7 +326,7 @@ public Object run() { final List filesWithWrongPermissions = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { - final Path confPath = new Environment(settings, configPath).configFile().toAbsolutePath(); + final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().filter(p -> checkFilePermissions(p)).collect(Collectors.toList()); @@ -356,7 +356,7 @@ public List run() { final List files = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { - final Path confPath = new Environment(settings, configPath).configFile().toAbsolutePath(); + final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().map(p -> sha256(p)).collect(Collectors.toList()); 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 bc5e240c77..d6f59028fa 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java @@ -560,7 +560,7 @@ public Map run() { (key.contains("filepath") || key.contains("file_path"))) { String value = settings.get(key); if(value != null && !value.isEmpty()) { - Path path = value.startsWith("/")?Paths.get(value):environment.configFile().resolve(value); + Path path = value.startsWith("/")?Paths.get(value):environment.configDir().resolve(value); paths.put(key, path); } } diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java index 84d3059942..4b2fa7af8b 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java @@ -120,7 +120,7 @@ public void run() { try { String lookupDir = System.getProperty("security.default_init.dir"); - final String cd = lookupDir != null? (lookupDir+"/") : new Environment(settings, configPath).configFile().toAbsolutePath().toString()+"/opensearch-security/"; + final String cd = lookupDir != null? (lookupDir+"/") : new Environment(settings, configPath).configDir().toAbsolutePath().toString()+"/opensearch-security/"; File confFile = new File(cd+"config.yml"); if(confFile.exists()) { final ThreadContext threadContext = threadPool.getThreadContext(); diff --git a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java index 72d18fc0c9..026165f95e 100644 --- a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java +++ b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java @@ -226,8 +226,8 @@ private String resolve(String propName, boolean mustBeValid) { log.debug("Value for {} is {}", propName, originalPath); if (env != null && originalPath != null && originalPath.length() > 0) { - path = env.configFile().resolve(originalPath).toAbsolutePath().toString(); - log.debug("Resolved {} to {} against {}", originalPath, path, env.configFile().toAbsolutePath().toString()); + path = env.configDir().resolve(originalPath).toAbsolutePath().toString(); + log.debug("Resolved {} to {} against {}", originalPath, path, env.configDir().toAbsolutePath().toString()); } if (mustBeValid) { @@ -247,7 +247,7 @@ private void initSSLConfig() { log.info("No config directory, key- and truststore files are resolved absolutely"); } else { log.info("Config directory is {}/, from there the key- and truststore files are resolved relatively", - env.configFile().toAbsolutePath()); + env.configDir().toAbsolutePath()); } diff --git a/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java b/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java index 5bd72fba5d..893fb04fac 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java @@ -199,7 +199,7 @@ private static boolean validate(X509Certificate[] x509Certs, final Settings sett final String crlFile = settings.get(SSLConfigConstants.SSECURITY_SSL_HTTP_CRL_FILE); if(crlFile != null) { - final File crl = env.configFile().resolve(crlFile).toAbsolutePath().toFile(); + final File crl = env.configDir().resolve(crlFile).toAbsolutePath().toFile(); try(FileInputStream crlin = new FileInputStream(crl)) { crls = CertificateFactory.getInstance("X.509").generateCRLs(crlin); } @@ -222,12 +222,12 @@ private static boolean validate(X509Certificate[] x509Certs, final Settings sett //final String truststoreAlias = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_ALIAS, null); final KeyStore ts = KeyStore.getInstance(truststoreType); - try(FileInputStream fin = new FileInputStream(new File(env.configFile().resolve(truststore).toAbsolutePath().toString()))) { + try(FileInputStream fin = new FileInputStream(new File(env.configDir().resolve(truststore).toAbsolutePath().toString()))) { ts.load(fin, (truststorePassword == null || truststorePassword.length() == 0) ?null:truststorePassword.toCharArray()); } validator = new CertificateValidator(ts, crls); } else { - final File trustedCas = env.configFile().resolve(settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, "")).toAbsolutePath().toFile(); + final File trustedCas = env.configDir().resolve(settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, "")).toAbsolutePath().toFile(); try(FileInputStream trin = new FileInputStream(trustedCas)) { Collection cert = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); validator = new CertificateValidator(cert.toArray(new X509Certificate[0]), crls); diff --git a/src/main/java/org/opensearch/security/support/PemKeyReader.java b/src/main/java/org/opensearch/security/support/PemKeyReader.java index 53eeb21736..97aea87c13 100644 --- a/src/main/java/org/opensearch/security/support/PemKeyReader.java +++ b/src/main/java/org/opensearch/security/support/PemKeyReader.java @@ -325,8 +325,8 @@ public static String resolve(String originalPath, String propName, Settings sett final Environment env = new Environment(settings, configPath); if(env != null && originalPath != null && originalPath.length() > 0) { - path = env.configFile().resolve(originalPath).toAbsolutePath().toString(); - log.debug("Resolved {} to {} against {}", originalPath, path, env.configFile().toAbsolutePath().toString()); + path = env.configDir().resolve(originalPath).toAbsolutePath().toString(); + log.debug("Resolved {} to {} against {}", originalPath, path, env.configDir().toAbsolutePath().toString()); } if(mustBeValid) { From d507ebb668e4665c8952412afcc79fa0e5e112f3 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Mon, 27 Jun 2022 12:03:13 -0500 Subject: [PATCH 006/369] ComplianceAuditlogTest to use signal/wait (#1914) Signed-off-by: Peter Nied --- .../compliance/ComplianceAuditlogTest.java | 206 +++++++----------- .../integration/TestAuditlogImpl.java | 28 ++- 2 files changed, 103 insertions(+), 131 deletions(-) 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 a88baef90f..d0b20de3c0 100644 --- a/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java @@ -11,7 +11,9 @@ package org.opensearch.security.auditlog.compliance; +import java.io.IOException; import java.util.Collections; +import java.util.List; import com.google.common.collect.ImmutableMap; import org.apache.http.Header; @@ -30,26 +32,28 @@ import org.opensearch.security.auditlog.AbstractAuditlogiUnitTest; import org.opensearch.security.auditlog.AuditTestUtils; import org.opensearch.security.auditlog.config.AuditConfig; +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.HttpResponse; -import static org.junit.Assert.assertFalse; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; 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") @@ -66,7 +70,6 @@ public void testSourceFilter() throws Exception { rh.sendAdminCertificate = sendAdminCertificate; rh.keystore = keystore; - System.out.println("#### test source includes"); String search = "{" + " \"_source\":[" + " \"Gender\""+ @@ -80,13 +83,11 @@ 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); + final AuditMessage message = TestAuditlogImpl.doThenWaitForMessage(() -> { + final HttpResponse response = rh.executePostRequest("_search?pretty", search, encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + }); + Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_DOC_READ")); Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("Designation")); Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("Salary")); @@ -102,8 +103,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"; @@ -112,21 +111,21 @@ 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}"); + TestAuditlogImpl.doThenWaitForMessages(() -> { + rh.executePutRequest("emp/_doc/0?refresh", "{\"Designation\" : \"CEO\", \"Gender\" : \"female\", \"Salary\" : 100}"); + }, 7); assertTrue(TestAuditlogImpl.messages.toString().contains("COMPLIANCE_DOC_WRITE")); - // 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 @@ -154,7 +153,6 @@ public void testSourceFilterMsearch() throws Exception { rh.sendAdminCertificate = sendAdminCertificate; rh.keystore = keystore; - System.out.println("#### test source includes"); String search = "{}"+System.lineSeparator() + "{" + " \"_source\":[" + @@ -211,22 +209,23 @@ public void testInternalConfig() throws Exception { .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") .build(); - TestAuditlogImpl.clear(); setup(additionalSettings); - try (RestHighLevelClient restHighLevelClient = getRestClient(clusterInfo, "kirk-keystore.jks", "truststore.jks")) { - for(IndexRequest ir: new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) { - restHighLevelClient.index(ir, RequestOptions.DEFAULT); - GetResponse getDocumentResponse = restHighLevelClient.get(new GetRequest(ir.index(), ir.id()), RequestOptions.DEFAULT); - Assert.assertTrue("Document not found:" + getDocumentResponse, getDocumentResponse.isExists()); + final List messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + try (RestHighLevelClient restHighLevelClient = getRestClient(clusterInfo, "kirk-keystore.jks", "truststore.jks")) { + for (IndexRequest ir: new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) { + restHighLevelClient.index(ir, RequestOptions.DEFAULT); + GetResponse getDocumentResponse = restHighLevelClient.get(new GetRequest(ir.index(), ir.id()), RequestOptions.DEFAULT); + assertThat(getDocumentResponse.isExists(), equalTo(true)); + } + } catch (IOException ioe) { + throw new RuntimeException("Unexpected exception", ioe); } - } - 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() >= 15); + HttpResponse response = rh.executeGetRequest("_search?pretty", encodeBasicHeader("admin", "admin")); + assertThat(response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + }, 14); + 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")); @@ -247,7 +246,7 @@ public void testInternalConfig() throws Exception { @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) @@ -258,23 +257,23 @@ public void testExternalConfig() throws Exception { .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") .build(); - TestAuditlogImpl.clear(); - - setup(additionalSettings); - - try (Client tc = getClient()) { + TestAuditlogImpl.doThenWaitForMessages(() -> { + try { + setup(additionalSettings); + } catch (final Exception ex) { + throw new RuntimeException(ex); + } - for(IndexRequest ir: new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) { - tc.index(ir).actionGet(); + try (Client tc = getClient()) { + 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()); + }, 4); - 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")); @@ -306,73 +305,29 @@ public void testUpdate() throws Exception { .actionGet(); } - TestAuditlogImpl.clear(); - - String body = "{\"doc\": {\"Age\":123}}"; - - HttpResponse response = rh.executePostRequest("humanresources/_doc/100?pretty", body, encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - - body = "{\"doc\": {\"Age\":456}}"; + 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_CREATED, 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)); - response = rh.executePostRequest("humanresources/_update/100?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/_doc/"+i+"", "{\"customer\": {\"Age\":"+i+"}}", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - response = rh.executePostRequest("humanresources/_doc/"+i+"", "{\"customer\": {\"Age\":"+(i+2)+"}}", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executePostRequest("humanresources/_update/"+i+"?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 { @@ -387,7 +342,6 @@ public void testWriteHistory() throws Exception { setup(additionalSettings); - try (Client tc = getClient()) { tc.prepareIndex("humanresources") .setRefreshPolicy(RefreshPolicy.IMMEDIATE) @@ -396,24 +350,18 @@ public void testWriteHistory() throws Exception { .actionGet(); } - TestAuditlogImpl.clear(); - - String body = "{\"doc\": {\"Age\":123}}"; - - HttpResponse response = rh.executePostRequest("humanresources/_doc/100?pretty", body, encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - System.out.println(response.getBody()); - Thread.sleep(1500); - System.out.println(TestAuditlogImpl.sb.toString()); + 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_CREATED, response.getStatusCode()); + }); Assert.assertTrue(TestAuditlogImpl.sb.toString().split(".*audit_compliance_diff_content.*replace.*").length == 1); - body = "{\"doc\": {\"Age\":555}}"; - TestAuditlogImpl.clear(); - response = rh.executePostRequest("humanresources/_update/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()); + 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/TestAuditlogImpl.java b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java index ede03fb49e..f9bf7bb41d 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java @@ -38,7 +38,8 @@ public TestAuditlogImpl(String name, Settings settings, String settingsPrefix, A public synchronized boolean doStore(AuditMessage msg) { if (messagesRef.get() == null || countDownRef.get() == null) { - throw new RuntimeException("No message latch is waiting"); + // Ignore any messages that are sent before TestAuditlogImpl is waiting. + return true; } sb.append(msg.toPrettyString()+System.lineSeparator()); messagesRef.get().add(msg); @@ -69,7 +70,7 @@ public static List doThenWaitForMessages(final Runnable action, fi final int maxSecondsToWaitForMessages = 1; final boolean foundAll = latch.await(maxSecondsToWaitForMessages, TimeUnit.SECONDS); if (!foundAll) { - throw new RuntimeException("Did not recieve all " + expectedCount +" audit messages after a short wait."); + throw new MessagesNotFoundException(expectedCount, (int)latch.getCount()); } if (messages.size() != expectedCount) { throw new RuntimeException("Unexpected number of messages, was expecting " + expectedCount + ", recieved " + messages.size()); @@ -80,10 +81,33 @@ public static List doThenWaitForMessages(final Runnable action, fi return new ArrayList<>(messages); } + /** + * 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 public boolean isHandlingBackpressure() { return true; } + public static class MessagesNotFoundException extends RuntimeException { + private final int expectedCount; + private final int missingCount; + public MessagesNotFoundException(final int expectedCount, final int missingCount) { + super("Did not recieve all " + expectedCount +" audit messages after a short wait, missing " + missingCount + " messages"); + this.expectedCount = expectedCount; + this.missingCount = missingCount; + } + public int getExpectedCount() { + return expectedCount; + } + + public int getMissingCount() { + return missingCount; + } + } } From 15f1fbd6191dc3a80eda02ba2e2a1b835f57db88 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Date: Mon, 27 Jun 2022 16:59:53 -0400 Subject: [PATCH 007/369] Adds release notes for 2.1.0 and revert #1890 (#1901) * Adds release notes for 2.1.0 Signed-off-by: Darshit Chanpura * Revert "Bump version to 3.0.0.0 (#1890)" This reverts commit 00e2a5d146a3e25b36e7c98069ae7f749b13b82f. Signed-off-by: Darshit Chanpura --- .github/workflows/ci.yml | 6 ++-- build.gradle | 2 +- bwc-test/build.gradle | 8 ++--- ...ensearch-security.release-notes-2.1.0.0.md | 34 +++++++++++++++++++ .../kerberos/HTTPSpnegoAuthenticator.java | 2 +- .../saml/SamlFilesystemMetadataResolver.java | 2 +- .../security/OpenSearchSecurityPlugin.java | 4 +-- .../auditlog/impl/AbstractAuditLog.java | 2 +- .../ConfigurationRepository.java | 2 +- .../security/ssl/DefaultSecurityKeyStore.java | 6 ++-- .../security/ssl/util/SSLRequestHelper.java | 6 ++-- .../security/support/PemKeyReader.java | 4 +-- 12 files changed, 56 insertions(+), 22 deletions(-) create mode 100644 release-notes/opensearch-security.release-notes-2.1.0.0.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bb24077cf..d8972ac82b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,9 +72,9 @@ jobs: cp -r build/ ./bwc-test/ mkdir ./bwc-test/src/test/resources/security_plugin_version_no_snapshot cp build/distributions/opensearch-security-${security_plugin_version_no_snapshot}.zip ./bwc-test/src/test/resources/${security_plugin_version_no_snapshot} - mkdir bwc-test/src/test/resources/2.1.0.0 - wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.1.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.1.0.0.zip - mv opensearch-security-2.1.0.0.zip bwc-test/src/test/resources/2.1.0.0/ + mkdir bwc-test/src/test/resources/2.0.0.0 + wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.0.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.0.0.0.zip + mv opensearch-security-2.0.0.0.zip bwc-test/src/test/resources/2.0.0.0/ cd bwc-test/ ./gradlew bwcTestSuite -Dtests.security.manager=false diff --git a/build.gradle b/build.gradle index aa68041b5d..6c3d584350 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { ext { - opensearch_version = System.getProperty("opensearch.version", "3.0.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.1.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") diff --git a/bwc-test/build.gradle b/bwc-test/build.gradle index 60270bf7f5..8ad987b1a7 100644 --- a/bwc-test/build.gradle +++ b/bwc-test/build.gradle @@ -47,7 +47,7 @@ ext { buildscript { ext { - opensearch_version = System.getProperty("opensearch.version", "3.0.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.1.0-SNAPSHOT") opensearch_group = "org.opensearch" } repositories { @@ -73,16 +73,16 @@ dependencies { testImplementation "org.opensearch.test:framework:${opensearch_version}" } -String bwcVersion = "2.1.0.0"; +String bwcVersion = "2.0.0.0"; String baseName = "securityBwcCluster" String bwcFilePath = "src/test/resources/" -String projectVersion = "3.0.0.0" +String projectVersion = "2.1.0.0" 2.times {i -> testClusters { "${baseName}$i" { testDistribution = "ARCHIVE" - versions = ["2.1.0","3.0.0"] + versions = ["2.0.0","2.1.0"] numberOfNodes = 3 plugin(provider(new Callable() { @Override diff --git a/release-notes/opensearch-security.release-notes-2.1.0.0.md b/release-notes/opensearch-security.release-notes-2.1.0.0.md new file mode 100644 index 0000000000..7cafaa9b45 --- /dev/null +++ b/release-notes/opensearch-security.release-notes-2.1.0.0.md @@ -0,0 +1,34 @@ +## 2022-06-30 Version 2.1.0.0 + +Compatible with OpenSearch 2.1.0 + +### Enhancements +* Delegate to NettyAllocator.getAllocator() for ByteBufAllocator instead of hard-coding PooledByteBufAllocator. ([#1396](https://github.com/opensearch-project/security/pull/1396)) +* Tenant Permissions : added the possibility to specify tenants via parameter ([#1813](https://github.com/opensearch-project/security/pull/1813)) +* JWT: validate issuer and audience ([#1780](https://github.com/opensearch-project/security/pull/1780), [#1781](https://github.com/opensearch-project/security/pull/1781)) ([#1785](https://github.com/opensearch-project/security/pull/1785)) + +### Refactoring +* Remove master keywords ([#1886](https://github.com/opensearch-project/security/pull/1886)) + +### Bug Fix +* Cluster permissions evaluation logic will now include `index_template` type action ([#1885](https://github.com/opensearch-project/security/pull/1885)) +* Add missing settings to plugin allowed list ([#1814](https://github.com/opensearch-project/security/pull/1814)) +* Updates license headers ([#1829](https://github.com/opensearch-project/security/pull/1829)) +* Prevent recursive action groups ([#1868](https://github.com/opensearch-project/security/pull/1868)) +* Update `org.springframework:spring-core` to `5.3.20` ([#1850](https://github.com/opensearch-project/security/pull/1850)) + +### Test Fix +* Bump version to 2.1.0.0 ([#1883](https://github.com/opensearch-project/security/pull/1883)) +* ComplianceAuditlogTest to use signal/wait ([#1914](https://github.com/opensearch-project/security/pull/1914)) + +### Maintenance +* Revert "Bump version to 2.1.0.0 (#1865)" ([#1882](https://github.com/opensearch-project/security/pull/1882)) +* Bump version to 2.1.0.0 ([#1865](https://github.com/opensearch-project/security/pull/1865)) +* Revert "Bump version to 2.1.0.0 (#1855)" ([#1864](https://github.com/opensearch-project/security/pull/1864)) +* Bump version to 2.1.0.0 ([#1855](https://github.com/opensearch-project/security/pull/1855)) +* Add suppression for all removal warnings ([#1828](https://github.com/opensearch-project/security/pull/1828)) +* Update support link ([#1851](https://github.com/opensearch-project/security/pull/1851)) +* Create 2.0.0 release notes ([#1854](https://github.com/opensearch-project/security/pull/1854)) +* Switch to standard OpenSearch gradle build ([#1888](https://github.com/opensearch-project/security/pull/1888)) +* Fix build break from cluster manager changes ([#1911](https://github.com/opensearch-project/security/pull/1911)) +* Update org.apache.zookeeper:zookeeper to 3.7.1 ([#1912](https://github.com/opensearch-project/security/pull/1912)) diff --git a/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java index 3603aeb94e..812ca4f82f 100644 --- a/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java @@ -70,7 +70,7 @@ public class HTTPSpnegoAuthenticator implements HTTPAuthenticator { public HTTPSpnegoAuthenticator(final Settings settings, final Path configPath) { super(); try { - final Path configDir = new Environment(settings, configPath).configDir(); + final Path configDir = new Environment(settings, configPath).configFile(); final String krb5PathSetting = settings.get("plugins.security.kerberos.krb5_filepath"); final SecurityManager sm = System.getSecurityManager(); diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java b/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java index 302b1f41ea..80f272b43b 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java @@ -51,6 +51,6 @@ public byte[] run() throws ResolverException { private static File getMetadataFile(String filePath, Settings settings, Path configPath) { Environment env = new Environment(settings, configPath); - return env.configDir().resolve(filePath).toAbsolutePath().toFile(); + return env.configFile().resolve(filePath).toAbsolutePath().toFile(); } } diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 69dce00d41..66530cfaed 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -326,7 +326,7 @@ public Object run() { final List filesWithWrongPermissions = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { - final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); + final Path confPath = new Environment(settings, configPath).configFile().toAbsolutePath(); if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().filter(p -> checkFilePermissions(p)).collect(Collectors.toList()); @@ -356,7 +356,7 @@ public List run() { final List files = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { - final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); + final Path confPath = new Environment(settings, configPath).configFile().toAbsolutePath(); if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().map(p -> sha256(p)).collect(Collectors.toList()); 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 d6f59028fa..bc5e240c77 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java @@ -560,7 +560,7 @@ public Map run() { (key.contains("filepath") || key.contains("file_path"))) { String value = settings.get(key); if(value != null && !value.isEmpty()) { - Path path = value.startsWith("/")?Paths.get(value):environment.configDir().resolve(value); + Path path = value.startsWith("/")?Paths.get(value):environment.configFile().resolve(value); paths.put(key, path); } } diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java index 4b2fa7af8b..84d3059942 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java @@ -120,7 +120,7 @@ public void run() { try { String lookupDir = System.getProperty("security.default_init.dir"); - final String cd = lookupDir != null? (lookupDir+"/") : new Environment(settings, configPath).configDir().toAbsolutePath().toString()+"/opensearch-security/"; + final String cd = lookupDir != null? (lookupDir+"/") : new Environment(settings, configPath).configFile().toAbsolutePath().toString()+"/opensearch-security/"; File confFile = new File(cd+"config.yml"); if(confFile.exists()) { final ThreadContext threadContext = threadPool.getThreadContext(); diff --git a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java index 026165f95e..72d18fc0c9 100644 --- a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java +++ b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java @@ -226,8 +226,8 @@ private String resolve(String propName, boolean mustBeValid) { log.debug("Value for {} is {}", propName, originalPath); if (env != null && originalPath != null && originalPath.length() > 0) { - path = env.configDir().resolve(originalPath).toAbsolutePath().toString(); - log.debug("Resolved {} to {} against {}", originalPath, path, env.configDir().toAbsolutePath().toString()); + path = env.configFile().resolve(originalPath).toAbsolutePath().toString(); + log.debug("Resolved {} to {} against {}", originalPath, path, env.configFile().toAbsolutePath().toString()); } if (mustBeValid) { @@ -247,7 +247,7 @@ private void initSSLConfig() { log.info("No config directory, key- and truststore files are resolved absolutely"); } else { log.info("Config directory is {}/, from there the key- and truststore files are resolved relatively", - env.configDir().toAbsolutePath()); + env.configFile().toAbsolutePath()); } diff --git a/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java b/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java index 893fb04fac..5bd72fba5d 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java @@ -199,7 +199,7 @@ private static boolean validate(X509Certificate[] x509Certs, final Settings sett final String crlFile = settings.get(SSLConfigConstants.SSECURITY_SSL_HTTP_CRL_FILE); if(crlFile != null) { - final File crl = env.configDir().resolve(crlFile).toAbsolutePath().toFile(); + final File crl = env.configFile().resolve(crlFile).toAbsolutePath().toFile(); try(FileInputStream crlin = new FileInputStream(crl)) { crls = CertificateFactory.getInstance("X.509").generateCRLs(crlin); } @@ -222,12 +222,12 @@ private static boolean validate(X509Certificate[] x509Certs, final Settings sett //final String truststoreAlias = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_ALIAS, null); final KeyStore ts = KeyStore.getInstance(truststoreType); - try(FileInputStream fin = new FileInputStream(new File(env.configDir().resolve(truststore).toAbsolutePath().toString()))) { + try(FileInputStream fin = new FileInputStream(new File(env.configFile().resolve(truststore).toAbsolutePath().toString()))) { ts.load(fin, (truststorePassword == null || truststorePassword.length() == 0) ?null:truststorePassword.toCharArray()); } validator = new CertificateValidator(ts, crls); } else { - final File trustedCas = env.configDir().resolve(settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, "")).toAbsolutePath().toFile(); + final File trustedCas = env.configFile().resolve(settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, "")).toAbsolutePath().toFile(); try(FileInputStream trin = new FileInputStream(trustedCas)) { Collection cert = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); validator = new CertificateValidator(cert.toArray(new X509Certificate[0]), crls); diff --git a/src/main/java/org/opensearch/security/support/PemKeyReader.java b/src/main/java/org/opensearch/security/support/PemKeyReader.java index 97aea87c13..53eeb21736 100644 --- a/src/main/java/org/opensearch/security/support/PemKeyReader.java +++ b/src/main/java/org/opensearch/security/support/PemKeyReader.java @@ -325,8 +325,8 @@ public static String resolve(String originalPath, String propName, Settings sett final Environment env = new Environment(settings, configPath); if(env != null && originalPath != null && originalPath.length() > 0) { - path = env.configDir().resolve(originalPath).toAbsolutePath().toString(); - log.debug("Resolved {} to {} against {}", originalPath, path, env.configDir().toAbsolutePath().toString()); + path = env.configFile().resolve(originalPath).toAbsolutePath().toString(); + log.debug("Resolved {} to {} against {}", originalPath, path, env.configFile().toAbsolutePath().toString()); } if(mustBeValid) { From 192ef3533cea3cfc87df97490a382ecda0bd7838 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Date: Tue, 28 Jun 2022 19:36:56 -0400 Subject: [PATCH 008/369] Adds build script for publishing plugin zip and makes it executable (#1921) --- scripts/build.sh | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100755 scripts/build.sh diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000000..4b2893f304 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# + +set -ex + +function usage() { + echo "Usage: $0 [args]" + echo "" + echo "Arguments:" + echo -e "-v VERSION\t[Required] OpenSearch version." + echo -e "-q QUALIFIER\t[Optional] Version qualifier." + echo -e "-s SNAPSHOT\t[Optional] Build a snapshot, default is 'false'." + echo -e "-p PLATFORM\t[Optional] Platform, ignored." + echo -e "-a ARCHITECTURE\t[Optional] Build architecture, ignored." + echo -e "-o OUTPUT\t[Optional] Output path, default is 'artifacts'." + echo -e "-h help" +} + +while getopts ":h:v:q:s:o:p:a:" arg; do + case $arg in + h) + usage + exit 1 + ;; + v) + VERSION=$OPTARG + ;; + q) + QUALIFIER=$OPTARG + ;; + s) + SNAPSHOT=$OPTARG + ;; + o) + OUTPUT=$OPTARG + ;; + p) + PLATFORM=$OPTARG + ;; + a) + ARCHITECTURE=$OPTARG + ;; + :) + echo "Error: -${OPTARG} requires an argument" + usage + exit 1 + ;; + ?) + echo "Invalid option: -${arg}" + exit 1 + ;; + esac +done + +if [ -z "$VERSION" ]; then + echo "Error: You must specify the OpenSearch version" + usage + exit 1 +fi + +[[ ! -z "$QUALIFIER" ]] && VERSION=$VERSION-$QUALIFIER +[[ "$SNAPSHOT" == "true" ]] && VERSION=$VERSION-SNAPSHOT +[ -z "$OUTPUT" ] && OUTPUT=artifacts + +mkdir -p $OUTPUT + +./gradlew assemble --no-daemon --refresh-dependencies -DskipTests=true -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER + +zipPath=$(find . -path \*build/distributions/*.zip) +distributions="$(dirname "${zipPath}")" + +echo "COPY ${distributions}/*.zip" +mkdir -p $OUTPUT/plugins +cp ${distributions}/*.zip ./$OUTPUT/plugins + +./gradlew publishPluginZipPublicationToZipStagingRepository -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER +mkdir -p $OUTPUT/maven/org/opensearch +cp -r ./build/local-staging-repo/org/opensearch/. $OUTPUT/maven/org/opensearch From 1904db559960fca9d87e334172017c412f3e3d0e Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Wed, 29 Jun 2022 14:13:16 -0500 Subject: [PATCH 009/369] testComplianceEnable supports variable number of audit messages (#1920) We were seeing test failures where on higher end computers there would be duplicate audit messages for the index mapping creation. Then when run inside GitHub Actions there would only be 2 messages. This doesn't look like an overt product issue, overlogging of requests, but the test case was not handling it well. Also improved the failure message response for faster future debugging. Signed-off-by: Peter Nied --- .../compliance/ComplianceAuditlogTest.java | 29 +++++++++++++++---- .../integration/TestAuditlogImpl.java | 28 ++++++++++++++---- 2 files changed, 47 insertions(+), 10 deletions(-) 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 d0b20de3c0..6436f9436d 100644 --- a/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import com.google.common.collect.ImmutableMap; import org.apache.http.Header; @@ -32,6 +33,7 @@ import org.opensearch.security.auditlog.AbstractAuditlogiUnitTest; 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.auditlog.integration.TestAuditlogImpl; import org.opensearch.security.auditlog.integration.TestAuditlogImpl.MessagesNotFoundException; @@ -41,9 +43,9 @@ import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.AnyOf.anyOf; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; public class ComplianceAuditlogTest extends AbstractAuditlogiUnitTest { @@ -111,10 +113,27 @@ public void testComplianceEnable() throws Exception { updateAuditConfig(AuditTestUtils.createAuditPayload(auditConfig)); // make an event happen - TestAuditlogImpl.doThenWaitForMessages(() -> { - rh.executePutRequest("emp/_doc/0?refresh", "{\"Designation\" : \"CEO\", \"Gender\" : \"female\", \"Salary\" : 100}"); - }, 7); - 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)); 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 f9bf7bb41d..5c23f1b37c 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java @@ -16,6 +16,7 @@ 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; @@ -67,10 +68,10 @@ public static List doThenWaitForMessages(final Runnable action, fi try { action.run(); - final int maxSecondsToWaitForMessages = 1; + final int maxSecondsToWaitForMessages = 1; final boolean foundAll = latch.await(maxSecondsToWaitForMessages, TimeUnit.SECONDS); if (!foundAll) { - throw new MessagesNotFoundException(expectedCount, (int)latch.getCount()); + throw new MessagesNotFoundException(expectedCount, messages); } if (messages.size() != expectedCount) { throw new RuntimeException("Unexpected number of messages, was expecting " + expectedCount + ", recieved " + messages.size()); @@ -96,10 +97,12 @@ public boolean isHandlingBackpressure() { public static class MessagesNotFoundException extends RuntimeException { private final int expectedCount; private final int missingCount; - public MessagesNotFoundException(final int expectedCount, final int missingCount) { - super("Did not recieve all " + expectedCount +" audit messages after a short wait, missing " + missingCount + " messages"); + private final List foundMessages; + public MessagesNotFoundException(final int expectedCount, List foundMessages) { + super(MessagesNotFoundException.createDetailMessage(expectedCount, foundMessages)); this.expectedCount = expectedCount; - this.missingCount = missingCount; + this.missingCount = expectedCount - foundMessages.size(); + this.foundMessages = foundMessages; } public int getExpectedCount() { @@ -109,5 +112,20 @@ public int getExpectedCount() { 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 recieve 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(); + } } } From be876c0078b23dc6567f6bd2f585d5f14245b201 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 1 Jul 2022 14:16:48 -0400 Subject: [PATCH 010/369] Use version of netty from core's version.properties (#1926) Signed-off-by: Craig Perkins --- build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 6c3d584350..6dbe7952ca 100644 --- a/build.gradle +++ b/build.gradle @@ -220,10 +220,10 @@ configurations.all { force "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" force "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:${versions.jackson}" force "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" - force 'io.netty:netty-buffer:4.1.73.Final' - force 'io.netty:netty-common:4.1.73.Final' - force 'io.netty:netty-handler:4.1.73.Final' - force 'io.netty:netty-transport:4.1.73.Final' + force "io.netty:netty-buffer:${versions.netty}" + force "io.netty:netty-common:${versions.netty}" + force "io.netty:netty-handler:${versions.netty}" + force "io.netty:netty-transport:${versions.netty}" } } From b63f7a86af349f73c4e72340e8cd08d5c7c6f353 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 14 Jul 2022 16:24:33 -0400 Subject: [PATCH 011/369] Force netty-transport-native-unix-common version (#1945) Signed-off-by: Craig Perkins --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 6dbe7952ca..35c25c020c 100644 --- a/build.gradle +++ b/build.gradle @@ -224,6 +224,7 @@ configurations.all { force "io.netty:netty-common:${versions.netty}" force "io.netty:netty-handler:${versions.netty}" force "io.netty:netty-transport:${versions.netty}" + force "io.netty:netty-transport-native-unix-common:${versions.netty}" } } From f153c27a3f546052394a2323001d1afa66b1923b Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 14 Jul 2022 16:11:43 -0500 Subject: [PATCH 012/369] Create a manually started workflow for bulk run of integration tests (#1937) --- .github/workflows/integration-tests.yml | 36 +++++++++++++++++++++++++ DEVELOPER_GUIDE.md | 11 ++++++++ 2 files changed, 47 insertions(+) create mode 100644 .github/workflows/integration-tests.yml diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 0000000000..b609ad7293 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,36 @@ +name: Bulk Integration Test + +on: [ workflow_dispatch ] + +env: + GRADLE_OPTS: -Dhttp.keepAlive=false + +jobs: + bulk-integration-test-run: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + jdk: [11, 17] + test-run: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + + steps: + - uses: actions/setup-java@v2 + with: + distribution: temurin # Temurin is a distribution of adoptium + java-version: ${{ matrix.jdk }} + + - uses: actions/checkout@v2 + + - run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew test + + - uses: actions/upload-artifact@v3 + if: always() + with: + name: ${{ matrix.jdk }}-${{ matrix.test-run }}-reports + path: | + ./build/reports/ + + - name: check archive for debugging + if: always() + run: echo "Check the artifact ${{ matrix.jdk }}-${{ matrix.test-run }}-reports.zip for detailed test results" diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 9fc1715033..f1e42e1663 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -6,6 +6,8 @@ So you want to contribute code to this project? Excellent! We're glad you're her - [Native platforms](#native-platforms) - [Building](#building) - [Using IntelliJ IDEA](#using-intellij-idea) + - [Running integration tests](#running-integration-tests) + - [Bulk test runs](#bulk-test-runs) - [Submitting Changes](#submitting-changes) - [Backports](#backports) @@ -135,6 +137,15 @@ curl -XGET https://localhost:9200/_plugins/_security/authinfo -u 'admin:admin' - Launch IntelliJ IDEA, choose **Project from Existing Sources**, and select directory with Gradle build script (`build.gradle`). +## Running integration tests + +Locally these can be run with `./gradlew test` with detailed results being avaliable at `${project-root}/build/reports/tests/test/index.html`, or run through an IDEs JUnit test runner. + +Integration tests are automatically run on all pull requests for all supported versions of the JDK. These must pass for change(s) to be merged. Detailed logs of these test results are avaliable by going to the GitHub action workflow's summary view and downloading the associated jdk version run of the tests, after extracting this file onto your local machine integration tests results are at `./tests/tests/index.html`. + +### Bulk test runs +To collect reliability data on test runs there is a manual GitHub action workflow called `Bulk Integration Test`. The workflow is started for a branch on this project or in a fork by going to [GitHub action workflows](https://github.com/opensearch-project/security/actions/workflows/integration-tests.yml) and selecting `Run Workflow`. + ## Submitting Changes See [CONTRIBUTING](CONTRIBUTING.md). From d9bd0dd8ef9ef650c8cff92cf4abe3261565ce91 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Date: Tue, 26 Jul 2022 12:27:07 -0400 Subject: [PATCH 013/369] Increment version to 2.2.0.0 (#1948) Signed-off-by: Darshit Chanpura --- .github/workflows/ci.yml | 6 +++--- build.gradle | 2 +- bwc-test/build.gradle | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8972ac82b..3bb24077cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,9 +72,9 @@ jobs: cp -r build/ ./bwc-test/ mkdir ./bwc-test/src/test/resources/security_plugin_version_no_snapshot cp build/distributions/opensearch-security-${security_plugin_version_no_snapshot}.zip ./bwc-test/src/test/resources/${security_plugin_version_no_snapshot} - mkdir bwc-test/src/test/resources/2.0.0.0 - wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.0.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.0.0.0.zip - mv opensearch-security-2.0.0.0.zip bwc-test/src/test/resources/2.0.0.0/ + mkdir bwc-test/src/test/resources/2.1.0.0 + wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.1.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.1.0.0.zip + mv opensearch-security-2.1.0.0.zip bwc-test/src/test/resources/2.1.0.0/ cd bwc-test/ ./gradlew bwcTestSuite -Dtests.security.manager=false diff --git a/build.gradle b/build.gradle index 35c25c020c..64e15ddb85 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { ext { - opensearch_version = System.getProperty("opensearch.version", "2.1.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.2.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") diff --git a/bwc-test/build.gradle b/bwc-test/build.gradle index 8ad987b1a7..918c6ae8d1 100644 --- a/bwc-test/build.gradle +++ b/bwc-test/build.gradle @@ -73,16 +73,16 @@ dependencies { testImplementation "org.opensearch.test:framework:${opensearch_version}" } -String bwcVersion = "2.0.0.0"; +String bwcVersion = "2.1.0.0"; String baseName = "securityBwcCluster" String bwcFilePath = "src/test/resources/" -String projectVersion = "2.1.0.0" +String projectVersion = "2.2.0.0" 2.times {i -> testClusters { "${baseName}$i" { testDistribution = "ARCHIVE" - versions = ["2.0.0","2.1.0"] + versions = ["2.1.0","2.2.0"] numberOfNodes = 3 plugin(provider(new Callable() { @Override From cb24f6e140dbb4f36f48d03ce0fef7d80b50f247 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 26 Jul 2022 12:50:14 -0400 Subject: [PATCH 014/369] Tenant info action test (#1935) * Abstract waitForInit to AbstractSecurityUnitTest and increase configuration load timeout to 10s * Use waitForInit inside waitOrThrow for CCReplicationTest Signed-off-by: Craig Perkins --- .../ConfigurationRepository.java | 4 +-- .../security/RolesInjectorIntegTest.java | 12 ------- .../security/RolesValidationIntegTest.java | 13 -------- .../TransportUserInjectorIntegTest.java | 13 -------- .../integration/TestAuditlogImpl.java | 4 +-- .../ccstest/CrossClusterSearchTests.java | 22 ++----------- .../dlic/dlsfls/CCReplicationTest.java | 16 ++-------- .../dlic/rest/api/TenantInfoActionTest.java | 1 + .../test/AbstractSecurityUnitTest.java | 32 ++++++++++++++++++- 9 files changed, 40 insertions(+), 77 deletions(-) diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java index 84d3059942..81f5c5d60d 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java @@ -363,13 +363,13 @@ public Map> getConfigurationsFromIndex(Co } else { LOGGER.debug("security index exists and was created with ES 7 (new layout)"); } - retVal.putAll(validate(cl.load(configTypes.toArray(new CType[0]), 5, TimeUnit.SECONDS, acceptInvalid), configTypes.size())); + retVal.putAll(validate(cl.load(configTypes.toArray(new CType[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size())); } else { //wait (and use new layout) LOGGER.debug("security index not exists (yet)"); - retVal.putAll(validate(cl.load(configTypes.toArray(new CType[0]), 5, TimeUnit.SECONDS, acceptInvalid), configTypes.size())); + retVal.putAll(validate(cl.load(configTypes.toArray(new CType[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size())); } } catch (Exception e) { diff --git a/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java b/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java index aac9a93b98..9a356ff92e 100644 --- a/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java @@ -74,18 +74,6 @@ public Collection createComponents(Client client, ClusterService cluster } } - //Wait for the security plugin to load roles. - private void waitForInit(Client client) throws Exception { - try { - client.admin().cluster().health(new ClusterHealthRequest()).actionGet(); - } catch (OpenSearchSecurityException ex) { - if(ex.getMessage().contains("OpenSearch Security not initialized")) { - Thread.sleep(500); - waitForInit(client); - } - } - } - @Test public void testRolesInject() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityRoles("roles.yml"), Settings.EMPTY); diff --git a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java index 86168c0c14..588bcbb7fc 100644 --- a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java @@ -20,7 +20,6 @@ import org.junit.Test; import org.opensearch.OpenSearchSecurityException; -import org.opensearch.action.admin.cluster.health.ClusterHealthRequest; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.admin.indices.create.CreateIndexResponse; import org.opensearch.action.admin.indices.exists.indices.IndicesExistsRequest; @@ -71,18 +70,6 @@ public Collection createComponents(Client client, ClusterService cluster } } - //Wait for the security plugin to load roles. - private void waitForInit(Client client) throws Exception { - try { - client.admin().cluster().health(new ClusterHealthRequest()).actionGet(); - } catch (OpenSearchSecurityException ex) { - if(ex.getMessage().contains("OpenSearch Security not initialized")) { - Thread.sleep(500); - waitForInit(client); - } - } - } - @Test public void testRolesValidation() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityRoles("roles.yml"), Settings.EMPTY); diff --git a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java index 065b99146b..56a7b727b4 100644 --- a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java @@ -20,7 +20,6 @@ import org.junit.Test; import org.opensearch.OpenSearchSecurityException; -import org.opensearch.action.admin.cluster.health.ClusterHealthRequest; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.admin.indices.create.CreateIndexResponse; import org.opensearch.client.Client; @@ -67,18 +66,6 @@ public Collection createComponents(Client client, ClusterService cluster } } - //Wait for the security plugin to load roles. - private void waitForInit(Client client) throws Exception { - try { - client.admin().cluster().health(new ClusterHealthRequest()).actionGet(); - } catch (OpenSearchSecurityException ex) { - if(ex.getMessage().contains("OpenSearch Security not initialized")) { - Thread.sleep(500); - waitForInit(client); - } - } - } - @Test public void testSecurityUserInjection() throws Exception { final Settings clusterNodeSettings = Settings.builder() 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 5c23f1b37c..4677bc37a9 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java @@ -74,7 +74,7 @@ public static List doThenWaitForMessages(final Runnable action, fi throw new MessagesNotFoundException(expectedCount, messages); } if (messages.size() != expectedCount) { - throw new RuntimeException("Unexpected number of messages, was expecting " + expectedCount + ", recieved " + messages.size()); + throw new RuntimeException("Unexpected number of messages, was expecting " + expectedCount + ", received " + messages.size()); } } catch (final InterruptedException e) { throw new RuntimeException("Unexpected exception", e); @@ -119,7 +119,7 @@ public List getFoundMessages() { private static String createDetailMessage(final int expectedCount, final List foundMessages) { return new StringBuilder() - .append("Did not recieve all " + expectedCount + " audit messages after a short wait. ") + .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() diff --git a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java index 246e159c5a..e6b533c0b9 100644 --- a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java +++ b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java @@ -982,24 +982,6 @@ public void testCcsWithDiffCertsWithNodesDnDynamicallyAdded() throws Exception { assertThat(ccs.getBody(), containsString("cross_cluster_two:twitter")); } - //Wait for the security plugin to load roles. - private void waitOrThrow(Client client) throws Exception { - int failures = 0; - while(failures < 5) { - try { - client.admin().cluster().health(new ClusterHealthRequest()).actionGet(); - break; - } catch (OpenSearchSecurityException ex) { - if (ex.getMessage().contains("OpenSearch Security not initialized")) { - Thread.sleep(500); - failures++; - } else { - throw ex; - } - } - } - } - @Test public void testCcsWithRoleInjection() throws Exception { setupCcs(new DynamicSecurityConfig().setSecurityRoles("roles.yml")); @@ -1041,7 +1023,7 @@ public void testCcsWithRoleInjection() throws Exception { RolesInjectorIntegTest.RolesInjectorPlugin.injectedRoles = "invalid_user|invalid_role"; try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class, RolesInjectorIntegTest.RolesInjectorPlugin.class).start()) { - waitOrThrow(node.client()); + waitForInit(node.client()); Client remoteClient = node.client().getRemoteClusterClient("cross_cluster_two"); GetRequest getReq = new GetRequest("twitter", "0"); getReq.realtime(true); @@ -1061,7 +1043,7 @@ public void testCcsWithRoleInjection() throws Exception { RolesInjectorIntegTest.RolesInjectorPlugin.injectedRoles = "valid_user|opendistro_security_all_access"; try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class, RolesInjectorIntegTest.RolesInjectorPlugin.class).start()) { - waitOrThrow(node.client()); + waitForInit(node.client()); Client remoteClient = node.client().getRemoteClusterClient("cross_cluster_two"); GetRequest getReq = new GetRequest("twitter", "0"); getReq.realtime(true); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java index 8ceb4c336c..51cab5107f 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java @@ -158,20 +158,8 @@ protected void doExecute(Task task, MockReplicationRequest request, ActionListen //Wait for the security plugin to load roles. private void waitOrThrow(Client client, String index) throws Exception { - int failures = 0; - while(failures < 5) { - try { - client.execute(MockReplicationAction.INSTANCE, new MockReplicationRequest(index)).actionGet(); - break; - } catch (OpenSearchSecurityException ex) { - if (ex.getMessage().contains("OpenSearch Security not initialized")) { - Thread.sleep(500); - failures++; - } else { - throw ex; - } - } - } + waitForInit(client); + client.execute(MockReplicationAction.INSTANCE, new MockReplicationRequest(index)).actionGet(); } void populateData(Client tc) { diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java index 01004faba7..e6864b8244 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java @@ -60,6 +60,7 @@ public void testTenantInfoAPIAccess() throws Exception { public void testTenantInfoAPIUpdate() throws Exception { Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build(); setup(settings); + rh.keystore = "restapi/kirk-keystore.jks"; rh.sendHTTPClientCredentials = true; rh.sendAdminCertificate = true; diff --git a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java index c5c3172855..f9913d9478 100644 --- a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java +++ b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java @@ -34,6 +34,7 @@ import java.util.Base64; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; import javax.net.ssl.SSLContext; @@ -57,6 +58,8 @@ import org.junit.rules.TestName; import org.junit.rules.TestWatcher; +import org.opensearch.OpenSearchSecurityException; +import org.opensearch.action.admin.cluster.health.ClusterHealthRequest; import org.opensearch.action.admin.cluster.node.info.NodesInfoRequest; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.index.IndexRequest; @@ -83,7 +86,7 @@ /* * There are real thread leaks during test execution, not all threads are - * properly waited on or interupted. While this normally doesn't create test + * properly waited on or interrupted. While this normally doesn't create test * failures, retries mitigate this. Remove this attribute to explore these * issues. */ @@ -164,6 +167,33 @@ protected RestHighLevelClient getRestClient(ClusterInfo info, String keyStoreNam } } + /** Wait for the security plugin to load roles. */ + public void waitForInit(Client client) { + int maxRetries = 5; + Optional retainedException = Optional.empty(); + for (int i = 0; i < maxRetries; i++) { + try { + client.admin().cluster().health(new ClusterHealthRequest()).actionGet(); + retainedException = Optional.empty(); + return; + } catch (OpenSearchSecurityException ex) { + if(ex.getMessage().contains("OpenSearch Security not initialized")) { + retainedException = Optional.of(ex); + try { + Thread.sleep(500); + } catch (InterruptedException e) { /* ignored */ } + } else { + // plugin is initialized, but another error received. + // Example could be user does not have permissions for cluster:monitor/health + retainedException = Optional.empty(); + } + } + } + if (retainedException.isPresent()) { + throw new RuntimeException(retainedException.get()); + } + } + protected void initialize(ClusterHelper clusterHelper, ClusterInfo clusterInfo, DynamicSecurityConfig securityConfig) throws IOException { try (Client tc = clusterHelper.nodeClient()) { Assert.assertEquals(clusterInfo.numNodes, From d96da6c0c519495fd89fafcccd67c6d407ec8d02 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Tue, 26 Jul 2022 14:29:55 -0400 Subject: [PATCH 015/369] Update to Gradle 7.5 (#1963) Signed-off-by: Andriy Redko --- bwc-test/build.gradle | 2 +- bwc-test/gradle/wrapper/gradle-wrapper.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bwc-test/build.gradle b/bwc-test/build.gradle index 918c6ae8d1..572563ec06 100644 --- a/bwc-test/build.gradle +++ b/bwc-test/build.gradle @@ -47,7 +47,7 @@ ext { buildscript { ext { - opensearch_version = System.getProperty("opensearch.version", "2.1.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.2.0-SNAPSHOT") opensearch_group = "org.opensearch" } repositories { diff --git a/bwc-test/gradle/wrapper/gradle-wrapper.properties b/bwc-test/gradle/wrapper/gradle-wrapper.properties index 92f06b50fd..2ec77e51a9 100644 --- a/bwc-test/gradle/wrapper/gradle-wrapper.properties +++ b/bwc-test/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 92f06b50fd..2ec77e51a9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 62cf906cefaa488e12dec04b59b50339d1e7adfb Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Mon, 1 Aug 2022 12:14:42 -0400 Subject: [PATCH 016/369] Add release notes for 2.2.0.0 release (#1974) Signed-off-by: Craig Perkins --- .../opensearch-security.release-notes-2.2.0.0.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-2.2.0.0.md diff --git a/release-notes/opensearch-security.release-notes-2.2.0.0.md b/release-notes/opensearch-security.release-notes-2.2.0.0.md new file mode 100644 index 0000000000..2d98e61c13 --- /dev/null +++ b/release-notes/opensearch-security.release-notes-2.2.0.0.md @@ -0,0 +1,14 @@ +## 2022-08-11 Version 2.2.0.0 + +Compatible with OpenSearch 2.2.0 + +### Test Fix + +* Abstract waitForInit to minimize duplication and improve test reliability ([#1935](https://github.com/opensearch-project/security/pull/1935)) +* Create a manually started workflow for bulk run of integration tests ([#1937](https://github.com/opensearch-project/security/pull/1937)) + +### Maintenance + +* Update to Gradle 7.5 ([#1963](https://github.com/opensearch-project/security/pull/1963)) +* Increment version to 2.2.0.0 ([#1948](https://github.com/opensearch-project/security/pull/1948)) +* Force netty-transport-native-unix-common version ([#1945](https://github.com/opensearch-project/security/pull/1945)) From 235d25672139c0663b422c3f7a2354e0250f23b0 Mon Sep 17 00:00:00 2001 From: Prudhvi Godithi Date: Mon, 1 Aug 2022 12:25:42 -0400 Subject: [PATCH 017/369] Staging for version increment automation (#1932) * Version increment automation Signed-off-by: pgodithi --- .github/workflows/ci.yml | 8 ++++++++ build.gradle | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bb24077cf..177ed1c789 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,6 +125,14 @@ jobs: - run: ./gradlew clean assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip + - run: | + ## 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)) + ./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/distributions/ if: failure() diff --git a/build.gradle b/build.gradle index 64e15ddb85..8631fa7a5b 100644 --- a/build.gradle +++ b/build.gradle @@ -487,3 +487,20 @@ afterEvaluate { tasks = ['build', 'buildRpm', 'buildDeb'] } } + +// updateVersion: Task to auto increment to the next development iteration +task updateVersion { + onlyIf { System.getProperty('newVersion') } + doLast { + ext.newVersion = System.getProperty('newVersion') + println "Setting version to ${newVersion}." + // String tokenization to support -SNAPSHOT + ant.replaceregexp(match: opensearch_version.tokenize('-')[0], replace: newVersion.tokenize('-')[0], flags:'g', byline:true) { + fileset(dir: projectDir) { + // Include the required files that needs to be updated with new Version + include(name: "bwc-test/build.gradle") + } + } + ant.replaceregexp(file:'build.gradle', match: '"opensearch.version", "\\d.*"', replace: '"opensearch.version", "' + newVersion.tokenize('-')[0] + '-SNAPSHOT"', flags:'g', byline:true) + } +} From 50a94b47da5986120f4a5cbdd03d1f9d3ccae55a Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Mon, 1 Aug 2022 12:45:11 -0400 Subject: [PATCH 018/369] Use Collections.synchronizedSet and Collections.synchronizedMap for roles, securityRoles and attributes in User (#1970) Signed-off-by: Craig Perkins --- src/main/java/org/opensearch/security/user/User.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opensearch/security/user/User.java b/src/main/java/org/opensearch/security/user/User.java index d18e587ee8..83c7ea2eb5 100644 --- a/src/main/java/org/opensearch/security/user/User.java +++ b/src/main/java/org/opensearch/security/user/User.java @@ -62,10 +62,10 @@ public class User implements Serializable, Writeable, CustomAttributesAware { /** * roles == backend_roles */ - private final Set roles = new HashSet(); - private final Set securityRoles = new HashSet(); + private final Set roles = Collections.synchronizedSet(new HashSet()); + private final Set securityRoles = Collections.synchronizedSet(new HashSet()); private String requestedTenant; - private Map attributes = new HashMap<>(); + private Map attributes = Collections.synchronizedMap(new HashMap<>()); private boolean isInjected = false; public User(final StreamInput in) throws IOException { @@ -73,7 +73,7 @@ public User(final StreamInput in) throws IOException { name = in.readString(); roles.addAll(in.readList(StreamInput::readString)); requestedTenant = in.readString(); - attributes = in.readMap(StreamInput::readString, StreamInput::readString); + attributes = Collections.synchronizedMap(in.readMap(StreamInput::readString, StreamInput::readString)); securityRoles.addAll(in.readList(StreamInput::readString)); } @@ -250,7 +250,7 @@ public void writeTo(StreamOutput out) throws IOException { */ public synchronized final Map getCustomAttributesMap() { if(attributes == null) { - attributes = new HashMap<>(); + attributes = Collections.synchronizedMap(new HashMap<>()); } return attributes; } @@ -262,6 +262,6 @@ public final void addSecurityRoles(final Collection securityRoles) { } public final Set getSecurityRoles() { - return this.securityRoles == null ? Collections.emptySet() : Collections.unmodifiableSet(this.securityRoles); + return this.securityRoles == null ? Collections.synchronizedSet(Collections.emptySet()) : Collections.unmodifiableSet(this.securityRoles); } } From 437eb02c1d1f450af95feacafe072fec3861ec5a Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 3 Aug 2022 12:16:33 -0400 Subject: [PATCH 019/369] Fix gradle build issue after upgrade to Lucene 9.3.0 (#1988) Signed-off-by: Craig Perkins --- .../security/configuration/DlsFlsFilterLeafReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java index 873fb46de8..2b390b7c85 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java @@ -965,7 +965,7 @@ public long nextOrd() throws IOException { } @Override - public long docValueCount() { + public int docValueCount() { return sortedSetDocValues.docValueCount(); } From f7b6fe588f0eb44c1767b8b6c00ca0bf3f121606 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Date: Wed, 3 Aug 2022 16:18:34 -0400 Subject: [PATCH 020/369] Adds a basic sanity test to run against a remote cluster (#1958) * Initial commit with a base test, gradle change and execution script Signed-off-by: Darshit Chanpura * Disable JarHell with classpsloading override Signed-off-by: Peter Nied * Sample certficates for sanity tests Signed-off-by: Darshit Chanpura * Updates test and adds a base class that extends OpenSearchRestTestCase and modifies client builder methods Signed-off-by: Darshit Chanpura * Adds common-utils dependency and modifies integTestRemote task Signed-off-by: Darshit Chanpura * Updates integtest script Signed-off-by: Darshit Chanpura * Makes integtest script executable Signed-off-by: Darshit Chanpura * Updates code hygiene Signed-off-by: Darshit Chanpura * Disabling integTest task that was auto-triggered Signed-off-by: Darshit Chanpura * Fix broken github action for build-artifacts Signed-off-by: Darshit Chanpura * Updates bwc gradle to skip sanity tests Signed-off-by: Darshit Chanpura * Updates test task to skip sanity test Signed-off-by: Darshit Chanpura * Code cleanup Signed-off-by: Darshit Chanpura * Documenting the changes Signed-off-by: Darshit Chanpura * Updates exclusion filter for build gradle test task Signed-off-by: Darshit Chanpura * Addresses requested PR changes Signed-off-by: Darshit Chanpura * Fixes incorrect license headers Signed-off-by: Darshit Chanpura * Adds sanity tests CI check Signed-off-by: Darshit Chanpura * Hard codes common-utils version to stop CI from failing Signed-off-by: Darshit Chanpura * Updates TODO comment with tracking issue Signed-off-by: Darshit Chanpura * Makes common-utils version dynamic and acceptable as input parameter to gradle command Signed-off-by: Darshit Chanpura * Update bwc build gradle to exclude sanity tests Signed-off-by: Darshit Chanpura * Hardcodes default common utils version Signed-off-by: Darshit Chanpura * Uses assertThat Signed-off-by: Darshit Chanpura * Removes incorrect license headers Signed-off-by: Darshit Chanpura * Fixes CI errors Signed-off-by: Darshit Chanpura Co-authored-by: Peter Nied --- .github/workflows/plugin_install.yml | 3 + README.md | 10 ++ build.gradle | 31 ++++- bwc-test/build.gradle | 9 +- scripts/integtest.sh | 110 ++++++++++++++++++ .../org/opensearch/bootstrap/JarHell.java | 32 +++++ .../sanity/tests/SecurityRestTestCase.java | 99 ++++++++++++++++ .../sanity/tests/SingleClusterSanityIT.java | 51 ++++++++ .../sanity-tests/opensearch-node-key.pem | 28 +++++ .../sanity-tests/opensearch-node.pem | 28 +++++ src/test/resources/sanity-tests/root-ca.pem | 24 ++++ src/test/resources/sanity-tests/test-kirk.jks | Bin 0 -> 3874 bytes 12 files changed, 422 insertions(+), 3 deletions(-) create mode 100755 scripts/integtest.sh create mode 100644 src/test/java/org/opensearch/bootstrap/JarHell.java create mode 100644 src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java create mode 100644 src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java create mode 100644 src/test/resources/sanity-tests/opensearch-node-key.pem create mode 100644 src/test/resources/sanity-tests/opensearch-node.pem create mode 100644 src/test/resources/sanity-tests/root-ca.pem create mode 100644 src/test/resources/sanity-tests/test-kirk.jks diff --git a/.github/workflows/plugin_install.yml b/.github/workflows/plugin_install.yml index 68d2be5f05..c0c6df60a4 100644 --- a/.github/workflows/plugin_install.yml +++ b/.github/workflows/plugin_install.yml @@ -60,3 +60,6 @@ jobs: - name: Get Docker Logs if: always() run: docker logs ops + + - name: Run sanity tests + run: ./gradlew integTestRemote -Dtests.rest.cluster=localhost:9200 -Dtests.cluster=localhost:9200 -Dtests.clustername="opensearch" -Dhttps=true -Duser=admin -Dpassword=admin diff --git a/README.md b/README.md index a5eb49e0b7..7283705a4c 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,16 @@ Run all tests: ./gradlew clean test ``` +Run tests against local cluster: +```bash +./gradlew integTestRemote -Dtests.rest.cluster=localhost:9200 -Dtests.cluster=localhost:9200 -Dtests.clustername=docker-cluster -Dsecurity=true -Dhttps=true -Duser=admin -Dpassword=admin -Dcommon_utils.version="2.2.0.0" +``` +OR +```bash +./scripts/integtest.sh +``` +Note: To run against a remote cluster replace cluster-name and `localhost:9200` with the IPAddress:Port of that cluster. + Build artifacts (zip, deb, rpm): ```bash ./gradlew clean assemble diff --git a/build.gradle b/build.gradle index 8631fa7a5b..0063c0e8e5 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,8 @@ * GitHub history for details. */ +import org.opensearch.gradle.test.RestIntegTestTask + buildscript { ext { opensearch_version = System.getProperty("opensearch.version", "2.2.0-SNAPSHOT") @@ -18,6 +20,10 @@ buildscript { // 2.0.0-rc1-SNAPSHOT -> 2.0.0.0-rc1-SNAPSHOT version_tokens = opensearch_version.tokenize('-') opensearch_build = version_tokens[0] + '.0' + + common_utils_version = System.getProperty("common_utils.version", '2.1.0.0') + + if (buildVersionQualifier) { opensearch_build += "-${buildVersionQualifier}" } @@ -57,6 +63,8 @@ allprojects { apply plugin: 'opensearch.opensearchplugin' apply plugin: 'opensearch.pluginzip' +apply plugin: 'opensearch.rest-test' +apply plugin: 'opensearch.testclusters' licenseFile = rootProject.file('LICENSE.txt') noticeFile = rootProject.file('NOTICE.txt') @@ -87,6 +95,9 @@ jarHell.enabled = false test { include '**/*.class' + filter { + excludeTestsMatching "org.opensearch.security.sanity.tests.*" + } maxParallelForks = 8 jvmArgs += "-Xmx3072m" if (JavaVersion.current() > JavaVersion.VERSION_1_8) { @@ -330,6 +341,7 @@ dependencies { testImplementation 'org.springframework:spring-beans:5.3.20' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation "org.opensearch:common-utils:${common_utils_version}" // JUnit build requirement testCompileOnly 'org.apiguardian:apiguardian-api:1.0.0' // Kafka test execution @@ -425,6 +437,23 @@ publishing { } } +task integTestRemote(type: RestIntegTestTask) { + + systemProperty "tests.security.manager", "false" + systemProperty "user", System.getProperty("user") + systemProperty "password", System.getProperty("password") + systemProperty "https", System.getProperty("https") + systemProperty "security.enabled", "true" + + filter { + setIncludePatterns("org.opensearch.security.sanity.tests.*IT") + } +} + +integTestRemote.enabled = System.getProperty("tests.rest.cluster") != null +// should be updated appropriately, when we add integTests in future +integTest.enabled = false + // This is afterEvaluate because the bundlePlugin ZIP task is updated afterEvaluate and changes the ZIP name to match the plugin name afterEvaluate { ospackage { @@ -503,4 +532,4 @@ task updateVersion { } ant.replaceregexp(file:'build.gradle', match: '"opensearch.version", "\\d.*"', replace: '"opensearch.version", "' + newVersion.tokenize('-')[0] + '-SNAPSHOT"', flags:'g', byline:true) } -} +} diff --git a/bwc-test/build.gradle b/bwc-test/build.gradle index 572563ec06..2ee89844ee 100644 --- a/bwc-test/build.gradle +++ b/bwc-test/build.gradle @@ -82,7 +82,7 @@ String projectVersion = "2.2.0.0" testClusters { "${baseName}$i" { testDistribution = "ARCHIVE" - versions = ["2.1.0","2.2.0"] + versions = ["2.1.0","2.2.0"] numberOfNodes = 3 plugin(provider(new Callable() { @Override @@ -149,8 +149,8 @@ List> plugins = [ 2.times {i -> task "${baseName}#oldVersionClusterTask$i"(type: StandaloneRestIntegTestTask) { exclude '**/*Test*' + exclude '**/*Sanity*' useCluster testClusters."${baseName}$i" - exclude '**/*Test*' if (System.getProperty("mixedCluster") != null) { filter { includeTest("org.opensearch.security.bwc.SecurityBackwardsCompatibilityIT", "testPluginUpgradeInAMixedCluster") @@ -179,6 +179,7 @@ List> plugins = [ // This is also used as a one third upgraded cluster for a rolling upgrade. task "${baseName}#mixedClusterTask"(type: StandaloneRestIntegTestTask) { exclude '**/*Test*' + exclude '**/*Sanity*' dependsOn "${baseName}#oldVersionClusterTask0" useCluster testClusters."${baseName}0" doFirst { @@ -206,6 +207,7 @@ task "${baseName}#mixedClusterTask"(type: StandaloneRestIntegTestTask) { // This is used for rolling upgrade. task "${baseName}#twoThirdsUpgradedClusterTask"(type: StandaloneRestIntegTestTask) { exclude '**/*Test*' + exclude '**/*Sanity*' dependsOn "${baseName}#mixedClusterTask" useCluster testClusters."${baseName}0" doFirst { @@ -228,6 +230,7 @@ task "${baseName}#twoThirdsUpgradedClusterTask"(type: StandaloneRestIntegTestTas // This is used for rolling upgrade. task "${baseName}#rollingUpgradeClusterTask"(type: StandaloneRestIntegTestTask) { exclude '**/*Test*' + exclude '**/*Sanity*' dependsOn "${baseName}#twoThirdsUpgradedClusterTask" useCluster testClusters."${baseName}0" doFirst { @@ -249,6 +252,7 @@ task "${baseName}#rollingUpgradeClusterTask"(type: StandaloneRestIntegTestTask) // at the same time resulting in a fully upgraded cluster. tasks.register("${baseName}#fullRestartClusterTask", StandaloneRestIntegTestTask) { exclude '**/*Test*' + exclude '**/*Sanity*' dependsOn "${baseName}#oldVersionClusterTask1" useCluster testClusters."${baseName}1" doFirst { @@ -268,6 +272,7 @@ tasks.register("${baseName}#fullRestartClusterTask", StandaloneRestIntegTestTask // A bwc test suite which runs all the bwc tasks combined. task bwcTestSuite(type: StandaloneRestIntegTestTask) { exclude '**/*Test*' + exclude '**/*Sanity*' dependsOn tasks.named("${baseName}#mixedClusterTask") dependsOn tasks.named("${baseName}#rollingUpgradeClusterTask") dependsOn tasks.named("${baseName}#fullRestartClusterTask") diff --git a/scripts/integtest.sh b/scripts/integtest.sh new file mode 100755 index 0000000000..961b91299c --- /dev/null +++ b/scripts/integtest.sh @@ -0,0 +1,110 @@ +#!/bin/bash + +set -e + +function usage() { + echo "" + echo "This script is used to run integration tests for plugin installed on a remote OpenSearch/Dashboards cluster." + echo "--------------------------------------------------------------------------" + echo "Usage: $0 [args]" + echo "" + echo "Required arguments:" + echo "None" + echo "" + echo "Optional arguments:" + echo -e "-b BIND_ADDRESS\t, defaults to localhost | 127.0.0.1, can be changed to any IP or domain name for the cluster location." + echo -e "-p BIND_PORT\t, defaults to 9200, can be changed to any port for the cluster location." + echo -e "-s SECURITY_ENABLED\t(true | false), defaults to true. Specify the OpenSearch/Dashboards have security enabled or not." + echo -e "-c CREDENTIAL\t(usename:password), no defaults, effective when SECURITY_ENABLED=true." + echo -e "-h\tPrint this message." + echo -e "-v OPENSEARCH_VERSION\t, no defaults" + echo -e "-n SNAPSHOT\t, defaults to false" + echo -e "-m CLUSTER_NAME\t, defaults to docker-cluster" + echo -e "-u COMMON_UTILS_VERSION\t, defaults to 2.2.0.0" + echo "--------------------------------------------------------------------------" +} + +while getopts ":h:b:p:s:c:v:n:t:m:u:" arg; do + case $arg in + h) + usage + exit 1 + ;; + b) + BIND_ADDRESS=$OPTARG + ;; + p) + BIND_PORT=$OPTARG + ;; + t) + TRANSPORT_PORT=$OPTARG + ;; + s) + SECURITY_ENABLED=$OPTARG + ;; + c) + CREDENTIAL=$OPTARG + ;; + m) + CLUSTER_NAME=$OPTARG + ;; + v) + # Do nothing as we're not consuming this param. + ;; + n) + # Do nothing as we're not consuming this param. + ;; + u) + COMMON_UTILS_VERSION=$OPTARG + ;; + :) + echo "-${OPTARG} requires an argument" + usage + exit 1 + ;; + ?) + echo "Invalid option: -${OPTARG}" + exit 1 + ;; + esac +done + + +if [ -z "$BIND_ADDRESS" ] +then + BIND_ADDRESS="localhost" +fi + +if [ -z "$BIND_PORT" ] +then + BIND_PORT="9200" +fi + +if [ -z "$SECURITY_ENABLED" ] +then + SECURITY_ENABLED="true" +fi + +if [ -z "$CREDENTIAL" ] +then + CREDENTIAL="admin:admin" +fi + +if [ -z "$CREDENTIAL" ] +then + CREDENTIAL="admin:admin" +fi + +if [ -z "$CLUSTER_NAME" ] +then + CLUSTER_NAME="docker-cluster" +fi +if [ -z "$COMMON_UTILS_VERSION" ] +then + COMMON_UTILS_VERSION="2.2.0.0" +fi + +USERNAME=`echo $CREDENTIAL | awk -F ':' '{print $1}'` +PASSWORD=`echo $CREDENTIAL | awk -F ':' '{print $2}'` + +./gradlew integTestRemote -Dtests.rest.cluster="$BIND_ADDRESS:$BIND_PORT" -Dtests.cluster="$BIND_ADDRESS:$BIND_PORT" -Dsecurity_enabled=$SECURITY_ENABLED -Dtests.clustername=$CLUSTER_NAME -Dhttps=true -Duser=$USERNAME -Dpassword=$PASSWORD -Dcommon_utils.version=$COMMON_UTILS_VERSION diff --git a/src/test/java/org/opensearch/bootstrap/JarHell.java b/src/test/java/org/opensearch/bootstrap/JarHell.java new file mode 100644 index 0000000000..11978a780c --- /dev/null +++ b/src/test/java/org/opensearch/bootstrap/JarHell.java @@ -0,0 +1,32 @@ +/* + * 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.bootstrap; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +/** + * Disable JarHell to unblock test development + * https://github.com/opensearch-project/security/issues/1938 + */ +public class JarHell { + private JarHell() {} + public static void checkJarHell(Consumer output) throws IOException, Exception {} + public static void checkJarHell(Set urls, Consumer output) throws URISyntaxException, IOException {} + public static void checkVersionFormat(String targetVersion) {} + public static void checkJavaVersion(String resource, String targetVersion) {} + public static Set parseClassPath() {return new HashSet();} +} diff --git a/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java b/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java new file mode 100644 index 0000000000..2418bd2194 --- /dev/null +++ b/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java @@ -0,0 +1,99 @@ +/* + * 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.sanity.tests; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; + +import org.apache.http.HttpHost; + +import org.opensearch.client.Request; +import org.opensearch.client.Response; +import org.opensearch.client.RestClient; +import org.opensearch.client.RestClientBuilder; +import org.opensearch.common.io.PathUtils; +import org.opensearch.common.settings.Settings; +import org.opensearch.commons.rest.SecureRestClientBuilder; +import org.opensearch.test.rest.OpenSearchRestTestCase; + +import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_ENABLED; +import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_FILEPATH; +import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD; +import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_PASSWORD; +import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_PEMCERT_FILEPATH; + +/** + * Overrides OpenSearchRestTestCase to fit the use-case for testing + * against remote cluster for Security Plugin. + * + * Modify this test class as needed + */ +@SuppressWarnings("unchecked") +public class SecurityRestTestCase extends OpenSearchRestTestCase { + + private static final String CERT_FILE_DIRECTORY = "sanity-tests/"; + private boolean isHttps() { + return System.getProperty("https").equals("true"); + } + private boolean securityEnabled() { + return System.getProperty("security.enabled").equals("true"); + } + + @Override + protected Settings restAdminSettings(){ + + return Settings + .builder() + .put("http.port", 9200) + .put(OPENSEARCH_SECURITY_SSL_HTTP_ENABLED, isHttps()) + .put(OPENSEARCH_SECURITY_SSL_HTTP_PEMCERT_FILEPATH, CERT_FILE_DIRECTORY + "opensearch-node.pem") + .put("plugins.security.ssl.http.pemkey_filepath", CERT_FILE_DIRECTORY + "opensearch-node-key.pem") + .put("plugins.security.ssl.transport.pemtrustedcas_filepath", CERT_FILE_DIRECTORY + "root-ca.pem") + .put(OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, CERT_FILE_DIRECTORY + "test-kirk.jks") + .put(OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_PASSWORD, "changeit") + .put(OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD, "changeit") + .build(); + } + + @Override + protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOException { + + if(securityEnabled()){ + String keystore = settings.get(OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_FILEPATH); + + if(keystore != null){ + // create adminDN (super-admin) client + File file = new File(getClass().getClassLoader().getResource(CERT_FILE_DIRECTORY).getFile()); + Path configPath = PathUtils.get(file.toURI()).getParent().toAbsolutePath(); + return new SecureRestClientBuilder(settings, configPath).setSocketTimeout(60000).build(); + } + + // create client with passed user + String userName = System.getProperty("user"); + String password = System.getProperty("password"); + return new SecureRestClientBuilder(hosts, isHttps(), userName, password).setSocketTimeout(60000).build(); + } + else { + RestClientBuilder builder = RestClient.builder(hosts); + configureClient(builder, settings); + builder.setStrictDeprecationMode(true); + return builder.build(); + } + } + + protected static Map getAsMapByAdmin(final String endpoint) throws IOException { + Response response = adminClient().performRequest(new Request("GET", endpoint)); + return responseAsMap(response); + } +} diff --git a/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java b/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java new file mode 100644 index 0000000000..c327687371 --- /dev/null +++ b/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java @@ -0,0 +1,51 @@ +/* + * 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.sanity.tests; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.hamcrest.MatcherAssert; +import org.junit.Test; + +import static org.hamcrest.Matchers.anEmptyMap; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + + +@SuppressWarnings("unchecked") +public class SingleClusterSanityIT extends SecurityRestTestCase { + + private static final String SECURITY_PLUGIN_NAME = "opensearch-security"; + + @Test + public void testSecurityPluginInstallation() throws Exception { + verifyPluginInstallationOnAllNodes(); + } + + private void verifyPluginInstallationOnAllNodes() throws Exception { + + Map> nodesInCluster = (Map>) getAsMapByAdmin("_nodes").get("nodes"); + + for (Map node : nodesInCluster.values()) { + + List> plugins = (List>) node.get("plugins"); + Set pluginNames = plugins.stream().map(map -> map.get("name")).collect(Collectors.toSet()); + + MatcherAssert.assertThat(pluginNames, contains(SECURITY_PLUGIN_NAME)); + } + MatcherAssert.assertThat(nodesInCluster, is(not(anEmptyMap()))); + } +} diff --git a/src/test/resources/sanity-tests/opensearch-node-key.pem b/src/test/resources/sanity-tests/opensearch-node-key.pem new file mode 100644 index 0000000000..4ac2cb57a7 --- /dev/null +++ b/src/test/resources/sanity-tests/opensearch-node-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCWvn+O+rywfgMC +ud24mAclMDfuNA/IzCKLxl5usIE/PvUm7PPfXQ14LfQhNQXqOuaD9fiVM+HO1BzK +wmN3j4g7eHInR1cxENoNGKFa0Fr9EXnUv8sfwyobPD8NTu9eaH7T+d6f9oow+Q4n +xb9Xin5IRR/pcJ8v7zEjcXpZaZejcSU4iVZ0PR2Di4H9rfe9SEyR5wLrsVBePB3L +jaL1uK4bZF3n/JGgDe3BNy1PgPU+O+FCzQipBBTyJWQCjd4iTRXVbMa01PglAR85 +O9w6NXApBLyWdGRY6dGd8vMC2P4KlhnxlcgPZdglKniGTX+eTzT7Rszq77zjYrou +PLwSh9S7AgMBAAECggEABwiohxFoEIwws8XcdKqTWsbfNTw0qFfuHLuK2Htf7IWR +htlzn66F3F+4jnwc5IsPCoVFriCXnsEC/usHHSMTZkL+gJqxlNaGdin6DXS/aiOQ +nb69SaQfqNmsz4ApZyxVDqsQGkK0vAhDAtQVU45gyhp/nLLmmqP8lPzMirOEodmp +U9bA8t/ttrzng7SVAER42f6IVpW0iTKTLyFii0WZbq+ObViyqib9hVFrI6NJuQS+ +IelcZB0KsSi6rqIjXg1XXyMiIUcSlhq+GfEa18AYgmsbPwMbExate7/8Ci7ZtCbh +lx9bves2+eeqq5EMm3sMHyhdcg61yzd5UYXeZhwJkQKBgQDS9YqrAtztvLY2gMgv +d+wOjb9awWxYbQTBjx33kf66W+pJ+2j8bI/XX2CpZ98w/oq8VhMqbr9j5b8MfsrF +EoQvedA4joUo8sXd4j1mR2qKF4/KLmkgy6YYusNP2UrVSw7sh77bzce+YaVVoO/e +0wIVTHuD/QZ6fG6MasOqcbl6hwKBgQC27cQruaHFEXR/16LrMVAX+HyEEv44KOCZ +ij5OE4P7F0twb+okngG26+OJV3BtqXf0ULlXJ+YGwXCRf6zUZkld3NMy3bbKPgH6 +H/nf3BxqS2tudj7+DV52jKtisBghdvtlKs56oc9AAuwOs37DvhptBKUPdzDDqfys +Qchv5JQdLQKBgERev+pcqy2Bk6xmYHrB6wdseS/4sByYeIoi0BuEfYH4eB4yFPx6 +UsQCbVl6CKPgWyZe3ydJbU37D8gE78KfFagtWoZ56j4zMF2RDUUwsB7BNCDamce/ +OL2bCeG/Erm98cBG3lxufOX+z47I8fTNfkdY2k8UmhzoZwurLm73HJ3RAoGBAKsp +6yamuXF2FbYRhUXgjHsBbTD/vJO72/yO2CGiLRpi/5mjfkjo99269trp0C8sJSub +5PBiSuADXFsoRgUv+HI1UAEGaCTwxFTQWrRWdtgW3d0sE2EQDVWL5kmfT9TwSeat +mSoyAYR5t3tCBNkPJhbgA7pm4mASzHQ50VyxWs25AoGBAKPFx9X2oKhYQa+mW541 +bbqRuGFMoXIIcr/aeM3LayfLETi48o5NDr2NDP11j4yYuz26YLH0Dj8aKpWuehuH +uB27n6j6qu0SVhQi6mMJBe1JrKbzhqMKQjYOoy8VsC2gdj5pCUP/kLQPW7zm9diX +CiKTtKgPIeYdigor7V3AHcVT +-----END PRIVATE KEY----- diff --git a/src/test/resources/sanity-tests/opensearch-node.pem b/src/test/resources/sanity-tests/opensearch-node.pem new file mode 100644 index 0000000000..7ba92534e4 --- /dev/null +++ b/src/test/resources/sanity-tests/opensearch-node.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEyTCCA7GgAwIBAgIGAWLrc1O2MA0GCSqGSIb3DQEBCwUAMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwHhcNMTgwNDIy +MDM0MzQ3WhcNMjgwNDE5MDM0MzQ3WjBeMRIwEAYKCZImiZPyLGQBGRYCZGUxDTAL +BgNVBAcMBHRlc3QxDTALBgNVBAoMBG5vZGUxDTALBgNVBAsMBG5vZGUxGzAZBgNV +BAMMEm5vZGUtMC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAJa+f476vLB+AwK53biYByUwN+40D8jMIovGXm6wgT8+9Sbs899dDXgt +9CE1Beo65oP1+JUz4c7UHMrCY3ePiDt4cidHVzEQ2g0YoVrQWv0RedS/yx/DKhs8 +Pw1O715oftP53p/2ijD5DifFv1eKfkhFH+lwny/vMSNxellpl6NxJTiJVnQ9HYOL +gf2t971ITJHnAuuxUF48HcuNovW4rhtkXef8kaAN7cE3LU+A9T474ULNCKkEFPIl +ZAKN3iJNFdVsxrTU+CUBHzk73Do1cCkEvJZ0ZFjp0Z3y8wLY/gqWGfGVyA9l2CUq +eIZNf55PNPtGzOrvvONiui48vBKH1LsCAwEAAaOCAVkwggFVMIG8BgNVHSMEgbQw +gbGAFJI1DOAPHitF9k0583tfouYSl0BzoYGVpIGSMIGPMRMwEQYKCZImiZPyLGQB +GRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQRXhhbXBs +ZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMSEw +HwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0GCAQEwHQYDVR0OBBYEFKyv +78ZmFjVKM9g7pMConYH7FVBHMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgXg +MCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA1BgNVHREELjAsiAUq +AwQFBYISbm9kZS0wLmV4YW1wbGUuY29tgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZI +hvcNAQELBQADggEBAIOKuyXsFfGv1hI/Lkpd/73QNqjqJdxQclX57GOMWNbOM5H0 +5/9AOIZ5JQsWULNKN77aHjLRr4owq2jGbpc/Z6kAd+eiatkcpnbtbGrhKpOtoEZy +8KuslwkeixpzLDNISSbkeLpXz4xJI1ETMN/VG8ZZP1bjzlHziHHDu0JNZ6TnNzKr +XzCGMCohFfem8vnKNnKUneMQMvXd3rzUaAgvtf7Hc2LTBlf4fZzZF1EkwdSXhaMA +1lkfHiqOBxtgeDLxCHESZ2fqgVqsWX+t3qHQfivcPW6txtDyrFPRdJOGhiMGzT/t +e/9kkAtQRgpTb3skYdIOOUOV0WGQ60kJlFhAzIs= +-----END CERTIFICATE----- diff --git a/src/test/resources/sanity-tests/root-ca.pem b/src/test/resources/sanity-tests/root-ca.pem new file mode 100644 index 0000000000..4015d866e1 --- /dev/null +++ b/src/test/resources/sanity-tests/root-ca.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjzETMBEGCgmSJomT8ixk +ARkWA2NvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1w +bGUgQ29tIEluYy4xITAfBgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEh +MB8GA1UEAwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMB4XDTE4MDQyMjAzNDM0 +NloXDTI4MDQxOTAzNDM0NlowgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJ +kiaJk/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEw +HwYDVQQLDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1w +bGUgQ29tIEluYy4gUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAK/u+GARP5innhpXK0c0q7s1Su1VTEaIgmZr8VWI6S8amf5cU3ktV7WT9SuV +TsAm2i2A5P+Ctw7iZkfnHWlsC3HhPUcd6mvzGZ4moxnamM7r+a9otRp3owYoGStX +ylVTQusAjbq9do8CMV4hcBTepCd+0w0v4h6UlXU8xjhj1xeUIz4DKbRgf36q0rv4 +VIX46X72rMJSETKOSxuwLkov1ZOVbfSlPaygXIxqsHVlj1iMkYRbQmaTib6XWHKf +MibDaqDejOhukkCjzpptGZOPFQ8002UtTTNv1TiaKxkjMQJNwz6jfZ53ws3fh1I0 +RWT6WfM4oeFRFnyFRmc4uYTUgAkCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAf +BgNVHSMEGDAWgBSSNQzgDx4rRfZNOfN7X6LmEpdAczAdBgNVHQ4EFgQUkjUM4A8e +K0X2TTnze1+i5hKXQHMwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQBoQHvwsR34hGO2m8qVR9nQ5Klo5HYPyd6ySKNcT36OZ4AQfaCGsk+SecTi35QF +RHL3g2qffED4tKR0RBNGQSgiLavmHGCh3YpDupKq2xhhEeS9oBmQzxanFwWFod4T +nnsG2cCejyR9WXoRzHisw0KJWeuNlwjUdJY0xnn16srm1zL/M/f0PvCyh9HU1mF1 +ivnOSqbDD2Z7JSGyckgKad1Omsg/rr5XYtCeyJeXUPcmpeX6erWJJNTUh6yWC/hY +G/dFC4xrJhfXwz6Z0ytUygJO32bJG4Np2iGAwvvgI9EfxzEv/KP+FGrJOvQJAq4/ +BU36ZAa80W/8TBnqZTkNnqZV +-----END CERTIFICATE----- diff --git a/src/test/resources/sanity-tests/test-kirk.jks b/src/test/resources/sanity-tests/test-kirk.jks new file mode 100644 index 0000000000000000000000000000000000000000..174dbda656f41b10341adb78ab91a46afaae8a1c GIT binary patch literal 3874 zcmY+GcQhM}zs8e@RFtSn>{Y9_XzfvZl*TSQG6F9FNd(wu zFab99cRY+FKt2CO5E21u`*&mo0s{VisDB9%$qk|Z?*;}S1PKGv1*1XCugHHEKp;B6 z69Saqd|bch;ZdXcj@o48Or^T{VjiQWQ)um?koax&EW2Jd6%cmO+99&?<0M#TkhMY0 z>TOc9NNj$5o%GwnI2>ZpA<-syd;YVlrkqVstJxqe_w8#F0dlKW!#D3WVDWfwaN@uX z{)l!>hgv`=r)M_tPedAH8wS zrMCsCM3^vbf3iWkdUoK)O(h9`bxp3s^zq4CU5%IJN;Y04OLiLfXPS%;Duo}L?EKtE z$4DyO?uRf+Ovm@OBmMKYjcI;;3k(jA`wJ`_W&){Es6Nv(A-s;NYZhfPTZJ%tBZ{1@ zc|_(P(o|Du6c{sJ4@Q6w- zF)*aVb&dDqmGoH8(8Y;T2S?DR9+P|nUT>q8177|so}DjY7IWc!jB(9r?rJ%YyVvh5 z4`BJLeFX6F2g1N^WT?dWin3^|1>$*MQP~CSqFMgQ4m&bJp``1>I(!5Pe9&NB7{wXc z+p)Bs6Durb104tWmIOYRkBU~Waz;l#k`+@Fye00vbTIQq3dY*R{KBH-UF3%r{=+v` zqu(DD1~xv;*N0vqhN9l+bCm(5u37KF+&JF&or0qB&J%}ZmdviHekDmr#GlPK60J4Q zJ#vSZYt1pSxEPM~S27`bL-X}ig&?t1ubwy1&P?lEwQUs|t?a7>dqM7^&@^5tSL9pMp+&5H?jk>BGMj!JcQ+3*rxFcY4MY2z z4C?1*^xq&(g`+u7JnXS-Yuq8?$%DG-Zs#VDo=cTmcJRfEFTG1T4~(u1j$Snc+7Cs; zyB9?mE4rqbq_*xqj?#OlN%@YGt*PgH+-~Fy+blur5jn zu_S?>vGKl_57zp6>#CW5Q&HHKl|qVToNrM`8!zz5n*{CQ+r2#n4{2tk@;0m{ zM8pbY25rVQv1<0iw2CPT?uG+>NVZVLalVoRSZQdC(&M@`0$mC@6l?zxF&LAM8XHR1Ah3S zb?4&7@N$w<+PVC^0ws=h2pqrozQ!=b!?Zy2@uQjFh1)BEPT$JlDa9Q8(%YHT_r)w# z<4bW`j)gX^ktonho#Uf=U=ZH5QT!;ug%qe!Fi?N(OjphEVY3YTU5B*j^ZMOg+XmnL zPpT%`zoHjGCw~=w|5zC`KWOFwsF`=Jjwez^hwA2rgTt^ z^10Gp<3*%@mI37QZ>P3$*PX4;4LpFQqK9AnvMxAg!|B)unEQ{13w`0LO;;mgV22L5 z=Y8bwo8Fch2UFgZEqeTdMGZMKmz)4Uzb#-R)&H4zUC45?<4&g?`6XX-=`F2|(~Esf z4P+-+Y;J{*hV8L55?o`K^wL+ zE>e|WH7ZW48)vi%Zq4nbkLikeTd&2pCr5A#jJC9jypS>*@uF<#i}Xp$3X7~b0>bXQ zd@CV7FY-$A{IR_m5uZie z+ckdOpNC4bjck=wZ@3lTl5+`W3~_4oPuGx4#mk-f?CsbGulgu|BAb)LTI|hBYM==Q zPLdu6@x)I_O{qq^{%cI*Q`-C+WZjpp^GjGiWv(#7Vr(pZ@A532u&Rn|3@4+xgKqNc zMhtgDOn)7lv}KZc^U}jD!KU{3;=7as(>uBwDx5}ii8iIz!F(WDlbe(V`WH5PS-XhZ zPJFI;eV}4{aJ?&?Sv%?zMZJ9SRFL%?ZZ0C(FdozY2R@i=1>&&E< z<(hauSRE!6;QE6ujbYrYrWNm9;!ixJV`}*=J$7wZ^0l>rTb7|)`olK^*^m3Ex%nq2 zL({r^1)T=Q7qM>-F~1lC817t!PNhq1c&?{#kiAuiMtlDELuI?Ut6LMQ6()675@U5L z_g(P7&7MR-N3z!C5a+qZ$!xmrg0qbsQn*7vqc!v-^yqc6`tlc%aQl-Fe+IYP5Pe^K z^%zx2w*a+^&+F*;<~HZ&=XwRTB6z)Uec2XkH=^cl)cHs|VxGqSQStks&td*NQbTPW z@??ewN#dRVCH?t{p-$)JDIxkVF$#9Q?iS!Qqby9p zttQuw3k2_4Hs9`5TG}3Jwk97Nste6#I!jG)f$b(~xI#)Bs7nQ7es#6RzYPh=8vCY$@K;aE z0JYYxSm&6)?GS&eI-ibs8vhi$EXK)Yhv7%bHy2C$czjfz?F4J+b%lJkXj+1&h?Ti_R;#D>}h%qh-ltN3^kJE=J$q9lGN z97&*c`aeQNBG8(G3ADz4#|D3&4&?Ix=oLK>L?VDUkp%Gi|FbTdf2<2!*X4kUenR(; zb%6=se)ca%eZ zOyn3`1eb66NoONNlb!Qgq|BuMxwULjnW>4u2iuhj(ZUV8fC!eY=nsZF*}w6V0(LxJ zVJ|ew^cV0%UizR_Y1yOEtM1}iw*f#fPAX(#E)%*G)QD7W7O$XT5e!*pv0krMED!yw zv)_h?54B@8<=GZ6ukEmkmrx<@jaUud2Y%EQU-vBcCChZ&9Xf`1Rw3w4G=@{y>I<<5 zr)BfiiXe`(Z@ksE4@BqB5d!$>pA(N&9b7XX5GBfr?j{H(J6=OSr*~9Ff8Zh0^d;HS3|V9O<+-Py zxI&YAI-gM^t2+X1O6JyQ*^8SfuZ5{?m1F14fGg;0aeF|P)4c8tw{C;?*J)`bjV2~qOsSjk^$@gQ1{3jw}OGfYhan!3#Y zHIQX-5|4fmT69zTvDd3aW(AkQqj4t}?Md}bd>>Q>N!29V@klLOr#L%^gPrlgw8ASS>!fstf*6i;ka?xLu@MUq>?r_mf*HCZ0jHy2N^B`x>Y90Tt5-jn7*G)Ai~?r^6!i zChFK}Z-Np|s#K(ct1NYcNSoxM%p~ng6bf7}uXm#_v&(wHHp4Tljgd6EW$Kg0xZkkr zi&o;({o`MC#=#JXFx-Py14vyFMbGypX`-a>1F9n21b`MXKk|zU$zEO&>l1Rjkx$4Vg-UeUetqM3xCVt2 z#4}QY$t__sQxkuq9U8E_JbjM8#9JvlSK48A@`?q^I*~JnT-!@f$l49YlT>fpGqYJ9 zr+k*tw-oT8l~Dr<$GT8lt$6D+{n7Af1%CX7h0*}>N)s;I);DZqq{57a Date: Mon, 8 Aug 2022 17:38:30 -0500 Subject: [PATCH 021/369] Update indices resolution to be clearer (#1999) --- .../security/securityconf/ConfigModelV7.java | 51 ++-- .../dlic/dlsfls/FlsIndexingTests.java | 160 +++++++++++++ .../impl/v7/IndexPatternTests.java | 225 ++++++++++++++++++ src/test/resources/dlsfls/internal_users.yml | 7 + .../resources/dlsfls/roles_fls_indexing.yml | 39 +++ .../dlsfls/roles_mapping_fls_indexing.yml | 31 +++ 6 files changed, 491 insertions(+), 22 deletions(-) create mode 100644 src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java create mode 100644 src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java create mode 100644 src/test/resources/dlsfls/roles_fls_indexing.yml create mode 100644 src/test/resources/dlsfls/roles_mapping_fls_indexing.yml diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index 5c3201aaeb..c7612cf0d8 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -45,6 +45,7 @@ import com.google.common.collect.SetMultimap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.util.Strings; import org.opensearch.ExceptionsHelper; import org.opensearch.action.support.IndicesOptions; @@ -379,8 +380,7 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, for (SecurityRole role : roles) { for (IndexPattern ip : role.getIpatterns()) { - Set concreteIndices; - concreteIndices = ip.getResolvedIndexPattern(user, resolver, cs, false); + final Set concreteIndices = ip.concreteIndexNames(user, resolver, cs); String dls = ip.getDlsQuery(user); if (dls != null && dls.length() > 0) { @@ -561,7 +561,7 @@ private Set getAllResolvedPermittedIndices(Resolved resolved, User user, // } if (patternMatch) { //resolved but can contain patterns for nonexistent indices - final WildcardMatcher permitted = WildcardMatcher.from(p.getResolvedIndexPattern(user, resolver, cs, true)); //maybe they do not exist + final WildcardMatcher permitted = WildcardMatcher.from(p.attemptResolveIndexNames(user, resolver, cs)); //maybe they do not exist final Set res = new HashSet<>(); if (!resolved.isLocalAll() && !resolved.getAllIndices().contains("*") && !resolved.getAllIndices().contains("_all")) { //resolved but can contain patterns for nonexistent indices @@ -753,35 +753,42 @@ public String getUnresolvedIndexPattern(User user) { return replaceProperties(indexPattern, user); } - public Set getResolvedIndexPattern(User user, IndexNameExpressionResolver resolver, ClusterService cs, boolean appendUnresolved) { - String unresolved = getUnresolvedIndexPattern(user); - WildcardMatcher matcher = WildcardMatcher.from(unresolved); - String[] resolved = null; + /** Finds the indices accessible to the user and resolves them to concrete names */ + public Set concreteIndexNames(final User user, final IndexNameExpressionResolver resolver, final ClusterService cs) { + return getResolvedIndexPattern(user, resolver, cs, false); + } + + /** Finds the indices accessible to the user and attempts to resolve them to names, also includes any unresolved names */ + public Set attemptResolveIndexNames(final User user, final IndexNameExpressionResolver resolver, final ClusterService cs) { + return getResolvedIndexPattern(user, resolver, cs, true); + } + + public Set getResolvedIndexPattern(final User user, final IndexNameExpressionResolver resolver, final ClusterService cs, final boolean appendUnresolved) { + final String unresolved = getUnresolvedIndexPattern(user); + final ImmutableSet.Builder resolvedIndices = new ImmutableSet.Builder<>(); + + final WildcardMatcher matcher = WildcardMatcher.from(unresolved); if (!(matcher instanceof WildcardMatcher.Exact)) { final String[] aliasesForPermittedPattern = cs.state().getMetadata().getIndicesLookup().entrySet().stream() .filter(e -> e.getValue().getType() == ALIAS) .filter(e -> matcher.test(e.getKey())) .map(e -> e.getKey()) .toArray(String[]::new); - if (aliasesForPermittedPattern.length > 0) { - resolved = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), aliasesForPermittedPattern); + final String[] resolvedAliases = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), aliasesForPermittedPattern); + resolvedIndices.addAll(Arrays.asList(resolvedAliases)); } } - if (resolved == null && !unresolved.isEmpty()) { - resolved = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), unresolved); + if (Strings.isNotBlank(unresolved)) { + final String[] resolvedIndicesFromPattern = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), unresolved); + resolvedIndices.addAll(Arrays.asList(resolvedIndicesFromPattern)); } - if (resolved == null || resolved.length == 0) { - return ImmutableSet.of(unresolved); - } else { - ImmutableSet.Builder builder = ImmutableSet.builder() - .addAll(Arrays.asList(resolved)); - if (appendUnresolved) { - builder.add(unresolved); - } - return builder.build(); + + if (appendUnresolved || resolvedIndices.build().isEmpty()) { + resolvedIndices.add(unresolved); } + return resolvedIndices.build(); } public String getDlsQuery(User user) { @@ -996,12 +1003,12 @@ private static boolean impliesTypePerm(Set ipatterns, Resolved res indexMatcherAndPermissions = ipatterns .stream() .filter(indexPattern -> "*".equals(indexPattern.getUnresolvedIndexPattern(user))) - .map(p -> new IndexMatcherAndPermissions(p.getResolvedIndexPattern(user, resolver, cs, true), p.perms)) + .map(p -> new IndexMatcherAndPermissions(p.attemptResolveIndexNames(user, resolver, cs), p.perms)) .toArray(IndexMatcherAndPermissions[]::new); } else { indexMatcherAndPermissions = ipatterns .stream() - .map(p -> new IndexMatcherAndPermissions(p.getResolvedIndexPattern(user, resolver, cs, true), p.perms)) + .map(p -> new IndexMatcherAndPermissions(p.attemptResolveIndexNames(user, resolver, cs), p.perms)) .toArray(IndexMatcherAndPermissions[]::new); } return resolvedRequestedIndices diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java new file mode 100644 index 0000000000..73a152eca4 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java @@ -0,0 +1,160 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.dlsfls; + +import org.apache.http.Header; +import org.apache.http.HttpStatus; +import org.junit.Test; + +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest.RefreshPolicy; +import org.opensearch.client.Client; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.security.test.DynamicSecurityConfig; +import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.IsNot.not; +import static org.hamcrest.core.StringContains.containsString; + +public class FlsIndexingTests extends AbstractDlsFlsTest { + + protected void populateData(final Client tc) { + // Create several documents in different indices with shared field names, + // different roles will have different levels of FLS restrictions + tc.index(new IndexRequest("yellow-pages").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"phone-all\":1001,\"phone-some\":1002,\"phone-one\":1003}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("green-pages").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"phone-all\":2001,\"phone-some\":2002,\"phone-one\":2003}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("blue-book").id("3").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"phone-all\":3001,\"phone-some\":3002,\"phone-one\":3003}", XContentType.JSON)).actionGet(); + + // Seperate index used to test aliasing + tc.index(new IndexRequest(".hidden").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{}", XContentType.JSON)).actionGet(); + } + + private Header asPhoneOneUser = encodeBasicHeader("user_aaa", "password"); + private Header asPhoneSomeUser = encodeBasicHeader("user_bbb", "password"); + private Header asPhoneAllUser = encodeBasicHeader("user_ccc", "password"); + + private final String searchQuery = "/*/_search?filter_path=hits.hits&pretty"; + + @Test + public void testSingleIndexFlsApplied() throws Exception { + setup(new DynamicSecurityConfig() + .setSecurityRoles("roles_fls_indexing.yml") + .setSecurityRolesMapping("roles_mapping_fls_indexing.yml")); + + final HttpResponse phoneOneFilteredResponse = rh.executeGetRequest(searchQuery, asPhoneOneUser); + assertThat(phoneOneFilteredResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(phoneOneFilteredResponse.getBody(), not(containsString("1003"))); + assertThat(phoneOneFilteredResponse.getBody(), containsString("1002")); + assertThat(phoneOneFilteredResponse.getBody(), containsString("1001")); + + assertThat(phoneOneFilteredResponse.getBody(), containsString("2003")); + assertThat(phoneOneFilteredResponse.getBody(), containsString("2002")); + assertThat(phoneOneFilteredResponse.getBody(), containsString("2001")); + + assertThat(phoneOneFilteredResponse.getBody(), containsString("3003")); + assertThat(phoneOneFilteredResponse.getBody(), containsString("3002")); + assertThat(phoneOneFilteredResponse.getBody(), containsString("3001")); + } + + @Test + public void testSingleIndexFlsAppliedForLimitedResults() throws Exception { + setup(new DynamicSecurityConfig() + .setSecurityRoles("roles_fls_indexing.yml") + .setSecurityRolesMapping("roles_mapping_fls_indexing.yml")); + + final HttpResponse phoneOneFilteredResponse = rh.executeGetRequest("/yellow-pages/_search?filter_path=hits.hits&pretty", asPhoneOneUser); + assertThat(phoneOneFilteredResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(phoneOneFilteredResponse.getBody(), not(containsString("1003"))); + assertThat(phoneOneFilteredResponse.getBody(), containsString("1002")); + assertThat(phoneOneFilteredResponse.getBody(), containsString("1001")); + + assertThat(phoneOneFilteredResponse.getBody(), not(containsString("2003"))); + assertThat(phoneOneFilteredResponse.getBody(), not(containsString("2002"))); + assertThat(phoneOneFilteredResponse.getBody(), not(containsString("2001"))); + + assertThat(phoneOneFilteredResponse.getBody(), not(containsString("3003"))); + assertThat(phoneOneFilteredResponse.getBody(), not(containsString("3002"))); + assertThat(phoneOneFilteredResponse.getBody(), not(containsString("3001"))); + } + + @Test + public void testSeveralIndexFlsApplied() throws Exception { + setup(new DynamicSecurityConfig() + .setSecurityRoles("roles_fls_indexing.yml") + .setSecurityRolesMapping("roles_mapping_fls_indexing.yml")); + + final HttpResponse phoneSomeFilteredResponse = rh.executeGetRequest(searchQuery, asPhoneSomeUser); + assertThat(phoneSomeFilteredResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(phoneSomeFilteredResponse.getBody(), containsString("1003")); + assertThat(phoneSomeFilteredResponse.getBody(), not(containsString("1002"))); + assertThat(phoneSomeFilteredResponse.getBody(), containsString("1001")); + + assertThat(phoneSomeFilteredResponse.getBody(), containsString("2003")); + assertThat(phoneSomeFilteredResponse.getBody(), not(containsString("2002"))); + assertThat(phoneSomeFilteredResponse.getBody(), containsString("2001")); + + assertThat(phoneSomeFilteredResponse.getBody(), containsString("3003")); + assertThat(phoneSomeFilteredResponse.getBody(), containsString("3002")); + assertThat(phoneSomeFilteredResponse.getBody(), containsString("3001")); + } + + @Test + public void testAllIndexFlsApplied() throws Exception { + setup(new DynamicSecurityConfig() + .setSecurityRoles("roles_fls_indexing.yml") + .setSecurityRolesMapping("roles_mapping_fls_indexing.yml")); + + final HttpResponse phoneAllFilteredResponse = rh.executeGetRequest(searchQuery, asPhoneAllUser); + assertThat(phoneAllFilteredResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(phoneAllFilteredResponse.getBody(), containsString("1003")); + assertThat(phoneAllFilteredResponse.getBody(), containsString("1002")); + assertThat(phoneAllFilteredResponse.getBody(), not(containsString("1001"))); + + assertThat(phoneAllFilteredResponse.getBody(), containsString("2003")); + assertThat(phoneAllFilteredResponse.getBody(), containsString("2002")); + assertThat(phoneAllFilteredResponse.getBody(), not(containsString("2001"))); + + assertThat(phoneAllFilteredResponse.getBody(), containsString("3003")); + assertThat(phoneAllFilteredResponse.getBody(), containsString("3002")); + assertThat(phoneAllFilteredResponse.getBody(), not(containsString("3001"))); + } + + @Test + public void testAllIndexFlsAppliedWithAlias() throws Exception { + setup(new DynamicSecurityConfig() + .setSecurityRoles("roles_fls_indexing.yml") + .setSecurityRolesMapping("roles_mapping_fls_indexing.yml")); + + final HttpResponse createAlias = rh.executePostRequest("_aliases", "{\"actions\":[{\"add\":{\"index\":\".hidden\",\"alias\":\"ducky\"}}]}", asPhoneAllUser); + assertThat(createAlias.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + final HttpResponse phoneAllFilteredResponse = rh.executeGetRequest(searchQuery, asPhoneAllUser); + assertThat(phoneAllFilteredResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(phoneAllFilteredResponse.getBody(), containsString("1003")); + assertThat(phoneAllFilteredResponse.getBody(), containsString("1002")); + assertThat(phoneAllFilteredResponse.getBody(), not(containsString("1001"))); + + assertThat(phoneAllFilteredResponse.getBody(), containsString("2003")); + assertThat(phoneAllFilteredResponse.getBody(), containsString("2002")); + assertThat(phoneAllFilteredResponse.getBody(), not(containsString("2001"))); + + assertThat(phoneAllFilteredResponse.getBody(), containsString("3003")); + assertThat(phoneAllFilteredResponse.getBody(), containsString("3002")); + assertThat(phoneAllFilteredResponse.getBody(), not(containsString("3001"))); + } +} diff --git a/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java b/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java new file mode 100644 index 0000000000..0c65e2bea9 --- /dev/null +++ b/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java @@ -0,0 +1,225 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.securityconf.impl.v7; + +import java.util.Arrays; +import java.util.Set; +import java.util.TreeMap; + +import com.google.common.collect.ImmutableSet; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.quality.Strictness; + +import org.opensearch.action.support.IndicesOptions; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.metadata.IndexAbstraction; +import org.opensearch.cluster.metadata.IndexAbstraction.Type; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.security.securityconf.ConfigModelV7.IndexPattern; +import org.opensearch.security.user.User; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +@RunWith(MockitoJUnitRunner.class) +public class IndexPatternTests { + + @Mock + private User user; + @Mock + private IndexNameExpressionResolver resolver; + @Mock + private ClusterService clusterService; + + private IndexPattern ip; + + @Before + public void before() { + ip = spy(new IndexPattern("defaultPattern")); + } + + @After + public void after() { + verifyNoMoreInteractions(user, resolver, clusterService); + } + + @Test + public void testCtor() { + assertThrows(NullPointerException.class, () -> new IndexPattern(null)); + } + + /** Ensure that concreteIndexNames sends correct parameters are sent to getResolvedIndexPattern */ + @Test + public void testConcreteIndexNamesOverload() { + doReturn(ImmutableSet.of("darn")).when(ip).getResolvedIndexPattern(user, resolver, clusterService, false); + + final Set results = ip.concreteIndexNames(user, resolver, clusterService); + + assertThat(results, contains("darn")); + + verify(ip).getResolvedIndexPattern(user, resolver, clusterService, false); + verify(ip).concreteIndexNames(user, resolver, clusterService); + verifyNoMoreInteractions(ip); + } + + /** Ensure that attemptResolveIndexNames sends correct parameters are sent to getResolvedIndexPattern */ + @Test + public void testAttemptResolveIndexNamesOverload() { + doReturn(ImmutableSet.of("yarn")).when(ip).getResolvedIndexPattern(user, resolver, clusterService, true); + + final Set results = ip.attemptResolveIndexNames(user, resolver, clusterService); + + assertThat(results, contains("yarn")); + + verify(ip).getResolvedIndexPattern(user, resolver, clusterService, true); + verify(ip).attemptResolveIndexNames(user, resolver, clusterService); + verifyNoMoreInteractions(ip); + } + + /** Verify concreteIndexNames when there are no matches */ + @Test + public void testExactNameWithNoMatches() { + doReturn("index-17").when(ip).getUnresolvedIndexPattern(user); + when(clusterService.state()).thenReturn(mock(ClusterState.class)); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-17"))).thenReturn(new String[]{}); + + final Set results = ip.concreteIndexNames(user, resolver, clusterService); + + assertThat(results, contains("index-17")); + + verify(clusterService).state(); + verify(ip).getUnresolvedIndexPattern(user); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-17")); + } + + /** Verify concreteIndexNames on exact name matches */ + @Test + public void testExactName() { + doReturn("index-17").when(ip).getUnresolvedIndexPattern(user); + when(clusterService.state()).thenReturn(mock(ClusterState.class)); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-17"))).thenReturn(new String[]{"resolved-index-17"}); + + final Set results = ip.concreteIndexNames(user, resolver, clusterService); + + assertThat(results, contains("resolved-index-17")); + + verify(clusterService).state(); + verify(ip).getUnresolvedIndexPattern(user); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-17")); + } + + /** Verify concreteIndexNames on multiple matches */ + @Test + public void testMultipleConcreteIndices() { + doReturn("index-1*").when(ip).getUnresolvedIndexPattern(user); + doReturn(createClusterState()).when(clusterService).state(); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-1*"))).thenReturn(new String[]{"resolved-index-17", "resolved-index-18"}); + + final Set results = ip.concreteIndexNames(user, resolver, clusterService); + + assertThat(results, contains("resolved-index-17", "resolved-index-18")); + + verify(clusterService, times(2)).state(); + verify(ip).getUnresolvedIndexPattern(user); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-1*")); + } + + /** Verify concreteIndexNames when there is an alias */ + @Test + public void testMultipleConcreteIndicesWithOneAlias() { + doReturn("index-1*").when(ip).getUnresolvedIndexPattern(user); + + doReturn(createClusterState( + new IndexShorthand("index-111", Type.DATA_STREAM), // Name matches/wrong type + new IndexShorthand("index-100", Type.ALIAS), // Name and type match + new IndexShorthand("19", Type.ALIAS) // Type matches/wrong name + )).when(clusterService).state(); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-100"))).thenReturn(new String[]{"resolved-index-100"}); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-1*"))).thenReturn(new String[]{"resolved-index-17", "resolved-index-18"}); + + final Set results = ip.concreteIndexNames(user, resolver, clusterService); + + assertThat(results, contains("resolved-index-100", "resolved-index-17", "resolved-index-18")); + + verify(clusterService, times(3)).state(); + verify(ip).getUnresolvedIndexPattern(user); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-100")); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-1*")); + } + + /** Verify attemptResolveIndexNames with multiple aliases */ + @Test + public void testMultipleConcreteAliasedAndUnresolved() { + doReturn("index-1*").when(ip).getUnresolvedIndexPattern(user); + doReturn(createClusterState( + new IndexShorthand("index-111", Type.DATA_STREAM), // Name matches/wrong type + new IndexShorthand("index-100", Type.ALIAS), // Name and type match + new IndexShorthand("index-101", Type.ALIAS), // Name and type match + new IndexShorthand("19", Type.ALIAS) // Type matches/wrong name + )).when(clusterService).state(); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-100"), eq("index-101"))).thenReturn(new String[]{"resolved-index-100", "resolved-index-101"}); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-1*"))).thenReturn(new String[]{"resolved-index-17", "resolved-index-18"}); + + final Set results = ip.attemptResolveIndexNames(user, resolver, clusterService); + + assertThat(results, contains("resolved-index-100", "resolved-index-101", "resolved-index-17", "resolved-index-18", "index-1*")); + + verify(clusterService, times(3)).state(); + verify(ip).getUnresolvedIndexPattern(user); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-100"), eq("index-101")); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-1*")); + } + + private ClusterState createClusterState(final IndexShorthand... indices) { + final TreeMap indexMap = new TreeMap(); + Arrays.stream(indices).forEach(indexShorthand -> { + final IndexAbstraction indexAbstraction = mock(IndexAbstraction.class); + when(indexAbstraction.getType()).thenReturn(indexShorthand.type); + indexMap.put(indexShorthand.name, indexAbstraction); + }); + + final Metadata mockMetadata = mock(Metadata.class, withSettings().strictness(Strictness.LENIENT)); + when(mockMetadata.getIndicesLookup()).thenReturn(indexMap); + + final ClusterState mockClusterState = mock(ClusterState.class, withSettings().strictness(Strictness.LENIENT)); + when(mockClusterState.getMetadata()).thenReturn(mockMetadata); + + return mockClusterState; + } + + private class IndexShorthand { + public final String name; + public final Type type; + public IndexShorthand(final String name, final Type type) { + this.name = name; + this.type = type; + } + } +} diff --git a/src/test/resources/dlsfls/internal_users.yml b/src/test/resources/dlsfls/internal_users.yml index acda68c42a..c3347c103f 100644 --- a/src/test/resources/dlsfls/internal_users.yml +++ b/src/test/resources/dlsfls/internal_users.yml @@ -51,6 +51,13 @@ perf_named_only: backend_roles: [] attributes: {} description: "Migrated from v6" +user_ccc: + hash: "$2a$12$YCBrpxYyFusK609FurY5Ee3BlmuzWw0qHwpwqEyNhM2.XnQY3Bxpe" + reserved: false + hidden: false + backend_roles: [] + attributes: {} + description: "Migrated from v6" user_bbb: hash: "$2a$12$YCBrpxYyFusK609FurY5Ee3BlmuzWw0qHwpwqEyNhM2.XnQY3Bxpe" reserved: false diff --git a/src/test/resources/dlsfls/roles_fls_indexing.yml b/src/test/resources/dlsfls/roles_fls_indexing.yml new file mode 100644 index 0000000000..fb08749a74 --- /dev/null +++ b/src/test/resources/dlsfls/roles_fls_indexing.yml @@ -0,0 +1,39 @@ +--- +_meta: + type: "roles" + config_version: 2 +all_indices_no_phone: + cluster_permissions: + - "*" + index_permissions: + - index_patterns: + - "*" + dls: "" + fls: + - "~phone-all" + allowed_actions: + - "ALL" +some_indices_no_phone: + index_permissions: + - index_patterns: + - "*pages*" + fls: + - "~phone-some" + allowed_actions: + - "ALL" + - index_patterns: + - "*" + allowed_actions: + - "read" +once_indices_no_phone: + index_permissions: + - index_patterns: + - "yellow-pages" + fls: + - "~phone-one" + allowed_actions: + - "ALL" + - index_patterns: + - "*" + allowed_actions: + - "read" diff --git a/src/test/resources/dlsfls/roles_mapping_fls_indexing.yml b/src/test/resources/dlsfls/roles_mapping_fls_indexing.yml new file mode 100644 index 0000000000..509f3811cf --- /dev/null +++ b/src/test/resources/dlsfls/roles_mapping_fls_indexing.yml @@ -0,0 +1,31 @@ +--- +_meta: + type: "rolesmapping" + config_version: 2 +all_indices_no_phone: + reserved: false + hidden: false + backend_roles: [] + hosts: [] + and_backend_roles: [] + description: "All indices do not have phone access" + users: + - "user_ccc" +some_indices_no_phone: + reserved: false + hidden: false + backend_roles: [] + hosts: [] + and_backend_roles: [] + description: "Some indices do not have phone access" + users: + - "user_bbb" +once_indices_no_phone: + reserved: false + hidden: false + backend_roles: [] + hosts: [] + and_backend_roles: [] + description: "One indices do not have phone access" + users: + - "user_aaa" From e121c7d4d177f75064cda602202906210215fd2a Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Tue, 9 Aug 2022 10:24:40 -0500 Subject: [PATCH 022/369] Adding @cwperks to Security maintainers. (#2001) Signed-off-by: Peter Nied --- MAINTAINERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index ceb525084a..a6bc5928ea 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -14,6 +14,7 @@ | Darshit Chanpura | [DarshitChanpura](https://github.com/DarshitChanpura) | Amazon | | Dave Lago | [davidlago](https://github.com/davidlago) | Amazon | | Peter Nied | [peternied](https://github.com/peternied) | Amazon | +| Craig Perkins | [cwperks](https://github.com/cwperks) | Amazon | ### Updating Practices To ensure common practices as maintainers, all practices are expected to be documented here or enforced through github actions. There should be no expectations beyond what is documented in the repo [CONTRIBUTING.md](./CONTRIBUTING.md) and OpenSearch-Project [CONTRIBUTING.md](https://github.com/opensearch-project/.github/blob/main/CONTRIBUTING.md). To modify an existing processes or create a new one, make a pull request on this MAINTAINERS.md for review and merge it after all maintainers approve of it. From 9ce8b4fada325cedb2d927757ba35ab8e5ff73c6 Mon Sep 17 00:00:00 2001 From: Bharathwaj G <58062316+bharath-techie@users.noreply.github.com> Date: Tue, 9 Aug 2022 23:11:36 +0530 Subject: [PATCH 023/369] Point in time security changes (#1989) Signed-off-by: Bharathwaj G --- src/main/resources/static_config/static_action_groups.yml | 3 +++ src/main/resources/static_config/static_roles.yml | 1 + 2 files changed, 4 insertions(+) diff --git a/src/main/resources/static_config/static_action_groups.yml b/src/main/resources/static_config/static_action_groups.yml index 4d6afd9615..29a24fda2e 100644 --- a/src/main/resources/static_config/static_action_groups.yml +++ b/src/main/resources/static_config/static_action_groups.yml @@ -116,6 +116,7 @@ cluster_composite_ops: - "indices:admin/aliases*" - "indices:data/write/reindex" - "cluster_composite_ops_ro" + - "indices:data/read/point_in_time/delete" type: "cluster" description: "Allow read/write bulk and m* operations" cluster_composite_ops_ro: @@ -130,6 +131,8 @@ cluster_composite_ops_ro: - "indices:admin/aliases/get*" - "indices:data/read/scroll" - "indices:admin/resolve/index" + - "indices:data/read/point_in_time/read*" + - "indices:data/read/point_in_time/create" type: "cluster" description: "Allow readonly bulk and m* operations" get: diff --git a/src/main/resources/static_config/static_roles.yml b/src/main/resources/static_config/static_roles.yml index 6af0c6ffc9..21d7692a71 100644 --- a/src/main/resources/static_config/static_roles.yml +++ b/src/main/resources/static_config/static_roles.yml @@ -87,6 +87,7 @@ kibana_server: - "cluster_composite_ops" - "indices:admin/template*" - "indices:data/read/scroll*" + - "indices:data/read/point_in_time*" index_permissions: - index_patterns: - ".kibana" From 7eaaafec2939d7db23a02ffca9cc68e0343de246 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 9 Aug 2022 17:09:36 -0400 Subject: [PATCH 024/369] Update release notes for 2.2.0.0 release (#1984) Signed-off-by: Craig Perkins --- .../opensearch-security.release-notes-2.2.0.0.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/release-notes/opensearch-security.release-notes-2.2.0.0.md b/release-notes/opensearch-security.release-notes-2.2.0.0.md index 2d98e61c13..053d4f3b95 100644 --- a/release-notes/opensearch-security.release-notes-2.2.0.0.md +++ b/release-notes/opensearch-security.release-notes-2.2.0.0.md @@ -2,13 +2,25 @@ Compatible with OpenSearch 2.2.0 -### Test Fix +### Enhancements -* Abstract waitForInit to minimize duplication and improve test reliability ([#1935](https://github.com/opensearch-project/security/pull/1935)) +* Adds a basic sanity test to run against a remote cluster ([#1958](https://github.com/opensearch-project/security/pull/1958)) * Create a manually started workflow for bulk run of integration tests ([#1937](https://github.com/opensearch-project/security/pull/1937)) +### Bug Fixes + +* Use Collections.synchronizedSet and Collections.synchronizedMap for roles, securityRoles and attributes in User ([#1970](https://github.com/opensearch-project/security/pull/1970)) + ### Maintenance * Update to Gradle 7.5 ([#1963](https://github.com/opensearch-project/security/pull/1963)) * Increment version to 2.2.0.0 ([#1948](https://github.com/opensearch-project/security/pull/1948)) * Force netty-transport-native-unix-common version ([#1945](https://github.com/opensearch-project/security/pull/1945)) +* Add release notes for 2.2.0.0 release ([#1974](https://github.com/opensearch-project/security/pull/1974)) +* Staging for version increment automation ([#1932](https://github.com/opensearch-project/security/pull/1932)) +* Fix breaking API change introduced in Lucene 9.3.0 ([#1988](https://github.com/opensearch-project/security/pull/1988)) +* Update indices resolution to be clearer ([#1999](https://github.com/opensearch-project/security/pull/1999)) + +### Refactoring + +* Abstract waitForInit to minimize duplication and improve test reliability ([#1935](https://github.com/opensearch-project/security/pull/1935)) From 63edf437d7e2556e872b82d2e6b5976bf97c6344 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Date: Wed, 10 Aug 2022 18:28:29 -0400 Subject: [PATCH 025/369] Updates matcher to use hasItem (#2018) Signed-off-by: Darshit Chanpura Signed-off-by: Darshit Chanpura --- .../security/sanity/tests/SingleClusterSanityIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java b/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java index c327687371..55d4a6ea0b 100644 --- a/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java +++ b/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java @@ -20,7 +20,7 @@ import org.junit.Test; import static org.hamcrest.Matchers.anEmptyMap; -import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -44,7 +44,7 @@ private void verifyPluginInstallationOnAllNodes() throws Exception { List> plugins = (List>) node.get("plugins"); Set pluginNames = plugins.stream().map(map -> map.get("name")).collect(Collectors.toSet()); - MatcherAssert.assertThat(pluginNames, contains(SECURITY_PLUGIN_NAME)); + MatcherAssert.assertThat(pluginNames, hasItem(SECURITY_PLUGIN_NAME)); } MatcherAssert.assertThat(nodesInCluster, is(not(anEmptyMap()))); } From 68f5624902f7b2df6a05c4f7e965809421fecceb Mon Sep 17 00:00:00 2001 From: Andrey Pustovetov Date: Fri, 12 Aug 2022 22:29:35 +0300 Subject: [PATCH 026/369] Triple audit logging fix (#1995) (#1996) Revert some changes introduced by https://github.com/opensearch-project/security/pull/1563 to correct work with log4j. Signed-off-by: Andrey Pustovetov --- .../security/auditlog/sink/Log4JSink.java | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java b/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java index 757508ff68..8794b90f41 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java @@ -30,40 +30,17 @@ public Log4JSink(final String name, final Settings settings, final String settin loggerName = settings.get( settingsPrefix + ".log4j.logger_name","sgaudit"); auditLogger = LogManager.getLogger(loggerName); logLevel = Level.toLevel(settings.get(settingsPrefix + ".log4j.level","INFO").toUpperCase()); - enabled = isLogLevelEnabled(auditLogger, logLevel); + enabled = auditLogger.isEnabled(logLevel); } public boolean isHandlingBackpressure() { return !enabled; //no submit to thread pool if not enabled } - public boolean doStore(final AuditMessage msg) { if(enabled) { - logAtLevel(auditLogger, logLevel, msg.toJson()); + auditLogger.log(logLevel, msg.toJson()); } return true; } - - private boolean isLogLevelEnabled(Logger logger, Level level) { - boolean isEnabled = false; - switch(level.toString()) { - case "TRACE": isEnabled = logger.isTraceEnabled(); - case "DEBUG": isEnabled = logger.isDebugEnabled(); - case "INFO": isEnabled = logger.isInfoEnabled(); - case "WARN": isEnabled = logger.isWarnEnabled(); - case "ERROR": isEnabled = logger.isErrorEnabled(); - } - return isEnabled; - } - - private void logAtLevel(Logger logger, Level level, String msg) { - switch(level.toString()) { - case "TRACE": logger.trace(msg); - case "DEBUG": logger.debug(msg); - case "INFO": logger.info(msg); - case "WARN": logger.warn(msg); - case "ERROR": logger.error(msg); - } - } } From b57359af071b46b78246cc29643e655f2d1d45c4 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Mon, 15 Aug 2022 11:45:19 -0400 Subject: [PATCH 027/369] Update Gradle to 7.5.1 (#2027) Signed-off-by: Andriy Redko Signed-off-by: Andriy Redko --- bwc-test/gradle/wrapper/gradle-wrapper.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bwc-test/gradle/wrapper/gradle-wrapper.properties b/bwc-test/gradle/wrapper/gradle-wrapper.properties index 2ec77e51a9..8fad3f5a98 100644 --- a/bwc-test/gradle/wrapper/gradle-wrapper.properties +++ b/bwc-test/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2ec77e51a9..8fad3f5a98 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From f4b3a3a8c14b47b883b80d5ac514c2e43768fd38 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 16 Aug 2022 13:11:46 -0400 Subject: [PATCH 028/369] Increment version to 2.3.0.0 (#2022) Signed-off-by: Craig Perkins --- .github/workflows/ci.yml | 6 +++--- build.gradle | 2 +- bwc-test/build.gradle | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 177ed1c789..4c9b6f55f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,9 +72,9 @@ jobs: cp -r build/ ./bwc-test/ mkdir ./bwc-test/src/test/resources/security_plugin_version_no_snapshot cp build/distributions/opensearch-security-${security_plugin_version_no_snapshot}.zip ./bwc-test/src/test/resources/${security_plugin_version_no_snapshot} - mkdir bwc-test/src/test/resources/2.1.0.0 - wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.1.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.1.0.0.zip - mv opensearch-security-2.1.0.0.zip bwc-test/src/test/resources/2.1.0.0/ + mkdir bwc-test/src/test/resources/2.2.0.0 + wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.2.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.2.0.0.zip + mv opensearch-security-2.2.0.0.zip bwc-test/src/test/resources/2.2.0.0/ cd bwc-test/ ./gradlew bwcTestSuite -Dtests.security.manager=false diff --git a/build.gradle b/build.gradle index 0063c0e8e5..9da6f841c3 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ import org.opensearch.gradle.test.RestIntegTestTask buildscript { ext { - opensearch_version = System.getProperty("opensearch.version", "2.2.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.3.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") diff --git a/bwc-test/build.gradle b/bwc-test/build.gradle index 2ee89844ee..59cd7c9cfb 100644 --- a/bwc-test/build.gradle +++ b/bwc-test/build.gradle @@ -47,7 +47,7 @@ ext { buildscript { ext { - opensearch_version = System.getProperty("opensearch.version", "2.2.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.3.0-SNAPSHOT") opensearch_group = "org.opensearch" } repositories { @@ -73,16 +73,16 @@ dependencies { testImplementation "org.opensearch.test:framework:${opensearch_version}" } -String bwcVersion = "2.1.0.0"; +String bwcVersion = "2.2.0.0"; String baseName = "securityBwcCluster" String bwcFilePath = "src/test/resources/" -String projectVersion = "2.2.0.0" +String projectVersion = "2.3.0.0" 2.times {i -> testClusters { "${baseName}$i" { testDistribution = "ARCHIVE" - versions = ["2.1.0","2.2.0"] + versions = ["2.2.0","2.3.0"] numberOfNodes = 3 plugin(provider(new Callable() { @Override From 6b7a5869c84d453bb22be861a88b744512fa0d64 Mon Sep 17 00:00:00 2001 From: Bharathwaj G <58062316+bharath-techie@users.noreply.github.com> Date: Thu, 18 Aug 2022 09:36:53 +0530 Subject: [PATCH 029/369] Point in time API security changes (#2033) Signed-off-by: Bharathwaj G --- .../static_config/static_action_groups.yml | 14 +++++++++++--- src/main/resources/static_config/static_roles.yml | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/resources/static_config/static_action_groups.yml b/src/main/resources/static_config/static_action_groups.yml index 29a24fda2e..d0ce7613a2 100644 --- a/src/main/resources/static_config/static_action_groups.yml +++ b/src/main/resources/static_config/static_action_groups.yml @@ -116,7 +116,6 @@ cluster_composite_ops: - "indices:admin/aliases*" - "indices:data/write/reindex" - "cluster_composite_ops_ro" - - "indices:data/read/point_in_time/delete" type: "cluster" description: "Allow read/write bulk and m* operations" cluster_composite_ops_ro: @@ -131,8 +130,6 @@ cluster_composite_ops_ro: - "indices:admin/aliases/get*" - "indices:data/read/scroll" - "indices:admin/resolve/index" - - "indices:data/read/point_in_time/read*" - - "indices:data/read/point_in_time/create" type: "cluster" description: "Allow readonly bulk and m* operations" get: @@ -230,3 +227,14 @@ manage_data_streams: - "indices:monitor/data_stream/stats" type: "index" description: "Manage data streams" +manage_point_in_time: + reserved: true + hidden: false + static: true + allowed_actions: + - "indices:data/read/point_in_time/create" + - "cluster:admin/point_in_time/delete" + - "cluster:admin/point_in_time/read*" + - "indices:monitor/point_in_time/segments" + type: "cluster" + description: "Manage point in time actions" diff --git a/src/main/resources/static_config/static_roles.yml b/src/main/resources/static_config/static_roles.yml index 21d7692a71..0d7f66531a 100644 --- a/src/main/resources/static_config/static_roles.yml +++ b/src/main/resources/static_config/static_roles.yml @@ -85,9 +85,9 @@ kibana_server: cluster_permissions: - "cluster_monitor" - "cluster_composite_ops" + - "manage_point_in_time" - "indices:admin/template*" - "indices:data/read/scroll*" - - "indices:data/read/point_in_time*" index_permissions: - index_patterns: - ".kibana" From 618d30d609d11cc1c643f3cd98cf66100f143d4b Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Mon, 22 Aug 2022 18:33:48 -0400 Subject: [PATCH 030/369] Add release note for 1.3.5 (#2042) Signed-off-by: Ryan Liang --- .../opensearch-security.release-notes-1.3.5.0.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-1.3.5.0.md diff --git a/release-notes/opensearch-security.release-notes-1.3.5.0.md b/release-notes/opensearch-security.release-notes-1.3.5.0.md new file mode 100644 index 0000000000..6f696b374e --- /dev/null +++ b/release-notes/opensearch-security.release-notes-1.3.5.0.md @@ -0,0 +1,12 @@ +## 2022-08-25 Version 1.3.5.0 + +Compatible with OpenSearch 1.3.5 + +### Bug fixes + +* Triple audit logging fix ([#1996](https://github.com/opensearch-project/security/pull/1996)) +* Cluster permissions evaluation logic will now include index_template type action ([#1885](https://github.com/opensearch-project/security/pull/1885)) + +### Maintenance + +* Upgrade jackson-databind from 2.13.2 to 2.13.2.2 to match core's version.properties and upgrade kafka dependencies ([#2000](https://github.com/opensearch-project/security/pull/2000)) From 287e945bdba66c01953ce1fac5a3bc4e12ee7283 Mon Sep 17 00:00:00 2001 From: Chang Liu Date: Tue, 23 Aug 2022 14:47:56 -0700 Subject: [PATCH 031/369] Add allowlist.yml to 3 places in securityadmin tool (#2046) Signed-off-by: cliu123 --- .../java/org/opensearch/security/tools/SecurityAdmin.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 6360f508b3..dcfb34e9b1 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -736,6 +736,7 @@ public static int execute(final String[] args) throws Exception { final boolean populateFileIfEmpty = true; success = retrieveFile(restHighLevelClient, cd+"nodes_dn_"+date+".yml", index, "nodesdn", legacy, populateFileIfEmpty) && success; success = retrieveFile(restHighLevelClient, cd+"whitelist_"+date+".yml", index, "whitelist", legacy, populateFileIfEmpty) && success; + success = retrieveFile(restHighLevelClient, cd+"allowlist_"+date+".yml", index, "allowlist", legacy, populateFileIfEmpty) && success; return (success?0:-1); } @@ -1195,6 +1196,7 @@ private static int backup(RestHighLevelClient tc, String index, File backupDir, } success = retrieveFile(tc, backupDir.getAbsolutePath()+"/nodes_dn.yml", index, "nodesdn", legacy, true) && success; success = retrieveFile(tc, backupDir.getAbsolutePath()+"/whitelist.yml", index, "whitelist", legacy, true) && success; + success = retrieveFile(tc, backupDir.getAbsolutePath()+"/allowlist.yml", index, "allowlist", legacy, true) && success; success = retrieveFile(tc, backupDir.getAbsolutePath() + "/audit.yml", index, "audit", legacy) && success; return success?0:-1; @@ -1218,6 +1220,9 @@ private static int upload(RestHighLevelClient tc, String index, String cd, boole if (new File(cd+"audit.yml").exists()) { success = uploadFile(tc, cd + "audit.yml", index, "audit", legacy, resolveEnvVars) && success; } + if (new File(cd+"allowlist.yml").exists()) { + success = uploadFile(tc, cd + "allowlist.yml", index, "allowlist", legacy, resolveEnvVars) && success; + } if(!success) { System.out.println("ERR: cannot upload configuration, see errors above"); From 1efc512eb66884375bed5ea320d795922888f48b Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Tue, 30 Aug 2022 14:05:54 -0400 Subject: [PATCH 032/369] Fix the inconsistent variable for DLS (#2056) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang --- .../DlsFilterLevelActionHandler.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java b/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java index d2c02630e8..c0ecd6b9be 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java @@ -115,7 +115,7 @@ public static boolean handle(String action, ActionRequest request, ActionListene private final ThreadContext threadContext; private final IndexNameExpressionResolver resolver; private BoolQueryBuilder filterLevelQueryBuilder; - private DocumentAllowList documentWhitelist; + private DocumentAllowList documentAllowlist; DlsFilterLevelActionHandler(String action, ActionRequest request, ActionListener listener, EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, Resolved resolved, Client nodeClient, ClusterService clusterService, IndicesService indicesService, @@ -178,8 +178,8 @@ private boolean handle() { } private boolean handle(SearchRequest searchRequest, StoredContext ctx) { - if (documentWhitelist != null) { - documentWhitelist.applyTo(threadContext); + if (documentAllowlist != null) { + documentAllowlist.applyTo(threadContext); } String localClusterAlias = LOCAL_CLUSTER_ALIAS_GETTER.apply(searchRequest); @@ -225,8 +225,8 @@ public void onFailure(Exception e) { } private boolean handle(GetRequest getRequest, StoredContext ctx) { - if (documentWhitelist != null) { - documentWhitelist.applyTo(threadContext); + if (documentAllowlist != null) { + documentAllowlist.applyTo(threadContext); } SearchRequest searchRequest = new SearchRequest(getRequest.indices()); @@ -270,8 +270,8 @@ public void onFailure(Exception e) { } private boolean handle(MultiGetRequest multiGetRequest, StoredContext ctx) { - if (documentWhitelist != null) { - documentWhitelist.applyTo(threadContext); + if (documentAllowlist != null) { + documentAllowlist.applyTo(threadContext); } Map> idsGroupedByIndex = multiGetRequest.getItems().stream() @@ -395,7 +395,7 @@ private boolean createQueryExtension(String localClusterAlias) throws IOExceptio Map> filterLevelQueries = evaluatedDlsFlsConfig.getDlsQueriesByIndex(); BoolQueryBuilder dlsQueryBuilder = QueryBuilders.boolQuery().minimumShouldMatch(1); - DocumentAllowList documentWhitelist = new DocumentAllowList(); + DocumentAllowList documentAllowlist = new DocumentAllowList(); int queryCount = 0; @@ -450,7 +450,7 @@ private boolean createQueryExtension(String localClusterAlias) throws IOExceptio for (QueryBuilder queryBuilder : queryBuilders) { TermsQueryBuilder termsQueryBuilder = (TermsQueryBuilder) queryBuilder; - documentWhitelist.add(termsQueryBuilder.termsLookup().index(), termsQueryBuilder.termsLookup().id()); + documentAllowlist.add(termsQueryBuilder.termsLookup().index(), termsQueryBuilder.termsLookup().id()); } } @@ -461,7 +461,7 @@ private boolean createQueryExtension(String localClusterAlias) throws IOExceptio return false; } else { this.filterLevelQueryBuilder = dlsQueryBuilder; - this.documentWhitelist = documentWhitelist; + this.documentAllowlist = documentAllowlist; return true; } } From f83a7e82e9c305f330aa011676c20cca40c22ed7 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 7 Sep 2022 11:08:35 -0400 Subject: [PATCH 033/369] Fix legacy check in SecurityAdmin (#2052) * Fix legacy check in SecurityAdmin Signed-off-by: Craig Perkins * Add unit test for legacy index check logic Signed-off-by: Craig Perkins * Add SECURITY_ADMIN_TESTS to describe tests performed Signed-off-by: Craig Perkins * Address lint error Signed-off-by: Craig Perkins Signed-off-by: Craig Perkins --- .../security/tools/SecurityAdmin.java | 3 +- .../security/SecurityAdminTests.java | 45 +++++++++++ tools/SECURITY_ADMIN_TESTS.md | 76 +++++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 tools/SECURITY_ADMIN_TESTS.md diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index dcfb34e9b1..0329ebf6fe 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -713,7 +713,8 @@ public static int execute(final String[] args) throws Exception { final boolean legacy = createLegacyMode || (indexExists && securityIndex.getMappings() != null - && securityIndex.getMappings().get(index) != null); + && securityIndex.getMappings().get(index) != null + && securityIndex.getMappings().get(index).getSourceAsMap().containsKey("security")); if(legacy) { System.out.println("Legacy index '"+index+"' (ES 6) detected (or forced). You should migrate the configuration!"); diff --git a/src/test/java/org/opensearch/security/SecurityAdminTests.java b/src/test/java/org/opensearch/security/SecurityAdminTests.java index 0de30943de..c2f6b9ab27 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminTests.java @@ -17,7 +17,9 @@ package org.opensearch.security; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.PrintStream; import java.util.ArrayList; import java.util.List; @@ -27,6 +29,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.security.ssl.util.SSLConfigConstants; +import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.security.test.helper.file.FileHelper; @@ -494,6 +497,48 @@ public void testSecurityAdminValidateConfig() throws Exception { Assert.assertNotEquals(0, returnCode); } + @Test + public void testIsLegacySecurityIndexOnV7Index() throws Exception { + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(Settings.EMPTY, null, settings, false); + + final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + + List argsAsList = new ArrayList<>(); + argsAsList.add("-ts"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add("-ks"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add("-p"); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); + argsAsList.add("-cn"); + argsAsList.add(clusterInfo.clustername); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); + argsAsList.add("-nhnv"); + + // Execute first time to create the index + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + Assert.assertEquals(0, returnCode); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos); + PrintStream old = System.out; + System.setOut(ps); + + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + Assert.assertEquals(0, returnCode); + + System.out.flush(); + System.setOut(old); + String standardOut = baos.toString(); + String legacyIndexOutput = "Legacy index '"+ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX+"' (ES 6) detected (or forced). You should migrate the configuration!"; + Assert.assertFalse(standardOut.contains(legacyIndexOutput)); + } + private void addDirectoryPath(final List args, final String path) { args.add("-cd"); args.add(path); diff --git a/tools/SECURITY_ADMIN_TESTS.md b/tools/SECURITY_ADMIN_TESTS.md new file mode 100644 index 0000000000..bb6ef71619 --- /dev/null +++ b/tools/SECURITY_ADMIN_TESTS.md @@ -0,0 +1,76 @@ +## Security Admin Tests + +A collection of tests to perform when making changes to `securityadmin.sh` + +### Running Security Admin + +Details about the Security Admin tool can be found on the [OpenSearch Documentation Website](https://opensearch.org/docs/latest/security-plugin/configuration/security-admin/). + +When running a cluster with the demo configuration, run the `securityadmin.sh` tool using: + +``` +./securityadmin.sh -cd ../../../config/opensearch-security/ -icl -nhnv \ + -cacert ../../../config/root-ca.pem \ + -cert ../../../config/kirk.pem \ + -key ../../../config/kirk-key.pem +``` + +### Legacy Check Tests + +#### ODFE:<=0.10.0 (ES 6) + +In opendistro-for-elasticsearch:0.10.0 and before (See a full list of ODFE versions [here](https://opendistro.github.io/for-elasticsearch-docs/version-history/)), opendistro-for-elasticsearch (ODFE) security was configured with the legacy Security Config v6 format. + +When running `securityadmin.sh` with the security index in the legacy v6 format, the following line will appear in the output when running the tool. + +``` +Legacy index '.opendistro_security' (ES 6) detected (or forced). You should migrate the configuration! +```` + +For information on how to migrate the security config from v6 to v7, see the [Backup, restore, and migrate](https://opensearch.org/docs/latest/security-plugin/configuration/security-admin/#backup-restore-and-migrate) section on the Security Admin Documentation page. + +#### OpenSearch and ODFE:>=1.0.0 (ES 7) + +OpenSearch clusters and clusters running opendistro-for-elasticsearch:>=1.0.0 use the Security Config v7 format. When running the tool with the security index the in v7 format, the output will resemble: + +``` +./securityadmin.sh -cd ../../../config/opensearch-security/ -icl -nhnv \ +> -cacert ../../../config/root-ca.pem \ +> -cert ../../../config/kirk.pem \ +> -key ../../../config/kirk-key.pem +************************************************************************** +** This tool will be deprecated in the next major release of OpenSearch ** +** https://github.com/opensearch-project/security/issues/1755 ** +************************************************************************** +Security Admin v7 +Will connect to localhost:9200 ... done +Connected as "CN=kirk,OU=client,O=client,L=test,C=de" +OpenSearch Version: 2.2.0 +Contacting opensearch cluster 'opensearch' and wait for YELLOW clusterstate ... +Clustername: opensearch-cluster +Clusterstate: GREEN +Number of nodes: 2 +Number of data nodes: 2 +.opendistro_security index already exists, so we do not need to create one. +Populate config from /usr/share/opensearch/config/opensearch-security +Will update '/config' with ../../../config/opensearch-security/config.yml + SUCC: Configuration for 'config' created or updated +Will update '/roles' with ../../../config/opensearch-security/roles.yml + SUCC: Configuration for 'roles' created or updated +Will update '/rolesmapping' with ../../../config/opensearch-security/roles_mapping.yml + SUCC: Configuration for 'rolesmapping' created or updated +Will update '/internalusers' with ../../../config/opensearch-security/internal_users.yml + SUCC: Configuration for 'internalusers' created or updated +Will update '/actiongroups' with ../../../config/opensearch-security/action_groups.yml + SUCC: Configuration for 'actiongroups' created or updated +Will update '/tenants' with ../../../config/opensearch-security/tenants.yml + SUCC: Configuration for 'tenants' created or updated +Will update '/nodesdn' with ../../../config/opensearch-security/nodes_dn.yml + SUCC: Configuration for 'nodesdn' created or updated +Will update '/whitelist' with ../../../config/opensearch-security/whitelist.yml + SUCC: Configuration for 'whitelist' created or updated +Will update '/audit' with ../../../config/opensearch-security/audit.yml + SUCC: Configuration for 'audit' created or updated +Will update '/allowlist' with ../../../config/opensearch-security/allowlist.yml + SUCC: Configuration for 'allowlist' created or updated +``` From 8f7f1b5a66f5100811b19b9bd449555b158224f6 Mon Sep 17 00:00:00 2001 From: Chang Liu Date: Wed, 7 Sep 2022 11:20:00 -0700 Subject: [PATCH 034/369] Add 2.3.0.0 release notes (#2079) Signed-off-by: Chang Liu Signed-off-by: Chang Liu --- .../opensearch-security.release-notes-2.3.0.0.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-2.3.0.0.md diff --git a/release-notes/opensearch-security.release-notes-2.3.0.0.md b/release-notes/opensearch-security.release-notes-2.3.0.0.md new file mode 100644 index 0000000000..ee1967e62a --- /dev/null +++ b/release-notes/opensearch-security.release-notes-2.3.0.0.md @@ -0,0 +1,15 @@ +## 2022-08-15 Version 2.3.0.0 + +Compatible with OpenSearch 2.3.0 + +### Enhancements +* Point in time API security changes ([#2033](https://github.com/opensearch-project/security/pull/2033)) + +### Bug Fixes +* Triple audit logging fix ([#1996](https://github.com/opensearch-project/security/pull/1996)) +* Add allowlist.yml to 3 places in securityadmin tool ([#2046](https://github.com/opensearch-project/security/pull/2046)) +* Fix legacy check in SecurityAdmin ([#2052](https://github.com/opensearch-project/security/pull/2052)) + +### Maintenance +* Increment version to 2.3.0.0 ([#2022](https://github.com/opensearch-project/security/pull/2022)) +* Update Gradle to 7.5.1 ([#2027](https://github.com/opensearch-project/security/pull/2027)) From a9f752d73792cf22fb970a21488627a3c2c1542f Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 7 Sep 2022 15:20:03 -0400 Subject: [PATCH 035/369] Correct release date to 2022-09-15 (#2081) Signed-off-by: Craig Perkins Signed-off-by: Craig Perkins --- release-notes/opensearch-security.release-notes-2.3.0.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-notes/opensearch-security.release-notes-2.3.0.0.md b/release-notes/opensearch-security.release-notes-2.3.0.0.md index ee1967e62a..45d6b589a0 100644 --- a/release-notes/opensearch-security.release-notes-2.3.0.0.md +++ b/release-notes/opensearch-security.release-notes-2.3.0.0.md @@ -1,4 +1,4 @@ -## 2022-08-15 Version 2.3.0.0 +## 2022-09-15 Version 2.3.0.0 Compatible with OpenSearch 2.3.0 From fd53d1d6770eed3f19c9f2e48776deb0cb626b6c Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Mon, 12 Sep 2022 14:09:48 -0400 Subject: [PATCH 036/369] Increment version to 2.4.0.0 (#2084) Signed-off-by: Craig Perkins --- .github/workflows/ci.yml | 6 +++--- build.gradle | 2 +- bwc-test/build.gradle | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c9b6f55f7..cdbcb45ab2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,9 +72,9 @@ jobs: cp -r build/ ./bwc-test/ mkdir ./bwc-test/src/test/resources/security_plugin_version_no_snapshot cp build/distributions/opensearch-security-${security_plugin_version_no_snapshot}.zip ./bwc-test/src/test/resources/${security_plugin_version_no_snapshot} - mkdir bwc-test/src/test/resources/2.2.0.0 - wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.2.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.2.0.0.zip - mv opensearch-security-2.2.0.0.zip bwc-test/src/test/resources/2.2.0.0/ + mkdir bwc-test/src/test/resources/2.3.0.0 + wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.3.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.3.0.0.zip + mv opensearch-security-2.3.0.0.zip bwc-test/src/test/resources/2.3.0.0/ cd bwc-test/ ./gradlew bwcTestSuite -Dtests.security.manager=false diff --git a/build.gradle b/build.gradle index 9da6f841c3..02e32182ef 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ import org.opensearch.gradle.test.RestIntegTestTask buildscript { ext { - opensearch_version = System.getProperty("opensearch.version", "2.3.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.4.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") diff --git a/bwc-test/build.gradle b/bwc-test/build.gradle index 59cd7c9cfb..583d9d173c 100644 --- a/bwc-test/build.gradle +++ b/bwc-test/build.gradle @@ -47,7 +47,7 @@ ext { buildscript { ext { - opensearch_version = System.getProperty("opensearch.version", "2.3.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.4.0-SNAPSHOT") opensearch_group = "org.opensearch" } repositories { @@ -73,16 +73,16 @@ dependencies { testImplementation "org.opensearch.test:framework:${opensearch_version}" } -String bwcVersion = "2.2.0.0"; +String bwcVersion = "2.3.0.0"; String baseName = "securityBwcCluster" String bwcFilePath = "src/test/resources/" -String projectVersion = "2.3.0.0" +String projectVersion = "2.4.0.0" 2.times {i -> testClusters { "${baseName}$i" { testDistribution = "ARCHIVE" - versions = ["2.2.0","2.3.0"] + versions = ["2.3.0","2.4.0"] numberOfNodes = 3 plugin(provider(new Callable() { @Override From 7f992ebfa51e5c4cb3423d9c04bb414b0ed12838 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Mon, 12 Sep 2022 14:48:18 -0500 Subject: [PATCH 037/369] Switch to ClusterManager terminology (#2062) * Cluster manager inclusive checks on codebase Enforce usage of ClusterManager terminology in the codebase Include mechanism to disable checkstyle rule Cross cluster needs to have an additional role in the `node.roles` list, which I am guessing was backwards compatiable if the legacy versions of the role assignments were used. With this change cross cluster tests properly include this value during setup and the settings for these values are merged instead of being overridden. Signed-off-by: Peter Nied --- DEVELOPER_GUIDE.md | 30 +++++++++++++++ checkstyle/sun_checks.xml | 13 ++++++- .../configuration/ClusterInfoHolder.java | 2 +- .../configuration/DlsFlsValveImpl.java | 2 + .../dlic/rest/api/MigrateApiAction.java | 2 + .../security/tools/SecurityAdmin.java | 2 + .../security/RolesInjectorIntegTest.java | 6 +-- .../security/RolesValidationIntegTest.java | 6 +-- .../security/SlowIntegrationTests.java | 16 ++------ .../TransportUserInjectorIntegTest.java | 11 ++---- .../ccstest/CrossClusterSearchTests.java | 10 ++--- .../dlic/dlsfls/CCReplicationTest.java | 10 ++--- .../dlsfls/DlsFlsCrossClusterSearchTest.java | 5 ++- .../opensearch/security/ssl/OpenSSLTest.java | 7 ++-- .../org/opensearch/security/ssl/SSLTest.java | 7 ++-- .../test/AbstractSecurityUnitTest.java | 27 +++++++++++++- .../test/helper/cluster/ClusterHelper.java | 37 +++++++++++++------ 17 files changed, 130 insertions(+), 63 deletions(-) diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index f1e42e1663..ba036b2022 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -8,6 +8,7 @@ So you want to contribute code to this project? Excellent! We're glad you're her - [Using IntelliJ IDEA](#using-intellij-idea) - [Running integration tests](#running-integration-tests) - [Bulk test runs](#bulk-test-runs) + - [Checkstyle Violations](#checkstyle-violations) - [Submitting Changes](#submitting-changes) - [Backports](#backports) @@ -146,6 +147,35 @@ Integration tests are automatically run on all pull requests for all supported v ### Bulk test runs To collect reliability data on test runs there is a manual GitHub action workflow called `Bulk Integration Test`. The workflow is started for a branch on this project or in a fork by going to [GitHub action workflows](https://github.com/opensearch-project/security/actions/workflows/integration-tests.yml) and selecting `Run Workflow`. +### Checkstyle Violations +Checkstyle enforced several rules within this codebase. Sometimes exceptions will be necessary for components that are set for deprecation but the new version is unavailable. There are two formats of suppression that can be used when dealing with violations of this nature, one for disabling a single rule, or another for disabling all rules - its best to be as specific as possible. + +*Execute Checkstyle* +``` +./gradlew checkstyleMain checkstyleTest +``` + +*Example violation* +``` +[ant:checkstyle] [ERROR] /local/home/security/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java:178: Usage should be switched to cluster manager [RegexpSingleline] +``` + +*Single Rule Suppression* +``` + // CS-SUPPRESS-SINGLE: RegexpSingleline See http://github/issues/1234 + ... + Code that violates the rule + ... + // CS-ENFORCE-SINGLE +``` + +*Suppression All Checkstyle Rules* +``` + // CS-SUPRESS-ALL: Legacy code to be deleted in Z.Y.X see http://github/issues/1234 + ... + // CS-ENFORCE-ALL +``` + ## Submitting Changes See [CONTRIBUTING](CONTRIBUTING.md). diff --git a/checkstyle/sun_checks.xml b/checkstyle/sun_checks.xml index 099c8d39a5..5ffbedaf5a 100644 --- a/checkstyle/sun_checks.xml +++ b/checkstyle/sun_checks.xml @@ -201,7 +201,18 @@ - + + + + + + + + + + + + diff --git a/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java b/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java index b289eba0ef..c0569e8390 100644 --- a/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java +++ b/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java @@ -77,7 +77,7 @@ public void clusterChanged(ClusterChangedEvent event) { initialized = true; } - isLocalNodeElectedClusterManager = event.localNodeMaster()?Boolean.TRUE:Boolean.FALSE; + isLocalNodeElectedClusterManager = event.localNodeClusterManager()?Boolean.TRUE:Boolean.FALSE; } public Boolean getHas6xNodes() { diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java index 4a81e517a0..f69e736b8f 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java @@ -174,7 +174,9 @@ public boolean invoke(String action, ActionRequest request, final ActionListener //When we encounter a terms or sampler aggregation with masked fields activated we forcibly //need to switch off global ordinals because field masking can break ordering + // CS-SUPPRESS-SINGLE: RegexpSingleline Ignore term inside of url //https://www.elastic.co/guide/en/elasticsearch/reference/master/eager-global-ordinals.html#_avoiding_global_ordinal_loading + // CS-ENFORCE-SINGLE if (evaluatedDlsFlsConfig.hasFieldMasking()) { if (searchRequest.source() != null && searchRequest.source().aggregations() != null) { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java index 7ea87cba09..3403dc1ee8 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java @@ -11,6 +11,7 @@ package org.opensearch.security.dlic.rest.api; +// CS-SUPPRESS-SINGLE: RegexpSingleline https://github.com/opensearch-project/OpenSearch/issues/3663 import java.io.IOException; import java.nio.file.Path; import java.util.Collections; @@ -69,6 +70,7 @@ import org.opensearch.threadpool.ThreadPool; import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; +// CS-ENFORCE-SINGLE public class MigrateApiAction extends AbstractApiAction { private static final List routes = addRoutesPrefix(Collections.singletonList( diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 0329ebf6fe..2553a13677 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -26,6 +26,7 @@ package org.opensearch.security.tools; +// CS-SUPPRESS-SINGLE: RegexpSingleline https://github.com/opensearch-project/OpenSearch/issues/3663 import java.io.ByteArrayInputStream; import java.io.Console; import java.io.File; @@ -139,6 +140,7 @@ import static org.opensearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; import static org.opensearch.security.support.SecurityUtils.replaceEnvVars; +// CS-ENFORCE-SINGLE @SuppressWarnings("deprecation") public class SecurityAdmin { diff --git a/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java b/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java index 9a356ff92e..8a4129e32b 100644 --- a/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java @@ -45,6 +45,7 @@ import org.opensearch.repositories.RepositoriesService; import org.opensearch.script.ScriptService; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.test.AbstractSecurityUnitTest; import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.threadpool.ThreadPool; @@ -83,12 +84,9 @@ public void testRolesInject() throws Exception { Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster(). health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - final Settings tcSettings = Settings.builder() + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) - .put("node.data", false) - .put("node.master", false) - .put("node.ingest", false) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") .put("path.home", "./target") diff --git a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java index 588bcbb7fc..57a2d45a28 100644 --- a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java @@ -39,6 +39,7 @@ import org.opensearch.repositories.RepositoriesService; import org.opensearch.script.ScriptService; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.test.AbstractSecurityUnitTest; import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.threadpool.ThreadPool; @@ -74,12 +75,9 @@ public Collection createComponents(Client client, ClusterService cluster public void testRolesValidation() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityRoles("roles.yml"), Settings.EMPTY); - final Settings tcSettings = Settings.builder() + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) - .put("node.data", false) - .put("node.master", false) - .put("node.ingest", false) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") .put("path.home", "./target") diff --git a/src/test/java/org/opensearch/security/SlowIntegrationTests.java b/src/test/java/org/opensearch/security/SlowIntegrationTests.java index 6352c920ea..fd01dc7bdd 100644 --- a/src/test/java/org/opensearch/security/SlowIntegrationTests.java +++ b/src/test/java/org/opensearch/security/SlowIntegrationTests.java @@ -42,6 +42,7 @@ import org.opensearch.node.PluginAwareNode; import org.opensearch.security.ssl.util.SSLConfigConstants; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.test.AbstractSecurityUnitTest; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.security.test.helper.cluster.ClusterConfiguration; import org.opensearch.security.test.helper.file.FileHelper; @@ -70,12 +71,9 @@ public void testNodeClientAllowedWithServerCertificate() throws Exception { Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - final Settings tcSettings = Settings.builder() + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) - .put("node.data", false) - .put("node.master", false) - .put("node.ingest", false) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") .put("path.home", "./target") @@ -100,12 +98,9 @@ public void testNodeClientDisallowedWithNonServerCertificate() throws Exception Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - final Settings tcSettings = Settings.builder() + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) - .put("node.data", false) - .put("node.master", false) - .put("node.ingest", false) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") .put("path.home", "./target") @@ -134,12 +129,9 @@ public void testNodeClientDisallowedWithNonServerCertificate2() throws Exception Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - final Settings tcSettings = Settings.builder() + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) - .put("node.data", false) - .put("node.master", false) - .put("node.ingest", false) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") .put("path.home", "./target") diff --git a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java index 56a7b727b4..4f3105501f 100644 --- a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java @@ -37,6 +37,7 @@ import org.opensearch.repositories.RepositoriesService; import org.opensearch.script.ScriptService; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.test.AbstractSecurityUnitTest; import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.threadpool.ThreadPool; @@ -72,12 +73,9 @@ public void testSecurityUserInjection() throws Exception { .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) .build(); setup(clusterNodeSettings, new DynamicSecurityConfig().setSecurityRolesMapping("roles_transport_inject_user.yml"), Settings.EMPTY); - final Settings tcSettings = Settings.builder() + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) - .put("node.data", false) - .put("node.master", false) - .put("node.ingest", false) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") .put("path.home", "./target") @@ -129,12 +127,9 @@ public void testSecurityUserInjectionWithConfigDisabled() throws Exception { .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false) .build(); setup(clusterNodeSettings, new DynamicSecurityConfig().setSecurityRolesMapping("roles_transport_inject_user.yml"), Settings.EMPTY); - final Settings tcSettings = Settings.builder() + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) - .put("node.data", false) - .put("node.master", false) - .put("node.ingest", false) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") .put("path.home", "./target") diff --git a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java index e6b533c0b9..acd5e37b68 100644 --- a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java +++ b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java @@ -84,7 +84,9 @@ public ClusterTransportClientSettings() { } public ClusterTransportClientSettings(Settings clusterSettings, Settings transportSettings) { - super(clusterSettings, transportSettings); + super(Settings.builder() + .put(clusterSettings) + .putList("node.roles", "remote_cluster_client").build(), transportSettings); } public Settings clusterSettings() { @@ -1001,12 +1003,10 @@ public void testCcsWithRoleInjection() throws Exception { .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); } - final Settings tcSettings = Settings.builder() + final Settings.Builder clusterClientSettings = Settings.builder().putList("node.roles", "remote_cluster_client"); + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(clusterClientSettings, false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", cl1Info.clustername) - .put("node.data", false) - .put("node.master", false) - .put("node.ingest", false) .put("path.data", "./target/data/" + cl1Info.clustername + "/cert/data") .put("path.logs", "./target/data/" + cl1Info.clustername + "/cert/logs") .put("path.home", "./target") diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java index 51cab5107f..720f59980d 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java @@ -11,6 +11,7 @@ package org.opensearch.security.dlic.dlsfls; +// CS-SUPPRESS-SINGLE: RegexpSingleline https://github.com/opensearch-project/OpenSearch/issues/3663 import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -59,12 +60,14 @@ import org.opensearch.script.ScriptService; import org.opensearch.security.OpenSearchSecurityPlugin; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.test.AbstractSecurityUnitTest; import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.tasks.Task; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.Netty4Plugin; import org.opensearch.transport.TransportService; import org.opensearch.watcher.ResourceWatcherService; +// CS-ENFORCE-SINGLE public class CCReplicationTest extends AbstractDlsFlsTest { public static class MockReplicationPlugin extends Plugin implements ActionPlugin { @@ -180,14 +183,11 @@ public void testReplication() throws Exception { Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health( new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster(). - health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); + health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - final Settings tcSettings = Settings.builder() + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) - .put("node.data", false) - .put("node.master", false) - .put("node.ingest", false) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") .put("path.home", "./target") diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java index d44d5b4f6e..3fd7d0a406 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java @@ -47,7 +47,7 @@ private void setupCcs(String remoteRoles) throws Exception { System.setProperty("security.display_lic_none","true"); - cl2Info = cl2.startCluster(minimumSecuritySettings(Settings.EMPTY), ClusterConfiguration.DEFAULT); + cl2Info = cl2.startCluster(minimumSecuritySettings(Settings.builder().putList("node.roles", "remote_cluster_client").build()), ClusterConfiguration.DEFAULT); initialize(cl2, cl2Info, new DynamicSecurityConfig().setSecurityRoles(remoteRoles)); System.out.println("### cl2 complete ###"); @@ -66,7 +66,8 @@ public void tearDown() throws Exception { private Settings crossClusterNodeSettings(ClusterInfo remote) { Settings.Builder builder = Settings.builder() - .putList("cluster.remote.cross_cluster_two.seeds", remote.nodeHost+":"+remote.nodePort); + .putList("cluster.remote.cross_cluster_two.seeds", remote.nodeHost+":"+remote.nodePort) + .putList("node.roles", "remote_cluster_client"); return builder.build(); } diff --git a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java index 790477ffe3..961eadeab5 100644 --- a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java @@ -40,6 +40,7 @@ import org.opensearch.security.OpenSearchSecurityPlugin; import org.opensearch.security.ssl.util.SSLConfigConstants; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.test.AbstractSecurityUnitTest; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper; import org.opensearch.transport.Netty4Plugin; @@ -206,11 +207,9 @@ public void testNodeClientSSLwithOpenSslTLSv13() throws Exception { RestHelper rh = nonSslRestHelper(); - final Settings tcSettings = Settings.builder().put("cluster.name", clusterInfo.clustername).put("path.home", "/tmp") + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + .put("cluster.name", clusterInfo.clustername).put("path.home", "/tmp") .put("node.name", "client_node_" + new Random().nextInt()) - .put("node.data", false) - .put("node.master", false) - .put("node.ingest", false) .put("path.data", "./target/data/" + clusterInfo.clustername + "/ssl/data") .put("path.logs", "./target/data/" + clusterInfo.clustername + "/ssl/logs") .put("path.home", "./target") diff --git a/src/test/java/org/opensearch/security/ssl/SSLTest.java b/src/test/java/org/opensearch/security/ssl/SSLTest.java index d98ee3f3f2..ab28b4a88f 100644 --- a/src/test/java/org/opensearch/security/ssl/SSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/SSLTest.java @@ -56,6 +56,7 @@ import org.opensearch.security.ssl.util.ExceptionUtils; import org.opensearch.security.ssl.util.SSLConfigConstants; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.test.AbstractSecurityUnitTest; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper; @@ -495,11 +496,9 @@ public void testNodeClientSSL() throws Exception { RestHelper rh = nonSslRestHelper(); - final Settings tcSettings = Settings.builder().put("cluster.name", clusterInfo.clustername).put("path.home", ".") + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + .put("cluster.name", clusterInfo.clustername).put("path.home", ".") .put("node.name", "client_node_" + new Random().nextInt()) - .put("node.data", false) - .put("node.master", false) - .put("node.ingest", false) .put("path.data", "./target/data/"+clusterInfo.clustername+"/ssl/data") .put("path.logs", "./target/data/"+clusterInfo.clustername+"/ssl/logs") .put("path.home", "./target") diff --git a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java index f9913d9478..b95104dd9f 100644 --- a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java +++ b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java @@ -42,6 +42,8 @@ import com.carrotsearch.randomizedtesting.RandomizedTest; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope.Scope; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import io.netty.handler.ssl.OpenSsl; import org.apache.http.Header; import org.apache.http.HttpHost; @@ -69,6 +71,7 @@ import org.opensearch.client.RestClient; import org.opensearch.client.RestClientBuilder; import org.opensearch.client.RestHighLevelClient; +import org.opensearch.cluster.node.DiscoveryNodeRole; import org.opensearch.common.settings.Settings; import org.opensearch.security.action.configupdate.ConfigUpdateAction; import org.opensearch.security.action.configupdate.ConfigUpdateRequest; @@ -93,6 +96,7 @@ @ThreadLeakScope(Scope.NONE) public abstract class AbstractSecurityUnitTest extends RandomizedTest { + private static final String NODE_ROLE_KEY = "node.roles"; protected static final AtomicLong num = new AtomicLong(); protected static boolean withRemoteCluster; @@ -194,6 +198,28 @@ public void waitForInit(Client client) { } } + public static Settings.Builder nodeRolesSettings(final Settings.Builder settingsBuilder, final boolean isClusterManager, final boolean isDataNode) { + final ImmutableList.Builder nodeRolesBuilder = ImmutableList.builder(); + if (isDataNode) { + nodeRolesBuilder.add(DiscoveryNodeRole.DATA_ROLE.roleName()); + } + if (isClusterManager) { + nodeRolesBuilder.add(DiscoveryNodeRole.CLUSTER_MANAGER_ROLE.roleName()); + } + + final Settings nodeRoleSettings = Settings.builder().putList(NODE_ROLE_KEY, nodeRolesBuilder.build()).build(); + return mergeNodeRolesAndSettings(settingsBuilder, nodeRoleSettings); + } + + public static Settings.Builder mergeNodeRolesAndSettings(final Settings.Builder settingsBuilder, final Settings otherSettings) { + final ImmutableSet.Builder originalRoles = ImmutableSet.builder() + .addAll(settingsBuilder.build().getAsList(NODE_ROLE_KEY, ImmutableList.of())) + .addAll(otherSettings.getAsList(NODE_ROLE_KEY, ImmutableList.of())); + + return settingsBuilder.put(otherSettings) + .putList(NODE_ROLE_KEY, originalRoles.build().asList()); + } + protected void initialize(ClusterHelper clusterHelper, ClusterInfo clusterInfo, DynamicSecurityConfig securityConfig) throws IOException { try (Client tc = clusterHelper.nodeClient()) { Assert.assertEquals(clusterInfo.numNodes, @@ -244,7 +270,6 @@ protected Settings.Builder minimumSecuritySettingsBuilder(int node, boolean sslO } builder.put("cluster.routing.allocation.disk.threshold_enabled", false); builder.put(other); - return builder; } diff --git a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java index fdbef60d70..5aea8f7dfe 100644 --- a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java +++ b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java @@ -26,6 +26,7 @@ package org.opensearch.security.test.helper.cluster; +// CS-SUPPRESS-SINGLE: RegexpSingleline https://github.com/opensearch-project/OpenSearch/issues/3663 import java.io.File; import java.io.IOException; import java.util.Comparator; @@ -60,11 +61,13 @@ import org.opensearch.http.HttpInfo; import org.opensearch.node.Node; import org.opensearch.node.PluginAwareNode; +import org.opensearch.security.test.AbstractSecurityUnitTest; import org.opensearch.security.test.NodeSettingsSupplier; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.security.test.helper.cluster.ClusterConfiguration.NodeSettings; import org.opensearch.security.test.helper.network.SocketUtils; import org.opensearch.transport.TransportInfo; +// CS-ENFORCE-SINGLE public final class ClusterHelper { @@ -171,9 +174,15 @@ public final synchronized ClusterInfo startCluster(final NodeSettingsSupplier no for (int i = 0; i < internalClusterManagerNodeSettings.size(); i++) { NodeSettings setting = internalClusterManagerNodeSettings.get(i); int nodeNum = nodeNumCounter--; - PluginAwareNode node = new PluginAwareNode(setting.clusterManagerNode, - getMinimumNonSecurityNodeSettingsBuilder(nodeNum, setting.clusterManagerNode, setting.dataNode, internalNodeSettings.size(), tcpClusterManagerPortsOnly, tcpPortsAllIt.next(), httpPortsIt.next()) - .put(nodeSettingsSupplier == null ? Settings.Builder.EMPTY_SETTINGS : nodeSettingsSupplier.get(nodeNum)).build(), setting.getPlugins()); + final Settings.Builder nodeSettingsBuilder = getMinimumNonSecurityNodeSettingsBuilder(nodeNum, setting.clusterManagerNode, setting.dataNode, internalNodeSettings.size(), tcpClusterManagerPortsOnly, tcpPortsAllIt.next(), httpPortsIt.next()); + final Settings settingsForNode; + if (nodeSettingsSupplier != null) { + final Settings suppliedSettings = nodeSettingsSupplier.get(nodeNum); + settingsForNode = AbstractSecurityUnitTest.mergeNodeRolesAndSettings(nodeSettingsBuilder, suppliedSettings).build(); + } else { + settingsForNode = nodeSettingsBuilder.build(); + } + PluginAwareNode node = new PluginAwareNode(setting.clusterManagerNode, settingsForNode, setting.getPlugins()); System.out.println(node.settings()); new Thread(new Runnable() { @@ -197,9 +206,15 @@ public void run() { for (int i = 0; i < internalNonClusterManagerNodeSettings.size(); i++) { NodeSettings setting = internalNonClusterManagerNodeSettings.get(i); int nodeNum = nodeNumCounter--; - PluginAwareNode node = new PluginAwareNode(setting.clusterManagerNode, - getMinimumNonSecurityNodeSettingsBuilder(nodeNum, setting.clusterManagerNode, setting.dataNode, internalNodeSettings.size(), tcpClusterManagerPortsOnly, tcpPortsAllIt.next(), httpPortsIt.next()) - .put(nodeSettingsSupplier == null ? Settings.Builder.EMPTY_SETTINGS : nodeSettingsSupplier.get(nodeNum)).build(), setting.getPlugins()); + final Settings.Builder nodeSettingsBuilder = getMinimumNonSecurityNodeSettingsBuilder(nodeNum, setting.clusterManagerNode, setting.dataNode, internalNodeSettings.size(), tcpClusterManagerPortsOnly, tcpPortsAllIt.next(), httpPortsIt.next()); + final Settings settingsForNode; + if (nodeSettingsSupplier != null) { + final Settings suppliedSettings = nodeSettingsSupplier.get(nodeNum); + settingsForNode = AbstractSecurityUnitTest.mergeNodeRolesAndSettings(nodeSettingsBuilder, suppliedSettings).build(); + } else { + settingsForNode = nodeSettingsBuilder.build(); + } + PluginAwareNode node = new PluginAwareNode(setting.clusterManagerNode, settingsForNode, setting.getPlugins()); System.out.println(node.settings()); new Thread(new Runnable() { @@ -293,7 +308,7 @@ public ClusterInfo waitForCluster(final ClusterHealthStatus status, final TimeVa try { log.debug("waiting for cluster state {} and {} nodes", status.name(), expectedNodeCount); final ClusterHealthResponse healthResponse = client.admin().cluster().prepareHealth() - .setWaitForStatus(status).setTimeout(timeout).setMasterNodeTimeout(timeout).setWaitForNodes("" + expectedNodeCount).execute() + .setWaitForStatus(status).setTimeout(timeout).setClusterManagerNodeTimeout(timeout).setWaitForNodes("" + expectedNodeCount).execute() .actionGet(); if (healthResponse.isTimedOut()) { throw new IOException("cluster state is " + healthResponse.getStatus().name() + " with " @@ -366,13 +381,11 @@ public ClusterInfo waitForCluster(final ClusterHealthStatus status, final TimeVa } // @formatter:off - private Settings.Builder getMinimumNonSecurityNodeSettingsBuilder(final int nodenum, final boolean clusterManagerNode, - final boolean dataNode, int nodeCount, SortedSet clusterManagerTcpPorts, int tcpPort, int httpPort) { + private Settings.Builder getMinimumNonSecurityNodeSettingsBuilder(final int nodenum, final boolean isClusterManagerNode, + final boolean isDataNode, int nodeCount, SortedSet clusterManagerTcpPorts, int tcpPort, int httpPort) { - return Settings.builder() + return AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), isClusterManagerNode, isDataNode) .put("node.name", "node_"+clustername+ "_num" + nodenum) - .put("node.data", dataNode) - .put("node.master", clusterManagerNode) .put("cluster.name", clustername) .put("path.data", "./target/data/"+clustername+"/data") .put("path.logs", "./target/data/"+clustername+"/logs") From f591af649dafc38dc9d1d74731f9d7dd47253a28 Mon Sep 17 00:00:00 2001 From: Nils Bandener <33570290+nibix@users.noreply.github.com> Date: Tue, 20 Sep 2022 16:43:35 +0200 Subject: [PATCH 038/369] New test framework (#1967) This is the first iteration of a new framework for integration tests. The main focus is on conciseness and self-contained test. The framework offers: A programmatic way of defining the cluster setup A programmatic way of defining users and roles A programmatic way of defining indices A REST client with methods tailored for testing This is now all part of the test class, so you do not need to jump around in various configuration files to get an overview on the test setup. There are of course still a lot of features missing, like setting up test data. I propose to add these features in increments. Signed-off-by: Nils Bandener Signed-off-by: Jochen Kressin Signed-off-by: Lukasz Soszynski Signed-off-by: Kacper Trochimiak Signed-off-by: Nils Bandener Signed-off-by: Peter Nied Co-authored-by: Jochen Kressin Co-authored-by: Lukasz Soszynski Co-authored-by: Kacper Trochimiak Co-authored-by: Peter Nied --- .github/workflows/cd.yml | 4 +- .github/workflows/ci.yml | 8 +- build.gradle | 97 ++- .../logging/NodeAndClusterIdConverter.java | 33 ++ .../org/opensearch/node/PluginAwareNode.java | 49 ++ .../security/SecurityRolesTests.java | 60 ++ .../privileges/PrivilegesEvaluatorTest.java | 71 +++ .../opensearch/test/framework/TestIndex.java | 84 +++ .../test/framework/TestSecurityConfig.java | 556 ++++++++++++++++++ .../framework/certificate/Certificates.java | 165 ++++++ .../certificate/TestCertificates.java | 77 +++ .../framework/cluster/ClusterManager.java | 144 +++++ .../cluster/ContextHeaderDecoratorClient.java | 49 ++ .../test/framework/cluster/LocalCluster.java | 364 ++++++++++++ .../cluster/LocalOpenSearchCluster.java | 510 ++++++++++++++++ ...inimumSecuritySettingsSupplierFactory.java | 72 +++ .../cluster/NodeSettingsSupplier.java | 34 ++ .../test/framework/cluster/NodeType.java | 15 + .../cluster/OpenSearchClientProvider.java | 153 +++++ .../test/framework/cluster/PortAllocator.java | 164 ++++++ .../cluster/RestClientException.java | 16 + .../test/framework/cluster/SocketUtils.java | 312 ++++++++++ .../framework/cluster/SocketUtilsTests.java | 212 +++++++ .../test/framework/cluster/StartStage.java | 15 + .../framework/cluster/TestRestClient.java | 360 ++++++++++++ .../resources/log4j2-test.properties | 16 + .../securityconf/DynamicConfigFactory.java | 3 +- .../security/support/PemKeyReader.java | 20 +- 28 files changed, 3632 insertions(+), 31 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/common/logging/NodeAndClusterIdConverter.java create mode 100644 src/integrationTest/java/org/opensearch/node/PluginAwareNode.java create mode 100644 src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java create mode 100644 src/integrationTest/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/TestIndex.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/certificate/Certificates.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/ContextHeaderDecoratorClient.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/NodeSettingsSupplier.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/NodeType.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/PortAllocator.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/RestClientException.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtils.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtilsTests.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/StartStage.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java create mode 100644 src/integrationTest/resources/log4j2-test.properties diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f2bc154d3f..03d5d6bd9b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -34,9 +34,9 @@ jobs: - name: Build run: | - ./gradlew clean build -Dbuild.snapshot=false -x test + ./gradlew clean build -Dbuild.snapshot=false -x test -x integrationTest artifact_zip=`ls $(pwd)/build/distributions/opensearch-security-*.zip | grep -v admin-standalone` - ./gradlew build buildDeb buildRpm -ParchivePath=$artifact_zip -Dbuild.snapshot=false -x test + ./gradlew build buildDeb buildRpm -ParchivePath=$artifact_zip -Dbuild.snapshot=false -x test -x integrationTest mkdir artifacts cp $artifact_zip artifacts/ cp build/distributions/*.deb artifacts/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdbcb45ab2..f00e5bef68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,10 +36,10 @@ jobs: ${{ runner.os }}-gradle- - name: Package - run: ./gradlew clean build -Dbuild.snapshot=false -x test + run: ./gradlew clean build -Dbuild.snapshot=false -x test -x integrationTest - name: Test - run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew test -i + run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew test integrationTest -i - name: Coverage uses: codecov/codecov-action@v1 @@ -65,7 +65,7 @@ jobs: - uses: actions/setup-java@v1 with: java-version: 11 - - run: ./gradlew clean build -Dbuild.snapshot=false -x test + - run: ./gradlew clean build -Dbuild.snapshot=false -x test -x integrationTest - run: | echo "Running backwards compatibility tests ..." security_plugin_version_no_snapshot=$(./gradlew properties -q | grep -E '^version:' | awk '{print $2}' | sed 's/-SNAPSHOT//g') @@ -88,7 +88,7 @@ jobs: - uses: github/codeql-action/init@v1 with: languages: java - - run: ./gradlew clean build -Dbuild.snapshot=false -x test + - run: ./gradlew clean build -Dbuild.snapshot=false -x test -x integrationTest - uses: github/codeql-action/analyze@v1 build-artifact-names: diff --git a/build.gradle b/build.gradle index 02e32182ef..726dd06d6f 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,8 @@ * GitHub history for details. */ + +import com.diffplug.gradle.spotless.JavaExtension import org.opensearch.gradle.test.RestIntegTestTask buildscript { @@ -73,6 +75,12 @@ spotless { java { // note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') + targetExclude('src/integrationTest/**') + } + format("integrationTest", JavaExtension) { + target('src/integrationTest/java/**/*.java') + importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') + indentWithTabs(4) } } @@ -92,6 +100,12 @@ forbiddenPatterns.enabled = false testingConventions.enabled = false // Conflicts between runtime kafka-clients:3.0.1 & testRuntime kafka-clients:3.0.1:test jarHell.enabled = false +tasks.whenTaskAdded {task -> + if(task.name.contains("forbiddenApisIntegrationTest")) { + task.enabled = false + } +} + test { include '**/*.class' @@ -221,24 +235,62 @@ bundlePlugin { } } -configurations.all { - resolutionStrategy { - force 'commons-codec:commons-codec:1.14' - force 'org.slf4j:slf4j-api:1.7.30' - force 'org.scala-lang:scala-library:2.13.8' - force 'commons-io:commons-io:2.11.0' - force "com.fasterxml.jackson:jackson-bom:${versions.jackson}" - force "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" - force "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:${versions.jackson}" - force "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" - force "io.netty:netty-buffer:${versions.netty}" - force "io.netty:netty-common:${versions.netty}" - force "io.netty:netty-handler:${versions.netty}" - force "io.netty:netty-transport:${versions.netty}" - force "io.netty:netty-transport-native-unix-common:${versions.netty}" +configurations { + all { + resolutionStrategy { + force 'commons-codec:commons-codec:1.14' + force 'org.slf4j:slf4j-api:1.7.30' + force 'org.scala-lang:scala-library:2.13.8' + force 'commons-io:commons-io:2.11.0' + force "com.fasterxml.jackson:jackson-bom:${versions.jackson}" + force "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" + force "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:${versions.jackson}" + force "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" + force "io.netty:netty-buffer:${versions.netty}" + force "io.netty:netty-common:${versions.netty}" + force "io.netty:netty-handler:${versions.netty}" + force "io.netty:netty-transport:${versions.netty}" + force "io.netty:netty-transport-native-unix-common:${versions.netty}" + } + } + + integrationTestImplementation.extendsFrom implementation + integrationTestRuntimeOnly.extendsFrom runtimeOnly +} + +//create source set 'integrationTest' +//add classes from the main source set to the compilation and runtime classpaths of the integrationTest +sourceSets { + integrationTest { + java { + srcDir file ('src/integrationTest/java') + compileClasspath += sourceSets.main.output + runtimeClasspath += sourceSets.main.output + } + resources { + srcDir file('src/integrationTest/resources') + } + processIntegrationTestResources { + duplicatesStrategy(DuplicatesStrategy.INCLUDE) + } } } +//add new task that runs integration tests +task integrationTest(type: Test) { + description = 'Run integration tests.' + group = 'verification' + systemProperty "java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager" + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath + + //run the integrationTest task after the test task + shouldRunAfter test +} + +//run the integrationTest task before the check task +check.dependsOn integrationTest + dependencies { implementation 'jakarta.annotation:jakarta.annotation-api:1.3.5' implementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}" @@ -327,7 +379,7 @@ dependencies { testImplementation "org.opensearch.plugin:parent-join-client:${opensearch_version}" testImplementation "org.opensearch.plugin:aggs-matrix-stats-client:${opensearch_version}" testImplementation 'org.apache.logging.log4j:log4j-core:2.17.1' - testImplementation 'commons-io:commons-io:2.7' + testImplementation 'commons-io:commons-io:2.11.0' testImplementation 'javax.servlet:servlet-api:2.5' testImplementation 'com.unboundid:unboundid-ldapsdk:4.0.9' testImplementation 'com.github.stephenc.jcip:jcip-annotations:1.0-1' @@ -362,6 +414,19 @@ dependencies { implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" compileOnly "org.opensearch:opensearch:${opensearch_version}" + + //integration test framework: + integrationTestImplementation('com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.7.1') { + exclude(group: 'junit', module: 'junit') + } + integrationTestImplementation 'junit:junit:4.13.2' + integrationTestImplementation "org.opensearch.plugin:reindex-client:${opensearch_version}" + integrationTestImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}" + integrationTestImplementation 'commons-io:commons-io:2.11.0' + integrationTestImplementation 'org.apache.logging.log4j:log4j-core:2.17.1' + integrationTestImplementation 'org.apache.logging.log4j:log4j-jul:2.17.1' + integrationTestImplementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.1' + integrationTestImplementation 'org.hamcrest:hamcrest:2.2' } jar { diff --git a/src/integrationTest/java/org/opensearch/common/logging/NodeAndClusterIdConverter.java b/src/integrationTest/java/org/opensearch/common/logging/NodeAndClusterIdConverter.java new file mode 100644 index 0000000000..94242ecc28 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/common/logging/NodeAndClusterIdConverter.java @@ -0,0 +1,33 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.common.logging; + +/** +* Class uses to override OpenSearch NodeAndClusterIdConverter Log4j2 plugin in order to disable plugin and limit number of +* warn messages like "...ApplierService#updateTask][T#1] WARN ClusterApplierService:628 - failed to notify ClusterStateListener..." +* during tests execution. +* +* The class is rather a temporary solution and the real one should be developed in scope of: +* https://github.com/opensearch-project/OpenSearch/pull/4322 +*/ +import org.apache.logging.log4j.core.LogEvent; + +class NodeAndClusterIdConverter { + + + public NodeAndClusterIdConverter() { + } + + public static void setNodeIdAndClusterId(String nodeId, String clusterUUID) { + } + + public void format(LogEvent event, StringBuilder toAppendTo) { + } +} diff --git a/src/integrationTest/java/org/opensearch/node/PluginAwareNode.java b/src/integrationTest/java/org/opensearch/node/PluginAwareNode.java new file mode 100644 index 0000000000..1599cd2a37 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/node/PluginAwareNode.java @@ -0,0 +1,49 @@ +/* +* Copyright 2015-2018 _floragunn_ GmbH +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +*/ + +/* +* 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.node; + +import java.util.Arrays; +import java.util.Collections; + +import org.opensearch.common.settings.Settings; +import org.opensearch.plugins.Plugin; + +public class PluginAwareNode extends Node { + + private final boolean clusterManagerEligible; + + @SafeVarargs + public PluginAwareNode(boolean clusterManagerEligible, final Settings preparedSettings, final Class... plugins) { + super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, Collections.emptyMap(), null, () -> System.getenv("HOSTNAME")), Arrays.asList(plugins), true); + this.clusterManagerEligible = clusterManagerEligible; + } + + + public boolean isClusterManagerEligible() { + return clusterManagerEligible; + } +} diff --git a/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java b/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java new file mode 100644 index 0000000000..df20cded48 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java @@ -0,0 +1,60 @@ +/* +* 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; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.http.HttpStatus; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class SecurityRolesTests { + + protected final static TestSecurityConfig.User USER_SR = new TestSecurityConfig.User("sr_user").roles( + new Role("abc_ber").indexPermissions("*").on("*").clusterPermissions("*"), + new Role("def_efg").indexPermissions("*").on("*").clusterPermissions("*")); + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(true) + .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_SR).build(); + + @Test + public void testSecurityRoles() throws Exception { + try (TestRestClient client = cluster.getRestClient(USER_SR)) { + HttpResponse response = client.getAuthInfo(); + assertThat(response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + // Check username + assertThat(response.getTextFromJsonBody("/user_name"), equalTo("sr_user")); + + // Check security roles + assertThat(response.getTextFromJsonBody("/roles/0"), equalTo("user_sr_user__abc_ber")); + assertThat(response.getTextFromJsonBody("/roles/1"), equalTo("user_sr_user__def_efg")); + + } + } + +} diff --git a/src/integrationTest/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java b/src/integrationTest/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java new file mode 100644 index 0000000000..c3ea872537 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java @@ -0,0 +1,71 @@ +/* +* 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.privileges; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.http.HttpStatus; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; + +/** +* This is a port for the test +* org.opensearch.security.privileges.PrivilegesEvaluatorTest to the new test +* framework for direct comparison +*/ +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class PrivilegesEvaluatorTest { + + protected final static TestSecurityConfig.User NEGATIVE_LOOKAHEAD = new TestSecurityConfig.User( + "negative_lookahead_user") + .roles(new Role("negative_lookahead_role").indexPermissions("read").on("/^(?!t.*).*/") + .clusterPermissions("cluster_composite_ops")); + + protected final static TestSecurityConfig.User NEGATED_REGEX = new TestSecurityConfig.User("negated_regex_user") + .roles(new Role("negated_regex_role").indexPermissions("read").on("/^[a-z].*/") + .clusterPermissions("cluster_composite_ops")); + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).authc(AUTHC_HTTPBASIC_INTERNAL) + .users(NEGATIVE_LOOKAHEAD, NEGATED_REGEX).build(); + + @Test + public void testNegativeLookaheadPattern() throws Exception { + + try (TestRestClient client = cluster.getRestClient(NEGATIVE_LOOKAHEAD)) { + assertThat(client.get("*/_search").getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + assertThat(client.get("r*/_search").getStatusCode(), equalTo(HttpStatus.SC_OK)); + } + } + + @Test + public void testRegexPattern() throws Exception { + + try (TestRestClient client = cluster.getRestClient(NEGATED_REGEX)) { + assertThat(client.get("*/_search").getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + assertThat(client.get("r*/_search").getStatusCode(), equalTo(HttpStatus.SC_OK)); + } + + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestIndex.java b/src/integrationTest/java/org/opensearch/test/framework/TestIndex.java new file mode 100644 index 0000000000..9d5feb9eee --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/TestIndex.java @@ -0,0 +1,84 @@ +/* +* Copyright 2021-2022 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +* +*/ + +/* +* 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.test.framework; + +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.client.Client; +import org.opensearch.common.settings.Settings; + +public class TestIndex { + + private final String name; + private final Settings settings; + + public TestIndex(String name, Settings settings) { + this.name = name; + this.settings = settings; + + } + + public void create(Client client) { + client.admin().indices().create(new CreateIndexRequest(name).settings(settings)).actionGet(); + } + + public String getName() { + return name; + } + + + public static Builder name(String name) { + return new Builder().name(name); + } + + public static class Builder { + private String name; + private Settings.Builder settings = Settings.builder(); + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder setting(String name, int value) { + settings.put(name, value); + return this; + } + + public Builder shards(int value) { + settings.put("index.number_of_shards", 5); + return this; + } + + public TestIndex build() { + return new TestIndex(name, settings.build()); + } + + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java new file mode 100644 index 0000000000..f220df3eb6 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -0,0 +1,556 @@ +/* +* Copyright 2021 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +* +*/ + +/* +* 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.test.framework; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.crypto.generators.OpenBSDBCrypt; + +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest.RefreshPolicy; +import org.opensearch.client.Client; +import org.opensearch.common.Strings; +import org.opensearch.common.bytes.BytesReference; +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.security.action.configupdate.ConfigUpdateAction; +import org.opensearch.security.action.configupdate.ConfigUpdateRequest; +import org.opensearch.security.action.configupdate.ConfigUpdateResponse; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.test.framework.cluster.OpenSearchClientProvider.UserCredentialsHolder; + +/** +* This class allows the declarative specification of the security configuration; in particular: +* +* - config.yml +* - internal_users.yml +* - roles.yml +* - roles_mapping.yml +* +* The class does the whole round-trip, i.e., the configuration is serialized to YAML/JSON and then written to +* the configuration index of the security plugin. +*/ +public class TestSecurityConfig { + + private static final Logger log = LogManager.getLogger(TestSecurityConfig.class); + + private Config config = new Config(); + private Map internalUsers = new LinkedHashMap<>(); + private Map roles = new LinkedHashMap<>(); + + private String indexName = ".opendistro_security"; + + public TestSecurityConfig() { + + } + + public TestSecurityConfig configIndexName(String configIndexName) { + this.indexName = configIndexName; + return this; + } + + public TestSecurityConfig anonymousAuth(boolean anonymousAuthEnabled) { + config.anonymousAuth(anonymousAuthEnabled); + return this; + } + + public TestSecurityConfig authc(AuthcDomain authcDomain) { + config.authc(authcDomain); + return this; + } + public TestSecurityConfig user(User user) { + this.internalUsers.put(user.name, user); + + for (Role role : user.roles) { + this.roles.put(role.name, role); + } + + return this; + } + + public TestSecurityConfig roles(Role... roles) { + for (Role role : roles) { + this.roles.put(role.name, role); + } + + return this; + } + + public static class Config implements ToXContentObject { + private boolean anonymousAuth; + private Map authcDomainMap = new LinkedHashMap<>(); + + public Config anonymousAuth(boolean anonymousAuth) { + this.anonymousAuth = anonymousAuth; + return this; + } + + public Config authc(AuthcDomain authcDomain) { + authcDomainMap.put(authcDomain.id, authcDomain); + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.startObject("dynamic"); + + if (anonymousAuth) { + xContentBuilder.startObject("http"); + xContentBuilder.field("anonymous_auth_enabled", true); + xContentBuilder.endObject(); + } + + xContentBuilder.field("authc", authcDomainMap); + + xContentBuilder.endObject(); + xContentBuilder.endObject(); + return xContentBuilder; + } + } + + public static class User implements UserCredentialsHolder, ToXContentObject { + + public final static TestSecurityConfig.User USER_ADMIN = new TestSecurityConfig.User("admin") + .roles(new Role("allaccess").indexPermissions("*").on("*").clusterPermissions("*")); + + private String name; + private String password; + private List roles = new ArrayList<>(); + private Map attributes = new HashMap<>(); + + public User(String name) { + this.name = name; + this.password = "secret"; + } + + public User password(String password) { + this.password = password; + return this; + } + + public User roles(Role... roles) { + // We scope the role names by user to keep tests free of potential side effects + String roleNamePrefix = "user_" + this.name + "__"; + this.roles.addAll(Arrays.asList(roles).stream().map((r) -> r.clone().name(roleNamePrefix + r.name)).collect(Collectors.toSet())); + return this; + } + + public User attr(String key, Object value) { + this.attributes.put(key, value); + return this; + } + + public String getName() { + return name; + } + + public String getPassword() { + return password; + } + + public Set getRoleNames() { + return roles.stream().map(Role::getName).collect(Collectors.toSet()); + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + + xContentBuilder.field("hash", hash(password.toCharArray())); + + Set roleNames = getRoleNames(); + + if (!roleNames.isEmpty()) { + xContentBuilder.field("opendistro_security_roles", roleNames); + } + + if (attributes != null && attributes.size() != 0) { + xContentBuilder.field("attributes", attributes); + } + + xContentBuilder.endObject(); + return xContentBuilder; + } + } + + public static class Role implements ToXContentObject { + public static Role ALL_ACCESS = new Role("all_access").clusterPermissions("*").indexPermissions("*").on("*"); + + private String name; + private List clusterPermissions = new ArrayList<>(); + + private List indexPermissions = new ArrayList<>(); + + public Role(String name) { + this.name = name; + } + + public Role clusterPermissions(String... clusterPermissions) { + this.clusterPermissions.addAll(Arrays.asList(clusterPermissions)); + return this; + } + + public IndexPermission indexPermissions(String... indexPermissions) { + return new IndexPermission(this, indexPermissions); + } + + public Role name(String name) { + this.name = name; + return this; + } + + public String getName() { + return name; + } + + public Role clone() { + Role role = new Role(this.name); + role.clusterPermissions.addAll(this.clusterPermissions); + role.indexPermissions.addAll(this.indexPermissions); + return role; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + + if (!clusterPermissions.isEmpty()) { + xContentBuilder.field("cluster_permissions", clusterPermissions); + } + + if (!indexPermissions.isEmpty()) { + xContentBuilder.field("index_permissions", indexPermissions); + } + + xContentBuilder.endObject(); + return xContentBuilder; + } + } + + public static class IndexPermission implements ToXContentObject { + private List allowedActions; + private List indexPatterns; + private Role role; + private String dlsQuery; + private List fls; + private List maskedFields; + + IndexPermission(Role role, String... allowedActions) { + this.allowedActions = Arrays.asList(allowedActions); + this.role = role; + } + + public IndexPermission dls(String dlsQuery) { + this.dlsQuery = dlsQuery; + return this; + } + + public IndexPermission fls(String... fls) { + this.fls = Arrays.asList(fls); + return this; + } + + public IndexPermission maskedFields(String... maskedFields) { + this.maskedFields = Arrays.asList(maskedFields); + return this; + } + + public Role on(String... indexPatterns) { + this.indexPatterns = Arrays.asList(indexPatterns); + this.role.indexPermissions.add(this); + return this.role; + } + + public Role on(TestIndex... testindices) { + this.indexPatterns = Arrays.asList(testindices).stream().map(TestIndex::getName).collect(Collectors.toList()); + this.role.indexPermissions.add(this); + return this.role; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + + xContentBuilder.field("index_patterns", indexPatterns); + xContentBuilder.field("allowed_actions", allowedActions); + + if (dlsQuery != null) { + xContentBuilder.field("dls", dlsQuery); + } + + if (fls != null) { + xContentBuilder.field("fls", fls); + } + + if (maskedFields != null) { + xContentBuilder.field("masked_fields", maskedFields); + } + + xContentBuilder.endObject(); + return xContentBuilder; + } + } + + public static class AuthcDomain implements ToXContentObject { + + public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL = new TestSecurityConfig.AuthcDomain("basic", 0) + .httpAuthenticator("basic").backend("internal"); + + private final String id; + private boolean enabled = true; + private int order; + private List skipUsers = new ArrayList<>(); + private HttpAuthenticator httpAuthenticator; + private AuthenticationBackend authenticationBackend; + + public AuthcDomain(String id, int order) { + this.id = id; + this.order = order; + } + + public AuthcDomain httpAuthenticator(String type) { + this.httpAuthenticator = new HttpAuthenticator(type); + return this; + } + + public AuthcDomain challengingAuthenticator(String type) { + this.httpAuthenticator = new HttpAuthenticator(type).challenge(true); + return this; + } + + public AuthcDomain httpAuthenticator(HttpAuthenticator httpAuthenticator) { + this.httpAuthenticator = httpAuthenticator; + return this; + } + + public AuthcDomain backend(String type) { + this.authenticationBackend = new AuthenticationBackend(type); + return this; + } + + public AuthcDomain backend(AuthenticationBackend authenticationBackend) { + this.authenticationBackend = authenticationBackend; + return this; + } + + public AuthcDomain skipUsers(String... users) { + this.skipUsers.addAll(Arrays.asList(users)); + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + + xContentBuilder.field("http_enabled", enabled); + xContentBuilder.field("order", order); + + if (httpAuthenticator != null) { + xContentBuilder.field("http_authenticator", httpAuthenticator); + } + + if (authenticationBackend != null) { + xContentBuilder.field("authentication_backend", authenticationBackend); + } + + if (skipUsers != null && skipUsers.size() > 0) { + xContentBuilder.field("skip_users", skipUsers); + } + + xContentBuilder.endObject(); + return xContentBuilder; + } + + public static class HttpAuthenticator implements ToXContentObject { + private final String type; + private boolean challenge; + private Map config = new HashMap(); + + public HttpAuthenticator(String type) { + this.type = type; + } + + public HttpAuthenticator challenge(boolean challenge) { + this.challenge = challenge; + return this; + } + + public HttpAuthenticator config(Map config) { + this.config.putAll(config); + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + + xContentBuilder.field("type", type); + xContentBuilder.field("challenge", challenge); + xContentBuilder.field("config", config); + + xContentBuilder.endObject(); + return xContentBuilder; + } + } + + public static class AuthenticationBackend implements ToXContentObject { + private final String type; + private Map config = new HashMap(); + + public AuthenticationBackend(String type) { + this.type = type; + } + + public AuthenticationBackend config(Map config) { + this.config.putAll(config); + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + + xContentBuilder.field("type", type); + xContentBuilder.field("config", config); + + xContentBuilder.endObject(); + return xContentBuilder; + } + } + } + + public void initIndex(Client client) { + Map settings = new HashMap<>(); + if (indexName.startsWith(".")) { + settings.put("index.hidden", true); + } + client.admin().indices().create(new CreateIndexRequest(indexName).settings(settings)).actionGet(); + + writeSingleEntryConfigToIndex(client, CType.CONFIG, config); + writeConfigToIndex(client, CType.ROLES, roles); + writeConfigToIndex(client, CType.INTERNALUSERS, internalUsers); + writeEmptyConfigToIndex(client, CType.ROLESMAPPING); + writeEmptyConfigToIndex(client, CType.ACTIONGROUPS); + writeEmptyConfigToIndex(client, CType.TENANTS); + + ConfigUpdateResponse configUpdateResponse = client.execute(ConfigUpdateAction.INSTANCE, + new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0]))).actionGet(); + + if (configUpdateResponse.hasFailures()) { + throw new RuntimeException("ConfigUpdateResponse produced failures: " + configUpdateResponse.failures()); + } + } + + + private static String hash(final char[] clearTextPassword) { + final byte[] salt = new byte[16]; + new SecureRandom().nextBytes(salt); + final String hash = OpenBSDBCrypt.generate((Objects.requireNonNull(clearTextPassword)), salt, 12); + Arrays.fill(salt, (byte) 0); + Arrays.fill(clearTextPassword, '\0'); + return hash; + } + + private void writeEmptyConfigToIndex(Client client, CType configType) { + writeConfigToIndex(client, configType, Collections.emptyMap()); + } + + private void writeConfigToIndex(Client client, CType configType, Map config) { + try { + XContentBuilder builder = XContentFactory.jsonBuilder(); + + builder.startObject(); + builder.startObject("_meta"); + builder.field("type", configType.toLCString()); + builder.field("config_version", 2); + builder.endObject(); + + for (Map.Entry entry : config.entrySet()) { + builder.field(entry.getKey(), entry.getValue()); + } + + builder.endObject(); + + String json = Strings.toString(builder); + + log.info("Writing " + configType + ":\n" + json); + + client.index(new IndexRequest(indexName).id(configType.toLCString()) + .setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(configType.toLCString(), + BytesReference.fromByteBuffer(ByteBuffer.wrap(json.getBytes("utf-8"))))) + .actionGet(); + } catch (Exception e) { + throw new RuntimeException("Error while initializing config for " + indexName, e); + } + } + + private void writeSingleEntryConfigToIndex(Client client, CType configType, ToXContentObject config) { + try { + XContentBuilder builder = XContentFactory.jsonBuilder(); + + builder.startObject(); + builder.startObject("_meta"); + builder.field("type", configType.toLCString()); + builder.field("config_version", 2); + builder.endObject(); + + builder.field(configType.toLCString(), config); + + builder.endObject(); + + String json = Strings.toString(builder); + + log.info("Writing " + configType + ":\n" + json); + + client.index(new IndexRequest(indexName).id(configType.toLCString()) + .setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(configType.toLCString(), + BytesReference.fromByteBuffer(ByteBuffer.wrap(json.getBytes("utf-8"))))) + .actionGet(); + } catch (Exception e) { + throw new RuntimeException("Error while initializing config for " + indexName, e); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/Certificates.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/Certificates.java new file mode 100644 index 0000000000..9895fc1484 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/Certificates.java @@ -0,0 +1,165 @@ +/* +* 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.test.framework.certificate; + +/** +* Contains static certificates for the test cluster. +* Note: This is WIP and will be replaced by classes +* that can generate certificates on the fly. This +* class will be removed after that. +*/ +public class Certificates { + + final static String ROOT_CA_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" + + "MIID/jCCAuagAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjzETMBEGCgmSJomT8ixk\n" + + "ARkWA2NvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1w\n" + + "bGUgQ29tIEluYy4xITAfBgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEh\n" + + "MB8GA1UEAwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMB4XDTE4MDQyMjAzNDM0\n" + + "NloXDTI4MDQxOTAzNDM0NlowgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJ\n" + + "kiaJk/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEw\n" + + "HwYDVQQLDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1w\n" + + "bGUgQ29tIEluYy4gUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n" + + "ggEBAK/u+GARP5innhpXK0c0q7s1Su1VTEaIgmZr8VWI6S8amf5cU3ktV7WT9SuV\n" + + "TsAm2i2A5P+Ctw7iZkfnHWlsC3HhPUcd6mvzGZ4moxnamM7r+a9otRp3owYoGStX\n" + + "ylVTQusAjbq9do8CMV4hcBTepCd+0w0v4h6UlXU8xjhj1xeUIz4DKbRgf36q0rv4\n" + + "VIX46X72rMJSETKOSxuwLkov1ZOVbfSlPaygXIxqsHVlj1iMkYRbQmaTib6XWHKf\n" + + "MibDaqDejOhukkCjzpptGZOPFQ8002UtTTNv1TiaKxkjMQJNwz6jfZ53ws3fh1I0\n" + + "RWT6WfM4oeFRFnyFRmc4uYTUgAkCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAf\n" + + "BgNVHSMEGDAWgBSSNQzgDx4rRfZNOfN7X6LmEpdAczAdBgNVHQ4EFgQUkjUM4A8e\n" + + "K0X2TTnze1+i5hKXQHMwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB\n" + + "AQBoQHvwsR34hGO2m8qVR9nQ5Klo5HYPyd6ySKNcT36OZ4AQfaCGsk+SecTi35QF\n" + + "RHL3g2qffED4tKR0RBNGQSgiLavmHGCh3YpDupKq2xhhEeS9oBmQzxanFwWFod4T\n" + + "nnsG2cCejyR9WXoRzHisw0KJWeuNlwjUdJY0xnn16srm1zL/M/f0PvCyh9HU1mF1\n" + + "ivnOSqbDD2Z7JSGyckgKad1Omsg/rr5XYtCeyJeXUPcmpeX6erWJJNTUh6yWC/hY\n" + + "G/dFC4xrJhfXwz6Z0ytUygJO32bJG4Np2iGAwvvgI9EfxzEv/KP+FGrJOvQJAq4/\n" + + "BU36ZAa80W/8TBnqZTkNnqZV\n" + + "-----END CERTIFICATE-----\n" + + ""; + + final static String NODE_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" + + "MIIEyTCCA7GgAwIBAgIGAWLrc1O2MA0GCSqGSIb3DQEBCwUAMIGPMRMwEQYKCZIm\n" + + "iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ\n" + + "RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290\n" + + "IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwHhcNMTgwNDIy\n" + + "MDM0MzQ3WhcNMjgwNDE5MDM0MzQ3WjBeMRIwEAYKCZImiZPyLGQBGRYCZGUxDTAL\n" + + "BgNVBAcMBHRlc3QxDTALBgNVBAoMBG5vZGUxDTALBgNVBAsMBG5vZGUxGzAZBgNV\n" + + "BAMMEm5vZGUtMC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\n" + + "AQoCggEBAJa+f476vLB+AwK53biYByUwN+40D8jMIovGXm6wgT8+9Sbs899dDXgt\n" + + "9CE1Beo65oP1+JUz4c7UHMrCY3ePiDt4cidHVzEQ2g0YoVrQWv0RedS/yx/DKhs8\n" + + "Pw1O715oftP53p/2ijD5DifFv1eKfkhFH+lwny/vMSNxellpl6NxJTiJVnQ9HYOL\n" + + "gf2t971ITJHnAuuxUF48HcuNovW4rhtkXef8kaAN7cE3LU+A9T474ULNCKkEFPIl\n" + + "ZAKN3iJNFdVsxrTU+CUBHzk73Do1cCkEvJZ0ZFjp0Z3y8wLY/gqWGfGVyA9l2CUq\n" + + "eIZNf55PNPtGzOrvvONiui48vBKH1LsCAwEAAaOCAVkwggFVMIG8BgNVHSMEgbQw\n" + + "gbGAFJI1DOAPHitF9k0583tfouYSl0BzoYGVpIGSMIGPMRMwEQYKCZImiZPyLGQB\n" + + "GRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQRXhhbXBs\n" + + "ZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMSEw\n" + + "HwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0GCAQEwHQYDVR0OBBYEFKyv\n" + + "78ZmFjVKM9g7pMConYH7FVBHMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgXg\n" + + "MCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA1BgNVHREELjAsiAUq\n" + + "AwQFBYISbm9kZS0wLmV4YW1wbGUuY29tgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZI\n" + + "hvcNAQELBQADggEBAIOKuyXsFfGv1hI/Lkpd/73QNqjqJdxQclX57GOMWNbOM5H0\n" + + "5/9AOIZ5JQsWULNKN77aHjLRr4owq2jGbpc/Z6kAd+eiatkcpnbtbGrhKpOtoEZy\n" + + "8KuslwkeixpzLDNISSbkeLpXz4xJI1ETMN/VG8ZZP1bjzlHziHHDu0JNZ6TnNzKr\n" + + "XzCGMCohFfem8vnKNnKUneMQMvXd3rzUaAgvtf7Hc2LTBlf4fZzZF1EkwdSXhaMA\n" + + "1lkfHiqOBxtgeDLxCHESZ2fqgVqsWX+t3qHQfivcPW6txtDyrFPRdJOGhiMGzT/t\n" + + "e/9kkAtQRgpTb3skYdIOOUOV0WGQ60kJlFhAzIs=\n" + + "-----END CERTIFICATE-----\n" + + ""; + + final static String NODE_KEY = "-----BEGIN PRIVATE KEY-----\n" + + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCWvn+O+rywfgMC\n" + + "ud24mAclMDfuNA/IzCKLxl5usIE/PvUm7PPfXQ14LfQhNQXqOuaD9fiVM+HO1BzK\n" + + "wmN3j4g7eHInR1cxENoNGKFa0Fr9EXnUv8sfwyobPD8NTu9eaH7T+d6f9oow+Q4n\n" + + "xb9Xin5IRR/pcJ8v7zEjcXpZaZejcSU4iVZ0PR2Di4H9rfe9SEyR5wLrsVBePB3L\n" + + "jaL1uK4bZF3n/JGgDe3BNy1PgPU+O+FCzQipBBTyJWQCjd4iTRXVbMa01PglAR85\n" + + "O9w6NXApBLyWdGRY6dGd8vMC2P4KlhnxlcgPZdglKniGTX+eTzT7Rszq77zjYrou\n" + + "PLwSh9S7AgMBAAECggEABwiohxFoEIwws8XcdKqTWsbfNTw0qFfuHLuK2Htf7IWR\n" + + "htlzn66F3F+4jnwc5IsPCoVFriCXnsEC/usHHSMTZkL+gJqxlNaGdin6DXS/aiOQ\n" + + "nb69SaQfqNmsz4ApZyxVDqsQGkK0vAhDAtQVU45gyhp/nLLmmqP8lPzMirOEodmp\n" + + "U9bA8t/ttrzng7SVAER42f6IVpW0iTKTLyFii0WZbq+ObViyqib9hVFrI6NJuQS+\n" + + "IelcZB0KsSi6rqIjXg1XXyMiIUcSlhq+GfEa18AYgmsbPwMbExate7/8Ci7ZtCbh\n" + + "lx9bves2+eeqq5EMm3sMHyhdcg61yzd5UYXeZhwJkQKBgQDS9YqrAtztvLY2gMgv\n" + + "d+wOjb9awWxYbQTBjx33kf66W+pJ+2j8bI/XX2CpZ98w/oq8VhMqbr9j5b8MfsrF\n" + + "EoQvedA4joUo8sXd4j1mR2qKF4/KLmkgy6YYusNP2UrVSw7sh77bzce+YaVVoO/e\n" + + "0wIVTHuD/QZ6fG6MasOqcbl6hwKBgQC27cQruaHFEXR/16LrMVAX+HyEEv44KOCZ\n" + + "ij5OE4P7F0twb+okngG26+OJV3BtqXf0ULlXJ+YGwXCRf6zUZkld3NMy3bbKPgH6\n" + + "H/nf3BxqS2tudj7+DV52jKtisBghdvtlKs56oc9AAuwOs37DvhptBKUPdzDDqfys\n" + + "Qchv5JQdLQKBgERev+pcqy2Bk6xmYHrB6wdseS/4sByYeIoi0BuEfYH4eB4yFPx6\n" + + "UsQCbVl6CKPgWyZe3ydJbU37D8gE78KfFagtWoZ56j4zMF2RDUUwsB7BNCDamce/\n" + + "OL2bCeG/Erm98cBG3lxufOX+z47I8fTNfkdY2k8UmhzoZwurLm73HJ3RAoGBAKsp\n" + + "6yamuXF2FbYRhUXgjHsBbTD/vJO72/yO2CGiLRpi/5mjfkjo99269trp0C8sJSub\n" + + "5PBiSuADXFsoRgUv+HI1UAEGaCTwxFTQWrRWdtgW3d0sE2EQDVWL5kmfT9TwSeat\n" + + "mSoyAYR5t3tCBNkPJhbgA7pm4mASzHQ50VyxWs25AoGBAKPFx9X2oKhYQa+mW541\n" + + "bbqRuGFMoXIIcr/aeM3LayfLETi48o5NDr2NDP11j4yYuz26YLH0Dj8aKpWuehuH\n" + + "uB27n6j6qu0SVhQi6mMJBe1JrKbzhqMKQjYOoy8VsC2gdj5pCUP/kLQPW7zm9diX\n" + + "CiKTtKgPIeYdigor7V3AHcVT\n" + + "-----END PRIVATE KEY-----\n" + + ""; + + final static String ADMIN_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" + + "MIIEdzCCA1+gAwIBAgIGAWLrc1O4MA0GCSqGSIb3DQEBCwUAMIGPMRMwEQYKCZIm\n" + + "iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ\n" + + "RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290\n" + + "IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwHhcNMTgwNDIy\n" + + "MDM0MzQ3WhcNMjgwNDE5MDM0MzQ3WjBNMQswCQYDVQQGEwJkZTENMAsGA1UEBwwE\n" + + "dGVzdDEPMA0GA1UECgwGY2xpZW50MQ8wDQYDVQQLDAZjbGllbnQxDTALBgNVBAMM\n" + + "BGtpcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCwgBOoO88uMM8\n" + + "dREJsk58Yt4Jn0zwQ2wUThbvy3ICDiEWhiAhUbg6dTggpS5vWWJto9bvaaqgMVoh\n" + + "ElfYHdTDncX3UQNBEP8tqzHON6BFEFSGgJRGLd6f5dri6rK32nCotYS61CFXBFxf\n" + + "WumXjSukjyrcTsdkR3C5QDo2oN7F883MOQqRENPzAtZi9s3jNX48u+/e3yvJzXsB\n" + + "GS9Qmsye6C71enbIujM4CVwDT/7a5jHuaUp6OuNCFbdRPnu/wLYwOS2/yOtzAqk7\n" + + "/PFnPCe7YOa10ShnV/jx2sAHhp7ZQBJgFkkgnIERz9Ws74Au+EbptWnsWuB+LqRL\n" + + "x5G02IzpAgMBAAGjggEYMIIBFDCBvAYDVR0jBIG0MIGxgBSSNQzgDx4rRfZNOfN7\n" + + "X6LmEpdAc6GBlaSBkjCBjzETMBEGCgmSJomT8ixkARkWA2NvbTEXMBUGCgmSJomT\n" + + "8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1wbGUgQ29tIEluYy4xITAfBgNV\n" + + "BAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEhMB8GA1UEAwwYRXhhbXBsZSBD\n" + + "b20gSW5jLiBSb290IENBggEBMB0GA1UdDgQWBBRsdhuHn3MGDvZxOe22+1wliCJB\n" + + "mDAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF4DAWBgNVHSUBAf8EDDAKBggr\n" + + "BgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAkPrUTKKn+/6g0CjhTPBFeX8mKXhG\n" + + "zw5z9Oq+xnwefZwxV82E/tgFsPcwXcJIBg0f43BaVSygPiV7bXqWhxASwn73i24z\n" + + "lveIR4+z56bKIhP6c3twb8WWR9yDcLu2Iroin7dYEm3dfVUrhz/A90WHr6ddwmLL\n" + + "3gcFF2kBu3S3xqM5OmN/tqRXFmo+EvwrdJRiTh4Fsf0tX1ZT07rrGvBFYktK7Kma\n" + + "lqDl4UDCF1UWkiiFubc0Xw+DR6vNAa99E0oaphzvCmITU1wITNnYZTKzVzQ7vUCq\n" + + "kLmXOFLTcxTQpptxSo5xDD3aTpzWGCvjExCKpXQtsITUOYtZc02AGjjPOQ==\n" + + "-----END CERTIFICATE-----\n" + + ""; + + final static String ADMIN_KEY = "-----BEGIN PRIVATE KEY-----\n" + + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCwgBOoO88uMM8\n" + + "dREJsk58Yt4Jn0zwQ2wUThbvy3ICDiEWhiAhUbg6dTggpS5vWWJto9bvaaqgMVoh\n" + + "ElfYHdTDncX3UQNBEP8tqzHON6BFEFSGgJRGLd6f5dri6rK32nCotYS61CFXBFxf\n" + + "WumXjSukjyrcTsdkR3C5QDo2oN7F883MOQqRENPzAtZi9s3jNX48u+/e3yvJzXsB\n" + + "GS9Qmsye6C71enbIujM4CVwDT/7a5jHuaUp6OuNCFbdRPnu/wLYwOS2/yOtzAqk7\n" + + "/PFnPCe7YOa10ShnV/jx2sAHhp7ZQBJgFkkgnIERz9Ws74Au+EbptWnsWuB+LqRL\n" + + "x5G02IzpAgMBAAECggEAEzwnMkeBbqqDgyRqFbO/PgMNvD7i0b/28V0dCtCPEVY6\n" + + "klzrg3RCERP5V9AN8VVkppYjPkCzZ2A4b0JpMUu7ncOmr7HCnoSCj2IfEyePSVg+\n" + + "4OHbbcBOAoDTHiI2myM/M9++8izNS34qGV4t6pfjaDyeQQ/5cBVWNBWnKjS34S5H\n" + + "rJWpAcDgxYk5/ah2Xs2aULZlXDMxbSikjrv+n4JIYTKFQo8ydzL8HQDBRmXAFLjC\n" + + "gNOSHf+5u1JdpY3uPIxK1ugVf8zPZ4/OEB23j56uu7c8+sZ+kZwfRWAQmMhFVG/y\n" + + "OXxoT5mOruBsAw29m2Ijtxg252/YzSTxiDqFziB/eQKBgQDjeVAdi55GW/bvhuqn\n" + + "xME/An8E3hI/FyaaITrMQJUBjiCUaStTEqUgQ6A7ZfY/VX6qafOX7sli1svihrXC\n" + + "uelmKrdve/CFEEqzX9JWWRiPiQ0VZD+EQRsJvX85Tw2UGvVUh6dO3UGPS0BhplMD\n" + + "jeVpyXgZ7Gy5we+DWjfwhYrCmwKBgQDbLmQhRy+IdVljObZmv3QtJ0cyxxZETWzU\n" + + "MKmgBFvcRw+KvNwO+Iy0CHEbDu06Uj63kzI2bK3QdINaSrjgr8iftXIQpBmcgMF+\n" + + "a1l5HtHlCp6RWd55nWQOEvn36IGN3cAaQkXuh4UYM7QfEJaAbzJhyJ+wXA3jWqUd\n" + + "8bDTIAZ0ywKBgFuZ44gyTAc7S2JDa0Up90O/ZpT4NFLRqMrSbNIJg7d/m2EIRNkM\n" + + "HhCzCthAg/wXGo3XYq+hCdnSc4ICCzmiEfoBY6LyPvXmjJ5VDOeWs0xBvVIK74T7\n" + + "jr7KX2wdiHNGs9pZUidw89CXVhK8nptEzcheyA1wZowbK68yamph7HHXAoGBAK3x\n" + + "7D9Iyl1mnDEWPT7f1Gh9UpDm1TIRrDvd/tBihTCVKK13YsFy2d+LD5Bk0TpGyUVR\n" + + "STlOGMdloFUJFh4jA3pUOpkgUr8Uo/sbYN+x6Ov3+I3sH5aupRhSURVA7YhUIz/z\n" + + "tqIt5R+m8Nzygi6dkQNvf+Qruk3jw0S3ahizwsvvAoGAL7do6dTLp832wFVxkEf4\n" + + "gg1M6DswfkgML5V/7GQ3MkIX/Hrmiu+qSuHhDGrp9inZdCDDYg5+uy1+2+RBMRZ3\n" + + "vDUUacvc4Fep05zp7NcjgU5y+/HWpuKVvLIlZAO1MBY4Xinqqii6RdxukIhxw7eT\n" + + "C6TPL5KAcV1R/XAihDhI18Y=\n" + + "-----END PRIVATE KEY-----\n" + + ""; +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java new file mode 100644 index 0000000000..d810f0b32f --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java @@ -0,0 +1,77 @@ +/* +* Copyright 2021 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +* +*/ + +/* +* 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.test.framework.certificate; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** +* Provides TLS certificates required in test cases. +* WIP At the moment the certificates are hard coded. +* This will be replaced by classes +* that can generate certificates on the fly. +*/ +public class TestCertificates { + + public File getRootCertificate() { + return createTempFile("root", ".cert", Certificates.ROOT_CA_CERTIFICATE); + } + + public File getNodeCertificate(int node) { + return createTempFile("node-" + node, ".cert", Certificates.NODE_CERTIFICATE); + } + + public File getNodeKey(int node) { + return createTempFile("node-" + node, ".key", Certificates.NODE_KEY); + } + + public File getAdminCertificate() { + return createTempFile("admin", ".cert", Certificates.ADMIN_CERTIFICATE); + } + + public File getAdminKey() { + return createTempFile("admin", ".key", Certificates.ADMIN_KEY); + } + + public String[] getAdminDNs() { + return new String[] {"CN=kirk,OU=client,O=client,L=test,C=de"}; + } + + private File createTempFile(String name, String suffix, String contents) { + try { + Path path = Files.createTempFile(name, suffix); + Files.writeString(path, contents); + return path.toFile(); + } catch (IOException e) { + throw new RuntimeException(String.format("Error while creating a temp file: %s%s", name, suffix), e); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java new file mode 100644 index 0000000000..005321b24c --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java @@ -0,0 +1,144 @@ +/* +* Copyright 2015-2017 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +* +*/ + +/* +* 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.test.framework.cluster; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.opensearch.index.reindex.ReindexPlugin; +import org.opensearch.join.ParentJoinPlugin; +import org.opensearch.percolator.PercolatorPlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.search.aggregations.matrix.MatrixAggregationPlugin; +import org.opensearch.security.OpenSearchSecurityPlugin; +import org.opensearch.transport.Netty4Plugin; + +import static java.util.Collections.unmodifiableList; +import static org.opensearch.test.framework.cluster.NodeType.CLIENT; +import static org.opensearch.test.framework.cluster.NodeType.CLUSTER_MANAGER; +import static org.opensearch.test.framework.cluster.NodeType.DATA; + +public enum ClusterManager { + + //3 nodes (1m, 2d) + DEFAULT(new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true)), + + //1 node (1md) + SINGLENODE(new NodeSettings(true, true)), + + //4 node (1m, 2d, 1c) + CLIENTNODE(new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true), new NodeSettings(false, false)), + + THREE_CLUSTER_MANAGERS(new NodeSettings(true, false), new NodeSettings(true, false), new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true)); + + private List nodeSettings = new LinkedList<>(); + + private ClusterManager(NodeSettings... settings) { + nodeSettings.addAll(Arrays.asList(settings)); + } + + public List getNodeSettings() { + return unmodifiableList(nodeSettings); + } + + public List getClusterManagerNodeSettings() { + return unmodifiableList(nodeSettings.stream().filter(a -> a.clusterManagerNode).collect(Collectors.toList())); + } + + public List getNonClusterManagerNodeSettings() { + return unmodifiableList(nodeSettings.stream().filter(a -> !a.clusterManagerNode).collect(Collectors.toList())); + } + + public int getNodes() { + return nodeSettings.size(); + } + + public int getClusterManagerNodes() { + return (int) nodeSettings.stream().filter(a -> a.clusterManagerNode).count(); + } + + public int getDataNodes() { + return (int) nodeSettings.stream().filter(a -> a.dataNode).count(); + } + + public int getClientNodes() { + return (int) nodeSettings.stream().filter(a -> !a.clusterManagerNode && !a.dataNode).count(); + } + + public static class NodeSettings { + + private final static List> DEFAULT_PLUGINS = List.of(Netty4Plugin.class, OpenSearchSecurityPlugin.class, + MatrixAggregationPlugin.class, ParentJoinPlugin.class, PercolatorPlugin.class, ReindexPlugin.class); + public final boolean clusterManagerNode; + public final boolean dataNode; + public final List> plugins; + + public NodeSettings(boolean clusterManagerNode, boolean dataNode) { + this(clusterManagerNode, dataNode, Collections.emptyList()); + } + + public NodeSettings(boolean clusterManagerNode, boolean dataNode, List> additionalPlugins) { + super(); + this.clusterManagerNode = clusterManagerNode; + this.dataNode = dataNode; + this.plugins = mergePlugins(additionalPlugins, DEFAULT_PLUGINS); + } + NodeType recognizeNodeType() { + if (clusterManagerNode) { + return CLUSTER_MANAGER; + } else if (dataNode) { + return DATA; + } else { + return CLIENT; + } + } + + private List> mergePlugins(Collection>...plugins) { + List> mergedPlugins = Arrays.stream(plugins) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + return unmodifiableList(mergedPlugins); + } + + @SuppressWarnings("unchecked") + public Class[] getPlugins() { + return plugins.toArray(new Class[0]); + } + + public Class[] pluginsWithAddition(List> additionalPlugins) { + return mergePlugins(plugins, additionalPlugins).toArray(Class[]::new); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/ContextHeaderDecoratorClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/ContextHeaderDecoratorClient.java new file mode 100644 index 0000000000..890de49ed7 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/ContextHeaderDecoratorClient.java @@ -0,0 +1,49 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.cluster; + +import java.util.Collections; +import java.util.Map; + +import org.opensearch.action.ActionListener; +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionResponse; +import org.opensearch.action.ActionType; +import org.opensearch.action.support.ContextPreservingActionListener; +import org.opensearch.client.Client; +import org.opensearch.client.FilterClient; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.common.util.concurrent.ThreadContext.StoredContext; + +/** +* The class adds provided headers into context before sending request via wrapped {@link Client} +*/ +public class ContextHeaderDecoratorClient extends FilterClient { + + private Map headers; + + public ContextHeaderDecoratorClient(Client in, Map headers) { + super(in); + this.headers = headers != null ? headers : Collections.emptyMap(); + } + + @Override + protected void doExecute(ActionType action, Request request, + ActionListener listener) { + + ThreadContext threadContext = threadPool().getThreadContext(); + ContextPreservingActionListener wrappedListener = new ContextPreservingActionListener<>(threadContext.newRestorableContext(true), listener); + + try (StoredContext ctx = threadContext.stashContext()) { + threadContext.putHeader(this.headers); + super.doExecute(action, request, wrappedListener); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java new file mode 100644 index 0000000000..1170a25906 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -0,0 +1,364 @@ +/* +* Copyright 2015-2021 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +* +*/ + +/* +* 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.test.framework.cluster; + +import java.io.File; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.rules.ExternalResource; + +import org.opensearch.client.Client; +import org.opensearch.common.settings.Settings; +import org.opensearch.node.PluginAwareNode; +import org.opensearch.plugins.Plugin; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.test.framework.TestIndex; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.certificate.TestCertificates; + +/** +* This class allows to you start and manage a local cluster in an integration test. In contrast to the +* OpenSearchIntegTestCase class, this class can be used in a composite way and allows the specification +* of the security plugin configuration. +* +* This class can be both used as a JUnit @ClassRule (preferred) or in a try-with-resources block. The latter way should +* be only sparingly used, as starting a cluster is not a particularly fast operation. +*/ +public class LocalCluster extends ExternalResource implements AutoCloseable, OpenSearchClientProvider { + + private static final Logger log = LogManager.getLogger(LocalCluster.class); + + static { + System.setProperty("security.default_init.dir", new File("./securityconfig").getAbsolutePath()); + } + + protected static final AtomicLong num = new AtomicLong(); + + private final List> plugins; + private final ClusterManager clusterManager; + private final TestSecurityConfig testSecurityConfig; + private Settings nodeOverride; + private final String clusterName; + private final MinimumSecuritySettingsSupplierFactory minimumOpenSearchSettingsSupplierFactory; + private final TestCertificates testCertificates; + private final List clusterDependencies; + private final Map remotes; + private volatile LocalOpenSearchCluster localOpenSearchCluster; + private final List testIndices; + + private LocalCluster(String clusterName, TestSecurityConfig testSgConfig, Settings nodeOverride, + ClusterManager clusterManager, List> plugins, TestCertificates testCertificates, + List clusterDependencies, Map remotes, List testIndices) { + this.plugins = plugins; + this.testCertificates = testCertificates; + this.clusterManager = clusterManager; + this.testSecurityConfig = testSgConfig; + this.nodeOverride = nodeOverride; + this.clusterName = clusterName; + this.minimumOpenSearchSettingsSupplierFactory = new MinimumSecuritySettingsSupplierFactory(testCertificates); + this.remotes = remotes; + this.clusterDependencies = clusterDependencies; + this.testIndices = testIndices; + } + + @Override + public void before() throws Throwable { + if (localOpenSearchCluster == null) { + for (LocalCluster dependency : clusterDependencies) { + if (!dependency.isStarted()) { + dependency.before(); + } + } + + for (Map.Entry entry : remotes.entrySet()) { + @SuppressWarnings("resource") + InetSocketAddress transportAddress = entry.getValue().localOpenSearchCluster.clusterManagerNode().getTransportAddress(); + nodeOverride = Settings.builder().put(nodeOverride) + .putList("cluster.remote." + entry.getKey() + ".seeds", transportAddress.getHostString() + ":" + transportAddress.getPort()) + .build(); + } + + start(); + } + } + + @Override + protected void after() { + close(); + } + + @Override + public void close() { + if (localOpenSearchCluster != null && localOpenSearchCluster.isStarted()) { + try { + localOpenSearchCluster.destroy(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + localOpenSearchCluster = null; + } + } + } + + @Override + public String getClusterName() { + return clusterName; + } + + @Override + public InetSocketAddress getHttpAddress() { + return localOpenSearchCluster.clientNode().getHttpAddress(); + } + + @Override + public InetSocketAddress getTransportAddress() { + return localOpenSearchCluster.clientNode().getTransportAddress(); + } + + /** + * Returns a Client object that performs cluster-internal requests. As these requests are regard as cluster-internal, + * no authentication is performed and no user-information is attached to these requests. Thus, this client should + * be only used for preparing test environments, but not as a test subject. + */ + public Client getInternalNodeClient() { + return localOpenSearchCluster.clientNode().getInternalNodeClient(); + } + + /** + * Returns a random node of this cluster. + */ + public PluginAwareNode node() { + return this.localOpenSearchCluster.clusterManagerNode().esNode(); + } + + /** + * Returns all nodes of this cluster. + */ + public List nodes() { + return this.localOpenSearchCluster.getNodes(); + } + + public LocalOpenSearchCluster.Node getNodeByName(String name) { + return this.localOpenSearchCluster.getNodeByName(name); + } + + public boolean isStarted() { + return localOpenSearchCluster != null; + } + + public Random getRandom() { + return localOpenSearchCluster.getRandom(); + } + + private void start() { + try { + localOpenSearchCluster = new LocalOpenSearchCluster(clusterName, clusterManager, + minimumOpenSearchSettingsSupplierFactory.minimumOpenSearchSettings(nodeOverride), plugins, testCertificates); + + localOpenSearchCluster.start(); + + + if (testSecurityConfig != null) { + initSecurityIndex(testSecurityConfig); + } + + try (Client client = getInternalNodeClient()) { + for (TestIndex index : this.testIndices) { + index.create(client); + } + } + + } catch (Exception e) { + log.error("Local ES cluster start failed", e); + throw new RuntimeException(e); + } + } + + private void initSecurityIndex(TestSecurityConfig testSecurityConfig) { + log.info("Initializing OpenSearch Security index"); + try(Client client = new ContextHeaderDecoratorClient(this.getInternalNodeClient(), Map.of(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER , "true"))) { + testSecurityConfig.initIndex(client); + } + } + + public static class Builder { + + private final Settings.Builder nodeOverrideSettingsBuilder = Settings.builder(); + private final List> plugins = new ArrayList<>(); + private Map remoteClusters = new HashMap<>(); + private List clusterDependencies = new ArrayList<>(); + private List testIndices = new ArrayList<>(); + private ClusterManager clusterManager = ClusterManager.DEFAULT; + private TestSecurityConfig testSecurityConfig = new TestSecurityConfig(); + private String clusterName = "local_cluster"; + private TestCertificates testCertificates; + + public Builder() { + this.testCertificates = new TestCertificates(); + } + + public Builder dependsOn(Object object) { + // We just want to make sure that the object is already done + if (object == null) { + throw new IllegalStateException("Dependency not fulfilled"); + } + return this; + } + + public Builder clusterManager(ClusterManager clusterManager) { + this.clusterManager = clusterManager; + return this; + } + + /** + * Starts a cluster with only one node and thus saves some resources during startup. This shall be only used + * for tests where the node interactions are not relevant to the test. An example for this would be + * authentication tests, as authentication is always done on the directly connected node. + */ + public Builder singleNode() { + this.clusterManager = ClusterManager.SINGLENODE; + return this; + } + + /** + * Specifies the configuration of the security plugin that shall be used by this cluster. + */ + public Builder config(TestSecurityConfig testSecurityConfig) { + this.testSecurityConfig = testSecurityConfig; + return this; + } + + public Builder nodeSettings(Map settings) { + settings.forEach((key, value) -> { + if (value instanceof List) { + List values = ((List) value).stream().map(String::valueOf).collect(Collectors.toList()); + nodeOverrideSettingsBuilder.putList(key, values); + } else { + nodeOverrideSettingsBuilder.put(key, String.valueOf(value)); + } + }); + + return this; + } + + /** + * Adds additional plugins to the cluster + */ + public Builder plugin(Class plugin) { + this.plugins.add(plugin); + + return this; + } + + /** + * Specifies a remote cluster and its name. The remote cluster can be then used in Cross Cluster Search + * operations with the specified name. + */ + public Builder remote(String name, LocalCluster anotherCluster) { + remoteClusters.put(name, anotherCluster); + + clusterDependencies.add(anotherCluster); + + return this; + } + + /** + * Specifies test indices that shall be created upon startup of the cluster. + */ + public Builder indices(TestIndex... indices) { + this.testIndices.addAll(Arrays.asList(indices)); + return this; + } + + public Builder users(TestSecurityConfig.User... users) { + for (TestSecurityConfig.User user : users) { + testSecurityConfig.user(user); + } + return this; + } + + public Builder roles(Role... roles) { + testSecurityConfig.roles(roles); + return this; + } + + public Builder authc(TestSecurityConfig.AuthcDomain authc) { + testSecurityConfig.authc(authc); + return this; + } + + public Builder clusterName(String clusterName) { + this.clusterName = clusterName; + return this; + } + + public Builder configIndexName(String configIndexName) { + testSecurityConfig.configIndexName(configIndexName); + return this; + } + + public Builder anonymousAuth(boolean anonAuthEnabled) { + testSecurityConfig.anonymousAuth(anonAuthEnabled); + return this; + } + + public LocalCluster build() { + try { + + clusterName += "_" + num.incrementAndGet(); + Settings settings = nodeOverrideSettingsBuilder + .put(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, false) + .build(); + return new LocalCluster(clusterName, testSecurityConfig, settings, clusterManager, plugins, + testCertificates, clusterDependencies, remoteClusters, testIndices); + } catch (Exception e) { + log.error("Failed to build LocalCluster", e); + throw new RuntimeException(e); + } + } + + } + + @Override + public TestCertificates getTestCertificates() { + return testCertificates; + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java new file mode 100644 index 0000000000..2750080225 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java @@ -0,0 +1,510 @@ +/* +* Copyright 2015-2021 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +* +*/ + +/* +* 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.test.framework.cluster; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.SortedSet; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; +import com.google.common.net.InetAddresses; +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.action.admin.cluster.health.ClusterHealthResponse; +import org.opensearch.client.AdminClient; +import org.opensearch.client.Client; +import org.opensearch.cluster.health.ClusterHealthStatus; +import org.opensearch.common.Strings; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.http.BindHttpException; +import org.opensearch.node.PluginAwareNode; +import org.opensearch.plugins.Plugin; +import org.opensearch.test.framework.certificate.TestCertificates; +import org.opensearch.test.framework.cluster.ClusterManager.NodeSettings; +import org.opensearch.transport.BindTransportException; + +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertEquals; +import static org.opensearch.test.framework.cluster.NodeType.CLIENT; +import static org.opensearch.test.framework.cluster.NodeType.CLUSTER_MANAGER; +import static org.opensearch.test.framework.cluster.NodeType.DATA; +import static org.opensearch.test.framework.cluster.PortAllocator.TCP; + +/** +* Encapsulates all the logic to start a local OpenSearch cluster - without any configuration of the security plugin. +* +* The security plugin configuration is the job of LocalCluster, which uses this class under the hood. Thus, test code +* for the security plugin should always use LocalCluster. +*/ +public class LocalOpenSearchCluster { + + static { + System.setProperty("opensearch.enforce.bootstrap.checks", "true"); + } + + private static final Logger log = LogManager.getLogger(LocalOpenSearchCluster.class); + + private final String clusterName; + private final ClusterManager clusterManager; + private final NodeSettingsSupplier nodeSettingsSupplier; + private final List> additionalPlugins; + private final List nodes = new ArrayList<>(); + private final TestCertificates testCertificates; + + private File clusterHomeDir; + private List seedHosts; + private List initialClusterManagerHosts; + private int retry = 0; + private boolean started; + private Random random = new Random(); + + public LocalOpenSearchCluster(String clusterName, ClusterManager clusterManager, NodeSettingsSupplier nodeSettingsSupplier, + List> additionalPlugins, TestCertificates testCertificates) { + this.clusterName = clusterName; + this.clusterManager = clusterManager; + this.nodeSettingsSupplier = nodeSettingsSupplier; + this.additionalPlugins = additionalPlugins; + this.testCertificates = testCertificates; + try { + this.clusterHomeDir = Files.createTempDirectory("local_cluster_" + clusterName).toFile(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private List getNodesByType(NodeType nodeType) { + return nodes.stream() + .filter(currentNode -> currentNode.hasAssignedType(nodeType)) + .collect(Collectors.toList()); + } + + private long countNodesByType(NodeType nodeType) { + return getNodesByType(nodeType).stream().count(); + } + + public void start() throws Exception { + log.info("Starting {}", clusterName); + + int clusterManagerNodeCount = clusterManager.getClusterManagerNodes(); + int nonClusterManagerNodeCount = clusterManager.getDataNodes() + clusterManager.getClientNodes(); + + SortedSet clusterManagerNodeTransportPorts = TCP.allocate(clusterName, Math.max(clusterManagerNodeCount, 4), 5000 + 42 * 1000 + 300); + SortedSet clusterManagerNodeHttpPorts = TCP.allocate(clusterName, clusterManagerNodeCount, 5000 + 42 * 1000 + 200); + + this.seedHosts = toHostList(clusterManagerNodeTransportPorts); + Set clusterManagerPorts = clusterManagerNodeTransportPorts + .stream().limit(clusterManagerNodeCount).collect(Collectors.toSet()); + this.initialClusterManagerHosts = toHostList(clusterManagerPorts); + + started = true; + + CompletableFuture clusterManagerNodeFuture = startNodes( + clusterManager.getClusterManagerNodeSettings(), clusterManagerNodeTransportPorts, + clusterManagerNodeHttpPorts); + + SortedSet nonClusterManagerNodeTransportPorts = TCP.allocate(clusterName, nonClusterManagerNodeCount, 5000 + 42 * 1000 + 310); + SortedSet nonClusterManagerNodeHttpPorts = TCP.allocate(clusterName, nonClusterManagerNodeCount, 5000 + 42 * 1000 + 210); + + CompletableFuture nonClusterManagerNodeFuture = startNodes( + clusterManager.getNonClusterManagerNodeSettings(), nonClusterManagerNodeTransportPorts, + nonClusterManagerNodeHttpPorts); + + CompletableFuture.allOf(clusterManagerNodeFuture, nonClusterManagerNodeFuture).join(); + + if (isNodeFailedWithPortCollision()) { + log.info("Detected port collision for cluster manager node. Retrying."); + + retry(); + return; + } + + log.info("Startup finished. Waiting for GREEN"); + + waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(10), nodes.size()); + + log.info("Started: {}", this); + + } + + public String getClusterName() { + return clusterName; + } + + public boolean isStarted() { + return started; + } + + public void stop() { + List> stopFutures = new ArrayList<>(); + for (Node node : nodes) { + stopFutures.add(node.stop(2, TimeUnit.SECONDS)); + } + CompletableFuture.allOf(stopFutures.toArray(size -> new CompletableFuture[size])).join(); + } + + public void destroy() { + stop(); + nodes.clear(); + + try { + FileUtils.deleteDirectory(clusterHomeDir); + } catch (IOException e) { + log.warn("Error while deleting " + clusterHomeDir, e); + } + } + + public Node clientNode() { + return findRunningNode(getNodesByType(CLIENT), getNodesByType(DATA), getNodesByType(CLUSTER_MANAGER)); + } + + public Node clusterManagerNode() { + return findRunningNode(getNodesByType(CLUSTER_MANAGER)); + } + + public List getNodes() { + return Collections.unmodifiableList(nodes); + } + + public Node getNodeByName(String name) { + return nodes.stream().filter(node -> node.getNodeName().equals(name)).findAny().orElseThrow(() -> new RuntimeException( + "No such node with name: " + name + "; available: " + nodes.stream().map(Node::getNodeName).collect(Collectors.toList()))); + } + + private boolean isNodeFailedWithPortCollision() { + return nodes.stream().anyMatch(Node::isPortCollision); + } + + private void retry() throws Exception { + retry++; + + if (retry > 10) { + throw new RuntimeException("Detected port collisions for cluster manager node. Giving up."); + } + + stop(); + + this.nodes.clear(); + this.seedHosts = null; + this.initialClusterManagerHosts = null; + this.clusterHomeDir = Files.createTempDirectory("local_cluster_" + clusterName + "_retry_" + retry).toFile(); + + start(); + } + + @SafeVarargs + private final Node findRunningNode(List nodes, List... moreNodes) { + for (Node node : nodes) { + if (node.isRunning()) { + return node; + } + } + + if (moreNodes != null && moreNodes.length > 0) { + for (List nodesList : moreNodes) { + for (Node node : nodesList) { + if (node.isRunning()) { + return node; + } + } + } + } + + return null; + } + + private CompletableFuture startNodes(List nodeSettingList, SortedSet transportPorts, SortedSet httpPorts) { + Iterator transportPortIterator = transportPorts.iterator(); + Iterator httpPortIterator = httpPorts.iterator(); + List> futures = new ArrayList<>(); + + for (NodeSettings nodeSettings : nodeSettingList) { + Node node = new Node(nodeSettings, transportPortIterator.next(), httpPortIterator.next()); + futures.add(node.start()); + } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + public void waitForCluster(ClusterHealthStatus status, TimeValue timeout, int expectedNodeCount) throws IOException { + Client client = clientNode().getInternalNodeClient(); + + + log.debug("waiting for cluster state {} and {} nodes", status.name(), expectedNodeCount); + AdminClient adminClient = client.admin(); + + final ClusterHealthResponse healthResponse = adminClient.cluster().prepareHealth().setWaitForStatus(status).setTimeout(timeout) + .setClusterManagerNodeTimeout(timeout).setWaitForNodes("" + expectedNodeCount).execute().actionGet(); + + if (log.isDebugEnabled()) { + log.debug("Current ClusterState:\n{}", Strings.toString(healthResponse)); + } + + if (healthResponse.isTimedOut()) { + throw new IOException( + "cluster state is " + healthResponse.getStatus().name() + " with " + healthResponse.getNumberOfNodes() + " nodes"); + } else { + log.debug("... cluster state ok {} with {} nodes", healthResponse.getStatus().name(), healthResponse.getNumberOfNodes()); + } + + assertEquals(expectedNodeCount, healthResponse.getNumberOfNodes()); + + } + + @Override + public String toString() { + String clusterManagerNodes = nodeByTypeToString(CLUSTER_MANAGER); + String dataNodes = nodeByTypeToString(DATA); + String clientNodes = nodeByTypeToString(CLIENT); + return "\nES Cluster " + clusterName + + "\ncluster manager nodes: " + clusterManagerNodes + + "\n data nodes: " + dataNodes + + "\nclient nodes: " + clientNodes + "\n"; + } + + private String nodeByTypeToString(NodeType type) { + return getNodesByType(type).stream().map(Objects::toString).collect(Collectors.joining(", ")); + } + + private static List toHostList(Collection ports) { + return ports.stream().map(port -> "127.0.0.1:" + port).collect(Collectors.toList()); + } + + private String createNextNodeName(NodeSettings nodeSettings) { + NodeType type = nodeSettings.recognizeNodeType(); + long nodeTypeCount = countNodesByType(type); + String nodeType = type.name().toLowerCase(Locale.ROOT); + return nodeType + "_" + nodeTypeCount; + } + + public class Node implements OpenSearchClientProvider { + private final NodeType nodeType; + private final String nodeName; + private final NodeSettings nodeSettings; + private final File nodeHomeDir; + private final File dataDir; + private final File logsDir; + private final int transportPort; + private final int httpPort; + private final InetSocketAddress httpAddress; + private final InetSocketAddress transportAddress; + private PluginAwareNode node; + private boolean running = false; + private boolean portCollision = false; + + Node(NodeSettings nodeSettings, int transportPort, int httpPort) { + this.nodeName = createNextNodeName(requireNonNull(nodeSettings, "Node settings are required.")); + this.nodeSettings = nodeSettings; + this.nodeHomeDir = new File(clusterHomeDir, nodeName); + this.dataDir = new File(this.nodeHomeDir, "data"); + this.logsDir = new File(this.nodeHomeDir, "logs"); + this.transportPort = transportPort; + this.httpPort = httpPort; + InetAddress hostAddress = InetAddresses.forString("127.0.0.1"); + this.httpAddress = new InetSocketAddress(hostAddress, httpPort); + this.transportAddress = new InetSocketAddress(hostAddress, transportPort); + + this.nodeType = nodeSettings.recognizeNodeType(); + nodes.add(this); + } + + boolean hasAssignedType(NodeType type) { + return requireNonNull(type, "Node type is required.").equals(this.nodeType); + } + + CompletableFuture start() { + CompletableFuture completableFuture = new CompletableFuture<>(); + Class[] mergedPlugins = nodeSettings.pluginsWithAddition(additionalPlugins); + this.node = new PluginAwareNode(nodeSettings.clusterManagerNode, getOpenSearchSettings(), mergedPlugins); + + new Thread(new Runnable() { + + @Override + public void run() { + try { + node.start(); + running = true; + completableFuture.complete(StartStage.INITIALIZED); + } catch (BindTransportException | BindHttpException e) { + log.warn("Port collision detected for {}", this, e); + portCollision = true; + try { + node.close(); + } catch (IOException e1) { + log.error(e1); + } + + node = null; + TCP.reserve(transportPort, httpPort); + + completableFuture.complete(StartStage.RETRY); + + } catch (Throwable e) { + log.error("Unable to start {}", this, e); + node = null; + completableFuture.completeExceptionally(e); + } + } + }).start(); + + return completableFuture; + } + + public Client getInternalNodeClient() { + return node.client(); + } + + public PluginAwareNode esNode() { + return node; + } + + public boolean isRunning() { + return running; + } + + public X getInjectable(Class clazz) { + return node.injector().getInstance(clazz); + } + + public CompletableFuture stop(long timeout, TimeUnit timeUnit) { + return CompletableFuture.supplyAsync(() -> { + try { + log.info("Stopping {}", this); + + running = false; + + if (node != null) { + node.close(); + boolean stopped = node.awaitClose(timeout, timeUnit); + node = null; + return stopped; + } else { + return false; + } + } catch (Throwable e) { + String message = "Error while stopping " + this; + log.warn(message, e); + throw new RuntimeException(message, e); + } + }); + } + + @Override + public String toString() { + String state = running ? "RUNNING" : node != null ? "INITIALIZING" : "STOPPED"; + + return nodeName + " " + state + " [" + transportPort + ", " + httpPort + "]"; + } + + public boolean isPortCollision() { + return portCollision; + } + + public String getNodeName() { + return nodeName; + } + + @Override + public InetSocketAddress getHttpAddress() { + return httpAddress; + } + + @Override + public InetSocketAddress getTransportAddress() { + return transportAddress; + } + + private Settings getOpenSearchSettings() { + Settings settings = getMinimalOpenSearchSettings(); + + if (nodeSettingsSupplier != null) { + // TODO node number + return Settings.builder().put(settings).put(nodeSettingsSupplier.get(0)).build(); + } + + return settings; + } + + private Settings getMinimalOpenSearchSettings() { + return Settings.builder().put("node.name", nodeName).putList("node.roles", createNodeRolesSettings()) + .put("cluster.name", clusterName).put("path.home", nodeHomeDir.toPath()).put("path.data", dataDir.toPath()) + .put("path.logs", logsDir.toPath()).putList("cluster.initial_cluster_manager_nodes", initialClusterManagerHosts) + .put("discovery.initial_state_timeout", "8s").putList("discovery.seed_hosts", seedHosts).put("transport.tcp.port", transportPort) + .put("http.port", httpPort).put("cluster.routing.allocation.disk.threshold_enabled", false) + .put("discovery.probe.connect_timeout", "10s").put("discovery.probe.handshake_timeout", "10s").put("http.cors.enabled", true) + .put("plugins.security.compliance.salt", "1234567890123456") + .put("plugins.security.audit.type", "noop") + .put("gateway.auto_import_dangling_indices", "true") + .build(); + } + + private List createNodeRolesSettings() { + final ImmutableList.Builder nodeRolesBuilder = ImmutableList.builder(); + if (nodeSettings.dataNode) { + nodeRolesBuilder.add("data"); + } + if (nodeSettings.clusterManagerNode) { + nodeRolesBuilder.add("cluster_manager"); + } + return nodeRolesBuilder.build(); + } + + @Override + public String getClusterName() { + return clusterName; + } + + @Override + public TestCertificates getTestCertificates() { + return testCertificates; + } + } + + public Random getRandom() { + return random; + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java new file mode 100644 index 0000000000..37cf69b266 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java @@ -0,0 +1,72 @@ +/* +* Copyright 2021 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +* +*/ + +/* +* 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.test.framework.cluster; + +import org.opensearch.common.settings.Settings; +import org.opensearch.test.framework.certificate.TestCertificates; + +public class MinimumSecuritySettingsSupplierFactory { + + private TestCertificates testCertificates; + + public MinimumSecuritySettingsSupplierFactory(TestCertificates testCertificates) { + if (testCertificates == null) { + throw new IllegalArgumentException("certificates must not be null"); + } + this.testCertificates = testCertificates; + + } + + public NodeSettingsSupplier minimumOpenSearchSettings(Settings other) { + return i -> minimumOpenSearchSettingsBuilder(i, false).put(other).build(); + } + + public NodeSettingsSupplier minimumOpenSearchSettingsSslOnly(Settings other) { + return i -> minimumOpenSearchSettingsBuilder(i, true).put(other).build(); + } + + private Settings.Builder minimumOpenSearchSettingsBuilder(int node, boolean sslOnly) { + + Settings.Builder builder = Settings.builder(); + + builder.put("plugins.security.ssl.transport.pemtrustedcas_filepath", testCertificates.getRootCertificate().getAbsolutePath()); + builder.put("plugins.security.ssl.transport.pemcert_filepath", testCertificates.getNodeCertificate(node).getAbsolutePath()); + builder.put("plugins.security.ssl.transport.pemkey_filepath", testCertificates.getNodeKey(node).getAbsolutePath()); + + builder.put("plugins.security.ssl.http.enabled", true); + builder.put("plugins.security.ssl.http.pemtrustedcas_filepath", testCertificates.getRootCertificate().getAbsolutePath()); + builder.put("plugins.security.ssl.http.pemcert_filepath", testCertificates.getNodeCertificate(node).getAbsolutePath()); + builder.put("plugins.security.ssl.http.pemkey_filepath", testCertificates.getNodeKey(node).getAbsolutePath()); + + builder.putList("plugins.security.authcz.admin_dn", testCertificates.getAdminDNs()); + + return builder; + + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeSettingsSupplier.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeSettingsSupplier.java new file mode 100644 index 0000000000..75c728287b --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeSettingsSupplier.java @@ -0,0 +1,34 @@ +/* +* Copyright 2015-2018 _floragunn_ GmbH +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +*/ + +/* +* 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.test.framework.cluster; + +import org.opensearch.common.settings.Settings; + +@FunctionalInterface +public interface NodeSettingsSupplier { + Settings get(int i); +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeType.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeType.java new file mode 100644 index 0000000000..915f99daa8 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeType.java @@ -0,0 +1,15 @@ +/* +* 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.test.framework.cluster; + +enum NodeType { + CLIENT, DATA, CLUSTER_MANAGER +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java new file mode 100644 index 0000000000..d70afbaa1a --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -0,0 +1,153 @@ +/* +* Copyright 2020 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +* +*/ + +/* +* 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.test.framework.cluster; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +import org.apache.http.Header; +import org.apache.http.message.BasicHeader; + +import org.opensearch.security.support.PemKeyReader; +import org.opensearch.test.framework.certificate.TestCertificates; + +/** +* OpenSearchClientProvider provides methods to get a REST client for an underlying cluster or node. +* +* This interface is implemented by both LocalCluster and LocalOpenSearchCluster.Node. Thus, it is possible to get a +* REST client for a whole cluster (without choosing the node it is operating on) or to get a REST client for a specific +* node. +*/ +public interface OpenSearchClientProvider { + + String getClusterName(); + + TestCertificates getTestCertificates(); + + InetSocketAddress getHttpAddress(); + + InetSocketAddress getTransportAddress(); + + default URI getHttpAddressAsURI() { + InetSocketAddress address = getHttpAddress(); + return URI.create("https://" + address.getHostString() + ":" + address.getPort()); + } + + /** + * Returns a REST client that sends requests with basic authentication for the specified User object. Optionally, + * additional HTTP headers can be specified which will be sent with each request. + * + * This method should be usually preferred. The other getRestClient() methods shall be only used for specific + * situations. + */ + default TestRestClient getRestClient(UserCredentialsHolder user, Header... headers) { + return getRestClient(user.getName(), user.getPassword(), headers); + } + + /** + * Returns a REST client that sends requests with basic authentication for the specified user name and password. Optionally, + * additional HTTP headers can be specified which will be sent with each request. + * + * Normally, you should use the method with the User object argument instead. Use this only if you need more + * control over username and password - for example, when you want to send a wrong password. + */ + default TestRestClient getRestClient(String user, String password, Header... headers) { + BasicHeader basicAuthHeader = getBasicAuthHeader(user, password); + if (headers != null && headers.length > 0) { + List
concatenatedHeaders = Stream.concat(Stream.of(basicAuthHeader), Stream.of(headers)).collect(Collectors.toList()); + return getRestClient(concatenatedHeaders); + } + return getRestClient(basicAuthHeader); + } + + /** + * Returns a REST client. You can specify additional HTTP headers that will be sent with each request. Use this + * method to test non-basic authentication, such as JWT bearer authentication. + */ + default TestRestClient getRestClient(Header... headers) { + return getRestClient(Arrays.asList(headers)); + } + + default TestRestClient getRestClient(List
headers) { + return createGenericClientRestClient(headers); + } + + default TestRestClient createGenericClientRestClient(List
headers) { + return new TestRestClient(getHttpAddress(), headers, getSSLContext()); + } + + default BasicHeader getBasicAuthHeader(String user, String password) { + return new BasicHeader("Authorization", + "Basic " + Base64.getEncoder().encodeToString((user + ":" + Objects.requireNonNull(password)).getBytes(StandardCharsets.UTF_8))); + } + + private SSLContext getSSLContext() { + X509Certificate[] trustCertificates; + + try { + trustCertificates = PemKeyReader.loadCertificatesFromFile(getTestCertificates().getRootCertificate() ); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + + ks.load(null); + + for (int i = 0; i < trustCertificates.length; i++) { + ks.setCertificateEntry("caCert-" + i, trustCertificates[i]); + } + + tmf.init(ks); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), null); + return sslContext; + + } catch (Exception e) { + throw new RuntimeException("Error loading root CA ", e); + } + } + + public interface UserCredentialsHolder { + String getName(); + String getPassword(); + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/PortAllocator.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/PortAllocator.java new file mode 100644 index 0000000000..ed72fae91f --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/PortAllocator.java @@ -0,0 +1,164 @@ +/* +* Copyright 2021 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +* +*/ + +/* +* 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.test.framework.cluster; + +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.opensearch.test.framework.cluster.SocketUtils.SocketType; + +/** +* Helper class that allows you to allocate ports. This helps with avoiding port conflicts when running tests. +* +* NOTE: This class shall be only considered as a heuristic; ports allocated by this class are just likely to be unused; +* however, there is no guarantee that these will be unused. Thus, you still need to be prepared for port-conflicts +* and retry the procedure in such a case. If you notice a port conflict, you can use the method reserve() to mark the +* port as used. +*/ +public class PortAllocator { + + public static final PortAllocator TCP = new PortAllocator(SocketType.TCP, Duration.ofSeconds(100)); + public static final PortAllocator UDP = new PortAllocator(SocketType.UDP, Duration.ofSeconds(100)); + + private final SocketType socketType; + private final Duration timeoutDuration; + private final Map allocatedPorts = new HashMap<>(); + + PortAllocator(SocketType socketType, Duration timeoutDuration) { + this.socketType = socketType; + this.timeoutDuration = timeoutDuration; + } + + public SortedSet allocate(String clientName, int numRequested, int minPort) { + + int startPort = minPort; + + while (!isAvailable(startPort)) { + startPort += 10; + } + + SortedSet foundPorts = new TreeSet<>(); + + for (int currentPort = startPort; foundPorts.size() < numRequested && currentPort < SocketUtils.PORT_RANGE_MAX + && (currentPort - startPort) < 10000; currentPort++) { + if (allocate(clientName, currentPort)) { + foundPorts.add(currentPort); + } + } + + if (foundPorts.size() < numRequested) { + throw new IllegalStateException("Could not find " + numRequested + " free ports starting at " + minPort + " for " + clientName); + } + + return foundPorts; + } + + public int allocateSingle(String clientName, int minPort) { + + int startPort = minPort; + + for (int currentPort = startPort; currentPort < SocketUtils.PORT_RANGE_MAX && (currentPort - startPort) < 10000; currentPort++) { + if (allocate(clientName, currentPort)) { + return currentPort; + } + } + + throw new IllegalStateException("Could not find free port starting at " + minPort + " for " + clientName); + + } + + public void reserve(int... ports) { + + for (int port : ports) { + allocate("reserved", port); + } + } + + private boolean isInUse(int port) { + boolean result = !this.socketType.isPortAvailable(port); + + if (result) { + synchronized (this) { + allocatedPorts.put(port, new AllocatedPort("external")); + } + } + + return result; + } + + private boolean isAvailable(int port) { + return !isAllocated(port) && !isInUse(port); + } + + private synchronized boolean isAllocated(int port) { + AllocatedPort allocatedPort = this.allocatedPorts.get(port); + + return allocatedPort != null && !allocatedPort.isTimedOut(); + } + + private synchronized boolean allocate(String clientName, int port) { + + AllocatedPort allocatedPort = allocatedPorts.get(port); + + if (allocatedPort != null && allocatedPort.isTimedOut()) { + allocatedPort = null; + allocatedPorts.remove(port); + } + + if (allocatedPort == null && !isInUse(port)) { + allocatedPorts.put(port, new AllocatedPort(clientName)); + return true; + } else { + return false; + } + } + + private class AllocatedPort { + final String client; + final Instant allocatedAt; + + AllocatedPort(String client) { + this.client = client; + this.allocatedAt = Instant.now(); + } + + boolean isTimedOut() { + return allocatedAt.plus(timeoutDuration).isBefore(Instant.now()); + } + + @Override + public String toString() { + return "AllocatedPort [client=" + client + ", allocatedAt=" + allocatedAt + "]"; + } + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/RestClientException.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/RestClientException.java new file mode 100644 index 0000000000..527fe1cb2f --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/RestClientException.java @@ -0,0 +1,16 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.cluster; + +class RestClientException extends RuntimeException { + public RestClientException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtils.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtils.java new file mode 100644 index 0000000000..92ec47d658 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtils.java @@ -0,0 +1,312 @@ +/* +* Copyright 2002-2017 the original author or authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +*/ + +/* +* 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.test.framework.cluster; + +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.util.Random; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.net.ServerSocketFactory; + +/** +* Simple utility methods for working with network sockets — for example, +* for finding available ports on {@code localhost}. +* +*

Within this class, a TCP port refers to a port for a {@link ServerSocket}; +* whereas, a UDP port refers to a port for a {@link DatagramSocket}. +* +* @author Sam Brannen +* @author Ben Hale +* @author Arjen Poutsma +* @author Gunnar Hillert +* @author Gary Russell +* @since 4.0 +*/ +public class SocketUtils { + + /** + * The default minimum value for port ranges used when finding an available + * socket port. + */ + public static final int PORT_RANGE_MIN = 1024; + + /** + * The default maximum value for port ranges used when finding an available + * socket port. + */ + public static final int PORT_RANGE_MAX = 65535; + + + private static final Random random = new Random(System.currentTimeMillis()); + + + /** + * Although {@code SocketUtils} consists solely of static utility methods, + * this constructor is intentionally {@code public}. + *

Rationale

+ *

Static methods from this class may be invoked from within XML + * configuration files using the Spring Expression Language (SpEL) and the + * following syntax. + *

<bean id="bean1" ... p:port="#{T(org.springframework.util.SocketUtils).findAvailableTcpPort(12000)}" />
+ * If this constructor were {@code private}, you would be required to supply + * the fully qualified class name to SpEL's {@code T()} function for each usage. + * Thus, the fact that this constructor is {@code public} allows you to reduce + * boilerplate configuration with SpEL as can be seen in the following example. + *
<bean id="socketUtils" class="org.springframework.util.SocketUtils" />
+	* <bean id="bean1" ... p:port="#{socketUtils.findAvailableTcpPort(12000)}" />
+	* <bean id="bean2" ... p:port="#{socketUtils.findAvailableTcpPort(30000)}" />
+ */ + public SocketUtils() { + /* no-op */ + } + + + /** + * Find an available TCP port randomly selected from the range + * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort() { + return findAvailableTcpPort(PORT_RANGE_MIN); + } + + /** + * Find an available TCP port randomly selected from the range + * [{@code minPort}, {@value #PORT_RANGE_MAX}]. + * @param minPort the minimum port number + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort(int minPort) { + return findAvailableTcpPort(minPort, PORT_RANGE_MAX); + } + + /** + * Find an available TCP port randomly selected from the range + * [{@code minPort}, {@code maxPort}]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort(int minPort, int maxPort) { + return SocketType.TCP.findAvailablePort(minPort, maxPort); + } + + /** + * Find the requested number of available TCP ports, each randomly selected + * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @param numRequested the number of available ports to find + * @return a sorted set of available TCP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet findAvailableTcpPorts(int numRequested) { + return findAvailableTcpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + /** + * Find the requested number of available TCP ports, each randomly selected + * from the range [{@code minPort}, {@code maxPort}]. + * @param numRequested the number of available ports to find + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a sorted set of available TCP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet findAvailableTcpPorts(int numRequested, int minPort, int maxPort) { + return SocketType.TCP.findAvailablePorts(numRequested, minPort, maxPort); + } + + /** + * Find an available UDP port randomly selected from the range + * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @return an available UDP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableUdpPort() { + return findAvailableUdpPort(PORT_RANGE_MIN); + } + + /** + * Find an available UDP port randomly selected from the range + * [{@code minPort}, {@value #PORT_RANGE_MAX}]. + * @param minPort the minimum port number + * @return an available UDP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableUdpPort(int minPort) { + return findAvailableUdpPort(minPort, PORT_RANGE_MAX); + } + + /** + * Find an available UDP port randomly selected from the range + * [{@code minPort}, {@code maxPort}]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available UDP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableUdpPort(int minPort, int maxPort) { + return SocketType.UDP.findAvailablePort(minPort, maxPort); + } + + /** + * Find the requested number of available UDP ports, each randomly selected + * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @param numRequested the number of available ports to find + * @return a sorted set of available UDP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet findAvailableUdpPorts(int numRequested) { + return findAvailableUdpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + /** + * Find the requested number of available UDP ports, each randomly selected + * from the range [{@code minPort}, {@code maxPort}]. + * @param numRequested the number of available ports to find + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a sorted set of available UDP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet findAvailableUdpPorts(int numRequested, int minPort, int maxPort) { + return SocketType.UDP.findAvailablePorts(numRequested, minPort, maxPort); + } + + + public enum SocketType { + + TCP { + @Override + protected boolean isPortAvailable(int port) { + try { + ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket( + port, 1, InetAddress.getByName("localhost")); + serverSocket.close(); + return true; + } + catch (Exception ex) { + return false; + } + } + }, + + UDP { + @Override + protected boolean isPortAvailable(int port) { + try { + DatagramSocket socket = new DatagramSocket(port, InetAddress.getByName("localhost")); + socket.close(); + return true; + } + catch (Exception ex) { + return false; + } + } + }; + + /** + * Determine if the specified port for this {@code SocketType} is + * currently available on {@code localhost}. + */ + protected abstract boolean isPortAvailable(int port); + + /** + * Find a pseudo-random port number within the range + * [{@code minPort}, {@code maxPort}]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a random port number within the specified range + */ + private int findRandomPort(int minPort, int maxPort) { + int portRange = maxPort - minPort; + return minPort + random.nextInt(portRange + 1); + } + + /** + * Find an available port for this {@code SocketType}, randomly selected + * from the range [{@code minPort}, {@code maxPort}]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available port number for this socket type + * @throws IllegalStateException if no available port could be found + */ + int findAvailablePort(int minPort, int maxPort) { + //Assert.assertTrue(minPort > 0, "'minPort' must be greater than 0"); + //Assert.isTrue(maxPort >= minPort, "'maxPort' must be greater than or equal to 'minPort'"); + //Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX); + + int portRange = maxPort - minPort; + int candidatePort; + int searchCounter = 0; + do { + if (searchCounter > portRange) { + throw new IllegalStateException(String.format( + "Could not find an available %s port in the range [%d, %d] after %d attempts", + name(), minPort, maxPort, searchCounter)); + } + candidatePort = findRandomPort(minPort, maxPort); + searchCounter++; + } + while (!isPortAvailable(candidatePort)); + + return candidatePort; + } + + /** + * Find the requested number of available ports for this {@code SocketType}, + * each randomly selected from the range [{@code minPort}, {@code maxPort}]. + * @param numRequested the number of available ports to find + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a sorted set of available port numbers for this socket type + * @throws IllegalStateException if the requested number of available ports could not be found + */ + SortedSet findAvailablePorts(int numRequested, int minPort, int maxPort) { + SortedSet availablePorts = new TreeSet<>(); + int attemptCount = 0; + while ((++attemptCount <= numRequested + 100) && availablePorts.size() < numRequested) { + availablePorts.add(findAvailablePort(minPort, maxPort)); + } + + if (availablePorts.size() != numRequested) { + throw new IllegalStateException(String.format( + "Could not find %d available %s ports in the range [%d, %d]", + numRequested, name(), minPort, maxPort)); + } + + return availablePorts; + } + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtilsTests.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtilsTests.java new file mode 100644 index 0000000000..548bedbfa6 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtilsTests.java @@ -0,0 +1,212 @@ +/* +* Copyright 2002-2020 the original author or authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +*/ + +/* +* 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.test.framework.cluster; + +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.util.SortedSet; + +import javax.net.ServerSocketFactory; + +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertThrows; +import static org.opensearch.test.framework.cluster.SocketUtils.PORT_RANGE_MAX; +import static org.opensearch.test.framework.cluster.SocketUtils.PORT_RANGE_MIN; + +/** +* Unit tests for {@link SocketUtils}. +* +* @author Sam Brannen +* @author Gary Russell +*/ +public class SocketUtilsTests { + + // TCP + + @Test + public void findAvailableTcpPort() { + int port = SocketUtils.findAvailableTcpPort(); + assertPortInRange(port, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + @Test + public void findAvailableTcpPortWithMinPortEqualToMaxPort() { + int minMaxPort = SocketUtils.findAvailableTcpPort(); + int port = SocketUtils.findAvailableTcpPort(minMaxPort, minMaxPort); + assertThat(port, equalTo(minMaxPort)); + } + + @Test + public void findAvailableTcpPortWhenPortOnLoopbackInterfaceIsNotAvailable() throws Exception { + int port = SocketUtils.findAvailableTcpPort(); + try (ServerSocket socket = ServerSocketFactory.getDefault().createServerSocket(port, 1, InetAddress.getByName("localhost"))) { + assertThat(socket, notNullValue()); + // will only look for the exact port + IllegalStateException exception = assertThrows( + IllegalStateException.class, + () -> SocketUtils.findAvailableTcpPort(port, port) + ); + assertThat(exception.getMessage(), startsWith("Could not find an available TCP port")); + assertThat(exception.getMessage(), endsWith("after 1 attempts")); + } + } + + @Test + public void findAvailableTcpPortWithMin() { + int port = SocketUtils.findAvailableTcpPort(50000); + assertPortInRange(port, 50000, PORT_RANGE_MAX); + } + + @Test + public void findAvailableTcpPortInRange() { + int minPort = 20000; + int maxPort = minPort + 1000; + int port = SocketUtils.findAvailableTcpPort(minPort, maxPort); + assertPortInRange(port, minPort, maxPort); + } + + @Test + public void find4AvailableTcpPorts() { + findAvailableTcpPorts(4); + } + + @Test + public void find50AvailableTcpPorts() { + findAvailableTcpPorts(50); + } + + @Test + public void find4AvailableTcpPortsInRange() { + findAvailableTcpPorts(4, 30000, 35000); + } + + @Test + public void find50AvailableTcpPortsInRange() { + findAvailableTcpPorts(50, 40000, 45000); + } + + // UDP + + @Test + public void findAvailableUdpPort() { + int port = SocketUtils.findAvailableUdpPort(); + assertPortInRange(port, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + @Test + public void findAvailableUdpPortWhenPortOnLoopbackInterfaceIsNotAvailable() throws Exception { + int port = SocketUtils.findAvailableUdpPort(); + try (DatagramSocket socket = new DatagramSocket(port, InetAddress.getByName("localhost"))) { + assertThat(socket, notNullValue()); + // will only look for the exact port + IllegalStateException exception = assertThrows( + IllegalStateException.class, + () -> SocketUtils.findAvailableUdpPort(port, port) + ); + assertThat(exception.getMessage(), startsWith("Could not find an available UDP port")); + assertThat(exception.getMessage(), endsWith("after 1 attempts")); + } + } + + @Test + public void findAvailableUdpPortWithMin() { + int port = SocketUtils.findAvailableUdpPort(50000); + assertPortInRange(port, 50000, PORT_RANGE_MAX); + } + + @Test + public void findAvailableUdpPortInRange() { + int minPort = 20000; + int maxPort = minPort + 1000; + int port = SocketUtils.findAvailableUdpPort(minPort, maxPort); + assertPortInRange(port, minPort, maxPort); + } + + @Test + public void find4AvailableUdpPorts() { + findAvailableUdpPorts(4); + } + + @Test + public void find50AvailableUdpPorts() { + findAvailableUdpPorts(50); + } + + @Test + public void find4AvailableUdpPortsInRange() { + findAvailableUdpPorts(4, 30000, 35000); + } + + @Test + public void find50AvailableUdpPortsInRange() { + findAvailableUdpPorts(50, 40000, 45000); + } + + // Helpers + + private void findAvailableTcpPorts(int numRequested) { + SortedSet ports = SocketUtils.findAvailableTcpPorts(numRequested); + assertAvailablePorts(ports, numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + private void findAvailableTcpPorts(int numRequested, int minPort, int maxPort) { + SortedSet ports = SocketUtils.findAvailableTcpPorts(numRequested, minPort, maxPort); + assertAvailablePorts(ports, numRequested, minPort, maxPort); + } + + private void findAvailableUdpPorts(int numRequested) { + SortedSet ports = SocketUtils.findAvailableUdpPorts(numRequested); + assertAvailablePorts(ports, numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + private void findAvailableUdpPorts(int numRequested, int minPort, int maxPort) { + SortedSet ports = SocketUtils.findAvailableUdpPorts(numRequested, minPort, maxPort); + assertAvailablePorts(ports, numRequested, minPort, maxPort); + } + private void assertPortInRange(int port, int minPort, int maxPort) { + assertThat("port [" + port + "] >= " + minPort, port, greaterThanOrEqualTo(minPort)); + assertThat("port [" + port + "] <= " + maxPort, port, lessThanOrEqualTo(maxPort)); + } + + private void assertAvailablePorts(SortedSet ports, int numRequested, int minPort, int maxPort) { + assertThat("number of ports requested", ports.size(), equalTo(numRequested)); + for (int port : ports) { + assertPortInRange(port, minPort, maxPort); + } + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/StartStage.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/StartStage.java new file mode 100644 index 0000000000..80db4ba87a --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/StartStage.java @@ -0,0 +1,15 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.cluster; + +enum StartStage { + INITIALIZED, + RETRY +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java new file mode 100644 index 0000000000..70ad600283 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -0,0 +1,360 @@ +/* +* Copyright 2021 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +* +*/ + +/* +* 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.test.framework.cluster; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.net.ssl.SSLContext; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.commons.io.IOUtils; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpOptions; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.config.SocketConfig; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicHeader; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.common.Strings; +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.security.DefaultObjectMapper; + +/** +* A OpenSearch REST client, which is tailored towards use in integration tests. Instances of this class can be +* obtained via the OpenSearchClientProvider interface, which is implemented by LocalCluster and Node. +* +* Usually, an instance of this class sends constant authentication headers which are defined when obtaining the +* instance from OpenSearchClientProvider. +*/ +public class TestRestClient implements AutoCloseable { + + private static final Logger log = LogManager.getLogger(TestRestClient.class); + + private boolean enableHTTPClientSSL = true; + private boolean sendHTTPClientCertificate = false; + private InetSocketAddress nodeHttpAddress; + private RequestConfig requestConfig; + private List
headers = new ArrayList<>(); + private Header CONTENT_TYPE_JSON = new BasicHeader("Content-Type", "application/json"); + private SSLContext sslContext; + + public TestRestClient(InetSocketAddress nodeHttpAddress, List
headers, SSLContext sslContext) { + this.nodeHttpAddress = nodeHttpAddress; + this.headers.addAll(headers); + this.sslContext = sslContext; + } + + public HttpResponse get(String path, Header... headers) { + return executeRequest(new HttpGet(getHttpServerUri() + "/" + path), headers); + } + + public HttpResponse getAuthInfo( Header... headers) { + return executeRequest(new HttpGet(getHttpServerUri() + "/_opendistro/_security/authinfo?pretty"), headers); + } + + public HttpResponse head(String path, Header... headers) { + return executeRequest(new HttpHead(getHttpServerUri() + "/" + path), headers); + } + + public HttpResponse options(String path, Header... headers) { + return executeRequest(new HttpOptions(getHttpServerUri() + "/" + path), headers); + } + + public HttpResponse putJson(String path, String body, Header... headers) { + HttpPut uriRequest = new HttpPut(getHttpServerUri() + "/" + path); + uriRequest.setEntity(toStringEntity(body)); + return executeRequest(uriRequest, mergeHeaders(CONTENT_TYPE_JSON, headers)); + } + + private StringEntity toStringEntity(String body) { + try { + return new StringEntity(body); + } catch (UnsupportedEncodingException e) { + throw new RestClientException("Cannot create string entity", e); + } + } + + public HttpResponse putJson(String path, ToXContentObject body) { + return putJson(path, Strings.toString(body)); + } + + public HttpResponse put(String path) { + HttpPut uriRequest = new HttpPut(getHttpServerUri() + "/" + path); + return executeRequest(uriRequest); + } + + public HttpResponse delete(String path, Header... headers) { + return executeRequest(new HttpDelete(getHttpServerUri() + "/" + path), headers); + } + + public HttpResponse postJson(String path, String body, Header... headers) { + HttpPost uriRequest = new HttpPost(getHttpServerUri() + "/" + path); + uriRequest.setEntity(toStringEntity(body)); + return executeRequest(uriRequest, mergeHeaders(CONTENT_TYPE_JSON, headers)); + } + + public HttpResponse postJson(String path, ToXContentObject body) { + return postJson(path, Strings.toString(body)); + } + + public HttpResponse post(String path) { + HttpPost uriRequest = new HttpPost(getHttpServerUri() + "/" + path); + return executeRequest(uriRequest); + } + + public HttpResponse patch(String path, String body) { + HttpPatch uriRequest = new HttpPatch(getHttpServerUri() + "/" + path); + uriRequest.setEntity(toStringEntity(body)); + return executeRequest(uriRequest, CONTENT_TYPE_JSON); + } + + public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... requestSpecificHeaders) { + + try(CloseableHttpClient httpClient = getHTTPClient()) { + + + if (requestSpecificHeaders != null && requestSpecificHeaders.length > 0) { + for (int i = 0; i < requestSpecificHeaders.length; i++) { + Header h = requestSpecificHeaders[i]; + uriRequest.addHeader(h); + } + } + + for (Header header : headers) { + uriRequest.addHeader(header); + } + + HttpResponse res = new HttpResponse(httpClient.execute(uriRequest)); + log.debug(res.getBody()); + return res; + } catch (IOException e) { + throw new RestClientException("Error occured during HTTP request execution", e); + } + } + + protected final String getHttpServerUri() { + return "http" + (enableHTTPClientSSL ? "s" : "") + "://" + nodeHttpAddress.getHostString() + ":" + nodeHttpAddress.getPort(); + } + + protected final CloseableHttpClient getHTTPClient() { + + final HttpClientBuilder hcb = HttpClients.custom(); + + String[] protocols = null; + + final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(this.sslContext, protocols, null, + NoopHostnameVerifier.INSTANCE); + + hcb.setSSLSocketFactory(sslsf); + + hcb.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60 * 1000).build()); + + if (requestConfig != null) { + hcb.setDefaultRequestConfig(requestConfig); + } + + return hcb.build(); + } + + private Header[] mergeHeaders(Header header, Header... headers) { + + if (headers == null || headers.length == 0) { + return new Header[] { header }; + } else { + Header[] result = new Header[headers.length + 1]; + result[0] = header; + System.arraycopy(headers, 0, result, 1, headers.length); + return result; + } + } + + public static class HttpResponse { + private final CloseableHttpResponse inner; + private final String body; + private final Header[] header; + private final int statusCode; + private final String statusReason; + + public HttpResponse(CloseableHttpResponse inner) throws IllegalStateException, IOException { + super(); + this.inner = inner; + final HttpEntity entity = inner.getEntity(); + if (entity == null) { //head request does not have a entity + this.body = ""; + } else { + this.body = IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8); + } + this.header = inner.getAllHeaders(); + this.statusCode = inner.getStatusLine().getStatusCode(); + this.statusReason = inner.getStatusLine().getReasonPhrase(); + inner.close(); + } + + public String getContentType() { + Header h = getInner().getFirstHeader("content-type"); + if (h != null) { + return h.getValue(); + } + return null; + } + + public boolean isJsonContentType() { + String ct = getContentType(); + if (ct == null) { + return false; + } + return ct.contains("application/json"); + } + + public CloseableHttpResponse getInner() { + return inner; + } + + public String getBody() { + return body; + } + + public Header[] getHeader() { + return header; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatusReason() { + return statusReason; + } + + public List
getHeaders() { + return header == null ? Collections.emptyList() : Arrays.asList(header); + } + + public String getTextFromJsonBody(String jsonPointer) { + return getJsonNodeAt(jsonPointer).asText(); + } + + public int getIntFromJsonBody(String jsonPointer) { + return getJsonNodeAt(jsonPointer).asInt(); + } + + public Boolean getBooleanFromJsonBody(String jsonPointer) { + return getJsonNodeAt(jsonPointer).asBoolean(); + } + + public Double getDoubleFromJsonBody(String jsonPointer) { + return getJsonNodeAt(jsonPointer).asDouble(); + } + + public Long getLongFromJsonBody(String jsonPointer) { + return getJsonNodeAt(jsonPointer).asLong(); + } + + private JsonNode getJsonNodeAt(String jsonPointer) { + try { + return toJsonNode().at(jsonPointer); + } catch (IOException e) { + throw new IllegalArgumentException("Cound not convert response body to JSON node ",e); + } + } + + private JsonNode toJsonNode() throws JsonProcessingException, IOException { + return DefaultObjectMapper.objectMapper.readTree(getBody()); + } + + + + @Override + public String toString() { + return "HttpResponse [inner=" + inner + ", body=" + body + ", header=" + Arrays.toString(header) + ", statusCode=" + statusCode + + ", statusReason=" + statusReason + "]"; + } + + } + + @Override + public String toString() { + return "TestRestClient [server=" + getHttpServerUri() + ", node=" + nodeHttpAddress + "]"; + } + + public RequestConfig getRequestConfig() { + return requestConfig; + } + + public void setRequestConfig(RequestConfig requestConfig) { + this.requestConfig = requestConfig; + } + + public void setLocalAddress(InetAddress inetAddress) { + if (requestConfig == null) { + requestConfig = RequestConfig.custom().setLocalAddress(inetAddress).build(); + } else { + requestConfig = RequestConfig.copy(requestConfig).setLocalAddress(inetAddress).build(); + } + } + + public boolean isSendHTTPClientCertificate() { + return sendHTTPClientCertificate; + } + + public void setSendHTTPClientCertificate(boolean sendHTTPClientCertificate) { + this.sendHTTPClientCertificate = sendHTTPClientCertificate; + } + + @Override + public void close() { + // TODO: Is there anything to clean up here? + } + +} diff --git a/src/integrationTest/resources/log4j2-test.properties b/src/integrationTest/resources/log4j2-test.properties new file mode 100644 index 0000000000..b98cc92b78 --- /dev/null +++ b/src/integrationTest/resources/log4j2-test.properties @@ -0,0 +1,16 @@ +status = info +name = Integration test logging configuration + + + +appender.console.type = Console +appender.console.name = consoleLogger +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %threadName %-5p %c{1}:%L - %m%n +appender.console.filter.prerelease.type=RegexFilter +appender.console.filter.prerelease.regex=.+\\Qis a pre-release version of OpenSearch and is not suitable for production\\E +appender.console.filter.prerelease.onMatch=DENY +appender.console.filter.prerelease.onMismatch=NEUTRAL + +rootLogger.level = warn +rootLogger.appenderRef.stdout.ref = consoleLogger diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java index d812bd7bbb..cdf0eaf366 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java @@ -42,6 +42,7 @@ import org.apache.logging.log4j.Logger; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBusBuilder; +import org.greenrobot.eventbus.Logger.JavaLogger; import org.opensearch.client.Client; import org.opensearch.common.settings.Settings; @@ -120,7 +121,7 @@ public final static SecurityDynamicConfiguration addStatics(SecurityDynamicCo protected final Logger log = LogManager.getLogger(this.getClass()); private final ConfigurationRepository cr; private final AtomicBoolean initialized = new AtomicBoolean(); - private final EventBus eventBus = EVENT_BUS_BUILDER.build(); + private final EventBus eventBus = EVENT_BUS_BUILDER.logger(new JavaLogger(DynamicConfigFactory.class.getCanonicalName())).build(); private final Settings opensearchSettings; private final Path configPath; private final InternalAuthenticationBackend iab = new InternalAuthenticationBackend(); diff --git a/src/main/java/org/opensearch/security/support/PemKeyReader.java b/src/main/java/org/opensearch/security/support/PemKeyReader.java index 53eeb21736..66d1af8799 100644 --- a/src/main/java/org/opensearch/security/support/PemKeyReader.java +++ b/src/main/java/org/opensearch/security/support/PemKeyReader.java @@ -274,15 +274,19 @@ public static X509Certificate[] loadCertificatesFromFile(String file) throws Exc return null; } - CertificateFactory fact = CertificateFactory.getInstance("X.509"); try(FileInputStream is = new FileInputStream(file)) { - Collection certs = fact.generateCertificates(is); - X509Certificate[] x509Certs = new X509Certificate[certs.size()]; - int i=0; - for(Certificate cert: certs) { - x509Certs[i++] = (X509Certificate) cert; - } - return x509Certs; + return loadCertificatesFromStream(is); + } + + } + + public static X509Certificate[] loadCertificatesFromFile(File file) throws Exception { + if(file == null) { + return null; + } + + try(FileInputStream is = new FileInputStream(file)) { + return loadCertificatesFromStream(is); } } From 1fc02adff19c2ec8af3152f8a5af00a9961daf8e Mon Sep 17 00:00:00 2001 From: Martin-Kemp <30285179+Martin-Kemp@users.noreply.github.com> Date: Wed, 21 Sep 2022 16:09:37 +0200 Subject: [PATCH 039/369] Make ldap pool period and idle time configurable (#2091) --- .../com/amazon/dlic/auth/ldap/util/ConfigConstants.java | 3 +++ .../dlic/auth/ldap2/LDAPConnectionFactoryFactory.java | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java b/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java index 64cc868d83..806e542c08 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java @@ -82,6 +82,9 @@ public final class ConfigConstants { public static final String LDAP_POOL_TYPE = "pool.type"; + public static final String LDAP_POOL_PRUNING_PERIOD = "pool.pruning_period"; + public static final String LDAP_POOL_IDLE_TIME = "pool.idle_time"; + private ConfigConstants() { } diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPConnectionFactoryFactory.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPConnectionFactoryFactory.java index 79936e5b12..74d8989ae3 100644 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPConnectionFactoryFactory.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPConnectionFactoryFactory.java @@ -127,8 +127,11 @@ public ConnectionPool createConnectionPool() { } result.setValidator(getConnectionValidator()); - result.setPruneStrategy(new IdlePruneStrategy(Duration.ofMinutes(this.settings.getAsLong("pruning.period", 5l)), - Duration.ofMinutes(this.settings.getAsLong("pruning.idleTime", 10l)))); + + result.setPruneStrategy(new IdlePruneStrategy( + Duration.ofMinutes(this.settings.getAsLong(ConfigConstants.LDAP_POOL_PRUNING_PERIOD, this.settings.getAsLong("pruning.period", 5l))), + Duration.ofMinutes(this.settings.getAsLong(ConfigConstants.LDAP_POOL_IDLE_TIME, this.settings.getAsLong("pruning.idleTime", 10l)))) + ); result.initialize(); From b9b7e1f84a04746cb1a52c3cf614de7396982305 Mon Sep 17 00:00:00 2001 From: Martin Kemp Date: Tue, 27 Sep 2022 00:45:00 +0200 Subject: [PATCH 040/369] Allow custom return attributes (#2093) * Allow custom return attributes Signed-off-by: Martin Kemp --- .../backend/LDAPAuthenticationBackend.java | 25 ++++++++------ .../backend/LDAPAuthorizationBackend.java | 17 ++++++---- .../dlic/auth/ldap/util/ConfigConstants.java | 1 + .../dlic/auth/ldap/util/LdapHelper.java | 9 +++-- .../ldap2/LDAPAuthenticationBackend2.java | 7 ++-- .../auth/ldap2/LDAPAuthorizationBackend2.java | 18 ++++++---- .../dlic/auth/ldap2/LDAPUserSearcher.java | 16 +++++---- .../dlic/auth/ldap/LdapBackendTest.java | 33 ++++++++++++++++++- .../ldap/LdapBackendTestNewStyleConfig.java | 3 +- .../ldap2/LdapBackendTestNewStyleConfig2.java | 29 +++++++++++++++- .../ldap2/LdapBackendTestOldStyleConfig2.java | 3 +- 11 files changed, 121 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java index 00eb693e46..05fa70c821 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java @@ -28,6 +28,7 @@ import org.ldaptive.Connection; import org.ldaptive.ConnectionConfig; import org.ldaptive.LdapEntry; +import org.ldaptive.ReturnAttributes; import org.ldaptive.SearchFilter; import org.ldaptive.SearchScope; @@ -57,10 +58,13 @@ public class LDAPAuthenticationBackend implements AuthenticationBackend { private final int customAttrMaxValueLen; private final WildcardMatcher whitelistedCustomLdapAttrMatcher; + private final String[] returnAttributes; + public LDAPAuthenticationBackend(final Settings settings, final Path configPath) { this.settings = settings; this.configPath = configPath; this.userBaseSettings = getUserBaseSettings(settings); + this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); customAttrMaxValueLen = settings.getAsInt(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 36); whitelistedCustomLdapAttrMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, @@ -82,7 +86,7 @@ public User authenticate(final AuthCredentials credentials) throws OpenSearchSec try { ldapConnection = LDAPAuthorizationBackend.getConnection(settings, configPath); - entry = exists(user, ldapConnection, settings, userBaseSettings); + entry = exists(user, ldapConnection, settings, userBaseSettings, this.returnAttributes); // fake a user that no exists // makes guessing if a user exists or not harder when looking on the @@ -156,7 +160,7 @@ public boolean exists(final User user) { try { ldapConnection = LDAPAuthorizationBackend.getConnection(settings, configPath); - LdapEntry userEntry = exists(userName, ldapConnection, settings, userBaseSettings); + LdapEntry userEntry = exists(userName, ldapConnection, settings, userBaseSettings, this.returnAttributes); boolean exists = userEntry != null; if(exists) { @@ -197,20 +201,19 @@ static List> getUserBaseSettings(Settings settings) } static LdapEntry exists(final String user, Connection ldapConnection, Settings settings, - List> userBaseSettings) throws Exception { - + List> userBaseSettings, String[] returnAttributes) throws Exception { if (settings.getAsBoolean(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, false) || settings.getAsBoolean(ConfigConstants.LDAP_SEARCH_ALL_BASES, false) || settings.hasValue(ConfigConstants.LDAP_AUTHC_USERBASE)) { - return existsSearchingAllBases(user, ldapConnection, userBaseSettings); + return existsSearchingAllBases(user, ldapConnection, userBaseSettings, returnAttributes); } else { - return existsSearchingUntilFirstHit(user, ldapConnection, userBaseSettings); + return existsSearchingUntilFirstHit(user, ldapConnection, userBaseSettings, returnAttributes); } } private static LdapEntry existsSearchingUntilFirstHit(final String user, Connection ldapConnection, - List> userBaseSettings) throws Exception { + List> userBaseSettings, final String[] returnAttributes) throws Exception { final String username = user; final boolean isDebugEnabled = log.isDebugEnabled(); @@ -224,7 +227,8 @@ private static LdapEntry existsSearchingUntilFirstHit(final String user, Connect List result = LdapHelper.search(ldapConnection, baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), f, - SearchScope.SUBTREE); + SearchScope.SUBTREE, + returnAttributes); if (isDebugEnabled) { log.debug("Results for LDAP search for {} in base {} is {}", user, entry.getKey(), result); @@ -239,7 +243,7 @@ private static LdapEntry existsSearchingUntilFirstHit(final String user, Connect } private static LdapEntry existsSearchingAllBases(final String user, Connection ldapConnection, - List> userBaseSettings) throws Exception { + List> userBaseSettings, final String[] returnAttributes) throws Exception { final String username = user; Set result = new HashSet<>(); @@ -254,7 +258,8 @@ private static LdapEntry existsSearchingAllBases(final String user, Connection l List foundEntries = LdapHelper.search(ldapConnection, baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), f, - SearchScope.SUBTREE); + SearchScope.SUBTREE, + returnAttributes); if (isDebugEnabled) { log.debug("Results for LDAP search for " + user + " in base " + entry.getKey() + ":\n" + result); diff --git a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java index 49c9f75839..7146d0f678 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java @@ -53,6 +53,7 @@ import org.ldaptive.LdapEntry; import org.ldaptive.LdapException; import org.ldaptive.Response; +import org.ldaptive.ReturnAttributes; import org.ldaptive.SearchFilter; import org.ldaptive.SearchScope; import org.ldaptive.control.RequestControl; @@ -103,6 +104,8 @@ public class LDAPAuthorizationBackend implements AuthorizationBackend { private final List> roleBaseSettings; private final List> userBaseSettings; + private final String[] returnAttributes; + public LDAPAuthorizationBackend(final Settings settings, final Path configPath) { this.settings = settings; this.skipUsersMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS)); @@ -111,6 +114,7 @@ public LDAPAuthorizationBackend(final Settings settings, final Path configPath) this.configPath = configPath; this.roleBaseSettings = getRoleSearchSettings(settings); this.userBaseSettings = LDAPAuthenticationBackend.getUserBaseSettings(settings); + this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); } @SuppressWarnings("removal") @@ -724,7 +728,7 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) log.debug("DBGTRACE (4): authenticatedUser="+authenticatedUser+" -> "+Arrays.toString(authenticatedUser.getBytes(StandardCharsets.UTF_8))); } - entry = LdapHelper.lookup(connection, authenticatedUser); + entry = LdapHelper.lookup(connection, authenticatedUser, this.returnAttributes); if (entry == null) { throw new OpenSearchSecurityException("No user '" + authenticatedUser + "' found"); @@ -735,7 +739,7 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) if (isDebugEnabled) log.debug("DBGTRACE (5): authenticatedUser="+user.getName()+" -> "+Arrays.toString(user.getName().getBytes(StandardCharsets.UTF_8))); - entry = LDAPAuthenticationBackend.exists(user.getName(), connection, settings, userBaseSettings); + entry = LDAPAuthenticationBackend.exists(user.getName(), connection, settings, userBaseSettings, this.returnAttributes); if (isTraceEnabled) { log.trace("{} is not a valid DN and was resolved to {}", authenticatedUser, entry); @@ -848,7 +852,7 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) List rolesResult = LdapHelper.search(connection, roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), f, - SearchScope.SUBTREE); + SearchScope.SUBTREE, this.returnAttributes); if (isTraceEnabled) { log.trace("Results for LDAP group search for {} in base {}:\n{}", escapedDn, roleSearchSettingsEntry.getKey(), rolesResult); @@ -966,7 +970,7 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti final Set result = new HashSet<>(20); final HashMultimap> resultRoleSearchBaseKeys = HashMultimap.create(); - final LdapEntry e0 = LdapHelper.lookup(ldapConnection, roleDn.toString()); + final LdapEntry e0 = LdapHelper.lookup(ldapConnection, roleDn.toString(), this.returnAttributes); if (e0.getAttribute(userRoleName) != null) { final Collection userRoles = e0.getAttribute(userRoleName).getStringValues(); @@ -1018,7 +1022,8 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti List foundEntries = LdapHelper.search(ldapConnection, roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), f, - SearchScope.SUBTREE); + SearchScope.SUBTREE, + this.returnAttributes); if (isTraceEnabled) { log.trace("Results for LDAP group search for {} in base {}:\n{}", escapedDn, roleSearchBaseSettingsEntry.getKey(), foundEntries); @@ -1096,7 +1101,7 @@ private String getRoleFromEntry(final Connection ldapConnection, final LdapName } try { - final LdapEntry roleEntry = LdapHelper.lookup(ldapConnection, ldapName.toString()); + final LdapEntry roleEntry = LdapHelper.lookup(ldapConnection, ldapName.toString(), this.returnAttributes); if(roleEntry != null) { final LdapAttribute roleAttribute = roleEntry.getAttribute(role); diff --git a/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java b/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java index 806e542c08..12366daf82 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java @@ -73,6 +73,7 @@ public final class ConfigConstants { // custom attributes public static final String LDAP_CUSTOM_ATTR_MAXVAL_LEN = "custom_attr_maxval_len"; public static final String LDAP_CUSTOM_ATTR_WHITELIST = "custom_attr_whitelist"; + public static final String LDAP_RETURN_ATTRIBUTES = "custom_return_attributes"; public static final String LDAP_CONNECTION_STRATEGY = "connection_strategy"; diff --git a/src/main/java/com/amazon/dlic/auth/ldap/util/LdapHelper.java b/src/main/java/com/amazon/dlic/auth/ldap/util/LdapHelper.java index 89342bca64..39da2dea88 100644 --- a/src/main/java/com/amazon/dlic/auth/ldap/util/LdapHelper.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/util/LdapHelper.java @@ -26,7 +26,6 @@ import org.ldaptive.LdapEntry; import org.ldaptive.LdapException; import org.ldaptive.Response; -import org.ldaptive.ReturnAttributes; import org.ldaptive.SearchFilter; import org.ldaptive.SearchOperation; import org.ldaptive.SearchRequest; @@ -41,7 +40,7 @@ public class LdapHelper { private static SearchFilter ALL = new SearchFilter("(objectClass=*)"); @SuppressWarnings("removal") public static List search(final Connection conn, final String unescapedDn, SearchFilter filter, - final SearchScope searchScope) throws LdapException { + final SearchScope searchScope, final String[] returnAttributes) throws LdapException { final SecurityManager sm = System.getSecurityManager(); @@ -59,7 +58,7 @@ public List run() throws Exception { request.setReferralHandler(new SearchReferralHandler()); request.setSearchScope(searchScope); request.setDerefAliases(DerefAliases.ALWAYS); - request.setReturnAttributes(ReturnAttributes.ALL.value()); + request.setReturnAttributes(returnAttributes); final SearchOperation search = new SearchOperation(conn); // referrals will be followed to build the response final Response r = search.execute(request); @@ -81,9 +80,9 @@ public List run() throws Exception { } } - public static LdapEntry lookup(final Connection conn, final String unescapedDn) throws LdapException { + public static LdapEntry lookup(final Connection conn, final String unescapedDn, final String[] returnAttributes) throws LdapException { - final List entries = search(conn, unescapedDn, ALL, SearchScope.OBJECT); + final List entries = search(conn, unescapedDn, ALL, SearchScope.OBJECT, returnAttributes); if (entries.size() == 1) { return entries.get(0); diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthenticationBackend2.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthenticationBackend2.java index a54ff4ee3e..fecaab3467 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthenticationBackend2.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthenticationBackend2.java @@ -30,6 +30,7 @@ import org.ldaptive.LdapEntry; import org.ldaptive.LdapException; import org.ldaptive.Response; +import org.ldaptive.ReturnAttributes; import org.ldaptive.pool.ConnectionPool; import com.amazon.dlic.auth.ldap.LdapUser; @@ -58,6 +59,7 @@ public class LDAPAuthenticationBackend2 implements AuthenticationBackend, Destro private LDAPUserSearcher userSearcher; private final int customAttrMaxValueLen; private final WildcardMatcher whitelistedCustomLdapAttrMatcher; + private final String[] returnAttributes; public LDAPAuthenticationBackend2(final Settings settings, final Path configPath) throws SSLConfigException { this.settings = settings; @@ -75,6 +77,7 @@ public LDAPAuthenticationBackend2(final Settings settings, final Path configPath } this.userSearcher = new LDAPUserSearcher(settings); + this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); customAttrMaxValueLen = settings.getAsInt(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 36); whitelistedCustomLdapAttrMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, Collections.singletonList("*"))); @@ -119,7 +122,7 @@ private User authenticate0(final AuthCredentials credentials) throws OpenSearchS ldapConnection = connectionFactory.getConnection(); ldapConnection.open(); - LdapEntry entry = userSearcher.exists(ldapConnection, user); + LdapEntry entry = userSearcher.exists(ldapConnection, user, this.returnAttributes); // fake a user that no exists // makes guessing if a user exists or not harder when looking on the @@ -211,7 +214,7 @@ private boolean exists0(final User user) { try { ldapConnection = this.connectionFactory.getConnection(); ldapConnection.open(); - LdapEntry userEntry = this.userSearcher.exists(ldapConnection, userName); + LdapEntry userEntry = this.userSearcher.exists(ldapConnection, userName, this.returnAttributes); boolean exists = userEntry != null; diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java index d321049e6b..eb3efb5d10 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java @@ -15,6 +15,7 @@ import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -35,6 +36,7 @@ import org.ldaptive.LdapAttribute; import org.ldaptive.LdapEntry; import org.ldaptive.LdapException; +import org.ldaptive.ReturnAttributes; import org.ldaptive.SearchFilter; import org.ldaptive.SearchScope; import org.ldaptive.pool.ConnectionPool; @@ -73,6 +75,7 @@ public class LDAPAuthorizationBackend2 implements AuthorizationBackend, Destroya private ConnectionPool connectionPool; private ConnectionFactory connectionFactory; private LDAPUserSearcher userSearcher; + private final String[] returnAttributes; public LDAPAuthorizationBackend2(final Settings settings, final Path configPath) throws SSLConfigException { this.settings = settings; @@ -87,6 +90,7 @@ public LDAPAuthorizationBackend2(final Settings settings, final Path configPath) this.connectionPool = ldapConnectionFactoryFactory.createConnectionPool(); this.connectionFactory = ldapConnectionFactoryFactory.createConnectionFactory(this.connectionPool); this.userSearcher = new LDAPUserSearcher(settings); + this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); } private static List> getRoleSearchSettings(Settings settings) { @@ -203,14 +207,14 @@ private void fillRoles0(final User user, final AuthCredentials optionalAuthCreds log.trace("{} is a valid DN", authenticatedUser); } - entry = LdapHelper.lookup(connection, authenticatedUser); + entry = LdapHelper.lookup(connection, authenticatedUser, this.returnAttributes); if (entry == null) { throw new OpenSearchSecurityException("No user '" + authenticatedUser + "' found"); } } else { - entry = this.userSearcher.exists(connection, user.getName()); + entry = this.userSearcher.exists(connection, user.getName(), this.returnAttributes); if (isTraceEnabled) { log.trace("{} is not a valid DN and was resolved to {}", authenticatedUser, entry); @@ -309,7 +313,8 @@ private void fillRoles0(final User user, final AuthCredentials optionalAuthCreds List rolesResult = LdapHelper.search(connection, roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), f, - SearchScope.SUBTREE); + SearchScope.SUBTREE, + this.returnAttributes); if (isTraceEnabled) { log.trace("Results for LDAP group search for {} in base {}:\n{}", escapedDn, roleSearchSettingsEntry.getKey(), rolesResult); @@ -425,7 +430,7 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti final Set result = new HashSet<>(20); final HashMultimap> resultRoleSearchBaseKeys = HashMultimap.create(); - final LdapEntry e0 = LdapHelper.lookup(ldapConnection, roleDn.toString()); + final LdapEntry e0 = LdapHelper.lookup(ldapConnection, roleDn.toString(), this.returnAttributes); final boolean isDebugEnabled = log.isDebugEnabled(); if (e0.getAttribute(userRoleName) != null) { @@ -466,7 +471,8 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti List foundEntries = LdapHelper.search(ldapConnection, roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), f, - SearchScope.SUBTREE); + SearchScope.SUBTREE, + this.returnAttributes); if (isTraceEnabled) { log.trace("Results for LDAP group search for {} in base {}:\n{}", escapedDn, roleSearchBaseSettingsEntry.getKey(), foundEntries); @@ -544,7 +550,7 @@ private String getRoleFromEntry(final Connection ldapConnection, final LdapName } try { - final LdapEntry roleEntry = LdapHelper.lookup(ldapConnection, ldapName.toString()); + final LdapEntry roleEntry = LdapHelper.lookup(ldapConnection, ldapName.toString(), this.returnAttributes); if(roleEntry != null) { final LdapAttribute roleAttribute = roleEntry.getAttribute(role); diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPUserSearcher.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPUserSearcher.java index 3ba52151ec..ccd60f450d 100644 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPUserSearcher.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPUserSearcher.java @@ -70,19 +70,19 @@ static List> getUserBaseSettings(Settings settings) } } - LdapEntry exists(Connection ldapConnection, String user) throws Exception { + LdapEntry exists(Connection ldapConnection, String user, final String[] returnAttributes) throws Exception { if (settings.getAsBoolean(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, false) || settings.getAsBoolean(ConfigConstants.LDAP_SEARCH_ALL_BASES, false) || settings.hasValue(ConfigConstants.LDAP_AUTHC_USERBASE)) { - return existsSearchingAllBases(ldapConnection, user); + return existsSearchingAllBases(ldapConnection, user, returnAttributes); } else { - return existsSearchingUntilFirstHit(ldapConnection, user); + return existsSearchingUntilFirstHit(ldapConnection, user, returnAttributes); } } - private LdapEntry existsSearchingUntilFirstHit(Connection ldapConnection, String user) throws Exception { + private LdapEntry existsSearchingUntilFirstHit(Connection ldapConnection, String user, final String[] returnAttributes) throws Exception { final String username = user; final boolean isDebugEnabled = log.isDebugEnabled(); for (Map.Entry entry : userBaseSettings) { @@ -95,7 +95,8 @@ private LdapEntry existsSearchingUntilFirstHit(Connection ldapConnection, String List result = LdapHelper.search(ldapConnection, baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), f, - SearchScope.SUBTREE); + SearchScope.SUBTREE, + returnAttributes); if (isDebugEnabled) { log.debug("Results for LDAP search for {} in base {}:\n{}", user, entry.getKey(), result); @@ -109,7 +110,7 @@ private LdapEntry existsSearchingUntilFirstHit(Connection ldapConnection, String return null; } - private LdapEntry existsSearchingAllBases(Connection ldapConnection, String user) throws Exception { + private LdapEntry existsSearchingAllBases(Connection ldapConnection, String user, final String[] returnAttributes) throws Exception { final String username = user; Set result = new HashSet<>(); final boolean isDebugEnabled = log.isDebugEnabled(); @@ -123,7 +124,8 @@ private LdapEntry existsSearchingAllBases(Connection ldapConnection, String user List foundEntries = LdapHelper.search(ldapConnection, baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), f, - SearchScope.SUBTREE); + SearchScope.SUBTREE, + returnAttributes); if (isDebugEnabled) { log.debug("Results for LDAP search for {} in base {}:\n{}", user, entry.getKey(), result); diff --git a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTest.java b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTest.java index 965fa4a795..080dee1b1f 100755 --- a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTest.java +++ b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTest.java @@ -14,6 +14,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.TreeSet; import org.junit.AfterClass; @@ -23,6 +24,7 @@ import org.ldaptive.Connection; import org.ldaptive.LdapAttribute; import org.ldaptive.LdapEntry; +import org.ldaptive.ReturnAttributes; import com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend; import com.amazon.dlic.auth.ldap.backend.LDAPAuthorizationBackend; @@ -368,6 +370,34 @@ public void testLdapAuthorization() throws Exception { Assert.assertEquals(user.getName(), user.getUserEntry().getDn()); } + @Test + public void testLdapAuthenticationReturnAttributes() throws Exception { + + + final Settings settings = Settings.builder() + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, "mail", "cn", "uid") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" + .getBytes(StandardCharsets.UTF_8))); + + new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); + + final String[] attributes = user.getUserEntry().getAttributeNames(); + + Assert.assertNotNull(user); + Assert.assertEquals(3, attributes.length); + Assert.assertTrue(Arrays.asList(attributes).contains("mail")); + Assert.assertTrue(Arrays.asList(attributes).contains("cn")); + Assert.assertTrue(Arrays.asList(attributes).contains("uid")); + } + @Test public void testLdapAuthenticationReferral() throws Exception { @@ -378,7 +408,7 @@ public void testLdapAuthenticationReferral() throws Exception { final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); try { - final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST"); + final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value()); Assert.assertEquals("cn=refsolved,ou=people,o=TEST", ref1.getDn()); } finally { con.close(); @@ -462,6 +492,7 @@ public void testLdapAuthorizationOnly() throws Exception { } + @Test public void testLdapAuthorizationNonDNEntry() throws Exception { diff --git a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestNewStyleConfig.java b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestNewStyleConfig.java index 94874d897c..4e40e2d4d6 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestNewStyleConfig.java +++ b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestNewStyleConfig.java @@ -22,6 +22,7 @@ import org.junit.Test; import org.ldaptive.Connection; import org.ldaptive.LdapEntry; +import org.ldaptive.ReturnAttributes; import com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend; import com.amazon.dlic.auth.ldap.backend.LDAPAuthorizationBackend; @@ -340,7 +341,7 @@ public void testLdapAuthenticationReferral() throws Exception { final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); try { - final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST"); + final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value()); Assert.assertEquals("cn=refsolved,ou=people,o=TEST", ref1.getDn()); } finally { con.close(); diff --git a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestNewStyleConfig2.java b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestNewStyleConfig2.java index 064b68d1a9..7389f46d06 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestNewStyleConfig2.java +++ b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestNewStyleConfig2.java @@ -14,6 +14,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.TreeSet; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -28,6 +29,7 @@ import org.ldaptive.Connection; import org.ldaptive.LdapAttribute; import org.ldaptive.LdapEntry; +import org.ldaptive.ReturnAttributes; import com.amazon.dlic.auth.ldap.LdapUser; import com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend; @@ -364,6 +366,31 @@ public void testLdapAuthorization() throws Exception { Assert.assertEquals(user.getName(), user.getUserEntry().getDn()); } + @Test + public void testLdapAuthorizationReturnAttributes() throws Exception { + + final Settings settings = createBaseSettings() + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.search", "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, "mail", "cn", "uid") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) + .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + + new LDAPAuthorizationBackend2(settings, null).fillRoles(user, null); + + final String[] attributes = user.getUserEntry().getAttributeNames(); + + Assert.assertNotNull(user); + Assert.assertEquals(3, attributes.length); + Assert.assertTrue(Arrays.asList(attributes).contains("mail")); + Assert.assertTrue(Arrays.asList(attributes).contains("cn")); + Assert.assertTrue(Arrays.asList(attributes).contains("uid")); + } + @Test public void testLdapAuthenticationReferral() throws Exception { @@ -375,7 +402,7 @@ public void testLdapAuthenticationReferral() throws Exception { .getConnection(); try { con.open(); - final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST"); + final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value()); Assert.assertEquals("cn=refsolved,ou=people,o=TEST", ref1.getDn()); } finally { con.close(); diff --git a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestOldStyleConfig2.java b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestOldStyleConfig2.java index 827feb9483..5c40dd6b7d 100755 --- a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestOldStyleConfig2.java +++ b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestOldStyleConfig2.java @@ -29,6 +29,7 @@ import org.ldaptive.Connection; import org.ldaptive.LdapAttribute; import org.ldaptive.LdapEntry; +import org.ldaptive.ReturnAttributes; import com.amazon.dlic.auth.ldap.LdapUser; import com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend; @@ -453,7 +454,7 @@ public void testLdapAuthenticationReferral() throws Exception { .getConnection(); try { con.open(); - final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST"); + final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value()); Assert.assertEquals("cn=refsolved,ou=people,o=TEST", ref1.getDn()); } finally { con.close(); From 2a00e2b9415c927528b7bc659436487036a99158 Mon Sep 17 00:00:00 2001 From: scrawfor99 <65832608+scrawfor99@users.noreply.github.com> Date: Tue, 27 Sep 2022 11:43:47 -0400 Subject: [PATCH 041/369] Correct Java Native Access Description in THIRD-PARTY.txt (#2107) Signed-off-by: Stephen Crawford --- THIRD-PARTY.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/THIRD-PARTY.txt b/THIRD-PARTY.txt index 52e53f968a..d8a027321b 100644 --- a/THIRD-PARTY.txt +++ b/THIRD-PARTY.txt @@ -55,7 +55,7 @@ Lists of 69 third-party dependencies. (The Apache Software License, Version 2.0) server (org.elasticsearch:elasticsearch:6.2.0 - https://github.com/elastic/elasticsearch) (The Apache Software License, Version 2.0) cli (org.elasticsearch:elasticsearch-cli:6.2.0 - https://github.com/elastic/elasticsearch) (The Apache Software License, Version 2.0) elasticsearch-core (org.elasticsearch:elasticsearch-core:6.2.0 - https://github.com/elastic/elasticsearch) - (The Apache Software License, Version 2.0) Elastic JNA Distribution (org.elasticsearch:jna:4.5.1 - https://github.com/java-native-access/jna) + (The Apache Software License, Version 2.0) java native access (net.java.dev.jna:jna:5.5.0 - https://github.com/java-native-access/jna) (The Apache Software License, Version 2.0) Elasticsearch SecureSM (org.elasticsearch:securesm:1.2 - http://nexus.sonatype.org/oss-repository-hosting.html/securesm) (The Apache Software License, Version 2.0) rest (org.elasticsearch.client:elasticsearch-rest-client:6.2.0 - https://github.com/elastic/elasticsearch) (The Apache Software License, Version 2.0) aggs-matrix-stats (org.elasticsearch.plugin:aggs-matrix-stats-client:6.2.0 - https://github.com/elastic/elasticsearch) From 00f152b3dee372f3629ec9b33ff38db899b0651b Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Wed, 28 Sep 2022 17:38:33 +0200 Subject: [PATCH 042/369] X.509 certificates are generated dynamically during tests. (#2096) * X.509 certificates are generated dynamically during tests. Signed-off-by: Lukasz Soszynski Signed-off-by: Lukasz Soszynski <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> --- build.gradle | 5 +- .../framework/certificate/AlgorithmKit.java | 146 ++++++++++++ .../certificate/CertificateData.java | 73 ++++++ .../certificate/CertificateMetadata.java | 220 +++++++++++++++++ .../framework/certificate/Certificates.java | 165 ------------- .../certificate/CertificatesIssuer.java | 224 ++++++++++++++++++ .../CertificatesIssuerFactory.java | 68 ++++++ .../framework/certificate/PemConverter.java | 120 ++++++++++ .../framework/certificate/PublicKeyUsage.java | 72 ++++++ .../certificate/TestCertificates.java | 128 ++++++++-- ...inimumSecuritySettingsSupplierFactory.java | 9 +- 11 files changed, 1048 insertions(+), 182 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/test/framework/certificate/AlgorithmKit.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateData.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java delete mode 100644 src/integrationTest/java/org/opensearch/test/framework/certificate/Certificates.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuerFactory.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/certificate/PemConverter.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java diff --git a/build.gradle b/build.gradle index 726dd06d6f..8adff99244 100644 --- a/build.gradle +++ b/build.gradle @@ -298,7 +298,7 @@ dependencies { implementation 'com.google.guava:guava:30.0-jre' implementation 'org.greenrobot:eventbus:3.2.0' implementation 'commons-cli:commons-cli:1.3.1' - implementation 'org.bouncycastle:bcprov-jdk15on:1.67' + implementation "org.bouncycastle:bcprov-jdk15on:${versions.bouncycastle}" implementation 'org.ldaptive:ldaptive:1.2.3' implementation 'org.apache.httpcomponents:httpclient-cache:4.5.13' implementation 'io.jsonwebtoken:jjwt-api:0.10.8' @@ -328,6 +328,7 @@ dependencies { testImplementation 'org.apache.camel:camel-xmlsecurity:3.14.2' + implementation 'net.shibboleth.utilities:java-support:7.5.1' implementation 'org.opensaml:opensaml-core:3.4.5' implementation 'org.opensaml:opensaml-security-impl:3.4.5' @@ -427,6 +428,8 @@ dependencies { integrationTestImplementation 'org.apache.logging.log4j:log4j-jul:2.17.1' integrationTestImplementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.1' integrationTestImplementation 'org.hamcrest:hamcrest:2.2' + integrationTestImplementation "org.bouncycastle:bcpkix-jdk15on:${versions.bouncycastle}" + integrationTestImplementation "org.bouncycastle:bcutil-jdk15on:${versions.bouncycastle}" } jar { diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/AlgorithmKit.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/AlgorithmKit.java new file mode 100644 index 0000000000..270a2bad6f --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/AlgorithmKit.java @@ -0,0 +1,146 @@ +/* +* Copyright OpenSearch Contributors +* 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. +* +*/ +/* +* Copyright 2021 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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.test.framework.certificate; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.spec.ECGenParameterSpec; +import java.util.function.Supplier; + +import com.google.common.base.Strings; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import static java.util.Objects.requireNonNull; + +/** +* The class determines cryptographic algorithms used for certificate creation. To create certificate it is necessary to generate public +* and private key, so-called key pair. The class encapsulates the process of key pairs creation ({@link #generateKeyPair()}), +* thus determines algorithm used for key pair creation. Additionally, class defines also algorithms used to digitally sign a certificate. +* Please see {@link #getSignatureAlgorithmName()} +*/ +class AlgorithmKit { + + private static final Logger log = LogManager.getLogger(AlgorithmKit.class); + public static final String SIGNATURE_ALGORITHM_SHA_256_WITH_RSA = "SHA256withRSA"; + public static final String SIGNATURE_ALGORITHM_SHA_256_WITH_ECDSA = "SHA256withECDSA"; + + private final String signatureAlgorithmName; + private final Supplier keyPairSupplier; + + private AlgorithmKit(String signatureAlgorithmName, Supplier keyPairSupplier) { + notEmptyAlgorithmName(signatureAlgorithmName); + this.signatureAlgorithmName = signatureAlgorithmName; + this.keyPairSupplier = requireNonNull(keyPairSupplier, "Key pair supplier is required."); + } + + private static void notEmptyAlgorithmName(String signatureAlgorithmName) { + if(Strings.isNullOrEmpty(signatureAlgorithmName)){ + throw new RuntimeException("Algorithm name is required."); + } + } + + /** + * Static factory method. ECDSA algorithm used for key pair creation. Signature algorithm is defined by field + * {@link #SIGNATURE_ALGORITHM_SHA_256_WITH_ECDSA} + * + * @param securityProvider determines cryptographic algorithm implementation + * @param ellipticCurve + * @return new instance of class {@link AlgorithmKit} + */ + public static AlgorithmKit ecdsaSha256withEcdsa(Provider securityProvider, String ellipticCurve) { + notEmptyAlgorithmName(ellipticCurve); + Supplier supplier = ecdsaKeyPairSupplier(requireNonNull(securityProvider, "Security provider is required"), ellipticCurve); + return new AlgorithmKit(SIGNATURE_ALGORITHM_SHA_256_WITH_ECDSA, supplier); + } + + /** + * Static factory method. It creates object of {@link AlgorithmKit} which enforces usage of RSA algorithm for key pair generation. + * Signature algorithm is defined by {@link #SIGNATURE_ALGORITHM_SHA_256_WITH_RSA} + * + * @param securityProvider determines cryptographic algorithm implementation + * @param keySize defines key size for RSA algorithm + * @return new instance of class {@link AlgorithmKit} + */ + public static AlgorithmKit rsaSha256withRsa(Provider securityProvider, int keySize) { + positiveKeySize(keySize); + Supplier supplier = rsaKeyPairSupplier(securityProvider, keySize); + return new AlgorithmKit(SIGNATURE_ALGORITHM_SHA_256_WITH_RSA, supplier); + } + + private static void positiveKeySize(int keySize) { + if(keySize <= 0) { + throw new RuntimeException("Key size must be a positive integer value, provided: " + keySize); + } + } + + /** + * It determines algorithm used for digital signature + * @return algorithm name + */ + public String getSignatureAlgorithmName(){ + return signatureAlgorithmName; + } + + /** + * It creates new private and public key pair + * @return new pair of keys + */ + public KeyPair generateKeyPair(){ + return keyPairSupplier.get(); + } + private static Supplier rsaKeyPairSupplier(Provider securityProvider, int keySize) { + try { + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", securityProvider); + log.info("Initialize key pair generator with keySize: {}", keySize); + generator.initialize(keySize); + return generator::generateKeyPair; + } catch (NoSuchAlgorithmException e) { + String message = "Error while initializing RSA asymmetric key generator."; + log.error(message, e); + throw new RuntimeException(message, e); + } + } + + private static Supplier ecdsaKeyPairSupplier(Provider securityProvider, String ellipticCurve) { + try { + KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", securityProvider); + log.info("Initialize key pair generator with elliptic curve: {}", ellipticCurve); + ECGenParameterSpec ecsp = new ECGenParameterSpec(ellipticCurve); + generator.initialize(ecsp); + return generator::generateKeyPair; + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { + String message = "Error while initializing ECDSA asymmetric key generator."; + log.error(message, e); + throw new RuntimeException(message, e); + } + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateData.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateData.java new file mode 100644 index 0000000000..52aab926dc --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateData.java @@ -0,0 +1,73 @@ +/* +* Copyright OpenSearch Contributors +* 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. +* +*/ +/* +* Copyright 2021 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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.test.framework.certificate; + +import java.security.KeyPair; + +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.cert.X509CertificateHolder; + +/** +* The class contains all data related to Certificate including private key which is considered to be a secret. +*/ +class CertificateData { + + private final X509CertificateHolder certificate; + private final KeyPair keyPair; + + public CertificateData(X509CertificateHolder certificate, KeyPair keyPair) { + this.certificate = certificate; + this.keyPair = keyPair; + } + + /** + * The method returns X.509 certificate encoded in PEM format. PEM format is defined by + * RFC 1421. + * @return Certificate in PEM format + */ + public String certificateInPemFormat() { + return PemConverter.toPem(certificate); + } + + /** + * It returns the private key associated with certificate encoded in PEM format. PEM format is defined by + * RFC 1421. + * @param privateKeyPassword password used for private key encryption. null for unencrypted key. + * @return private key encoded in PEM format + */ + public String privateKeyInPemFormat(String privateKeyPassword) { + return PemConverter.toPem(keyPair.getPrivate(), privateKeyPassword); + } + + X500Name getCertificateSubject() { + return certificate.getSubject(); + } + + KeyPair getKeyPair() { + return keyPair; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java new file mode 100644 index 0000000000..75f933e0d3 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java @@ -0,0 +1,220 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.certificate; + + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import com.google.common.base.Strings; +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.x509.ExtendedKeyUsage; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.KeyUsage; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Objects.requireNonNull; + +/** +*

+* The class represents metadata which should be embedded in certificate to describe a certificate subject (person, company, web server, +* IoT device). The class contains some basic metadata and metadata which should be placed in certificate extensions. +*

+* +*

+* The class is immutable. +*

+* +*/ +class CertificateMetadata { + /** + * Certification subject (person, company, web server, IoT device). The subject of certificate is an owner of the certificate + * (simplification). The format of this field must adhere to RFC 4514. + * @see RFC 4514 + */ + private final String subject; + + /** + * It describes certificate expiration date + */ + private final int validityDays; + + /** + * Optionally used by Open Search to indicate that the certificate can be used by Open Search node to confirm the node identity. The + * value becomes a part of + * SAN (Subject Alternative Name) extension + * + * @see #dnsNames + * @see SAN (Subject Alternative Name) extension + */ + private final String nodeOid; + + /** + * The certificate contains only one {@link #subject}. This is a common limitation when a certificate is used by a web server which is + * associated with a few domains. To overcome this limitation SAN (Subject Alternative Name) extension was introduced. + * The field contains additional subject names which enables creation of so called multi-domain certificates. The extension is defined + * in section 4.2.1.6 of RFC 5280 + * + * @see RFC 5280 + */ + private final List dnsNames; + + /** + * Similar to {@link #dnsNames} but contains IP addresses instead of domains. + */ + private final List ipAddresses; + + /** + * If a private key associated with certificate is used to sign other certificate then this field has to be true. + */ + private final boolean basicConstrainIsCa; + + /** + * Allowed usages for public key associated with certificate + */ + private final Set keyUsages; + + + private CertificateMetadata(String subject, + int validityDays, + String nodeOid, + List dnsNames, + List ipAddresses, + boolean basicConstrainIsCa, + Set keyUsages) { + this.subject = subject; + this.validityDays = validityDays; + this.nodeOid = nodeOid; + this.dnsNames = requireNonNull(dnsNames, "List of dns names must not be null."); + this.ipAddresses = requireNonNull(ipAddresses, "List of IP addresses must not be null"); + this.basicConstrainIsCa = basicConstrainIsCa; + this.keyUsages = requireNonNull(keyUsages, "Key usage set must not be null."); + } + + /** + * Static factory method. It creates metadata which contains only basic information. + * @param subjectName please see {@link #subject} + * @param validityDays please see {@link #validityDays} + * @return new instance of {@link CertificateMetadata} + */ + public static CertificateMetadata basicMetadata(String subjectName, int validityDays) { + return new CertificateMetadata(subjectName, validityDays, null, emptyList(), emptyList(), false, emptySet()); + } + + /** + * It is related to private key associated with certificate. It specifies metadata related to allowed private key usage. + * @param basicConstrainIsCa {@link #basicConstrainIsCa} + * @param keyUsages {@link #keyUsages} + * @return returns newly created instance of {@link CertificateData} + */ + public CertificateMetadata withKeyUsage(boolean basicConstrainIsCa, PublicKeyUsage...keyUsages){ + Set usages = arrayToEnumSet(keyUsages); + return new CertificateMetadata(subject, validityDays, nodeOid, dnsNames, ipAddresses, basicConstrainIsCa, usages); + } + + private > Set arrayToEnumSet(T[] enumArray) { + if((enumArray == null) || (enumArray.length == 0)){ + return Collections.emptySet(); + } + return EnumSet.copyOf(asList(enumArray)); + } + + /** + * The method defines metadata related to SAN (Subject Alternative Name) extension. + * @param nodeOid {@link #nodeOid} + * @param dnsNames {@link #dnsNames} + * @param ipAddresses {@link #ipAddresses} + * @return new instance of {@link CertificateMetadata} + * @see SAN (Subject Alternative Name) extension + */ + public CertificateMetadata withSubjectAlternativeName(String nodeOid, List dnsNames, String...ipAddresses) { + return new CertificateMetadata(subject, validityDays, nodeOid, dnsNames, asList(ipAddresses), basicConstrainIsCa, keyUsages); + } + + /** + * {@link #subject} + * @return Subject name + */ + public String getSubject() { + return subject; + } + + /** + * {@link #validityDays} + * @return determines certificate expiration date + */ + public int getValidityDays() { + return validityDays; + } + + /** + * {@link #basicConstrainIsCa} + * @return Determines if another certificate can be derived from certificate. + */ + public boolean isBasicConstrainIsCa() { + return basicConstrainIsCa; + } + + KeyUsage asKeyUsage() { + Integer keyUsageBitMask = keyUsages + .stream() + .filter(PublicKeyUsage::isNotExtendedUsage) + .map(PublicKeyUsage::asInt) + .reduce(0, (accumulator, currentValue) -> accumulator | currentValue); + return new KeyUsage(keyUsageBitMask); + } + + boolean hasSubjectAlternativeNameExtension() { + return ((ipAddresses.size() + dnsNames.size()) > 0) || (Strings.isNullOrEmpty(nodeOid) == false); + } + + DERSequence createSubjectAlternativeNames() { + List subjectAlternativeNameList = new ArrayList<>(); + if (!Strings.isNullOrEmpty(nodeOid)) { + subjectAlternativeNameList.add(new GeneralName(GeneralName.registeredID, nodeOid)); + } + if (isNotEmpty(dnsNames)) { + for (String dnsName : dnsNames) { + subjectAlternativeNameList.add(new GeneralName(GeneralName.dNSName, dnsName)); + } + } + if (isNotEmpty(ipAddresses)) { + for (String ip : ipAddresses) { + subjectAlternativeNameList.add(new GeneralName(GeneralName.iPAddress, ip)); + } + } + return new DERSequence(subjectAlternativeNameList.toArray(ASN1Encodable[]::new)); + } + + private static boolean isNotEmpty(Collection collection) { + return (collection != null) && (!collection.isEmpty()); + } + + boolean hasExtendedKeyUsage() { + return keyUsages.stream().anyMatch(PublicKeyUsage::isNotExtendedUsage); + } + + ExtendedKeyUsage getExtendedKeyUsage() { + KeyPurposeId[] usages = keyUsages + .stream() + .filter(PublicKeyUsage::isExtendedUsage) + .map(PublicKeyUsage::getKeyPurposeId) + .toArray(KeyPurposeId[]::new); + return new ExtendedKeyUsage(usages); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/Certificates.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/Certificates.java deleted file mode 100644 index 9895fc1484..0000000000 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/Certificates.java +++ /dev/null @@ -1,165 +0,0 @@ -/* -* SPDX-License-Identifier: Apache-2.0 -* -* The OpenSearch Contributors require contributions made to -* this file be licensed under the Apache-2.0 license or a -* compatible open source license. -* -* Modifications Copyright OpenSearch Contributors. See -* GitHub history for details. -*/ - -package org.opensearch.test.framework.certificate; - -/** -* Contains static certificates for the test cluster. -* Note: This is WIP and will be replaced by classes -* that can generate certificates on the fly. This -* class will be removed after that. -*/ -public class Certificates { - - final static String ROOT_CA_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" - + "MIID/jCCAuagAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjzETMBEGCgmSJomT8ixk\n" - + "ARkWA2NvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1w\n" - + "bGUgQ29tIEluYy4xITAfBgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEh\n" - + "MB8GA1UEAwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMB4XDTE4MDQyMjAzNDM0\n" - + "NloXDTI4MDQxOTAzNDM0NlowgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJ\n" - + "kiaJk/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEw\n" - + "HwYDVQQLDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1w\n" - + "bGUgQ29tIEluYy4gUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n" - + "ggEBAK/u+GARP5innhpXK0c0q7s1Su1VTEaIgmZr8VWI6S8amf5cU3ktV7WT9SuV\n" - + "TsAm2i2A5P+Ctw7iZkfnHWlsC3HhPUcd6mvzGZ4moxnamM7r+a9otRp3owYoGStX\n" - + "ylVTQusAjbq9do8CMV4hcBTepCd+0w0v4h6UlXU8xjhj1xeUIz4DKbRgf36q0rv4\n" - + "VIX46X72rMJSETKOSxuwLkov1ZOVbfSlPaygXIxqsHVlj1iMkYRbQmaTib6XWHKf\n" - + "MibDaqDejOhukkCjzpptGZOPFQ8002UtTTNv1TiaKxkjMQJNwz6jfZ53ws3fh1I0\n" - + "RWT6WfM4oeFRFnyFRmc4uYTUgAkCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAf\n" - + "BgNVHSMEGDAWgBSSNQzgDx4rRfZNOfN7X6LmEpdAczAdBgNVHQ4EFgQUkjUM4A8e\n" - + "K0X2TTnze1+i5hKXQHMwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB\n" - + "AQBoQHvwsR34hGO2m8qVR9nQ5Klo5HYPyd6ySKNcT36OZ4AQfaCGsk+SecTi35QF\n" - + "RHL3g2qffED4tKR0RBNGQSgiLavmHGCh3YpDupKq2xhhEeS9oBmQzxanFwWFod4T\n" - + "nnsG2cCejyR9WXoRzHisw0KJWeuNlwjUdJY0xnn16srm1zL/M/f0PvCyh9HU1mF1\n" - + "ivnOSqbDD2Z7JSGyckgKad1Omsg/rr5XYtCeyJeXUPcmpeX6erWJJNTUh6yWC/hY\n" - + "G/dFC4xrJhfXwz6Z0ytUygJO32bJG4Np2iGAwvvgI9EfxzEv/KP+FGrJOvQJAq4/\n" - + "BU36ZAa80W/8TBnqZTkNnqZV\n" - + "-----END CERTIFICATE-----\n" - + ""; - - final static String NODE_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" - + "MIIEyTCCA7GgAwIBAgIGAWLrc1O2MA0GCSqGSIb3DQEBCwUAMIGPMRMwEQYKCZIm\n" - + "iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ\n" - + "RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290\n" - + "IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwHhcNMTgwNDIy\n" - + "MDM0MzQ3WhcNMjgwNDE5MDM0MzQ3WjBeMRIwEAYKCZImiZPyLGQBGRYCZGUxDTAL\n" - + "BgNVBAcMBHRlc3QxDTALBgNVBAoMBG5vZGUxDTALBgNVBAsMBG5vZGUxGzAZBgNV\n" - + "BAMMEm5vZGUtMC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\n" - + "AQoCggEBAJa+f476vLB+AwK53biYByUwN+40D8jMIovGXm6wgT8+9Sbs899dDXgt\n" - + "9CE1Beo65oP1+JUz4c7UHMrCY3ePiDt4cidHVzEQ2g0YoVrQWv0RedS/yx/DKhs8\n" - + "Pw1O715oftP53p/2ijD5DifFv1eKfkhFH+lwny/vMSNxellpl6NxJTiJVnQ9HYOL\n" - + "gf2t971ITJHnAuuxUF48HcuNovW4rhtkXef8kaAN7cE3LU+A9T474ULNCKkEFPIl\n" - + "ZAKN3iJNFdVsxrTU+CUBHzk73Do1cCkEvJZ0ZFjp0Z3y8wLY/gqWGfGVyA9l2CUq\n" - + "eIZNf55PNPtGzOrvvONiui48vBKH1LsCAwEAAaOCAVkwggFVMIG8BgNVHSMEgbQw\n" - + "gbGAFJI1DOAPHitF9k0583tfouYSl0BzoYGVpIGSMIGPMRMwEQYKCZImiZPyLGQB\n" - + "GRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQRXhhbXBs\n" - + "ZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMSEw\n" - + "HwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0GCAQEwHQYDVR0OBBYEFKyv\n" - + "78ZmFjVKM9g7pMConYH7FVBHMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgXg\n" - + "MCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA1BgNVHREELjAsiAUq\n" - + "AwQFBYISbm9kZS0wLmV4YW1wbGUuY29tgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZI\n" - + "hvcNAQELBQADggEBAIOKuyXsFfGv1hI/Lkpd/73QNqjqJdxQclX57GOMWNbOM5H0\n" - + "5/9AOIZ5JQsWULNKN77aHjLRr4owq2jGbpc/Z6kAd+eiatkcpnbtbGrhKpOtoEZy\n" - + "8KuslwkeixpzLDNISSbkeLpXz4xJI1ETMN/VG8ZZP1bjzlHziHHDu0JNZ6TnNzKr\n" - + "XzCGMCohFfem8vnKNnKUneMQMvXd3rzUaAgvtf7Hc2LTBlf4fZzZF1EkwdSXhaMA\n" - + "1lkfHiqOBxtgeDLxCHESZ2fqgVqsWX+t3qHQfivcPW6txtDyrFPRdJOGhiMGzT/t\n" - + "e/9kkAtQRgpTb3skYdIOOUOV0WGQ60kJlFhAzIs=\n" - + "-----END CERTIFICATE-----\n" - + ""; - - final static String NODE_KEY = "-----BEGIN PRIVATE KEY-----\n" - + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCWvn+O+rywfgMC\n" - + "ud24mAclMDfuNA/IzCKLxl5usIE/PvUm7PPfXQ14LfQhNQXqOuaD9fiVM+HO1BzK\n" - + "wmN3j4g7eHInR1cxENoNGKFa0Fr9EXnUv8sfwyobPD8NTu9eaH7T+d6f9oow+Q4n\n" - + "xb9Xin5IRR/pcJ8v7zEjcXpZaZejcSU4iVZ0PR2Di4H9rfe9SEyR5wLrsVBePB3L\n" - + "jaL1uK4bZF3n/JGgDe3BNy1PgPU+O+FCzQipBBTyJWQCjd4iTRXVbMa01PglAR85\n" - + "O9w6NXApBLyWdGRY6dGd8vMC2P4KlhnxlcgPZdglKniGTX+eTzT7Rszq77zjYrou\n" - + "PLwSh9S7AgMBAAECggEABwiohxFoEIwws8XcdKqTWsbfNTw0qFfuHLuK2Htf7IWR\n" - + "htlzn66F3F+4jnwc5IsPCoVFriCXnsEC/usHHSMTZkL+gJqxlNaGdin6DXS/aiOQ\n" - + "nb69SaQfqNmsz4ApZyxVDqsQGkK0vAhDAtQVU45gyhp/nLLmmqP8lPzMirOEodmp\n" - + "U9bA8t/ttrzng7SVAER42f6IVpW0iTKTLyFii0WZbq+ObViyqib9hVFrI6NJuQS+\n" - + "IelcZB0KsSi6rqIjXg1XXyMiIUcSlhq+GfEa18AYgmsbPwMbExate7/8Ci7ZtCbh\n" - + "lx9bves2+eeqq5EMm3sMHyhdcg61yzd5UYXeZhwJkQKBgQDS9YqrAtztvLY2gMgv\n" - + "d+wOjb9awWxYbQTBjx33kf66W+pJ+2j8bI/XX2CpZ98w/oq8VhMqbr9j5b8MfsrF\n" - + "EoQvedA4joUo8sXd4j1mR2qKF4/KLmkgy6YYusNP2UrVSw7sh77bzce+YaVVoO/e\n" - + "0wIVTHuD/QZ6fG6MasOqcbl6hwKBgQC27cQruaHFEXR/16LrMVAX+HyEEv44KOCZ\n" - + "ij5OE4P7F0twb+okngG26+OJV3BtqXf0ULlXJ+YGwXCRf6zUZkld3NMy3bbKPgH6\n" - + "H/nf3BxqS2tudj7+DV52jKtisBghdvtlKs56oc9AAuwOs37DvhptBKUPdzDDqfys\n" - + "Qchv5JQdLQKBgERev+pcqy2Bk6xmYHrB6wdseS/4sByYeIoi0BuEfYH4eB4yFPx6\n" - + "UsQCbVl6CKPgWyZe3ydJbU37D8gE78KfFagtWoZ56j4zMF2RDUUwsB7BNCDamce/\n" - + "OL2bCeG/Erm98cBG3lxufOX+z47I8fTNfkdY2k8UmhzoZwurLm73HJ3RAoGBAKsp\n" - + "6yamuXF2FbYRhUXgjHsBbTD/vJO72/yO2CGiLRpi/5mjfkjo99269trp0C8sJSub\n" - + "5PBiSuADXFsoRgUv+HI1UAEGaCTwxFTQWrRWdtgW3d0sE2EQDVWL5kmfT9TwSeat\n" - + "mSoyAYR5t3tCBNkPJhbgA7pm4mASzHQ50VyxWs25AoGBAKPFx9X2oKhYQa+mW541\n" - + "bbqRuGFMoXIIcr/aeM3LayfLETi48o5NDr2NDP11j4yYuz26YLH0Dj8aKpWuehuH\n" - + "uB27n6j6qu0SVhQi6mMJBe1JrKbzhqMKQjYOoy8VsC2gdj5pCUP/kLQPW7zm9diX\n" - + "CiKTtKgPIeYdigor7V3AHcVT\n" - + "-----END PRIVATE KEY-----\n" - + ""; - - final static String ADMIN_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" - + "MIIEdzCCA1+gAwIBAgIGAWLrc1O4MA0GCSqGSIb3DQEBCwUAMIGPMRMwEQYKCZIm\n" - + "iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ\n" - + "RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290\n" - + "IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwHhcNMTgwNDIy\n" - + "MDM0MzQ3WhcNMjgwNDE5MDM0MzQ3WjBNMQswCQYDVQQGEwJkZTENMAsGA1UEBwwE\n" - + "dGVzdDEPMA0GA1UECgwGY2xpZW50MQ8wDQYDVQQLDAZjbGllbnQxDTALBgNVBAMM\n" - + "BGtpcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCwgBOoO88uMM8\n" - + "dREJsk58Yt4Jn0zwQ2wUThbvy3ICDiEWhiAhUbg6dTggpS5vWWJto9bvaaqgMVoh\n" - + "ElfYHdTDncX3UQNBEP8tqzHON6BFEFSGgJRGLd6f5dri6rK32nCotYS61CFXBFxf\n" - + "WumXjSukjyrcTsdkR3C5QDo2oN7F883MOQqRENPzAtZi9s3jNX48u+/e3yvJzXsB\n" - + "GS9Qmsye6C71enbIujM4CVwDT/7a5jHuaUp6OuNCFbdRPnu/wLYwOS2/yOtzAqk7\n" - + "/PFnPCe7YOa10ShnV/jx2sAHhp7ZQBJgFkkgnIERz9Ws74Au+EbptWnsWuB+LqRL\n" - + "x5G02IzpAgMBAAGjggEYMIIBFDCBvAYDVR0jBIG0MIGxgBSSNQzgDx4rRfZNOfN7\n" - + "X6LmEpdAc6GBlaSBkjCBjzETMBEGCgmSJomT8ixkARkWA2NvbTEXMBUGCgmSJomT\n" - + "8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1wbGUgQ29tIEluYy4xITAfBgNV\n" - + "BAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEhMB8GA1UEAwwYRXhhbXBsZSBD\n" - + "b20gSW5jLiBSb290IENBggEBMB0GA1UdDgQWBBRsdhuHn3MGDvZxOe22+1wliCJB\n" - + "mDAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF4DAWBgNVHSUBAf8EDDAKBggr\n" - + "BgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAkPrUTKKn+/6g0CjhTPBFeX8mKXhG\n" - + "zw5z9Oq+xnwefZwxV82E/tgFsPcwXcJIBg0f43BaVSygPiV7bXqWhxASwn73i24z\n" - + "lveIR4+z56bKIhP6c3twb8WWR9yDcLu2Iroin7dYEm3dfVUrhz/A90WHr6ddwmLL\n" - + "3gcFF2kBu3S3xqM5OmN/tqRXFmo+EvwrdJRiTh4Fsf0tX1ZT07rrGvBFYktK7Kma\n" - + "lqDl4UDCF1UWkiiFubc0Xw+DR6vNAa99E0oaphzvCmITU1wITNnYZTKzVzQ7vUCq\n" - + "kLmXOFLTcxTQpptxSo5xDD3aTpzWGCvjExCKpXQtsITUOYtZc02AGjjPOQ==\n" - + "-----END CERTIFICATE-----\n" - + ""; - - final static String ADMIN_KEY = "-----BEGIN PRIVATE KEY-----\n" - + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCwgBOoO88uMM8\n" - + "dREJsk58Yt4Jn0zwQ2wUThbvy3ICDiEWhiAhUbg6dTggpS5vWWJto9bvaaqgMVoh\n" - + "ElfYHdTDncX3UQNBEP8tqzHON6BFEFSGgJRGLd6f5dri6rK32nCotYS61CFXBFxf\n" - + "WumXjSukjyrcTsdkR3C5QDo2oN7F883MOQqRENPzAtZi9s3jNX48u+/e3yvJzXsB\n" - + "GS9Qmsye6C71enbIujM4CVwDT/7a5jHuaUp6OuNCFbdRPnu/wLYwOS2/yOtzAqk7\n" - + "/PFnPCe7YOa10ShnV/jx2sAHhp7ZQBJgFkkgnIERz9Ws74Au+EbptWnsWuB+LqRL\n" - + "x5G02IzpAgMBAAECggEAEzwnMkeBbqqDgyRqFbO/PgMNvD7i0b/28V0dCtCPEVY6\n" - + "klzrg3RCERP5V9AN8VVkppYjPkCzZ2A4b0JpMUu7ncOmr7HCnoSCj2IfEyePSVg+\n" - + "4OHbbcBOAoDTHiI2myM/M9++8izNS34qGV4t6pfjaDyeQQ/5cBVWNBWnKjS34S5H\n" - + "rJWpAcDgxYk5/ah2Xs2aULZlXDMxbSikjrv+n4JIYTKFQo8ydzL8HQDBRmXAFLjC\n" - + "gNOSHf+5u1JdpY3uPIxK1ugVf8zPZ4/OEB23j56uu7c8+sZ+kZwfRWAQmMhFVG/y\n" - + "OXxoT5mOruBsAw29m2Ijtxg252/YzSTxiDqFziB/eQKBgQDjeVAdi55GW/bvhuqn\n" - + "xME/An8E3hI/FyaaITrMQJUBjiCUaStTEqUgQ6A7ZfY/VX6qafOX7sli1svihrXC\n" - + "uelmKrdve/CFEEqzX9JWWRiPiQ0VZD+EQRsJvX85Tw2UGvVUh6dO3UGPS0BhplMD\n" - + "jeVpyXgZ7Gy5we+DWjfwhYrCmwKBgQDbLmQhRy+IdVljObZmv3QtJ0cyxxZETWzU\n" - + "MKmgBFvcRw+KvNwO+Iy0CHEbDu06Uj63kzI2bK3QdINaSrjgr8iftXIQpBmcgMF+\n" - + "a1l5HtHlCp6RWd55nWQOEvn36IGN3cAaQkXuh4UYM7QfEJaAbzJhyJ+wXA3jWqUd\n" - + "8bDTIAZ0ywKBgFuZ44gyTAc7S2JDa0Up90O/ZpT4NFLRqMrSbNIJg7d/m2EIRNkM\n" - + "HhCzCthAg/wXGo3XYq+hCdnSc4ICCzmiEfoBY6LyPvXmjJ5VDOeWs0xBvVIK74T7\n" - + "jr7KX2wdiHNGs9pZUidw89CXVhK8nptEzcheyA1wZowbK68yamph7HHXAoGBAK3x\n" - + "7D9Iyl1mnDEWPT7f1Gh9UpDm1TIRrDvd/tBihTCVKK13YsFy2d+LD5Bk0TpGyUVR\n" - + "STlOGMdloFUJFh4jA3pUOpkgUr8Uo/sbYN+x6Ov3+I3sH5aupRhSURVA7YhUIz/z\n" - + "tqIt5R+m8Nzygi6dkQNvf+Qruk3jw0S3ahizwsvvAoGAL7do6dTLp832wFVxkEf4\n" - + "gg1M6DswfkgML5V/7GQ3MkIX/Hrmiu+qSuHhDGrp9inZdCDDYg5+uy1+2+RBMRZ3\n" - + "vDUUacvc4Fep05zp7NcjgU5y+/HWpuKVvLIlZAO1MBY4Xinqqii6RdxukIhxw7eT\n" - + "C6TPL5KAcV1R/XAihDhI18Y=\n" - + "-----END PRIVATE KEY-----\n" - + ""; -} diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java new file mode 100644 index 0000000000..887e197369 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java @@ -0,0 +1,224 @@ +/* +* Copyright OpenSearch Contributors +* 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. +* +*/ +/* +* Copyright 2021 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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.test.framework.certificate; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.atomic.AtomicLong; + +import com.google.common.base.Strings; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.RFC4519Style; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.CertIOException; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +import static java.util.Objects.requireNonNull; + +/** +*

+* The class is used to generate public key certificate. The class hides low level details related to certificate creation and +* usage of underlying Bouncy Castle library. +*

+*

+* The public key certificate according to its name contains a public key and some metadata. The metadata describes an entity (human, +* company, web server, IoT device, etc.) which is an owner of private key associated with the certificate (private key is not included +* into certificate and is a kind of secret). The responsibility of the class is to issue a certificate. To issue a certificate it is +* necessary to provide metadata which is embedded in the certificates. The metadata is represented by the class +* {@link CertificateMetadata}. Furthermore, the class needs a public key which also must be embedded in the certificate. To obtain public +* and private key pair the class uses {@link AlgorithmKit}. The result of creating certificate is data structure {@link CertificateData}. +* The class {@link CertificateData} contains entire information which is necessary to use the certificate by its owner, that is: +* certificate and private key. +*

+* +*

+* The class is able to create self-signed certificates or certificates signed by some entity. To create a self signed certificate +* the method {@link #issueSignedCertificate(CertificateMetadata, CertificateData)} is used, whereas to create signed certificates +* the method {@link #issueSignedCertificate(CertificateMetadata, CertificateData)} is employed. +*

+*

+* The instance of the class can be obtained by invocation of static method defined in class {@link CertificatesIssuerFactory}. +*

+*/ +class CertificatesIssuer { + + private static final Logger log = LogManager.getLogger(CertificatesIssuer.class); + + private static final AtomicLong ID_COUNTER = new AtomicLong(System.currentTimeMillis()); + + private final Provider securityProvider; + private final AlgorithmKit algorithmKit; + private final JcaX509ExtensionUtils extUtils; + + CertificatesIssuer(Provider securityProvider, AlgorithmKit algorithmKit) { + this.securityProvider = securityProvider; + this.algorithmKit = algorithmKit; + this.extUtils = getExtUtils(); + } + + /** + * The method creates a certificate with provided metadata and public key obtained from {@link #algorithmKit}. The result of invocation + * contains required data to use a certificate by its owner. + * + * @param certificateMetadata metadata which should be embedded into created certificate + * @return {@link CertificateData} which contain certificate and private key associated with the certificate. + */ + public CertificateData issueSelfSignedCertificate(CertificateMetadata certificateMetadata) { + try { + KeyPair publicAndPrivateKey = algorithmKit.generateKeyPair(); + X500Name issuerName = stringToX500Name(requireNonNull(certificateMetadata.getSubject(), "Certificate metadata are required.")); + X509CertificateHolder x509CertificateHolder = buildCertificateHolder( + certificateMetadata, + issuerName, + publicAndPrivateKey.getPublic(), + publicAndPrivateKey); + return new CertificateData(x509CertificateHolder, publicAndPrivateKey); + } catch (OperatorCreationException | CertIOException e) { + log.error("Error while generating certificate", e); + throw new RuntimeException("Error while generating self signed certificate", e); + } + } + + /** + * The method is similar to {@link #issueSignedCertificate(CertificateMetadata, CertificateData)} but additionally it signs created + * certificate using data from parentCertificateData. + * + * @param metadata metadata which should be embedded into created certificate + * @param parentCertificateData data required to signe a newly issued certificate (private key among others things). + * @return {@link CertificateData} which contain certificate and private key associated with the certificate. + */ + public CertificateData issueSignedCertificate(CertificateMetadata metadata, CertificateData parentCertificateData) { + try { + KeyPair publicAndPrivateKey = algorithmKit.generateKeyPair(); + KeyPair parentKeyPair = requireNonNull(parentCertificateData, "Issuer certificate data are required") + .getKeyPair(); + X500Name issuerName = parentCertificateData.getCertificateSubject(); + var x509CertificateHolder = buildCertificateHolder(requireNonNull(metadata, "Certificate metadata are required"), + issuerName, + publicAndPrivateKey.getPublic(), + parentKeyPair); + return new CertificateData(x509CertificateHolder, publicAndPrivateKey); + } catch (OperatorCreationException | CertIOException e) { + log.error("Error while generating signed certificate", e); + throw new RuntimeException("Error while generating signed certificate", e); + } + } + + private X509CertificateHolder buildCertificateHolder(CertificateMetadata certificateMetadata, + X500Name issuerName, + PublicKey certificatePublicKey, + KeyPair parentKeyPair) throws CertIOException, OperatorCreationException { + X509v3CertificateBuilder builder = builderWithBasicExtensions(certificateMetadata, issuerName, certificatePublicKey, parentKeyPair.getPublic()); + addSubjectAlternativeNameExtension(builder, certificateMetadata); + addExtendedKeyUsageExtension(builder, certificateMetadata); + return builder.build(createContentSigner(parentKeyPair.getPrivate())); + } + + private ContentSigner createContentSigner(PrivateKey privateKey) throws OperatorCreationException { + return new JcaContentSignerBuilder(algorithmKit.getSignatureAlgorithmName()) + .setProvider(securityProvider) + .build(privateKey); + } + + private void addExtendedKeyUsageExtension(X509v3CertificateBuilder builder, CertificateMetadata certificateMetadata) throws CertIOException { + if(certificateMetadata.hasExtendedKeyUsage()) { + builder.addExtension(Extension.extendedKeyUsage, true, certificateMetadata.getExtendedKeyUsage()); + } + } + + private X509v3CertificateBuilder builderWithBasicExtensions(CertificateMetadata certificateMetadata, + X500Name issuerName, + PublicKey certificatePublicKey, + PublicKey parentPublicKey) throws CertIOException { + X500Name subjectName = stringToX500Name(certificateMetadata.getSubject()); + Date validityStartDate = new Date(System.currentTimeMillis() - (24 * 3600 * 1000)); + Date validityEndDate = getEndDate(validityStartDate, certificateMetadata.getValidityDays()); + + BigInteger certificateSerialNumber = generateNextCertificateSerialNumber(); + return new X509v3CertificateBuilder(issuerName, certificateSerialNumber, validityStartDate, + validityEndDate, subjectName, SubjectPublicKeyInfo.getInstance(certificatePublicKey.getEncoded())) + .addExtension(Extension.basicConstraints, true, new BasicConstraints(certificateMetadata.isBasicConstrainIsCa())) + .addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(parentPublicKey)) + .addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(certificatePublicKey)) + .addExtension(Extension.keyUsage, true, certificateMetadata.asKeyUsage()); + } + + private void addSubjectAlternativeNameExtension(X509v3CertificateBuilder builder, CertificateMetadata metadata) throws CertIOException { + if(metadata.hasSubjectAlternativeNameExtension()){ + DERSequence subjectAlternativeNames = metadata.createSubjectAlternativeNames(); + builder.addExtension(Extension.subjectAlternativeName, false, subjectAlternativeNames); + } + } + + private Date getEndDate(Date startDate, int validityDays) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(startDate); + calendar.add(Calendar.DATE, validityDays); + return calendar.getTime(); + } + + private static JcaX509ExtensionUtils getExtUtils() { + try { + return new JcaX509ExtensionUtils(); + } catch (NoSuchAlgorithmException e) { + log.error("Getting certificate extension utils failed", e); + throw new RuntimeException("Getting certificate extension utils failed", e); + } + } + + private X500Name stringToX500Name(String distinguishedName) { + if (Strings.isNullOrEmpty(distinguishedName)) { + throw new RuntimeException("No DN (distinguished name) must not be null or empty"); + } + try { + return new X500Name(RFC4519Style.INSTANCE, distinguishedName); + } catch (IllegalArgumentException e) { + String message = String.format("Invalid DN (distinguished name) specified for %s certificate.", distinguishedName); + throw new RuntimeException(message, e); + } + } + + private BigInteger generateNextCertificateSerialNumber() { + return BigInteger.valueOf(ID_COUNTER.incrementAndGet()); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuerFactory.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuerFactory.java new file mode 100644 index 0000000000..823a0c04ca --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuerFactory.java @@ -0,0 +1,68 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.certificate; + +import java.security.Provider; +import java.util.Optional; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import static org.opensearch.test.framework.certificate.AlgorithmKit.ecdsaSha256withEcdsa; +import static org.opensearch.test.framework.certificate.AlgorithmKit.rsaSha256withRsa; + +/** +* The class defines static factory method for class {@link CertificatesIssuer}. Object of class {@link CertificatesIssuer} created by +* various factory methods differs in terms of cryptographic algorithms used for certificates creation. +* +*/ +class CertificatesIssuerFactory { + + private static final int KEY_SIZE = 2048; + + private CertificatesIssuerFactory() { + + } + + private static final Provider DEFAULT_SECURITY_PROVIDER = new BouncyCastleProvider(); + + /** + * @see {@link #rsaBaseCertificateIssuer(Provider)} + */ + public static CertificatesIssuer rsaBaseCertificateIssuer() { + return rsaBaseCertificateIssuer(null); + } + + /** + * The method creates {@link CertificatesIssuer} which uses RSA algorithm for certificate creation. + * @param securityProvider determines cryptographic algorithm implementation, can be null. + * @return new instance of {@link CertificatesIssuer} + */ + public static CertificatesIssuer rsaBaseCertificateIssuer(Provider securityProvider) { + Provider provider = Optional.ofNullable(securityProvider).orElse(DEFAULT_SECURITY_PROVIDER); + return new CertificatesIssuer(provider, rsaSha256withRsa(provider, KEY_SIZE)); + } + + /** + * {@link #rsaBaseCertificateIssuer(Provider)} + */ + public static CertificatesIssuer ecdsaBaseCertificatesIssuer() { + return ecdsaBaseCertificatesIssuer(null); + } + + /** + * It creates {@link CertificatesIssuer} which uses asymmetric cryptography algorithm which relays on elliptic curves. + * @param securityProvider determines cryptographic algorithm implementation, can be null. + * @return new instance of {@link CertificatesIssuer} + */ + public static CertificatesIssuer ecdsaBaseCertificatesIssuer(Provider securityProvider) { + Provider provider = Optional.ofNullable(securityProvider).orElse(DEFAULT_SECURITY_PROVIDER); + return new CertificatesIssuer(provider, ecdsaSha256withEcdsa(securityProvider, "P-384")); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/PemConverter.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/PemConverter.java new file mode 100644 index 0000000000..a92e2036d7 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/PemConverter.java @@ -0,0 +1,120 @@ +/* +* Copyright OpenSearch Contributors +* 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. +* +*/ +/* +* Copyright 2021 floragunn GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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.test.framework.certificate; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.security.PrivateKey; +import java.security.SecureRandom; + +import com.google.common.base.Strings; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.openssl.PKCS8Generator; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.OutputEncryptor; +import org.bouncycastle.util.io.pem.PemGenerationException; +import org.bouncycastle.util.io.pem.PemObject; + +import static java.util.Objects.requireNonNull; + +/** +* The class provides a method useful for converting certificate and private key into PEM format +* @see RFC 1421 +*/ +class PemConverter { + + private PemConverter() { + } + + private static final Logger log = LogManager.getLogger(PemConverter.class); + private static final SecureRandom secureRandom = new SecureRandom(); + + /** + * It converts certificate represented by {@link X509CertificateHolder} object to PEM format + * @param certificate is a certificate to convert + * @return {@link String} which contains PEM encoded certificate + */ + public static String toPem(X509CertificateHolder certificate) { + StringWriter stringWriter = new StringWriter(); + try (JcaPEMWriter writer = new JcaPEMWriter(stringWriter)) { + writer.writeObject(requireNonNull(certificate, "Certificate is required.")); + } catch (Exception e) { + throw new RuntimeException("Cannot write certificate in PEM format", e); + } + return stringWriter.toString(); + } + + /** + * It converts private key represented by class {@link PrivateKey} to PEM format. + * @param privateKey is a private key, cannot be null + * @param privateKeyPassword is a password used to encode private key, null for unencrypted private key + * @return {@link String} which contains PEM encoded private key + */ + public static String toPem(PrivateKey privateKey, String privateKeyPassword) { + try(StringWriter stringWriter = new StringWriter()){ + savePrivateKey(stringWriter, requireNonNull(privateKey, "Private key is required."), privateKeyPassword); + return stringWriter.toString(); + } catch (IOException e) { + throw new RuntimeException("Cannot convert private key into PEM format.", e); + } + } + + private static void savePrivateKey(Writer out, PrivateKey privateKey, String privateKeyPassword) { + try (JcaPEMWriter writer = new JcaPEMWriter(out)) { + writer.writeObject(createPkcs8PrivateKeyPem(privateKey, privateKeyPassword)); + } catch (Exception e) { + log.error("Error while writing private key.", e); + throw new RuntimeException("Error while writing private key ", e); + } + } + + private static PemObject createPkcs8PrivateKeyPem(PrivateKey privateKey, String password) { + try { + OutputEncryptor outputEncryptor = password == null ? null : getPasswordEncryptor(password); + return new PKCS8Generator(PrivateKeyInfo.getInstance(privateKey.getEncoded()), outputEncryptor).generate(); + } catch (PemGenerationException | OperatorCreationException e) { + log.error("Creating PKCS8 private key failed", e); + throw new RuntimeException("Creating PKCS8 private key failed", e); + } + } + + private static OutputEncryptor getPasswordEncryptor(String password) throws OperatorCreationException { + if (!Strings.isNullOrEmpty(password)) { + JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(PKCS8Generator.PBE_SHA1_3DES); + encryptorBuilder.setRandom(secureRandom); + encryptorBuilder.setPassword(password.toCharArray()); + return encryptorBuilder.build(); + } + return null; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java new file mode 100644 index 0000000000..8e9aba68e5 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java @@ -0,0 +1,72 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.certificate; + +import java.util.Objects; + +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.KeyUsage; + +/** +* The class is associated with certificate extensions related to key usages. These extensions are defined by +* RFC 5280 and describes allowed usage of public kay which is embedded in +* certificate. The class is related to the following extensions: +*
    +*
  1. Key Usage, defined in section 4.2.1.3
  2. +*
  3. Extended Key Usage, defined in section 4.2.1.12
  4. +*
+* +* @see RFC 5280 +*/ +enum PublicKeyUsage { + DIGITAL_SIGNATURE(KeyUsage.digitalSignature), + KEY_CERT_SIGN(KeyUsage.keyCertSign), + CRL_SIGN(KeyUsage.cRLSign), + NON_REPUDIATION(KeyUsage.nonRepudiation), + KEY_ENCIPHERMENT(KeyUsage.keyEncipherment), + + SERVER_AUTH(KeyPurposeId.id_kp_serverAuth), + + CLIENT_AUTH(KeyPurposeId.id_kp_clientAuth); + + private final int keyUsage; + private final KeyPurposeId id; + + PublicKeyUsage(int keyUsage) { + this.keyUsage = keyUsage; + this.id = null; + } + + PublicKeyUsage(KeyPurposeId id) { + this.id = Objects.requireNonNull(id, "Key purpose id is required."); + this.keyUsage = 0; + } + + boolean isExtendedUsage() { + return this.id != null; + } + + boolean isNotExtendedUsage() { + return this.id == null; + } + + int asInt(){ + if(isExtendedUsage()) { + throw new RuntimeException("Integer value is not available for extended key usage"); + } + return keyUsage; + } + KeyPurposeId getKeyPurposeId() { + if(isExtendedUsage() == false){ + throw new RuntimeException("Key purpose id is not available."); + } + return id; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java index d810f0b32f..df9bcfa41d 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java @@ -32,37 +32,137 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.opensearch.test.framework.certificate.PublicKeyUsage.CLIENT_AUTH; +import static org.opensearch.test.framework.certificate.PublicKeyUsage.CRL_SIGN; +import static org.opensearch.test.framework.certificate.PublicKeyUsage.DIGITAL_SIGNATURE; +import static org.opensearch.test.framework.certificate.PublicKeyUsage.KEY_CERT_SIGN; +import static org.opensearch.test.framework.certificate.PublicKeyUsage.KEY_ENCIPHERMENT; +import static org.opensearch.test.framework.certificate.PublicKeyUsage.NON_REPUDIATION; +import static org.opensearch.test.framework.certificate.PublicKeyUsage.SERVER_AUTH; /** -* Provides TLS certificates required in test cases. -* WIP At the moment the certificates are hard coded. -* This will be replaced by classes -* that can generate certificates on the fly. +* It provides TLS certificates required in test cases. The certificates are generated during process of creation objects of the class. +* The class exposes method which can be used to write certificates and private keys in temporally files. */ public class TestCertificates { + public static final Integer MAX_NUMBER_OF_NODE_CERTIFICATES = 3; + + private static final String CA_SUBJECT = "DC=com,DC=example,O=Example Com Inc.,OU=Example Com Inc. Root CA,CN=Example Com Inc. Root CA"; + private static final String ADMIN_DN = "CN=kirk,OU=client,O=client,L=test,C=de"; + private static final int CERTIFICATE_VALIDITY_DAYS = 365; + private static final String CERTIFICATE_FILE_EXTENSION = ".cert"; + private static final String KEY_FILE_EXTENSION = ".key"; + private final CertificateData caCertificate; + + private final CertificateData adminCertificate; + private final List nodeCertificates; + + public TestCertificates() { + this.caCertificate = createCaCertificate(); + this.nodeCertificates = IntStream.range(0, MAX_NUMBER_OF_NODE_CERTIFICATES) + .mapToObj(this::createNodeCertificate) + .collect(Collectors.toList()); + this.adminCertificate = createAdminCertificate(); + } + + + private CertificateData createCaCertificate() { + CertificateMetadata metadata = CertificateMetadata.basicMetadata(CA_SUBJECT, CERTIFICATE_VALIDITY_DAYS) + .withKeyUsage(true, DIGITAL_SIGNATURE, KEY_CERT_SIGN, CRL_SIGN); + return CertificatesIssuerFactory + .rsaBaseCertificateIssuer() + .issueSelfSignedCertificate(metadata); + } + + private CertificateData createAdminCertificate() { + CertificateMetadata metadata = CertificateMetadata.basicMetadata(ADMIN_DN, CERTIFICATE_VALIDITY_DAYS) + .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH); + return CertificatesIssuerFactory + .rsaBaseCertificateIssuer() + .issueSelfSignedCertificate(metadata); + } + + /** + * It returns the most trusted certificate. Certificates for nodes and users are derived from this certificate. + * @return file which contains certificate in PEM format, defined by RFC 1421 + */ public File getRootCertificate() { - return createTempFile("root", ".cert", Certificates.ROOT_CA_CERTIFICATE); + return createTempFile("root", CERTIFICATE_FILE_EXTENSION, caCertificate.certificateInPemFormat()); } + /** + * Certificate for Open Search node. The certificate is derived from root certificate, returned by method {@link #getRootCertificate()} + * @param node is a node index. It has to be less than {@link #MAX_NUMBER_OF_NODE_CERTIFICATES} + * @return file which contains certificate in PEM format, defined by RFC 1421 + */ public File getNodeCertificate(int node) { - return createTempFile("node-" + node, ".cert", Certificates.NODE_CERTIFICATE); + isCorrectNodeNumber(node); + CertificateData certificateData = nodeCertificates.get(node); + return createTempFile("node-" + node, CERTIFICATE_FILE_EXTENSION, certificateData.certificateInPemFormat()); + } + + private void isCorrectNodeNumber(int node) { + if (node >= MAX_NUMBER_OF_NODE_CERTIFICATES) { + String message = String.format("Cannot get certificate for node %d, number of created certificates for nodes is %d", node, + MAX_NUMBER_OF_NODE_CERTIFICATES); + throw new RuntimeException(message); + } + } + + private CertificateData createNodeCertificate(Integer node) { + String subject = String.format("DC=de,L=test,O=node,OU=node,CN=node-%d.example.com", node); + String domain = String.format("node-%d.example.com", node); + CertificateMetadata metadata = CertificateMetadata.basicMetadata(subject, CERTIFICATE_VALIDITY_DAYS) + .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH) + .withSubjectAlternativeName("1.2.3.4.5.5", List.of(domain, "localhost"), "127.0.0.1"); + return CertificatesIssuerFactory + .rsaBaseCertificateIssuer() + .issueSignedCertificate(metadata, caCertificate); } - public File getNodeKey(int node) { - return createTempFile("node-" + node, ".key", Certificates.NODE_KEY); + /** + * It returns private key associated with node certificate returned by method {@link #getNodeCertificate(int)} + * + * @param node is a node index. It has to be less than {@link #MAX_NUMBER_OF_NODE_CERTIFICATES} + * @param privateKeyPassword is a password used to encode private key, can be null to retrieve unencrypted key. + * @return file which contains private key encoded in PEM format, defined + * by RFC 1421 + * @throws IOException + */ + public File getNodeKey(int node, String privateKeyPassword) { + CertificateData certificateData = nodeCertificates.get(node); + return createTempFile("node-" + node, KEY_FILE_EXTENSION, certificateData.privateKeyInPemFormat(privateKeyPassword)); } + /** + * Certificate which proofs admin user identity. Certificate is derived from root certificate returned by + * method {@link #getRootCertificate()} + * @return file which contains certificate in PEM format, defined by RFC 1421 + * @throws IOException + */ public File getAdminCertificate() { - return createTempFile("admin", ".cert", Certificates.ADMIN_CERTIFICATE); + return createTempFile("admin", CERTIFICATE_FILE_EXTENSION, adminCertificate.certificateInPemFormat()); } - public File getAdminKey() { - return createTempFile("admin", ".key", Certificates.ADMIN_KEY); + /** + * It returns private key associated with admin certificate returned by {@link #getAdminCertificate()}. + * + * @param privateKeyPassword is a password used to encode private key, can be null to retrieve unencrypted key. + * @return file which contains private key encoded in PEM format, defined + * by RFC 1421 + * @throws IOException + */ + public File getAdminKey(String privateKeyPassword) throws IOException { + return createTempFile("admin", KEY_FILE_EXTENSION, adminCertificate.privateKeyInPemFormat(privateKeyPassword)); } public String[] getAdminDNs() { - return new String[] {"CN=kirk,OU=client,O=client,L=test,C=de"}; + return new String[] {ADMIN_DN}; } private File createTempFile(String name, String suffix, String contents) { @@ -70,8 +170,8 @@ private File createTempFile(String name, String suffix, String contents) { Path path = Files.createTempFile(name, suffix); Files.writeString(path, contents); return path.toFile(); - } catch (IOException e) { - throw new RuntimeException(String.format("Error while creating a temp file: %s%s", name, suffix), e); + } catch (IOException ex) { + throw new RuntimeException("Cannot create temp file with name " + name + " and suffix " + suffix); } } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java index 37cf69b266..b2660c87e9 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java @@ -33,6 +33,9 @@ public class MinimumSecuritySettingsSupplierFactory { + private final String PRIVATE_KEY_HTTP_PASSWORD = "aWVV63OJ4qzZyPrBwl2MFny4ZV8lQRZchjL"; + private final String PRIVATE_KEY_TRANSPORT_PASSWORD = "iWbUv9w79sbd5tcxvSJNfHXS9GhcPCvdw9x"; + private TestCertificates testCertificates; public MinimumSecuritySettingsSupplierFactory(TestCertificates testCertificates) { @@ -57,12 +60,14 @@ private Settings.Builder minimumOpenSearchSettingsBuilder(int node, boolean sslO builder.put("plugins.security.ssl.transport.pemtrustedcas_filepath", testCertificates.getRootCertificate().getAbsolutePath()); builder.put("plugins.security.ssl.transport.pemcert_filepath", testCertificates.getNodeCertificate(node).getAbsolutePath()); - builder.put("plugins.security.ssl.transport.pemkey_filepath", testCertificates.getNodeKey(node).getAbsolutePath()); + builder.put("plugins.security.ssl.transport.pemkey_filepath", testCertificates.getNodeKey(node, PRIVATE_KEY_TRANSPORT_PASSWORD).getAbsolutePath()); + builder.put("plugins.security.ssl.transport.pemkey_password", PRIVATE_KEY_TRANSPORT_PASSWORD); builder.put("plugins.security.ssl.http.enabled", true); builder.put("plugins.security.ssl.http.pemtrustedcas_filepath", testCertificates.getRootCertificate().getAbsolutePath()); builder.put("plugins.security.ssl.http.pemcert_filepath", testCertificates.getNodeCertificate(node).getAbsolutePath()); - builder.put("plugins.security.ssl.http.pemkey_filepath", testCertificates.getNodeKey(node).getAbsolutePath()); + builder.put("plugins.security.ssl.http.pemkey_filepath", testCertificates.getNodeKey(node, PRIVATE_KEY_HTTP_PASSWORD).getAbsolutePath()); + builder.put("plugins.security.ssl.http.pemkey_password", PRIVATE_KEY_HTTP_PASSWORD); builder.putList("plugins.security.authcz.admin_dn", testCertificates.getAdminDNs()); From aeb7ab5fa49a0c462a9f94472f98ff374768a488 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Wed, 28 Sep 2022 09:03:12 -0700 Subject: [PATCH 043/369] Keep version upgrade test working past v2 (#2106) --- .github/workflows/ci.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f00e5bef68..8ced7e509d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,13 +125,16 @@ jobs: - run: ./gradlew clean assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip - - run: | - ## 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)) - ./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: Verify updateVersion gradle tasks works + env: + ExpectedVersionString: "opensearch_version: 2.1.0-SNAPSHOT" + run: | + ## Make sure the current doesn't match the test version + test "$(./gradlew properties | grep opensearch.version)" != "$ExpectedVersionString" + ## Update the new version to 2.1.0 + ./gradlew clean updateVersion -DnewVersion=2.1.0-SNAPSHOT + ## Make sure the version matches expectation + test "$(./gradlew properties | grep opensearch.version)" = "$ExpectedVersionString" - name: List files in the build directory if there was an error run: ls -al ./build/distributions/ From 422f82878bccf89dbba7c1ab784d9c6cc778c92f Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Thu, 29 Sep 2022 15:43:01 -0400 Subject: [PATCH 044/369] Support for HTTP/2 (server-side) (#2051) * Support for HTTP/2 (server-side) Signed-off-by: Andriy Redko * Addressing code review comments Signed-off-by: Andriy Redko * Fixed ClusterManager compilation issues Signed-off-by: Andriy Redko * Fixing bwc test version Signed-off-by: Andriy Redko * Removed outdated comment Signed-off-by: Andriy Redko * Fixing exception propagation from Http2OrHttpHandler to server transport Signed-off-by: Andriy Redko * Switch to OpenSearch v3.0 Rev'ed version number and fixed compilation issues Signed-off-by: Andriy Redko Signed-off-by: Peter Nied Signed-off-by: Andriy Redko Signed-off-by: Peter Nied Co-authored-by: Peter Nied --- .github/workflows/ci.yml | 6 +-- build.gradle | 3 +- bwc-test/build.gradle | 8 +-- .../framework/cluster/ClusterManager.java | 14 +++--- .../kerberos/HTTPSpnegoAuthenticator.java | 2 +- .../saml/SamlFilesystemMetadataResolver.java | 2 +- .../security/OpenSearchSecurityPlugin.java | 4 +- .../auditlog/impl/AbstractAuditLog.java | 2 +- .../ConfigurationRepository.java | 2 +- .../security/ssl/DefaultSecurityKeyStore.java | 50 ++++++++++++++----- .../SecuritySSLNettyHttpServerTransport.java | 46 ++++++++++++++++- .../security/ssl/util/SSLRequestHelper.java | 16 +++--- .../security/support/PemKeyReader.java | 4 +- .../security/RolesInjectorIntegTest.java | 8 +-- .../security/RolesValidationIntegTest.java | 8 +-- .../security/SlowIntegrationTests.java | 8 +-- .../TransportUserInjectorIntegTest.java | 12 ++--- .../ccstest/CrossClusterSearchTests.java | 6 +-- .../dlic/dlsfls/CCReplicationTest.java | 10 ++-- .../opensearch/security/ssl/OpenSSLTest.java | 4 +- .../org/opensearch/security/ssl/SSLTest.java | 6 +-- .../helper/cluster/ClusterConfiguration.java | 14 +++--- 22 files changed, 155 insertions(+), 80 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ced7e509d..082b722e0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,9 +72,9 @@ jobs: cp -r build/ ./bwc-test/ mkdir ./bwc-test/src/test/resources/security_plugin_version_no_snapshot cp build/distributions/opensearch-security-${security_plugin_version_no_snapshot}.zip ./bwc-test/src/test/resources/${security_plugin_version_no_snapshot} - mkdir bwc-test/src/test/resources/2.3.0.0 - wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.3.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.3.0.0.zip - mv opensearch-security-2.3.0.0.zip bwc-test/src/test/resources/2.3.0.0/ + mkdir bwc-test/src/test/resources/2.4.0.0 + wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.4.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.4.0.0.zip + mv opensearch-security-2.4.0.0.zip bwc-test/src/test/resources/2.4.0.0/ cd bwc-test/ ./gradlew bwcTestSuite -Dtests.security.manager=false diff --git a/build.gradle b/build.gradle index 8adff99244..4a56a54d81 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ import org.opensearch.gradle.test.RestIntegTestTask buildscript { ext { - opensearch_version = System.getProperty("opensearch.version", "2.4.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "3.0.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") @@ -56,6 +56,7 @@ plugins { id 'checkstyle' id 'nebula.ospackage' version "8.3.0" id "org.gradle.test-retry" version "1.3.1" + id 'eclipse' } allprojects { diff --git a/bwc-test/build.gradle b/bwc-test/build.gradle index 583d9d173c..9badfd1c85 100644 --- a/bwc-test/build.gradle +++ b/bwc-test/build.gradle @@ -47,7 +47,7 @@ ext { buildscript { ext { - opensearch_version = System.getProperty("opensearch.version", "2.4.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "3.0.0-SNAPSHOT") opensearch_group = "org.opensearch" } repositories { @@ -73,16 +73,16 @@ dependencies { testImplementation "org.opensearch.test:framework:${opensearch_version}" } -String bwcVersion = "2.3.0.0"; +String bwcVersion = "2.4.0.0"; String baseName = "securityBwcCluster" String bwcFilePath = "src/test/resources/" -String projectVersion = "2.4.0.0" +String projectVersion = "3.0.0.0" 2.times {i -> testClusters { "${baseName}$i" { testDistribution = "ARCHIVE" - versions = ["2.3.0","2.4.0"] + versions = ["2.4.0","3.0.0"] numberOfNodes = 3 plugin(provider(new Callable() { @Override diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java index 005321b24c..760820a3b6 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java @@ -36,13 +36,13 @@ import java.util.Objects; import java.util.stream.Collectors; -import org.opensearch.index.reindex.ReindexPlugin; -import org.opensearch.join.ParentJoinPlugin; -import org.opensearch.percolator.PercolatorPlugin; +import org.opensearch.index.reindex.ReindexModulePlugin; +import org.opensearch.join.ParentJoinModulePlugin; +import org.opensearch.percolator.PercolatorModulePlugin; import org.opensearch.plugins.Plugin; -import org.opensearch.search.aggregations.matrix.MatrixAggregationPlugin; +import org.opensearch.search.aggregations.matrix.MatrixAggregationModulePlugin; import org.opensearch.security.OpenSearchSecurityPlugin; -import org.opensearch.transport.Netty4Plugin; +import org.opensearch.transport.Netty4ModulePlugin; import static java.util.Collections.unmodifiableList; import static org.opensearch.test.framework.cluster.NodeType.CLIENT; @@ -98,8 +98,8 @@ public int getClientNodes() { public static class NodeSettings { - private final static List> DEFAULT_PLUGINS = List.of(Netty4Plugin.class, OpenSearchSecurityPlugin.class, - MatrixAggregationPlugin.class, ParentJoinPlugin.class, PercolatorPlugin.class, ReindexPlugin.class); + private final static List> DEFAULT_PLUGINS = List.of(Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, + MatrixAggregationModulePlugin.class, ParentJoinModulePlugin.class, PercolatorModulePlugin.class, ReindexModulePlugin.class); public final boolean clusterManagerNode; public final boolean dataNode; public final List> plugins; diff --git a/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java index 812ca4f82f..3603aeb94e 100644 --- a/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java @@ -70,7 +70,7 @@ public class HTTPSpnegoAuthenticator implements HTTPAuthenticator { public HTTPSpnegoAuthenticator(final Settings settings, final Path configPath) { super(); try { - final Path configDir = new Environment(settings, configPath).configFile(); + final Path configDir = new Environment(settings, configPath).configDir(); final String krb5PathSetting = settings.get("plugins.security.kerberos.krb5_filepath"); final SecurityManager sm = System.getSecurityManager(); diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java b/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java index 80f272b43b..302b1f41ea 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/SamlFilesystemMetadataResolver.java @@ -51,6 +51,6 @@ public byte[] run() throws ResolverException { private static File getMetadataFile(String filePath, Settings settings, Path configPath) { Environment env = new Environment(settings, configPath); - return env.configFile().resolve(filePath).toAbsolutePath().toFile(); + return env.configDir().resolve(filePath).toAbsolutePath().toFile(); } } diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 66530cfaed..69dce00d41 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -326,7 +326,7 @@ public Object run() { final List filesWithWrongPermissions = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { - final Path confPath = new Environment(settings, configPath).configFile().toAbsolutePath(); + final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().filter(p -> checkFilePermissions(p)).collect(Collectors.toList()); @@ -356,7 +356,7 @@ public List run() { final List files = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { - final Path confPath = new Environment(settings, configPath).configFile().toAbsolutePath(); + final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().map(p -> sha256(p)).collect(Collectors.toList()); 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 bc5e240c77..d6f59028fa 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java @@ -560,7 +560,7 @@ public Map run() { (key.contains("filepath") || key.contains("file_path"))) { String value = settings.get(key); if(value != null && !value.isEmpty()) { - Path path = value.startsWith("/")?Paths.get(value):environment.configFile().resolve(value); + Path path = value.startsWith("/")?Paths.get(value):environment.configDir().resolve(value); paths.put(key, path); } } diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java index 81f5c5d60d..5a8c9a069c 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java @@ -120,7 +120,7 @@ public void run() { try { String lookupDir = System.getProperty("security.default_init.dir"); - final String cd = lookupDir != null? (lookupDir+"/") : new Environment(settings, configPath).configFile().toAbsolutePath().toString()+"/opensearch-security/"; + final String cd = lookupDir != null? (lookupDir+"/") : new Environment(settings, configPath).configDir().toAbsolutePath().toString()+"/opensearch-security/"; File confFile = new File(cd+"config.yml"); if(confFile.exists()) { final ThreadContext threadContext = threadPool.getThreadContext(); diff --git a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java index 72d18fc0c9..8562ea20e7 100644 --- a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java +++ b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java @@ -42,6 +42,8 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import javax.crypto.Cipher; import javax.net.ssl.SSLContext; @@ -49,12 +51,18 @@ import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; +import io.netty.handler.codec.http2.Http2SecurityUtil; import io.netty.handler.ssl.ApplicationProtocolConfig; +import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; +import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; +import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; +import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; +import io.netty.handler.ssl.SupportedCipherSuiteFilter; import io.netty.util.internal.PlatformDependent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -226,8 +234,8 @@ private String resolve(String propName, boolean mustBeValid) { log.debug("Value for {} is {}", propName, originalPath); if (env != null && originalPath != null && originalPath.length() > 0) { - path = env.configFile().resolve(originalPath).toAbsolutePath().toString(); - log.debug("Resolved {} to {} against {}", originalPath, path, env.configFile().toAbsolutePath().toString()); + path = env.configDir().resolve(originalPath).toAbsolutePath().toString(); + log.debug("Resolved {} to {} against {}", originalPath, path, env.configDir().toAbsolutePath().toString()); } if (mustBeValid) { @@ -247,7 +255,7 @@ private void initSSLConfig() { log.info("No config directory, key- and truststore files are resolved absolutely"); } else { log.info("Config directory is {}/, from there the key- and truststore files are resolved relatively", - env.configFile().toAbsolutePath()); + env.configDir().toAbsolutePath()); } @@ -426,7 +434,7 @@ public void initTransportSSLConfig() { /** * Initializes certs used for client https communication */ - public void initHttpSSLConfig() { + public void initHttpSSLConfig() { final boolean useKeyStore = settings.hasValue(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH); final boolean useRawFiles = settings.hasValue(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH); final ClientAuth httpClientAuthMode = ClientAuth.valueOf(settings @@ -879,10 +887,8 @@ private SslContext buildSSLServerContext(final PrivateKey _key, final X509Certif final X509Certificate[] _trustedCerts, final Iterable ciphers, final SslProvider sslProvider, final ClientAuth authMode) throws SSLException { - final SslContextBuilder _sslContextBuilder = SslContextBuilder.forServer(_key, _cert).ciphers(ciphers) - .applicationProtocolConfig(ApplicationProtocolConfig.DISABLED) - .clientAuth(Objects.requireNonNull(authMode)) // https://github.com/netty/netty/issues/4722 - .sessionCacheSize(0).sessionTimeout(0).sslProvider(sslProvider); + final SslContextBuilder _sslContextBuilder = configureSSLServerContextBuilder(SslContextBuilder.forServer(_key, _cert), + sslProvider, ciphers, authMode); if (_trustedCerts != null && _trustedCerts.length > 0) { _sslContextBuilder.trustManager(_trustedCerts); @@ -895,10 +901,8 @@ private SslContext buildSSLServerContext(final File _key, final File _cert, fina final String pwd, final Iterable ciphers, final SslProvider sslProvider, final ClientAuth authMode) throws SSLException { - final SslContextBuilder _sslContextBuilder = SslContextBuilder.forServer(_cert, _key, pwd).ciphers(ciphers) - .applicationProtocolConfig(ApplicationProtocolConfig.DISABLED) - .clientAuth(Objects.requireNonNull(authMode)) // https://github.com/netty/netty/issues/4722 - .sessionCacheSize(0).sessionTimeout(0).sslProvider(sslProvider); + final SslContextBuilder _sslContextBuilder = configureSSLServerContextBuilder(SslContextBuilder.forServer(_cert, _key, pwd), + sslProvider, ciphers, authMode); if (_trustedCerts != null) { _sslContextBuilder.trustManager(_trustedCerts); @@ -907,6 +911,28 @@ private SslContext buildSSLServerContext(final File _key, final File _cert, fina return buildSSLContext0(_sslContextBuilder); } + private SslContextBuilder configureSSLServerContextBuilder(final SslContextBuilder builder, final SslProvider sslProvider, + final Iterable ciphers, final ClientAuth authMode) { + return builder + .ciphers(Stream + .concat( + Http2SecurityUtil.CIPHERS.stream(), + StreamSupport.stream(ciphers.spliterator(), false)) + .collect(Collectors.toSet()), SupportedCipherSuiteFilter.INSTANCE) + .clientAuth(Objects.requireNonNull(authMode)) + .sessionCacheSize(0).sessionTimeout(0).sslProvider(sslProvider) + .applicationProtocolConfig( + new ApplicationProtocolConfig( + Protocol.ALPN, + // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers. + SelectorFailureBehavior.NO_ADVERTISE, + // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. + SelectedListenerFailureBehavior.ACCEPT, + ApplicationProtocolNames.HTTP_2, + ApplicationProtocolNames.HTTP_1_1 + )); + } + private SslContext buildSSLClientContext(final PrivateKey _key, final X509Certificate[] _cert, final X509Certificate[] _trustedCerts, final Iterable ciphers, final SslProvider sslProvider) throws SSLException { diff --git a/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java b/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java index 5e604beb87..c2df3cbf98 100644 --- a/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java +++ b/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java @@ -19,8 +19,12 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.DecoderException; +import io.netty.handler.ssl.ApplicationProtocolNames; +import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; import io.netty.handler.ssl.SslHandler; +import io.netty.util.AttributeKey; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -31,6 +35,7 @@ import org.opensearch.common.xcontent.NamedXContentRegistry; import org.opensearch.http.HttpChannel; import org.opensearch.http.HttpHandlingSettings; +import org.opensearch.http.netty4.Netty4HttpChannel; import org.opensearch.http.netty4.Netty4HttpServerTransport; import org.opensearch.security.ssl.SecurityKeyStore; import org.opensearch.security.ssl.SslExceptionHandler; @@ -38,6 +43,7 @@ import org.opensearch.transport.SharedGroupFactory; public class SecuritySSLNettyHttpServerTransport extends Netty4HttpServerTransport { + static final AttributeKey HTTP_CHANNEL_KEY = AttributeKey.valueOf("opensearch-http-channel"); private static final Logger logger = LogManager.getLogger(SecuritySSLNettyHttpServerTransport.class); private final SecurityKeyStore sks; @@ -72,7 +78,40 @@ public void onException(HttpChannel channel, Exception cause0) { } protected class SSLHttpChannelHandler extends Netty4HttpServerTransport.HttpChannelHandler { - + /** + * Application negotiation handler to select either HTTP 1.1 or HTTP 2 protocol, based + * on client/server ALPN negotiations. + */ + private class Http2OrHttpHandler extends ApplicationProtocolNegotiationHandler { + protected Http2OrHttpHandler() { + super(ApplicationProtocolNames.HTTP_1_1); + } + + @Override + protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception { + if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { + configureDefaultHttp2Pipeline(ctx.pipeline()); + } else if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) { + configureDefaultHttpPipeline(ctx.pipeline()); + } else { + throw new IllegalStateException("Unknown application protocol: " + protocol); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + super.exceptionCaught(ctx, cause); + Netty4HttpChannel channel = ctx.channel().attr(HTTP_CHANNEL_KEY).get(); + if (channel != null) { + if (cause instanceof Error) { + onException(channel, new Exception(cause)); + } else { + onException(channel, (Exception) cause); + } + } + } + } + protected SSLHttpChannelHandler(Netty4HttpServerTransport transport, final HttpHandlingSettings handlingSettings, final SecurityKeyStore odsks) { super(transport, handlingSettings); } @@ -83,5 +122,10 @@ protected void initChannel(Channel ch) throws Exception { final SslHandler sslHandler = new SslHandler(SecuritySSLNettyHttpServerTransport.this.sks.createHTTPSSLEngine()); ch.pipeline().addFirst("ssl_http", sslHandler); } + + @Override + protected void configurePipeline(Channel ch) { + ch.pipeline().addLast(new Http2OrHttpHandler()); + } } } diff --git a/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java b/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java index 5bd72fba5d..3ec6649013 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java @@ -109,12 +109,16 @@ public static SSLInfo getSSLInfo(final Settings settings, final Path configPath, return null; } - final SslHandler sslhandler = (SslHandler) ((Netty4HttpChannel)request.getHttpChannel()).getNettyChannel().pipeline().get("ssl_http"); - + final Netty4HttpChannel httpChannel = (Netty4HttpChannel)request.getHttpChannel(); + SslHandler sslhandler = (SslHandler) httpChannel.getNettyChannel().pipeline().get("ssl_http"); + if(sslhandler == null && httpChannel.inboundPipeline() != null) { + sslhandler = (SslHandler) httpChannel.inboundPipeline().get("ssl_http"); + } + if(sslhandler == null) { return null; } - + final SSLEngine engine = sslhandler.engine(); final SSLSession session = engine.getSession(); @@ -199,7 +203,7 @@ private static boolean validate(X509Certificate[] x509Certs, final Settings sett final String crlFile = settings.get(SSLConfigConstants.SSECURITY_SSL_HTTP_CRL_FILE); if(crlFile != null) { - final File crl = env.configFile().resolve(crlFile).toAbsolutePath().toFile(); + final File crl = env.configDir().resolve(crlFile).toAbsolutePath().toFile(); try(FileInputStream crlin = new FileInputStream(crl)) { crls = CertificateFactory.getInstance("X.509").generateCRLs(crlin); } @@ -222,12 +226,12 @@ private static boolean validate(X509Certificate[] x509Certs, final Settings sett //final String truststoreAlias = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_ALIAS, null); final KeyStore ts = KeyStore.getInstance(truststoreType); - try(FileInputStream fin = new FileInputStream(new File(env.configFile().resolve(truststore).toAbsolutePath().toString()))) { + try(FileInputStream fin = new FileInputStream(new File(env.configDir().resolve(truststore).toAbsolutePath().toString()))) { ts.load(fin, (truststorePassword == null || truststorePassword.length() == 0) ?null:truststorePassword.toCharArray()); } validator = new CertificateValidator(ts, crls); } else { - final File trustedCas = env.configFile().resolve(settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, "")).toAbsolutePath().toFile(); + final File trustedCas = env.configDir().resolve(settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, "")).toAbsolutePath().toFile(); try(FileInputStream trin = new FileInputStream(trustedCas)) { Collection cert = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); validator = new CertificateValidator(cert.toArray(new X509Certificate[0]), crls); diff --git a/src/main/java/org/opensearch/security/support/PemKeyReader.java b/src/main/java/org/opensearch/security/support/PemKeyReader.java index 66d1af8799..fb3a595f9e 100644 --- a/src/main/java/org/opensearch/security/support/PemKeyReader.java +++ b/src/main/java/org/opensearch/security/support/PemKeyReader.java @@ -329,8 +329,8 @@ public static String resolve(String originalPath, String propName, Settings sett final Environment env = new Environment(settings, configPath); if(env != null && originalPath != null && originalPath.length() > 0) { - path = env.configFile().resolve(originalPath).toAbsolutePath().toString(); - log.debug("Resolved {} to {} against {}", originalPath, path, env.configFile().toAbsolutePath().toString()); + path = env.configDir().resolve(originalPath).toAbsolutePath().toString(); + log.debug("Resolved {} to {} against {}", originalPath, path, env.configDir().toAbsolutePath().toString()); } if(mustBeValid) { diff --git a/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java b/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java index 8a4129e32b..06f7d31507 100644 --- a/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java @@ -49,7 +49,7 @@ import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.threadpool.ThreadPool; -import org.opensearch.transport.Netty4Plugin; +import org.opensearch.transport.Netty4ModulePlugin; import org.opensearch.watcher.ResourceWatcherService; public class RolesInjectorIntegTest extends SingleClusterTest { @@ -97,7 +97,7 @@ public void testRolesInject() throws Exception { .build(); //1. Without roles injection. - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, RolesInjectorPlugin.class).start()) { waitForInit(node.client()); @@ -110,7 +110,7 @@ public void testRolesInject() throws Exception { //2. With invalid roles, must throw security exception. RolesInjectorPlugin.injectedRoles = "invalid_user|invalid_role"; Exception exception = null; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class, RolesInjectorPlugin.class).start()) { + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, RolesInjectorPlugin.class).start()) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-2")).actionGet(); @@ -124,7 +124,7 @@ public void testRolesInject() throws Exception { //3. With valid roles - which has permission to create index. RolesInjectorPlugin.injectedRoles = "valid_user|opendistro_security_all_access"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class, RolesInjectorPlugin.class).start()) { + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, RolesInjectorPlugin.class).start()) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-3")).actionGet(); diff --git a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java index 57a2d45a28..36626b3428 100644 --- a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java @@ -43,7 +43,7 @@ import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.threadpool.ThreadPool; -import org.opensearch.transport.Netty4Plugin; +import org.opensearch.transport.Netty4ModulePlugin; import org.opensearch.watcher.ResourceWatcherService; public class RolesValidationIntegTest extends SingleClusterTest { @@ -88,7 +88,7 @@ public void testRolesValidation() throws Exception { .build(); // 1. Without roles validation - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, RolesValidationPlugin.class).start()) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-1")).actionGet(); @@ -100,7 +100,7 @@ public void testRolesValidation() throws Exception { OpenSearchSecurityException exception = null; // 2. with roles invalid to the user RolesValidationPlugin.rolesValidation = "invalid_role"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, RolesValidationPlugin.class).start()) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-2")).actionGet(); @@ -112,7 +112,7 @@ public void testRolesValidation() throws Exception { // 3. with roles valid to the user RolesValidationPlugin.rolesValidation = "opendistro_security_all_access"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, RolesValidationPlugin.class).start()) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-3")).actionGet(); diff --git a/src/test/java/org/opensearch/security/SlowIntegrationTests.java b/src/test/java/org/opensearch/security/SlowIntegrationTests.java index fd01dc7bdd..c08e3e3fd6 100644 --- a/src/test/java/org/opensearch/security/SlowIntegrationTests.java +++ b/src/test/java/org/opensearch/security/SlowIntegrationTests.java @@ -47,7 +47,7 @@ import org.opensearch.security.test.helper.cluster.ClusterConfiguration; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper; -import org.opensearch.transport.Netty4Plugin; +import org.opensearch.transport.Netty4ModulePlugin; public class SlowIntegrationTests extends SingleClusterTest { @@ -84,7 +84,7 @@ public void testNodeClientAllowedWithServerCertificate() throws Exception { log.debug("Start node client"); - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class).start()) { + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { Assert.assertFalse(node.client().admin().cluster().health(new ClusterHealthRequest().waitForNodes(String.valueOf(clusterInfo.numNodes+1))).actionGet().isTimedOut()); Assert.assertEquals(clusterInfo.numNodes+1, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); } @@ -113,7 +113,7 @@ public void testNodeClientDisallowedWithNonServerCertificate() throws Exception log.debug("Start node client"); - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class).start()) { + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { Thread.sleep(10000); Assert.assertEquals(1, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); } catch (Exception e) { @@ -144,7 +144,7 @@ public void testNodeClientDisallowedWithNonServerCertificate2() throws Exception log.debug("Start node client"); - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class).start()) { + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { Thread.sleep(10000); Assert.assertEquals(1, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); } catch (Exception e) { diff --git a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java index 4f3105501f..8ad576be53 100644 --- a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java @@ -41,7 +41,7 @@ import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.threadpool.ThreadPool; -import org.opensearch.transport.Netty4Plugin; +import org.opensearch.transport.Netty4ModulePlugin; import org.opensearch.watcher.ResourceWatcherService; public class TransportUserInjectorIntegTest extends SingleClusterTest { @@ -88,7 +88,7 @@ public void testSecurityUserInjection() throws Exception { // 1. without user injection - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, UserInjectorPlugin.class).start()) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-1")).actionGet(); @@ -99,7 +99,7 @@ public void testSecurityUserInjection() throws Exception { // 2. with invalid backend roles UserInjectorPlugin.injectedUser = "ttt|kkk"; Exception exception = null; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, UserInjectorPlugin.class).start()) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-2")).actionGet(); @@ -113,7 +113,7 @@ public void testSecurityUserInjection() throws Exception { // 3. with valid backend roles for injected user UserInjectorPlugin.injectedUser = "injectedadmin|injecttest"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, UserInjectorPlugin.class).start()) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-2")).actionGet(); @@ -141,7 +141,7 @@ public void testSecurityUserInjectionWithConfigDisabled() throws Exception { .build(); // 1. without user injection - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, UserInjectorPlugin.class).start()) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-1")).actionGet(); @@ -150,7 +150,7 @@ public void testSecurityUserInjectionWithConfigDisabled() throws Exception { // with invalid backend roles UserInjectorPlugin.injectedUser = "ttt|kkk"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, UserInjectorPlugin.class).start()) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-2")).actionGet(); diff --git a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java index acd5e37b68..69141be6e6 100644 --- a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java +++ b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java @@ -59,7 +59,7 @@ import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -import org.opensearch.transport.Netty4Plugin; +import org.opensearch.transport.Netty4ModulePlugin; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; @@ -1021,7 +1021,7 @@ public void testCcsWithRoleInjection() throws Exception { System.out.println("###################### with invalid role injection"); //1. With invalid roles injection RolesInjectorIntegTest.RolesInjectorPlugin.injectedRoles = "invalid_user|invalid_role"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, RolesInjectorIntegTest.RolesInjectorPlugin.class).start()) { waitForInit(node.client()); Client remoteClient = node.client().getRemoteClusterClient("cross_cluster_two"); @@ -1041,7 +1041,7 @@ public void testCcsWithRoleInjection() throws Exception { System.out.println("###################### with valid role injection"); //2. With valid roles injection RolesInjectorIntegTest.RolesInjectorPlugin.injectedRoles = "valid_user|opendistro_security_all_access"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, RolesInjectorIntegTest.RolesInjectorPlugin.class).start()) { waitForInit(node.client()); Client remoteClient = node.client().getRemoteClusterClient("cross_cluster_two"); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java index 720f59980d..fb557f038b 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java @@ -64,7 +64,7 @@ import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.tasks.Task; import org.opensearch.threadpool.ThreadPool; -import org.opensearch.transport.Netty4Plugin; +import org.opensearch.transport.Netty4ModulePlugin; import org.opensearch.transport.TransportService; import org.opensearch.watcher.ResourceWatcherService; // CS-ENFORCE-SINGLE @@ -199,7 +199,7 @@ public void testReplication() throws Exception { // Set roles for the user MockReplicationPlugin.injectedRoles = "ccr_user|opendistro_security_human_resources_trainee"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class, MockReplicationPlugin.class).start()) { + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, MockReplicationPlugin.class).start()) { waitOrThrow(node.client(), "hr-dls"); Assert.fail("Expecting exception"); } catch (OpenSearchSecurityException ex) { @@ -209,7 +209,7 @@ public void testReplication() throws Exception { Assert.assertEquals(ex.status(), RestStatus.FORBIDDEN); } - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class, MockReplicationPlugin.class).start()) { + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, MockReplicationPlugin.class).start()) { waitOrThrow(node.client(), "hr-fls"); Assert.fail("Expecting exception"); } catch (OpenSearchSecurityException ex) { @@ -219,7 +219,7 @@ public void testReplication() throws Exception { Assert.assertEquals(ex.status(), RestStatus.FORBIDDEN); } - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class, MockReplicationPlugin.class).start()) { + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, MockReplicationPlugin.class).start()) { waitOrThrow(node.client(), "hr-masking"); Assert.fail("Expecting exception"); } catch (OpenSearchSecurityException ex) { @@ -229,7 +229,7 @@ public void testReplication() throws Exception { Assert.assertEquals(ex.status(), RestStatus.FORBIDDEN); } - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class, MockReplicationPlugin.class).start()) { + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, MockReplicationPlugin.class).start()) { waitOrThrow(node.client(), "hr-normal"); AcknowledgedResponse res = node.client().execute(MockReplicationAction.INSTANCE, new MockReplicationRequest("hr-normal")).actionGet(); Assert.assertTrue(res.isAcknowledged()); diff --git a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java index 961eadeab5..7b97112a27 100644 --- a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java @@ -43,7 +43,7 @@ import org.opensearch.security.test.AbstractSecurityUnitTest; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper; -import org.opensearch.transport.Netty4Plugin; +import org.opensearch.transport.Netty4ModulePlugin; public class OpenSSLTest extends SSLTest { private static final String USE_NETTY_DEFAULT_ALLOCATOR_PROPERTY = "opensearch.unsafe.use_netty_default_allocator"; @@ -218,7 +218,7 @@ public void testNodeClientSSLwithOpenSslTLSv13() throws Exception { .put(settings)// ----- .build(); - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class).start()) { + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { ClusterHealthResponse res = node.client().admin().cluster().health(new ClusterHealthRequest().waitForNodes("4").timeout(TimeValue.timeValueSeconds(5))).actionGet(); Assert.assertFalse(res.isTimedOut()); Assert.assertEquals(4, res.getNumberOfNodes()); diff --git a/src/test/java/org/opensearch/security/ssl/SSLTest.java b/src/test/java/org/opensearch/security/ssl/SSLTest.java index ab28b4a88f..e028ac82e3 100644 --- a/src/test/java/org/opensearch/security/ssl/SSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/SSLTest.java @@ -60,7 +60,7 @@ import org.opensearch.security.test.SingleClusterTest; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper; -import org.opensearch.transport.Netty4Plugin; +import org.opensearch.transport.Netty4ModulePlugin; @SuppressWarnings({"resource", "unchecked"}) public class SSLTest extends SingleClusterTest { @@ -507,7 +507,7 @@ public void testNodeClientSSL() throws Exception { .put(settings)// ----- .build(); - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class).start()) { + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { ClusterHealthResponse res = node.client().admin().cluster().health(new ClusterHealthRequest().waitForNodes("4").timeout(TimeValue.timeValueSeconds(15))).actionGet(); Assert.assertFalse(res.isTimedOut()); Assert.assertEquals(4, res.getNumberOfNodes()); @@ -698,7 +698,7 @@ public void testNodeClientSSLwithJavaTLSv13() throws Exception { .put(settings)// ----- .build(); - try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenSearchSecurityPlugin.class).start()) { + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { ClusterHealthResponse res = node.client().admin().cluster().health(new ClusterHealthRequest().waitForNodes("4").timeout(TimeValue.timeValueSeconds(5))).actionGet(); Assert.assertFalse(res.isTimedOut()); Assert.assertEquals(4, res.getNumberOfNodes()); diff --git a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterConfiguration.java b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterConfiguration.java index 05815c55f3..871cf5a59d 100644 --- a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterConfiguration.java +++ b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterConfiguration.java @@ -34,15 +34,15 @@ import com.google.common.collect.Lists; -import org.opensearch.index.reindex.ReindexPlugin; -import org.opensearch.join.ParentJoinPlugin; -import org.opensearch.percolator.PercolatorPlugin; +import org.opensearch.index.reindex.ReindexModulePlugin; +import org.opensearch.join.ParentJoinModulePlugin; +import org.opensearch.percolator.PercolatorModulePlugin; import org.opensearch.plugins.Plugin; -import org.opensearch.script.mustache.MustachePlugin; -import org.opensearch.search.aggregations.matrix.MatrixAggregationPlugin; +import org.opensearch.script.mustache.MustacheModulePlugin; +import org.opensearch.search.aggregations.matrix.MatrixAggregationModulePlugin; import org.opensearch.security.OpenSearchSecurityPlugin; import org.opensearch.security.test.plugin.UserInjectorPlugin; -import org.opensearch.transport.Netty4Plugin; +import org.opensearch.transport.Netty4ModulePlugin; public enum ClusterConfiguration { //first one needs to be a cluster manager @@ -109,7 +109,7 @@ public int getClientNodes() { public static class NodeSettings { public boolean clusterManagerNode; public boolean dataNode; - public List> plugins = Lists.newArrayList(Netty4Plugin.class, OpenSearchSecurityPlugin.class, MatrixAggregationPlugin.class, MustachePlugin.class, ParentJoinPlugin.class, PercolatorPlugin.class, ReindexPlugin.class); + public List> plugins = Lists.newArrayList(Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, MatrixAggregationModulePlugin.class, MustacheModulePlugin.class, ParentJoinModulePlugin.class, PercolatorModulePlugin.class, ReindexModulePlugin.class); public NodeSettings(boolean clusterManagerNode, boolean dataNode) { super(); From 416e360500d5838a1f343b19989fb381c1889d49 Mon Sep 17 00:00:00 2001 From: Vinayak Kothari Date: Thu, 29 Sep 2022 16:15:34 -0400 Subject: [PATCH 045/369] Upgrade Kafka Client to 3.0.2 (#2123) * Upgrade Kafka Client to 3.0.2 Signed-off-by: Vinayak Kothari --- build.gradle | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 4a56a54d81..e01290fd39 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ buildscript { opensearch_build = version_tokens[0] + '.0' common_utils_version = System.getProperty("common_utils.version", '2.1.0.0') - + kafka_version = '3.0.2' if (buildVersionQualifier) { opensearch_build += "-${buildVersionQualifier}" @@ -99,7 +99,7 @@ forbiddenApisTest.enabled = false filepermissions.enabled = false forbiddenPatterns.enabled = false testingConventions.enabled = false -// Conflicts between runtime kafka-clients:3.0.1 & testRuntime kafka-clients:3.0.1:test +// Conflicts between runtime kafka-clients:x.y.z & testRuntime kafka-clients:x.y.z:test jarHell.enabled = false tasks.whenTaskAdded {task -> if(task.name.contains("forbiddenApisIntegrationTest")) { @@ -308,7 +308,7 @@ dependencies { } implementation 'com.github.wnameless:json-flattener:0.5.0' implementation 'com.flipkart.zjsonpatch:zjsonpatch:0.4.4' - implementation 'org.apache.kafka:kafka-clients:3.0.1' + implementation "org.apache.kafka:kafka-clients:${kafka_version}" implementation 'com.onelogin:java-saml:2.5.0' implementation 'com.onelogin:java-saml-core:2.5.0' @@ -329,7 +329,6 @@ dependencies { testImplementation 'org.apache.camel:camel-xmlsecurity:3.14.2' - implementation 'net.shibboleth.utilities:java-support:7.5.1' implementation 'org.opensaml:opensaml-core:3.4.5' implementation 'org.opensaml:opensaml-security-impl:3.4.5' @@ -388,9 +387,9 @@ dependencies { testImplementation 'com.unboundid:unboundid-ldapsdk:4.0.9' testImplementation 'javax.servlet:servlet-api:2.5' testImplementation 'org.apache.httpcomponents:fluent-hc:4.5.13' - testImplementation 'org.apache.kafka:kafka_2.13:3.0.1' - testImplementation 'org.apache.kafka:kafka_2.13:3.0.1:test' - testImplementation 'org.apache.kafka:kafka-clients:3.0.1:test' + testImplementation "org.apache.kafka:kafka_2.13:${kafka_version}" + testImplementation "org.apache.kafka:kafka_2.13:${kafka_version}:test" + testImplementation "org.apache.kafka:kafka-clients:${kafka_version}:test" testImplementation 'org.springframework.kafka:spring-kafka-test:2.8.6' testImplementation 'org.springframework:spring-beans:5.3.20' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' @@ -407,8 +406,8 @@ dependencies { testRuntimeOnly 'com.yammer.metrics:metrics-core:2.2.0' testRuntimeOnly 'com.typesafe.scala-logging:scala-logging_3:3.9.5' testRuntimeOnly 'org.apache.zookeeper:zookeeper:3.7.1' - testRuntimeOnly 'org.apache.kafka:kafka-metadata:3.0.1' - testRuntimeOnly 'org.apache.kafka:kafka-storage:3.0.1' + testRuntimeOnly "org.apache.kafka:kafka-metadata:${kafka_version}" + testRuntimeOnly "org.apache.kafka:kafka-storage:${kafka_version}" From fa391a8c6d8b9cdd74e62728c22277096b2dccfc Mon Sep 17 00:00:00 2001 From: Vinayak Kothari Date: Tue, 4 Oct 2022 13:31:39 -0400 Subject: [PATCH 046/369] Remove Extra New Line (#2136) Signed-off-by: Vinayak Kothari Signed-off-by: Vinayak Kothari --- .../opensearch-security.release-notes-1.3.6.0.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-1.3.6.0.md diff --git a/release-notes/opensearch-security.release-notes-1.3.6.0.md b/release-notes/opensearch-security.release-notes-1.3.6.0.md new file mode 100644 index 0000000000..d95ffbf83d --- /dev/null +++ b/release-notes/opensearch-security.release-notes-1.3.6.0.md @@ -0,0 +1,11 @@ +## 2022-10-06 Version 1.3.6.0 + +### Bug fixes + +* Scope updateVersion to only build.gradle ([#2121](https://github.com/opensearch-project/security/pull/2121)) + +### Maintenance + +* Updates jackson version to 2.13.4 ([#2128](https://github.com/opensearch-project/security/pull/2128)) +* Update Kafka Client to 3.0.2 ([#2129](https://github.com/opensearch-project/security/pull/2129)) +* Staging for version increment automation ([#2029](https://github.com/opensearch-project/security/pull/2029)) From b3452c1c11fc40b04c7483e88fdfa30097538eb9 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Tue, 4 Oct 2022 18:17:59 -0400 Subject: [PATCH 047/369] Remove HTTP_CHANNEL_KEY attributes since it is now available from the base class (#2130) Signed-off-by: Andriy Redko --- .../ssl/http/netty/SecuritySSLNettyHttpServerTransport.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java b/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java index c2df3cbf98..0738973483 100644 --- a/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java +++ b/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java @@ -24,7 +24,6 @@ import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; import io.netty.handler.ssl.SslHandler; -import io.netty.util.AttributeKey; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -43,8 +42,6 @@ import org.opensearch.transport.SharedGroupFactory; public class SecuritySSLNettyHttpServerTransport extends Netty4HttpServerTransport { - static final AttributeKey HTTP_CHANNEL_KEY = AttributeKey.valueOf("opensearch-http-channel"); - private static final Logger logger = LogManager.getLogger(SecuritySSLNettyHttpServerTransport.class); private final SecurityKeyStore sks; private final SslExceptionHandler errorHandler; From 54656a9e88ff99160637adca94462ca6c7b7fb86 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Wed, 5 Oct 2022 11:05:02 -0500 Subject: [PATCH 048/369] Document standard practice for squashing pull requests (#2134) Following up from discussion https://github.com/orgs/opensearch-project/teams/security/discussions/4 Signed-off-by: Peter Nied Signed-off-by: Peter Nied --- MAINTAINERS.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index a6bc5928ea..03860dc6bf 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -4,6 +4,7 @@ - [Practices](#practices) - [Reverting Commits](#reverting-commits) - [Performing Revert](#performing-revert) + - [Squashing a pull request](#squashing-a-pull-request) # OpenSearch Security Maintainers @@ -30,3 +31,11 @@ Exceptional, instead of immediately reverting, if a contributor knows how and wi Go to the pull request of the change that was an issue, there is a `Revert` button at the bottom. If there are no conflicts to resolve, this can be done immediately bypassing standard approval. Reverts can also be done via the command line using `git revert ` and creating a new pull request. If done in this way they should have references to the pull request that was reverted. + +## Squashing a pull request +When a PR is going to be merged, our repositories are set to automatically squash the commits into a single commit. This process needs human intervention to produce high quality commit messages, with the following steps to be followed as much as possible: + +- The commit subject is clean and conveys what is being merged +- The commit body should include the details (if any) about the commit, typically inline with the PR description +- The commit body should include the 'Signed-Off-By:*' for all committers involved in the change. +- There need to be a matching 'Signed-Off-By:' line for the `This commit will be authored by *` email address otherwise backport DCO checks will fail. From ca4917cf4922dd4fb1dc42010c5555ddf9cf8aeb Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Wed, 5 Oct 2022 23:35:39 +0200 Subject: [PATCH 049/369] Tests related basic authentication. (#2138) * A few first tests for basic authentication. Signed-off-by: Lukasz Soszynski Co-authored-by: Lukasz Soszynski --- .../security/SecurityRolesTests.java | 2 +- .../opensearch/security/http/AuthInfo.java | 30 ++++ .../security/http/BasicAuthTests.java | 146 ++++++++++++++++++ .../http/BasicAuthWithoutChallengeTests.java | 51 ++++++ .../security/http/DisabledBasicAuthTests.java | 47 ++++++ .../test/framework/TestSecurityConfig.java | 30 +++- .../framework/cluster/TestRestClient.java | 32 ++++ .../resources/log4j2-test.properties | 7 +- 8 files changed, 339 insertions(+), 6 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/http/AuthInfo.java create mode 100644 src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java create mode 100644 src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java create mode 100644 src/integrationTest/java/org/opensearch/security/http/DisabledBasicAuthTests.java diff --git a/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java b/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java index df20cded48..30c7ed46de 100644 --- a/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java +++ b/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java @@ -45,7 +45,7 @@ public class SecurityRolesTests { public void testSecurityRoles() throws Exception { try (TestRestClient client = cluster.getRestClient(USER_SR)) { HttpResponse response = client.getAuthInfo(); - assertThat(response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + response.assertStatusCode(HttpStatus.SC_OK); // Check username assertThat(response.getTextFromJsonBody("/user_name"), equalTo("sr_user")); diff --git a/src/integrationTest/java/org/opensearch/security/http/AuthInfo.java b/src/integrationTest/java/org/opensearch/security/http/AuthInfo.java new file mode 100644 index 0000000000..cb08c2f36d --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/AuthInfo.java @@ -0,0 +1,30 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.beans.ConstructorProperties; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +class AuthInfo { + + private final List customAttributeNames; + + @ConstructorProperties("custom_attribute_names") + public AuthInfo(List customAttributeNames) { + this.customAttributeNames = customAttributeNames; + } + + public List getCustomAttributeNames() { + return customAttributeNames; + } +} diff --git a/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java b/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java new file mode 100644 index 0000000000..70a368fd73 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java @@ -0,0 +1,146 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.util.List; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.http.HttpHeaders; +import org.hamcrest.Matchers; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain; +import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; + +import static org.apache.http.HttpStatus.SC_OK; +import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsStringIgnoringCase; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class BasicAuthTests { + static final User TEST_USER = new User("test_user").password("s3cret"); + + public static final String CUSTOM_ATTRIBUTE_NAME = "superhero"; + static final User SUPER_USER = new User("super-user").password("super-password") + .attr(CUSTOM_ATTRIBUTE_NAME, true); + public static final String NOT_EXISTING_USER = "not-existing-user"; + public static final String INVALID_PASSWORD = "secret-password"; + + public static final AuthcDomain AUTHC_DOMAIN = new AuthcDomain("basic", 0) + .challengingAuthenticator("basic").backend("internal"); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) + .authc(AUTHC_DOMAIN).users(TEST_USER, SUPER_USER).build(); + + @Test + public void shouldRespondWith401WhenUserDoesNotExist() { + try (TestRestClient client = cluster.getRestClient(NOT_EXISTING_USER, INVALID_PASSWORD)) { + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_UNAUTHORIZED); + } + } + + @Test + public void shouldRespondWith401WhenUserNameIsIncorrect() { + try (TestRestClient client = cluster.getRestClient(NOT_EXISTING_USER, TEST_USER.getPassword())) { + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_UNAUTHORIZED); + } + } + + @Test + public void shouldRespondWith401WhenPasswordIsIncorrect() { + try (TestRestClient client = cluster.getRestClient(TEST_USER.getName(), INVALID_PASSWORD)) { + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_UNAUTHORIZED); + } + } + + @Test + public void shouldRespondWith200WhenCredentialsAreCorrect() { + try (TestRestClient client = cluster.getRestClient(TEST_USER)) { + + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_OK); + } + } + + @Test + public void browserShouldRequestUserForCredentials() { + try (TestRestClient client = cluster.getRestClient()) { + + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_UNAUTHORIZED); + assertThatBrowserAskUserForCredentials(response); + } + } + + @Test + public void testUserShouldNotHaveAssignedCustomAttributes() { + try (TestRestClient client = cluster.getRestClient(TEST_USER)) { + + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_OK); + AuthInfo authInfo = response.getBodyAs(AuthInfo.class); + assertThat(authInfo, is(notNullValue())); + assertThat(authInfo.getCustomAttributeNames(), is(notNullValue())); + assertThat(authInfo.getCustomAttributeNames(), hasSize(0)); + } + } + + @Test + public void userShouldHaveAssignedCustomAttributes() { + try (TestRestClient client = cluster.getRestClient(SUPER_USER)) { + + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_OK); + AuthInfo authInfo = response.getBodyAs(AuthInfo.class); + assertThat(authInfo, is(notNullValue())); + List customAttributeNames = authInfo.getCustomAttributeNames(); + assertThat(customAttributeNames, is(notNullValue())); + assertThat(customAttributeNames, hasSize(1)); + assertThat(customAttributeNames.get(0), Matchers.equalTo("attr.internal." + CUSTOM_ATTRIBUTE_NAME)); + } + } + + private void assertThatBrowserAskUserForCredentials(HttpResponse response) { + String reason = "Browser does not ask user for credentials"; + assertThat(reason, response.containHeader(HttpHeaders.WWW_AUTHENTICATE), equalTo(true)); + assertThat(response.getHeader(HttpHeaders.WWW_AUTHENTICATE).getValue(), containsStringIgnoringCase("basic")); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java b/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java new file mode 100644 index 0000000000..b3ba22dc0b --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java @@ -0,0 +1,51 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.http.HttpHeaders; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class BasicAuthWithoutChallengeTests { + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) + .authc(AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE).build(); + + @Test + public void browserShouldNotRequestUserForCredentials() { + try (TestRestClient client = cluster.getRestClient()) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + assertThatBrowserDoesNotAskUserForCredentials(response); + } + } + + private void assertThatBrowserDoesNotAskUserForCredentials(HttpResponse response) { + String reason = "Browser ask user for credentials what is not expected"; + assertThat(reason, response.containHeader(HttpHeaders.WWW_AUTHENTICATE), equalTo(false)); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/http/DisabledBasicAuthTests.java b/src/integrationTest/java/org/opensearch/security/http/DisabledBasicAuthTests.java new file mode 100644 index 0000000000..2249083ab8 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/DisabledBasicAuthTests.java @@ -0,0 +1,47 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; + +import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; +import static org.opensearch.security.http.BasicAuthTests.TEST_USER; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.DISABLED_AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.JWT_AUTH_DOMAIN; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class DisabledBasicAuthTests { + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) + .authc(DISABLED_AUTHC_HTTPBASIC_INTERNAL).users(TEST_USER) + .authc(JWT_AUTH_DOMAIN) + .build(); + + @Test + public void shouldRespondWith401WhenCredentialsAreCorrectButBasicAuthIsDisabled() { + try (TestRestClient client = cluster.getRestClient(TEST_USER)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index f220df3eb6..c5536efc35 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -42,6 +42,7 @@ import java.util.Set; import java.util.stream.Collectors; +import com.google.common.collect.ImmutableMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.crypto.generators.OpenBSDBCrypt; @@ -336,8 +337,20 @@ public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params public static class AuthcDomain implements ToXContentObject { + private static String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqZbjLUAWc+DZTkinQAdvy1GFjPHPnxheU89hSiWoDD3NOW76H3u3T7cCDdOah2msdxSlBmCBH6wik8qLYkcV8owWukQg3PQmbEhrdPaKo0QCgomWs4nLgtmEYqcZ+QQldd82MdTlQ1QmoQmI9Uxqs1SuaKZASp3Gy19y8su5CV+FZ6BruUw9HELK055sAwl3X7j5ouabXGbcib2goBF3P52LkvbJLuWr5HDZEOeSkwIeqSeMojASM96K5SdotD+HwEyjaTjzRPL2Aa1BEQFWOQ6CFJLyLH7ZStDuPM1mJU1VxIVfMbZrhsUBjAnIhRynmWxML7YlNqkP9j6jyOIYQIDAQAB"; + public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL = new TestSecurityConfig.AuthcDomain("basic", 0) - .httpAuthenticator("basic").backend("internal"); + .challengingAuthenticator("basic").backend("internal"); + + public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE = new TestSecurityConfig.AuthcDomain("basic", 0) + .httpAuthenticator("basic").backend("internal"); + + public final static AuthcDomain DISABLED_AUTHC_HTTPBASIC_INTERNAL = new TestSecurityConfig + .AuthcDomain("basic", 0, false).httpAuthenticator("basic").backend("internal"); + + public final static AuthcDomain JWT_AUTH_DOMAIN = new TestSecurityConfig + .AuthcDomain("jwt", 1) + .jwtHttpAuthenticator("Authorization", PUBLIC_KEY).backend("noop"); private final String id; private boolean enabled = true; @@ -346,9 +359,14 @@ public static class AuthcDomain implements ToXContentObject { private HttpAuthenticator httpAuthenticator; private AuthenticationBackend authenticationBackend; - public AuthcDomain(String id, int order) { + public AuthcDomain(String id, int order, boolean enabled) { this.id = id; this.order = order; + this.enabled = enabled; + } + + public AuthcDomain(String id, int order) { + this(id, order, true); } public AuthcDomain httpAuthenticator(String type) { @@ -356,6 +374,12 @@ public AuthcDomain httpAuthenticator(String type) { return this; } + public AuthcDomain jwtHttpAuthenticator(String headerName, String signingKey) { + this.httpAuthenticator = new HttpAuthenticator("jwt") + .challenge(false).config(ImmutableMap.of("jwt_header", headerName, "signing_key", signingKey)); + return this; + } + public AuthcDomain challengingAuthenticator(String type) { this.httpAuthenticator = new HttpAuthenticator(type).challenge(true); return this; @@ -516,7 +540,7 @@ private void writeConfigToIndex(Client client, CType configType, Map findHeader(String name) { + return Arrays.stream(header) + .filter(header -> requireNonNull(name, "Header name is mandatory.").equalsIgnoreCase(header.getName())) + .findFirst(); + } + + public Header getHeader(String name) { + return findHeader(name).orElseThrow(); + } + + public boolean containHeader(String name) { + return findHeader(name).isPresent(); + } + public int getStatusCode() { return statusCode; } @@ -321,6 +341,18 @@ public String toString() { + ", statusReason=" + statusReason + "]"; } + public T getBodyAs(Class authInfoClass) { + try { + return DefaultObjectMapper.readValue(getBody(), authInfoClass); + } catch (IOException e) { + throw new RuntimeException("Cannot parse response body", e); + } + } + + public void assertStatusCode(int expectedHttpStatus) { + String reason = format("Expected status code is '%d', but was '%d'. Response body '%s'.", expectedHttpStatus, statusCode, body); + assertThat(reason, statusCode, equalTo(expectedHttpStatus)); + } } @Override diff --git a/src/integrationTest/resources/log4j2-test.properties b/src/integrationTest/resources/log4j2-test.properties index b98cc92b78..58bf82daf3 100644 --- a/src/integrationTest/resources/log4j2-test.properties +++ b/src/integrationTest/resources/log4j2-test.properties @@ -4,7 +4,7 @@ name = Integration test logging configuration appender.console.type = Console -appender.console.name = consoleLogger +appender.console.name = consoleAppender appender.console.layout.type = PatternLayout appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %threadName %-5p %c{1}:%L - %m%n appender.console.filter.prerelease.type=RegexFilter @@ -13,4 +13,7 @@ appender.console.filter.prerelease.onMatch=DENY appender.console.filter.prerelease.onMismatch=NEUTRAL rootLogger.level = warn -rootLogger.appenderRef.stdout.ref = consoleLogger +rootLogger.appenderRef.stdout.ref = consoleAppender + +logger.testsecconfig.name=org.opensearch.test.framework.TestSecurityConfig +logger.testsecconfig.level = info From e5ab646b3f91e0ee46f7d8bafb9acddda4da3e50 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 6 Oct 2022 15:52:43 -0500 Subject: [PATCH 050/369] Log deprecation message on legacy ldap pool settings (#2099) * Log deprecation message on legacy ldap pool settings Signed-off-by: Peter Nied Signed-off-by: Peter Nied Co-authored-by: Martin Kemp --- .../com/amazon/dlic/auth/ldap/LdapUser.java | 8 +-- .../backend/LDAPAuthenticationBackend.java | 16 +++-- .../dlic/auth/ldap/util/ConfigConstants.java | 4 ++ .../ldap2/LDAPConnectionFactoryFactory.java | 9 ++- .../security/setting/DeprecatedSettings.java | 26 ++++++++ .../setting/DeprecatedSettingsTest.java | 63 +++++++++++++++++++ 6 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 src/main/java/org/opensearch/security/setting/DeprecatedSettings.java create mode 100644 src/test/java/org/opensearch/security/setting/DeprecatedSettingsTest.java diff --git a/src/main/java/com/amazon/dlic/auth/ldap/LdapUser.java b/src/main/java/com/amazon/dlic/auth/ldap/LdapUser.java index 37f16dd3b5..ab26a94402 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/LdapUser.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/LdapUser.java @@ -31,12 +31,12 @@ public class LdapUser extends User { private final String originalUsername; public LdapUser(final String name, String originalUsername, final LdapEntry userEntry, - final AuthCredentials credentials, int customAttrMaxValueLen, WildcardMatcher whitelistedCustomLdapAttrMatcher) { + final AuthCredentials credentials, int customAttrMaxValueLen, WildcardMatcher allowlistedCustomLdapAttrMatcher) { super(name, null, credentials); this.originalUsername = originalUsername; this.userEntry = userEntry; Map attributes = getCustomAttributesMap(); - attributes.putAll(extractLdapAttributes(originalUsername, userEntry, customAttrMaxValueLen, whitelistedCustomLdapAttrMatcher)); + attributes.putAll(extractLdapAttributes(originalUsername, userEntry, customAttrMaxValueLen, allowlistedCustomLdapAttrMatcher)); } /** @@ -57,7 +57,7 @@ public String getOriginalUsername() { } public static Map extractLdapAttributes(String originalUsername, final LdapEntry userEntry, - int customAttrMaxValueLen, WildcardMatcher whitelistedCustomLdapAttrMatcher) { + int customAttrMaxValueLen, WildcardMatcher allowlistedCustomLdapAttrMatcher) { Map attributes = new HashMap<>(); attributes.put("ldap.original.username", originalUsername); attributes.put("ldap.dn", userEntry.getDn()); @@ -69,7 +69,7 @@ public static Map extractLdapAttributes(String originalUsername, // only consider attributes which are not binary and where its value is not // longer than customAttrMaxValueLen characters if (val != null && val.length() > 0 && val.length() <= customAttrMaxValueLen) { - if (whitelistedCustomLdapAttrMatcher.test(attr.getName())) { + if (allowlistedCustomLdapAttrMatcher.test(attr.getName())) { attributes.put("attr.ldap." + attr.getName(), val); } } diff --git a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java index 05fa70c821..fac06dc9a3 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java @@ -44,6 +44,8 @@ import org.opensearch.security.user.AuthCredentials; import org.opensearch.security.user.User; +import static org.opensearch.security.setting.DeprecatedSettings.checkForDeprecatedSetting; + public class LDAPAuthenticationBackend implements AuthenticationBackend { static final int ZERO_PLACEHOLDER = 0; @@ -56,7 +58,7 @@ public class LDAPAuthenticationBackend implements AuthenticationBackend { private final Path configPath; private final List> userBaseSettings; private final int customAttrMaxValueLen; - private final WildcardMatcher whitelistedCustomLdapAttrMatcher; + private final WildcardMatcher allowlistedCustomLdapAttrMatcher; private final String[] returnAttributes; @@ -67,8 +69,10 @@ public LDAPAuthenticationBackend(final Settings settings, final Path configPath) this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); customAttrMaxValueLen = settings.getAsInt(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 36); - whitelistedCustomLdapAttrMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, + checkForDeprecatedSetting(settings, ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, ConfigConstants.LDAP_CUSTOM_ATTR_ALLOWLIST); + final List customAttrAllowList = settings.getAsList(ConfigConstants.LDAP_CUSTOM_ATTR_ALLOWLIST, settings.getAsList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, Collections.singletonList("*"))); + allowlistedCustomLdapAttrMatcher = WildcardMatcher.from(customAttrAllowList); } @Override @@ -127,9 +131,9 @@ public User authenticate(final AuthCredentials credentials) throws OpenSearchSec // by default all ldap attributes which are not binary and with a max value // length of 36 are included in the user object - // if the whitelist contains at least one value then all attributes will be - // additional check if whitelisted (whitelist can contain wildcard and regex) - return new LdapUser(username, user, entry, credentials, customAttrMaxValueLen, whitelistedCustomLdapAttrMatcher); + // if the allowlist contains at least one value then all attributes will be + // additional check if allowlisted (allowlist can contain wildcard and regex) + return new LdapUser(username, user, entry, credentials, customAttrMaxValueLen, allowlistedCustomLdapAttrMatcher); } catch (final Exception e) { if (log.isDebugEnabled()) { @@ -164,7 +168,7 @@ public boolean exists(final User user) { boolean exists = userEntry != null; if(exists) { - user.addAttributes(LdapUser.extractLdapAttributes(userName, userEntry, customAttrMaxValueLen, whitelistedCustomLdapAttrMatcher)); + user.addAttributes(LdapUser.extractLdapAttributes(userName, userEntry, customAttrMaxValueLen, allowlistedCustomLdapAttrMatcher)); } return exists; diff --git a/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java b/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java index 12366daf82..25c3a89fb4 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java @@ -73,6 +73,7 @@ public final class ConfigConstants { // custom attributes public static final String LDAP_CUSTOM_ATTR_MAXVAL_LEN = "custom_attr_maxval_len"; public static final String LDAP_CUSTOM_ATTR_WHITELIST = "custom_attr_whitelist"; + public static final String LDAP_CUSTOM_ATTR_ALLOWLIST = "custom_attr_allowlist"; public static final String LDAP_RETURN_ATTRIBUTES = "custom_return_attributes"; public static final String LDAP_CONNECTION_STRATEGY = "connection_strategy"; @@ -83,6 +84,9 @@ public final class ConfigConstants { public static final String LDAP_POOL_TYPE = "pool.type"; + public static final String LDAP_LEGACY_POOL_PRUNING_PERIOD = "pruning.period"; + public static final String LDAP_LEGACY_POOL_IDLE_TIME = "pruning.idleTime"; + public static final String LDAP_POOL_PRUNING_PERIOD = "pool.pruning_period"; public static final String LDAP_POOL_IDLE_TIME = "pool.idle_time"; diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPConnectionFactoryFactory.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPConnectionFactoryFactory.java index 74d8989ae3..f711e41982 100644 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPConnectionFactoryFactory.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPConnectionFactoryFactory.java @@ -62,6 +62,8 @@ import org.opensearch.common.settings.Settings; +import static org.opensearch.security.setting.DeprecatedSettings.checkForDeprecatedSetting; + public class LDAPConnectionFactoryFactory { private static final Logger log = LogManager.getLogger(LDAPConnectionFactoryFactory.class); @@ -127,10 +129,13 @@ public ConnectionPool createConnectionPool() { } result.setValidator(getConnectionValidator()); + + checkForDeprecatedSetting(settings, ConfigConstants.LDAP_LEGACY_POOL_PRUNING_PERIOD, ConfigConstants.LDAP_POOL_PRUNING_PERIOD); + checkForDeprecatedSetting(settings, ConfigConstants.LDAP_LEGACY_POOL_IDLE_TIME, ConfigConstants.LDAP_POOL_IDLE_TIME); result.setPruneStrategy(new IdlePruneStrategy( - Duration.ofMinutes(this.settings.getAsLong(ConfigConstants.LDAP_POOL_PRUNING_PERIOD, this.settings.getAsLong("pruning.period", 5l))), - Duration.ofMinutes(this.settings.getAsLong(ConfigConstants.LDAP_POOL_IDLE_TIME, this.settings.getAsLong("pruning.idleTime", 10l)))) + Duration.ofMinutes(this.settings.getAsLong(ConfigConstants.LDAP_POOL_PRUNING_PERIOD, this.settings.getAsLong(ConfigConstants.LDAP_LEGACY_POOL_PRUNING_PERIOD, 5l))), + Duration.ofMinutes(this.settings.getAsLong(ConfigConstants.LDAP_POOL_IDLE_TIME, this.settings.getAsLong(ConfigConstants.LDAP_LEGACY_POOL_IDLE_TIME, 10l)))) ); result.initialize(); diff --git a/src/main/java/org/opensearch/security/setting/DeprecatedSettings.java b/src/main/java/org/opensearch/security/setting/DeprecatedSettings.java new file mode 100644 index 0000000000..7aa54fbea3 --- /dev/null +++ b/src/main/java/org/opensearch/security/setting/DeprecatedSettings.java @@ -0,0 +1,26 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.security.setting; + +import org.opensearch.common.logging.DeprecationLogger; +import org.opensearch.common.settings.Settings; + +/** + * Functionality around settings that have been deprecated + */ +public class DeprecatedSettings { + + static DeprecationLogger DEPRECATION_LOGGER = DeprecationLogger.getLogger(DeprecatedSettings.class); + + /** + * Checks for an deprecated key found in a setting, logs that it should be replaced with the another key + */ + public static void checkForDeprecatedSetting(final Settings settings, final String legacySettingKey, final String validSettingKey) { + if (settings.hasValue(legacySettingKey)) { + DEPRECATION_LOGGER.deprecate(legacySettingKey, "Found deprecated setting '{}', please replace with '{}'", legacySettingKey, validSettingKey); + } + } +} diff --git a/src/test/java/org/opensearch/security/setting/DeprecatedSettingsTest.java b/src/test/java/org/opensearch/security/setting/DeprecatedSettingsTest.java new file mode 100644 index 0000000000..d8227c53d7 --- /dev/null +++ b/src/test/java/org/opensearch/security/setting/DeprecatedSettingsTest.java @@ -0,0 +1,63 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.security.setting; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import org.opensearch.common.logging.DeprecationLogger; +import org.opensearch.common.settings.Settings; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.opensearch.security.setting.DeprecatedSettings.checkForDeprecatedSetting; + +@RunWith(MockitoJUnitRunner.class) +public class DeprecatedSettingsTest { + + @Mock + private DeprecationLogger logger; + + private DeprecationLogger original; + + @Before + public void before() { + original = DeprecatedSettings.DEPRECATION_LOGGER; + DeprecatedSettings.DEPRECATION_LOGGER = logger; + } + + @After + public void after() { + DeprecatedSettings.DEPRECATION_LOGGER = original; + verifyNoMoreInteractions(logger); + } + + @Test + public void testCheckForDeprecatedSettingNoLegacy() { + final Settings settings = Settings.builder().put("properKey", "value").build(); + + checkForDeprecatedSetting(settings, "legacyKey", "properKey"); + + verifyNoInteractions(logger); + } + + @Test + public void testCheckForDeprecatedSettingFoundLegacy() { + final Settings settings = Settings.builder().put("legacyKey", "value").build(); + + checkForDeprecatedSetting(settings, "legacyKey", "properKey"); + + verify(logger).deprecate(eq("legacyKey"), anyString(), any()); + } +} From 207cfcc379ffd4127e32b9fdfdd75ea394b48d0e Mon Sep 17 00:00:00 2001 From: Bharathwaj G <58062316+bharath-techie@users.noreply.github.com> Date: Fri, 7 Oct 2022 21:21:50 +0530 Subject: [PATCH 051/369] Point in time security changes (#2094) Description For 'Delete PIT' and 'PIT segments' API, when PIT IDs are passed as part of request, this custom evaluator decode the PITs to indices and resolve the indices with user permissions. If user has permission to all indices of PIT, then PIT is permitted to the user. Only when the user has permissions for all PITs in the request, then we allow the operation. For requests which operates on 'all' PITs, we skip the custom evaluator and evaluate via standard code Alias and data stream behavior : PIT IDs always contain the resolved indices ( underlying indices ) when saved. Based on this, For alias, user must have either 'index' or 'alias' permission for any PIT operation. For data stream, user must have both 'data stream' AND 'backing indices of data stream' permission ( eg : data-stream-11 + .ds-my-data-stream11-000001 ) for any PIT operation. With just data stream permission, user will be able to create pit but will not be able to use the PIT ID for other operations such as search without backing indices permission. Signed-off-by: Bharathwaj G Signed-off-by: Bharathwaj G <58062316+bharath-techie@users.noreply.github.com> --- config/roles.yml | 9 + .../security/OpenSearchSecurityPlugin.java | 8 +- .../privileges/PitPrivilegesEvaluator.java | 105 ++++++++ .../privileges/PrivilegesEvaluator.java | 8 + .../resolver/IndexResolverReplacer.java | 10 +- .../static_config/static_action_groups.yml | 7 +- .../security/PitIntegrationTests.java | 243 ++++++++++++++++++ .../security/test/helper/rest/RestHelper.java | 19 ++ src/test/resources/internal_users.yml | 9 + src/test/resources/roles.yml | 41 +++ src/test/resources/roles_mapping.yml | 15 ++ 11 files changed, 465 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/opensearch/security/privileges/PitPrivilegesEvaluator.java create mode 100644 src/test/java/org/opensearch/security/PitIntegrationTests.java diff --git a/config/roles.yml b/config/roles.yml index 721349c086..c96e8b27e9 100644 --- a/config/roles.yml +++ b/config/roles.yml @@ -246,3 +246,12 @@ snapshot_management_read_access: - 'cluster:admin/opensearch/snapshot_management/policy/explain' - 'cluster:admin/repository/get' - 'cluster:admin/snapshot/get' + +# Allows user to use point in time functionality +point_in_time_full_access: + reserved: true + index_permissions: + - index_patterns: + - '*' + allowed_actions: + - 'manage_point_in_time' diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 69dce00d41..e70aa01912 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -66,6 +66,7 @@ import org.opensearch.Version; import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionResponse; +import org.opensearch.action.search.PitService; import org.opensearch.action.search.SearchScrollAction; import org.opensearch.action.support.ActionFilter; import org.opensearch.client.Client; @@ -1161,12 +1162,15 @@ public static class GuiceHolder implements LifecycleComponent { private static RemoteClusterService remoteClusterService; private static IndicesService indicesService; + private static PitService pitService; + @Inject public GuiceHolder(final RepositoriesService repositoriesService, - final TransportService remoteClusterService, IndicesService indicesService) { + final TransportService remoteClusterService, IndicesService indicesService, PitService pitService) { GuiceHolder.repositoriesService = repositoriesService; GuiceHolder.remoteClusterService = remoteClusterService.getRemoteClusterService(); GuiceHolder.indicesService = indicesService; + GuiceHolder.pitService = pitService; } public static RepositoriesService getRepositoriesService() { @@ -1180,6 +1184,8 @@ public static RemoteClusterService getRemoteClusterService() { public static IndicesService getIndicesService() { return indicesService; } + + public static PitService getPitService() { return pitService; } @Override public void close() { diff --git a/src/main/java/org/opensearch/security/privileges/PitPrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PitPrivilegesEvaluator.java new file mode 100644 index 0000000000..b146365d57 --- /dev/null +++ b/src/main/java/org/opensearch/security/privileges/PitPrivilegesEvaluator.java @@ -0,0 +1,105 @@ +/* + * 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.privileges; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.admin.indices.segments.PitSegmentsRequest; +import org.opensearch.action.search.CreatePitRequest; +import org.opensearch.action.search.DeletePitRequest; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.security.OpenSearchSecurityPlugin; +import org.opensearch.security.resolver.IndexResolverReplacer; +import org.opensearch.security.securityconf.SecurityRoles; +import org.opensearch.security.user.User; + + +/** + * This class evaluates privileges for point in time (Delete and List all) operations. + * For aliases - users must have either alias permission or backing index permissions + * For data streams - users must have access to backing indices permission + data streams permission. + */ +public class PitPrivilegesEvaluator { + + public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final ClusterService clusterService, + final User user, final SecurityRoles securityRoles, final String action, + final IndexNameExpressionResolver resolver, + final PrivilegesEvaluatorResponse presponse, + final IndexResolverReplacer irr) { + + if(!(request instanceof DeletePitRequest || request instanceof PitSegmentsRequest)) { + return presponse; + } + List pitIds = new ArrayList<>(); + + if (request instanceof DeletePitRequest) { + DeletePitRequest deletePitRequest = (DeletePitRequest) request; + pitIds = deletePitRequest.getPitIds(); + } else if(request instanceof PitSegmentsRequest) { + PitSegmentsRequest pitSegmentsRequest = (PitSegmentsRequest) request; + pitIds = pitSegmentsRequest.getPitIds(); + } + // if request is for all PIT IDs, skip custom pit ids evaluation + if (pitIds.size() == 1 && "_all".equals(pitIds.get(0))) { + return presponse; + } else { + return handlePitsAccess(pitIds, clusterService, user, securityRoles, + action, resolver, presponse, irr); + } + } + + /** + * Handle access for delete operation / pit segments operation where PIT IDs are explicitly passed + */ + private PrivilegesEvaluatorResponse handlePitsAccess(List pitIds, ClusterService clusterService, + User user, SecurityRoles securityRoles, final String action, + IndexNameExpressionResolver resolver, PrivilegesEvaluatorResponse presponse, + final IndexResolverReplacer irr) { + Map pitToIndicesMap = OpenSearchSecurityPlugin. + GuiceHolder.getPitService().getIndicesForPits(pitIds); + Set pitIndices = new HashSet<>(); + // add indices across all PITs to a set and evaluate if user has access to all indices + for(String[] indices: pitToIndicesMap.values()) { + pitIndices.addAll(Arrays.asList(indices)); + } + Set allPermittedIndices = getPermittedIndices(pitIndices, clusterService, user, + securityRoles, action, resolver, irr); + // Only if user has access to all PIT's indices, allow operation, otherwise continue evaluation in PrivilegesEvaluator. + if(allPermittedIndices.containsAll(pitIndices)) { + presponse.allowed = true; + presponse.markComplete(); + } + return presponse; + } + + /** + * This method returns list of permitted indices for the PIT indices passed + */ + private Set getPermittedIndices(Set pitIndices, ClusterService clusterService, + User user, SecurityRoles securityRoles, final String action, + IndexNameExpressionResolver resolver, final IndexResolverReplacer irr) { + String[] indicesArr = new String[pitIndices.size()]; + CreatePitRequest req = new CreatePitRequest(new TimeValue(1, TimeUnit.DAYS), true, + pitIndices.toArray(indicesArr)); + final IndexResolverReplacer.Resolved pitResolved = irr.resolveRequest(req); + return securityRoles.reduce(pitResolved, + user, new String[]{action}, resolver, clusterService); + } +} diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index fd1b26d388..df9c432827 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -130,6 +130,7 @@ public class PrivilegesEvaluator { private final SecurityIndexAccessEvaluator securityIndexAccessEvaluator; private final ProtectedIndexAccessEvaluator protectedIndexAccessEvaluator; private final TermsAggregationEvaluator termsAggregationEvaluator; + private final PitPrivilegesEvaluator pitPrivilegesEvaluator; private final boolean dlsFlsEnabled; private final boolean dfmEmptyOverwritesAll; private DynamicConfigModel dcm; @@ -158,6 +159,7 @@ public PrivilegesEvaluator(final ClusterService clusterService, final ThreadPool securityIndexAccessEvaluator = new SecurityIndexAccessEvaluator(settings, auditLog, irr); protectedIndexAccessEvaluator = new ProtectedIndexAccessEvaluator(settings, auditLog); termsAggregationEvaluator = new TermsAggregationEvaluator(); + pitPrivilegesEvaluator = new PitPrivilegesEvaluator(); this.namedXContentRegistry = namedXContentRegistry; this.dlsFlsEnabled = dlsFlsEnabled; this.dfmEmptyOverwritesAll = settings.getAsBoolean(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, false); @@ -282,6 +284,12 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin return presponse; } + // check access for point in time requests + if(pitPrivilegesEvaluator.evaluate(request, clusterService, user, securityRoles, + action0, resolver, presponse, irr).isComplete()) { + return presponse; + } + final boolean dnfofEnabled = dcm.isDnfofEnabled(); final boolean isTraceEnabled = log.isTraceEnabled(); diff --git a/src/main/java/org/opensearch/security/resolver/IndexResolverReplacer.java b/src/main/java/org/opensearch/security/resolver/IndexResolverReplacer.java index e0eddf9993..d2d0685860 100644 --- a/src/main/java/org/opensearch/security/resolver/IndexResolverReplacer.java +++ b/src/main/java/org/opensearch/security/resolver/IndexResolverReplacer.java @@ -370,11 +370,11 @@ public final static class Resolved { private final boolean isLocalAll; private final IndicesOptions indicesOptions; - private Resolved(final ImmutableSet aliases, - final ImmutableSet allIndices, - final ImmutableSet originalRequested, - final ImmutableSet remoteIndices, - IndicesOptions indicesOptions) { + public Resolved(final ImmutableSet aliases, + final ImmutableSet allIndices, + final ImmutableSet originalRequested, + final ImmutableSet remoteIndices, + IndicesOptions indicesOptions) { this.aliases = aliases; this.allIndices = allIndices; this.originalRequested = originalRequested; diff --git a/src/main/resources/static_config/static_action_groups.yml b/src/main/resources/static_config/static_action_groups.yml index d0ce7613a2..c7c351d171 100644 --- a/src/main/resources/static_config/static_action_groups.yml +++ b/src/main/resources/static_config/static_action_groups.yml @@ -233,8 +233,9 @@ manage_point_in_time: static: true allowed_actions: - "indices:data/read/point_in_time/create" - - "cluster:admin/point_in_time/delete" - - "cluster:admin/point_in_time/read*" + - "indices:data/read/point_in_time/delete" + - "indices:data/read/point_in_time/readall" + - "indices:data/read/search" - "indices:monitor/point_in_time/segments" - type: "cluster" + type: "index" description: "Manage point in time actions" diff --git a/src/test/java/org/opensearch/security/PitIntegrationTests.java b/src/test/java/org/opensearch/security/PitIntegrationTests.java new file mode 100644 index 0000000000..b31450dcf7 --- /dev/null +++ b/src/test/java/org/opensearch/security/PitIntegrationTests.java @@ -0,0 +1,243 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.http.HttpStatus; +import org.junit.Assert; +import org.junit.Test; + +import org.opensearch.action.admin.indices.alias.Alias; +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.security.test.SingleClusterTest; +import org.opensearch.security.test.helper.rest.RestHelper; + +/** + * Integration tests to test point in time APIs permission model + */ +public class PitIntegrationTests extends SingleClusterTest { + + @Test + public void testPitExplicitAPIAccess() throws Exception { + setup(); + RestHelper rh = nonSslRestHelper(); + try (Client tc = getClient()) { + // create alias + tc.admin().indices().create(new CreateIndexRequest("pit_1") + .alias(new Alias("alias"))) + .actionGet(); + // create index + tc.index(new IndexRequest("pit_2").id("2").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE). + source("{\"content\":2}", XContentType.JSON)).actionGet(); + + } + + RestHelper.HttpResponse resc; + + // Create point in time in index should be successful since the user has permission for index + resc = rh.executePostRequest("/alias/_search/point_in_time?keep_alive=100m", "", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + String pitId1 = resc.findValueInJson("pit_id"); + + // Create point in time in index for which the user does not have permission + resc = rh.executePostRequest("/pit_2/_search/point_in_time?keep_alive=100m", "", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + + // Create point in time in index for which the user has permission for + resc = rh.executePostRequest("/pit_2/_search/point_in_time?keep_alive=100m", "", + encodeBasicHeader("pit-2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + String pitId2 = resc.findValueInJson("pit_id"); + resc = rh.executePostRequest("/pit*/_search/point_in_time?keep_alive=100m", "", + encodeBasicHeader("all-pit", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + // PIT segments should work since there is atleast one PIT for which user has access for + resc = rh.executeGetRequest("/_cat/pit_segments", + "{\"pit_id\":\"" + pitId1 +"\"}", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + // PIT segments should work since there is atleast one PIT for which user has access for + resc = rh.executeGetRequest("/_cat/pit_segments", + "{\"pit_id\":\"" + pitId1 +"\"}", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + // Should throw error since user does not have access for pitId2 + resc = rh.executeGetRequest("/_cat/pit_segments", + "{\"pit_id\":\"" + pitId2 +"\"}", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + + // Should throw error since user does not have access for pitId2 + resc = rh.executeGetRequest("/_cat/pit_segments", + "{\"pit_id\":[\"" + pitId1 +"\",\"" + pitId2 + "\"]}", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + + // Delete explicit PITs should work for PIT for which user has access for + resc = rh.executeDeleteRequest("/_search/point_in_time", + "{\"pit_id\":\"" + pitId1 +"\"}", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + Assert.assertEquals(pitId1, resc.findValueInJson("pits[0].pit_id")); + Assert.assertEquals("true", resc.findValueInJson("pits[0].successful")); + + // Should throw error since user does not have access for pitId2 + resc = rh.executeDeleteRequest("/_search/point_in_time", + "{\"pit_id\":\"" + pitId2 +"\"}", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + + // Should throw error since user does not have access for pitId2 + resc = rh.executeDeleteRequest("/_search/point_in_time", + "{\"pit_id\":[\"" + pitId1 +"\",\"" + pitId2 + "\"]}", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + + // Delete explicit PITs should work for PIT for which user has access for + resc = rh.executeDeleteRequest("/_search/point_in_time", + "{\"pit_id\":\"" + pitId2 +"\"}", + encodeBasicHeader("pit-2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + Assert.assertEquals(pitId2, resc.findValueInJson("pits[0].pit_id")); + Assert.assertEquals("true", resc.findValueInJson("pits[0].successful")); + + } + + @Test + public void testPitAllAPIAccess() throws Exception { + setup(); + RestHelper rh = nonSslRestHelper(); + + // Create two indices + try (Client tc = getClient()) { + tc.index(new IndexRequest("pit_1").id("1").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE). + source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("pit_2").id("2").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE). + source("{\"content\":2}", XContentType.JSON)).actionGet(); + } + + RestHelper.HttpResponse resc; + + // Create point in time in index should be successful since the user has permission for index + resc = rh.executePostRequest("/pit_1/_search/point_in_time?keep_alive=100m", "", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + String pitId1 = resc.findValueInJson("pit_id"); + + // Create point in time in index for which the user does not have permission + resc = rh.executePostRequest("/pit_2/_search/point_in_time?keep_alive=100m", "", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + + // Create point in time in index for which the user has permission for + resc = rh.executePostRequest("/pit_2/_search/point_in_time?keep_alive=100m", "", + encodeBasicHeader("pit-2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + String pitId2 = resc.findValueInJson("pit_id"); + + // Throw security error if user does not have all index permission + resc = rh.executeGetRequest("/_search/point_in_time/_all", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + + // List all PITs should work for user with all index access + resc = rh.executeGetRequest("/_search/point_in_time/_all", + encodeBasicHeader("all-pit", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + List pitList = new ArrayList<>(); + pitList.add(pitId1); + pitList.add(pitId2); + pitList.contains(resc.findValueInJson("pits[0].pit_id")); + pitList.contains(resc.findValueInJson("pits[1].pit_id")); + + // Throw security error if user does not have all index permission + resc = rh.executeGetRequest("/_cat/pit_segments/_all", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + + // PIT segments should work for user with all index access + resc = rh.executeGetRequest("/_cat/pit_segments/_all", + encodeBasicHeader("all-pit", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + + // Throw security error if user does not have all index permission + resc = rh.executeDeleteRequest("/_search/point_in_time/_all", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + + // Delete all PITs should work for user with all index access + resc = rh.executeDeleteRequest("/_search/point_in_time/_all", + encodeBasicHeader("all-pit", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + pitList.contains(resc.findValueInJson("pits[0].pit_id")); + pitList.contains(resc.findValueInJson("pits[1].pit_id")); + Assert.assertEquals("true", resc.findValueInJson("pits[0].successful")); + Assert.assertEquals("true", resc.findValueInJson("pits[1].successful")); + + } + + @Test + public void testDataStreamWithPits() throws Exception { + setup(); + RestHelper rh = nonSslRestHelper(); + String indexTemplate = "{\"index_patterns\": [ \"my-data-stream*\" ], \"data_stream\": { }, \"priority\": 200, " + + "\"template\": {\"settings\": { } } }"; + + rh.executePutRequest("/_index_template/my-data-stream-template", indexTemplate, encodeBasicHeader("ds1", "nagilum")); + + rh.executePutRequest("/_data_stream/my-data-stream11", indexTemplate, encodeBasicHeader("ds3", "nagilum")); + rh.executePutRequest("/_data_stream/my-data-stream21", indexTemplate, encodeBasicHeader("ds3", "nagilum")); + + RestHelper.HttpResponse resc; + // create pit should work since user has permission on data stream + resc = rh.executePostRequest("/my-data-stream11/_search/point_in_time?keep_alive=100m", "", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + String pitId1 = resc.findValueInJson("pit_id"); + + // PIT segments works since the user has access for backing indices + resc = rh.executeGetRequest("/_cat/pit_segments", + "{\"pit_id\":\"" + pitId1 +"\"}", + encodeBasicHeader("pit-1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + // create pit should work since user has permission on data stream + resc = rh.executePostRequest("/my-data-stream21/_search/point_in_time?keep_alive=100m", "", + encodeBasicHeader("pit-2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + String pitId2 = resc.findValueInJson("pit_id"); + + // since pit-2 doesn't have permission to backing data stream indices, throw security error + resc = rh.executeGetRequest("/_cat/pit_segments", + "{\"pit_id\":\"" + pitId2 +"\"}", + encodeBasicHeader("pit-2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + + // Delete all PITs should work for user with all index access + resc = rh.executeDeleteRequest("/_search/point_in_time/_all", + encodeBasicHeader("all-pit", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + } +} 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 6bc8a056b8..49d498833e 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 @@ -47,6 +47,7 @@ import org.apache.commons.io.IOUtils; import org.apache.http.Header; import org.apache.http.HttpEntity; +import org.apache.http.HttpHeaders; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; @@ -59,6 +60,7 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; import org.apache.http.config.SocketConfig; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; @@ -143,6 +145,14 @@ public HttpResponse[] executeMultipleAsyncPutRequest(final int numOfRequests, fi public HttpResponse executeGetRequest(final String request, Header... header) { return executeRequest(new HttpGet(getHttpServerUri() + "/" + request), header); } + + public HttpResponse executeGetRequest(final String request, String body, Header... header) { + HttpUriRequest uriRequest = RequestBuilder.get(getHttpServerUri() + "/" + request) + .setEntity(createStringEntity(body)) + .setHeader(HttpHeaders.CONTENT_TYPE, "application/json") + .build(); + return executeRequest(uriRequest, header); + } public HttpResponse executeHeadRequest(final String request, Header... header) { return executeRequest(new HttpHead(getHttpServerUri() + "/" + request), header); @@ -164,6 +174,15 @@ public HttpResponse executeDeleteRequest(final String request, Header... header) return executeRequest(new HttpDelete(getHttpServerUri() + "/" + request), header); } + public HttpResponse executeDeleteRequest(final String request, String body, Header... header) { + HttpUriRequest uriRequest = RequestBuilder.delete(getHttpServerUri() + "/" + request) + .setEntity(createStringEntity(body)) + .setHeader(HttpHeaders.CONTENT_TYPE, "application/json") + .build(); + return executeRequest(uriRequest, header); + } + + public HttpResponse executePostRequest(final String request, String body, Header... header) { HttpPost uriRequest = new HttpPost(getHttpServerUri() + "/" + request); if (body != null && !body.isEmpty()) { diff --git a/src/test/resources/internal_users.yml b/src/test/resources/internal_users.yml index 99d821ce33..44464c9cf7 100644 --- a/src/test/resources/internal_users.yml +++ b/src/test/resources/internal_users.yml @@ -346,6 +346,15 @@ ds2: ds3: hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m #password is: nagilum +pit-1: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum +pit-2: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum +all-pit: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum hidden_test: hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m opendistro_security_roles: diff --git a/src/test/resources/roles.yml b/src/test/resources/roles.yml index 20e7c38cdb..4da098e762 100644 --- a/src/test/resources/roles.yml +++ b/src/test/resources/roles.yml @@ -1129,6 +1129,47 @@ data_stream_3: allowed_actions: - "DATASTREAM_ALL" +point_in_time_1: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + index_permissions: + - index_patterns: + - "pit_1" + - "*my-data-stream11*" + dls: null + fls: null + masked_fields: null + allowed_actions: + - "manage_point_in_time" + +point_in_time_2: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + index_permissions: + - index_patterns: + - "my-data-stream21" + - "pit_2" + dls: null + fls: null + masked_fields: null + allowed_actions: + - "manage_point_in_time" + +point_in_time_all: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + index_permissions: + - index_patterns: + - "*" + dls: null + fls: null + masked_fields: null + allowed_actions: + - "manage_point_in_time" + hidden_test: cluster_permissions: - SGS_CLUSTER_COMPOSITE_OPS diff --git a/src/test/resources/roles_mapping.yml b/src/test/resources/roles_mapping.yml index 9253b0c970..bc32c5b403 100644 --- a/src/test/resources/roles_mapping.yml +++ b/src/test/resources/roles_mapping.yml @@ -413,6 +413,21 @@ data_stream_3: hidden: false users: - "ds3" +point_in_time_1: + reserved: false + hidden: false + users: + - "pit-1" +point_in_time_2: + reserved: false + hidden: false + users: + - "pit-2" +point_in_time_all: + reserved: false + hidden: false + users: + - "all-pit" sem-role: reserved: false hidden: false From f09af90b85080f4c5548e3cc7dd44a5fbb1ef92f Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Mon, 10 Oct 2022 19:24:11 +0200 Subject: [PATCH 052/369] Wide range of integration tests. (#2139) Adds: 1. Search requests test 2. Basic auth tests 3. A package containing matchers that can be used for future tests Signed-off-by: Lukasz Soszynski Signed-off-by: Lukasz Soszynski <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> --- build.gradle | 3 + .../security/SearchOperationTest.java | 1348 +++++++++++++++++ .../opensearch/security/SnapshotSteps.java | 87 ++ .../java/org/opensearch/security/Song.java | 44 + .../security/http/BasicAuthTests.java | 6 +- .../http/BasicAuthWithoutChallengeTests.java | 2 +- .../test/framework/TestSecurityConfig.java | 4 +- .../test/framework/cluster/LocalCluster.java | 4 + .../cluster/LocalOpenSearchCluster.java | 24 +- .../cluster/OpenSearchClientProvider.java | 29 + .../cluster/SearchRequestFactory.java | 79 + .../framework/cluster/TestRestClient.java | 15 +- ...sponseContainExceptionsAtIndexMatcher.java | 70 + .../BulkResponseContainExceptionsMatcher.java | 69 + .../matcher/BulkResponseMatchers.java | 37 + .../ClusterContainSuccessSnapshotMatcher.java | 66 + .../ClusterContainTemplateMatcher.java | 44 + ...lusterContainTemplateWithAliasMatcher.java | 71 + .../ClusterContainsDocumentMatcher.java | 58 + ...ContainsDocumentWithFieldValueMatcher.java | 80 + ...sterContainsSnapshotRepositoryMatcher.java | 66 + .../framework/matcher/ClusterMatchers.java | 49 + .../ContainNotEmptyScrollingIdMatcher.java | 34 + ...ainsAggregationWithNameAndTypeMatcher.java | 55 + .../matcher/ExceptionErrorMessageMatcher.java | 41 + .../matcher/ExceptionMatcherAssert.java | 40 + .../matcher/FailureBulkResponseMatcher.java | 32 + .../GetResponseDocumentFieldValueMatcher.java | 51 + .../matcher/GetResponseDocumentIdMatcher.java | 47 + .../matcher/GetResponseMatchers.java | 27 + .../NumberOfHitsInPageIsEqualToMatcher.java | 45 + .../NumberOfTotalHitsIsEqualToMatcher.java | 57 + .../matcher/OpenSearchExceptionMatchers.java | 33 + .../OpenSearchStatusExceptionMatcher.java | 48 + ...earchHitContainsFieldWithValueMatcher.java | 69 + ...earchHitsContainDocumentWithIdMatcher.java | 60 + .../matcher/SearchResponseMatchers.java | 63 + .../SearchResponseWithStatusCodeMatcher.java | 39 + .../SnapshotInClusterDoesNotExist.java | 47 + .../matcher/SuccessBulkResponseMatcher.java | 47 + .../SuccessfulSearchResponseMatcher.java | 37 + .../resources/log4j2-test.properties | 2 + 42 files changed, 3117 insertions(+), 12 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/SearchOperationTest.java create mode 100644 src/integrationTest/java/org/opensearch/security/SnapshotSteps.java create mode 100644 src/integrationTest/java/org/opensearch/security/Song.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsAtIndexMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseMatchers.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainSuccessSnapshotMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentWithFieldValueMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsSnapshotRepositoryMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ContainNotEmptyScrollingIdMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsAggregationWithNameAndTypeMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionErrorMessageMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionMatcherAssert.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/FailureBulkResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentFieldValueMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentIdMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfHitsInPageIsEqualToMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfTotalHitsIsEqualToMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchExceptionMatchers.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchStatusExceptionMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitContainsFieldWithValueMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentWithIdMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseWithStatusCodeMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SnapshotInClusterDoesNotExist.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessBulkResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulSearchResponseMatcher.java diff --git a/build.gradle b/build.gradle index e01290fd39..7d6b4dc299 100644 --- a/build.gradle +++ b/build.gradle @@ -430,6 +430,9 @@ dependencies { integrationTestImplementation 'org.hamcrest:hamcrest:2.2' integrationTestImplementation "org.bouncycastle:bcpkix-jdk15on:${versions.bouncycastle}" integrationTestImplementation "org.bouncycastle:bcutil-jdk15on:${versions.bouncycastle}" + integrationTestImplementation('org.awaitility:awaitility:4.2.0') { + exclude(group: 'org.hamcrest', module: 'hamcrest') + } } jar { diff --git a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java new file mode 100644 index 0000000000..63dc01738b --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java @@ -0,0 +1,1348 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import com.google.common.base.Stopwatch; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.awaitility.Awaitility; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.action.admin.cluster.repositories.delete.DeleteRepositoryRequest; +import org.opensearch.action.admin.cluster.repositories.put.PutRepositoryRequest; +import org.opensearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; +import org.opensearch.action.admin.indices.alias.Alias; +import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions; +import org.opensearch.action.admin.indices.delete.DeleteIndexRequest; +import org.opensearch.action.admin.indices.exists.indices.IndicesExistsRequest; +import org.opensearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest; +import org.opensearch.action.admin.indices.template.get.GetIndexTemplatesRequest; +import org.opensearch.action.admin.indices.template.get.GetIndexTemplatesResponse; +import org.opensearch.action.bulk.BulkRequest; +import org.opensearch.action.bulk.BulkResponse; +import org.opensearch.action.delete.DeleteRequest; +import org.opensearch.action.fieldcaps.FieldCapabilitiesRequest; +import org.opensearch.action.fieldcaps.FieldCapabilitiesResponse; +import org.opensearch.action.get.GetRequest; +import org.opensearch.action.get.GetResponse; +import org.opensearch.action.get.MultiGetItemResponse; +import org.opensearch.action.get.MultiGetRequest; +import org.opensearch.action.get.MultiGetRequest.Item; +import org.opensearch.action.get.MultiGetResponse; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.search.MultiSearchRequest; +import org.opensearch.action.search.MultiSearchResponse; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.action.search.SearchScrollRequest; +import org.opensearch.action.update.UpdateRequest; +import org.opensearch.client.Client; +import org.opensearch.client.ClusterAdminClient; +import org.opensearch.client.IndicesAdminClient; +import org.opensearch.client.RestHighLevelClient; +import org.opensearch.client.core.CountRequest; +import org.opensearch.client.indices.PutIndexTemplateRequest; +import org.opensearch.cluster.metadata.IndexTemplateMetadata; +import org.opensearch.index.query.BoolQueryBuilder; +import org.opensearch.index.query.MatchQueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.index.reindex.BulkByScrollResponse; +import org.opensearch.index.reindex.ReindexRequest; +import org.opensearch.repositories.RepositoryMissingException; +import org.opensearch.rest.RestStatus; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.arrayContainingInAnyOrder; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions.Type.ADD; +import static org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions.Type.REMOVE; +import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.opensearch.client.RequestOptions.DEFAULT; +import static org.opensearch.rest.RestStatus.ACCEPTED; +import static org.opensearch.rest.RestStatus.FORBIDDEN; +import static org.opensearch.rest.RestStatus.INTERNAL_SERVER_ERROR; +import static org.opensearch.security.Song.FIELD_ARTIST; +import static org.opensearch.security.Song.FIELD_STARS; +import static org.opensearch.security.Song.FIELD_TITLE; +import static org.opensearch.security.Song.QUERY_TITLE_MAGNUM_OPUS; +import static org.opensearch.security.Song.QUERY_TITLE_NEXT_SONG; +import static org.opensearch.security.Song.QUERY_TITLE_POISON; +import static org.opensearch.security.Song.SONGS; +import static org.opensearch.security.Song.TITLE_MAGNUM_OPUS; +import static org.opensearch.security.Song.TITLE_NEXT_SONG; +import static org.opensearch.security.Song.TITLE_POISON; +import static org.opensearch.security.Song.TITLE_SONG_1_PLUS_1; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.averageAggregationRequest; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.getSearchScrollRequest; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.queryStringQueryRequest; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.searchRequestWithScroll; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.statsAggregationRequest; +import static org.opensearch.test.framework.matcher.BulkResponseMatchers.bulkResponseContainExceptions; +import static org.opensearch.test.framework.matcher.BulkResponseMatchers.failureBulkResponse; +import static org.opensearch.test.framework.matcher.BulkResponseMatchers.successBulkResponse; +import static org.opensearch.test.framework.matcher.ClusterMatchers.clusterContainSuccessSnapshot; +import static org.opensearch.test.framework.matcher.ClusterMatchers.clusterContainTemplate; +import static org.opensearch.test.framework.matcher.ClusterMatchers.clusterContainTemplateWithAlias; +import static org.opensearch.test.framework.matcher.ClusterMatchers.clusterContainsDocument; +import static org.opensearch.test.framework.matcher.ClusterMatchers.clusterContainsDocumentWithFieldValue; +import static org.opensearch.test.framework.matcher.ClusterMatchers.clusterContainsSnapshotRepository; +import static org.opensearch.test.framework.matcher.ClusterMatchers.snapshotInClusterDoesNotExists; +import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy; +import static org.opensearch.test.framework.matcher.GetResponseMatchers.containDocument; +import static org.opensearch.test.framework.matcher.GetResponseMatchers.documentContainField; +import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.errorMessageContain; +import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.statusException; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.containAggregationWithNameAndType; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.containNotEmptyScrollingId; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.isSuccessfulSearchResponse; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.numberOfHitsInPageIsEqualTo; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.numberOfTotalHitsIsEqualTo; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitContainsFieldWithValue; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitsContainDocumentWithId; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class SearchOperationTest { + + private static final Logger log = LogManager.getLogger(SearchOperationTest.class); + + public static final String SONG_INDEX_NAME = "song_lyrics"; + public static final String PROHIBITED_SONG_INDEX_NAME = "prohibited_song_lyrics"; + public static final String WRITE_SONG_INDEX_NAME = "write_song_index"; + + public static final String SONG_LYRICS_ALIAS = "song_lyrics_index_alias"; + public static final String PROHIBITED_SONG_ALIAS = "prohibited_song_lyrics_index_alias"; + private static final String COLLECTIVE_INDEX_ALIAS = "collective-index-alias"; + private static final String TEMPLATE_INDEX_PREFIX = "song-transcription*"; + public static final String TEMPORARY_ALIAS_NAME = "temporary-alias"; + public static final String ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001 = "alias-used-in-musical-index-template-0001"; + public static final String ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002 = "alias-used-in-musical-index-template-0002"; + public static final String ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003 = "alias-used-in-musical-index-template-0003"; + public static final String INDEX_NAME_SONG_TRANSCRIPTION_JAZZ = "song-transcription-jazz"; + + public static final String MUSICAL_INDEX_TEMPLATE = "musical-index-template"; + + public static final String UNDELETABLE_TEMPLATE_NAME = "undeletable-template-name"; + + public static final String ALIAS_FROM_UNDELETABLE_TEMPLATE = "alias-from-undeletable-template"; + + public static final String TEST_SNAPSHOT_REPOSITORY_NAME = "test-snapshot-repository"; + + public static final String UNUSED_SNAPSHOT_REPOSITORY_NAME = "unused-snapshot-repository"; + + public static final String RESTORED_SONG_INDEX_NAME = "restored_" + WRITE_SONG_INDEX_NAME; + + public static final String ID_P4 = "4"; + public static final String ID_S3 = "3"; + public static final String ID_S2 = "2"; + public static final String ID_S1 = "1"; + + static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS); + + /** + * All user read permissions are related to {@link #SONG_INDEX_NAME} index + */ + static final User LIMITED_READ_USER = new User("limited_read_user") + .roles(new Role("limited-song-reader") + .clusterPermissions("indices:data/read/mget", "indices:data/read/msearch", "indices:data/read/scroll") + .indexPermissions("indices:data/read/search", "indices:data/read/get", "indices:data/read/mget*", "indices:admin/aliases", "indices:data/read/field_caps", "indices:data/read/field_caps*") + .on(SONG_INDEX_NAME)); + + static final User LIMITED_WRITE_USER = new User("limited_write_user") + .roles(new Role("limited-write-role") + .clusterPermissions("indices:data/write/bulk", "indices:admin/template/put", "indices:admin/template/delete", "cluster:admin/repository/put", "cluster:admin/repository/delete", "cluster:admin/snapshot/create", "cluster:admin/snapshot/status", "cluster:admin/snapshot/status[nodes]", "cluster:admin/snapshot/delete", "cluster:admin/snapshot/get", "cluster:admin/snapshot/restore") + .indexPermissions("indices:data/write/index", "indices:data/write/bulk[s]", "indices:admin/create", "indices:admin/mapping/put", + "indices:data/write/update", "indices:data/write/bulk[s]", "indices:data/write/delete", "indices:data/write/bulk[s]") + .on(WRITE_SONG_INDEX_NAME), + new Role("transcription-role") + .indexPermissions("indices:data/write/index", "indices:admin/create", "indices:data/write/bulk[s]", "indices:admin/mapping/put") + .on(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ), + new Role("limited-write-index-restore-role") + .indexPermissions("indices:data/write/index", "indices:admin/create", "indices:data/read/search") + .on(RESTORED_SONG_INDEX_NAME)); + + + /** + * User who is allowed read both index {@link #SONG_INDEX_NAME} and {@link #PROHIBITED_SONG_INDEX_NAME} + */ + static final User DOUBLE_READER_USER = new User("double_read_user") + .roles(new Role("full-song-reader").indexPermissions("indices:data/read/search") + .on(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME)); + + static final User REINDEXING_USER = new User("reindexing_user") + .roles(new Role("song-reindexing-target-write") + .clusterPermissions("indices:data/write/reindex", "indices:data/write/bulk") + .indexPermissions("indices:admin/create", "indices:data/write/index", "indices:data/write/bulk[s]", "indices:admin/mapping/put") + .on(WRITE_SONG_INDEX_NAME), + new Role("song-reindexing-source-read") + .clusterPermissions("indices:data/read/scroll") + .indexPermissions("indices:data/read/search") + .on(SONG_INDEX_NAME)); + + private Client internalClient; + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) + .authc(AUTHC_HTTPBASIC_INTERNAL).users(ADMIN_USER, LIMITED_READ_USER, LIMITED_WRITE_USER, DOUBLE_READER_USER, REINDEXING_USER) + .build(); + + @BeforeClass + public static void createTestData() { + try(Client client = cluster.getInternalNodeClient()){ + client.prepareIndex(SONG_INDEX_NAME).setId(ID_S1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0]).get(); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new AliasActions(ADD).indices(SONG_INDEX_NAME).alias(SONG_LYRICS_ALIAS))).actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SONG_INDEX_NAME).id(ID_S2).source(SONGS[1])).actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SONG_INDEX_NAME).id(ID_S3).source(SONGS[2])).actionGet(); + + client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(ID_P4).setSource(SONGS[3]).setRefreshPolicy(IMMEDIATE).get(); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new AliasActions(ADD).indices(PROHIBITED_SONG_INDEX_NAME).alias(PROHIBITED_SONG_ALIAS))).actionGet(); + + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new AliasActions(ADD).indices(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME).alias(COLLECTIVE_INDEX_ALIAS))).actionGet(); + var createTemplateRequest = new org.opensearch.action.admin.indices.template.put.PutIndexTemplateRequest(UNDELETABLE_TEMPLATE_NAME); + createTemplateRequest.patterns(List.of("pattern-does-not-match-to-any-index")); + createTemplateRequest.alias(new Alias(ALIAS_FROM_UNDELETABLE_TEMPLATE)); + client.admin().indices().putTemplate(createTemplateRequest).actionGet(); + + client.admin().cluster().putRepository(new PutRepositoryRequest(UNUSED_SNAPSHOT_REPOSITORY_NAME).type("fs").settings(Map.of("location", cluster.getSnapshotDirPath()))).actionGet(); + } + } + + @Before + public void retrieveClusterClient() { + this.internalClient = cluster.getInternalNodeClient(); + } + + @After + public void cleanData() throws ExecutionException, InterruptedException { + Stopwatch stopwatch = Stopwatch.createStarted(); + IndicesAdminClient indices = internalClient.admin().indices(); + for(String indexToBeDeleted : List.of(WRITE_SONG_INDEX_NAME, INDEX_NAME_SONG_TRANSCRIPTION_JAZZ, RESTORED_SONG_INDEX_NAME)) { + IndicesExistsRequest indicesExistsRequest = new IndicesExistsRequest(indexToBeDeleted); + var indicesExistsResponse = indices.exists(indicesExistsRequest).get(); + if (indicesExistsResponse.isExists()) { + DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexToBeDeleted); + indices.delete(deleteIndexRequest).actionGet(); + Awaitility.await().until(() -> indices.exists(indicesExistsRequest).get().isExists() == false); + } + } + + for(String aliasToBeDeleted : List.of(TEMPORARY_ALIAS_NAME, ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002)) { + if(indices.exists(new IndicesExistsRequest(aliasToBeDeleted)).get().isExists()) { + AliasActions aliasAction = new AliasActions(AliasActions.Type.REMOVE).indices(SONG_INDEX_NAME).alias(aliasToBeDeleted); + internalClient.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(aliasAction)).get(); + } + } + + GetIndexTemplatesResponse response = indices.getTemplates(new GetIndexTemplatesRequest(MUSICAL_INDEX_TEMPLATE)).get(); + for(IndexTemplateMetadata metadata : response.getIndexTemplates()) { + indices.deleteTemplate(new DeleteIndexTemplateRequest(metadata.getName())).get(); + } + + ClusterAdminClient clusterClient = internalClient.admin().cluster(); + try { + clusterClient.deleteRepository(new DeleteRepositoryRequest(TEST_SNAPSHOT_REPOSITORY_NAME)).actionGet(); + } catch (RepositoryMissingException e) { + log.debug("Repository '{}' does not exist. This is expected in most of test cases", TEST_SNAPSHOT_REPOSITORY_NAME, e); + } + internalClient.close(); + log.debug("Cleaning data after test took {}", stopwatch.stop()); + } + + @Test + public void shouldSearchForDocuments_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + } + + @Test + public void shouldSearchForDocuments_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(PROHIBITED_SONG_INDEX_NAME, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentsViaAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(SONG_LYRICS_ALIAS, QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + } + + @Test + public void shouldSearchForDocumentsViaAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(PROHIBITED_SONG_ALIAS, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldBeAbleToSearchSongViaMultiIndexAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(COLLECTIVE_INDEX_ALIAS, QUERY_TITLE_NEXT_SONG); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S3)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); + } + } + + @Test + public void shouldBeAbleToSearchAllIndexes_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + } + + @Test + public void shouldBeAbleToSearchSongViaMultiIndexAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(COLLECTIVE_INDEX_ALIAS, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldBeAbleToSearchAllIndexes_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(QUERY_TITLE_MAGNUM_OPUS); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldBeAbleToSearchSongIndexesWithAsterisk_prohibitedSongIndex_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("*" + SONG_INDEX_NAME, QUERY_TITLE_POISON); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, PROHIBITED_SONG_INDEX_NAME, ID_P4)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_POISON)); + } + } + + @Test + public void shouldBeAbleToSearchSongIndexesWithAsterisk_singIndex_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("*" + SONG_INDEX_NAME, QUERY_TITLE_NEXT_SONG); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S3)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); + } + } + + @Test + public void shouldBeAbleToSearchSongIndexesWithAsterisk_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("*" + SONG_INDEX_NAME, QUERY_TITLE_NEXT_SONG); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldFindSongUsingDslQuery_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = new SearchRequest(SONG_INDEX_NAME); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); + boolQueryBuilder.filter(QueryBuilders.regexpQuery(FIELD_ARTIST, "f.+")); + boolQueryBuilder.filter(new MatchQueryBuilder(FIELD_TITLE, TITLE_MAGNUM_OPUS)); + searchSourceBuilder.query(boolQueryBuilder); + searchRequest.source(searchSourceBuilder); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + } + + @Test + public void shouldFindSongUsingDslQuery_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = new SearchRequest(PROHIBITED_SONG_INDEX_NAME); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); + boolQueryBuilder.filter(QueryBuilders.regexpQuery(FIELD_ARTIST, "n.+")); + boolQueryBuilder.filter(new MatchQueryBuilder(FIELD_TITLE, TITLE_POISON)); + searchSourceBuilder.query(boolQueryBuilder); + searchRequest.source(searchSourceBuilder); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldPerformSearchWithAllIndexAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("_all", QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + } + + @Test + public void shouldPerformSearchWithAllIndexAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("_all", QUERY_TITLE_MAGNUM_OPUS); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldScrollOverSearchResults_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = searchRequestWithScroll(SONG_INDEX_NAME, 2); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containNotEmptyScrollingId()); + + SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); + + SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); + assertThat(scrollResponse, isSuccessfulSearchResponse()); + assertThat(scrollResponse, containNotEmptyScrollingId()); + assertThat(scrollResponse, numberOfTotalHitsIsEqualTo(3)); + assertThat(scrollResponse, numberOfHitsInPageIsEqualTo(1)); + } + } + + @Test + public void shouldScrollOverSearchResults_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { + SearchRequest searchRequest = searchRequestWithScroll(SONG_INDEX_NAME, 2); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containNotEmptyScrollingId()); + + SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); + + assertThatThrownBy(() -> restHighLevelClient.scroll(scrollRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldGetDocument_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + GetResponse response = restHighLevelClient.get(new GetRequest(SONG_INDEX_NAME, ID_S1), DEFAULT); + + assertThat(response, containDocument(SONG_INDEX_NAME, ID_S1)); + assertThat(response, documentContainField(FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + } + + @Test + public void shouldGetDocument_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + GetRequest getRequest = new GetRequest(PROHIBITED_SONG_INDEX_NAME, ID_P4); + assertThatThrownBy(() -> restHighLevelClient.get(getRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldPerformMultiGetDocuments_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + MultiGetRequest request = new MultiGetRequest(); + request.add(new Item(SONG_INDEX_NAME, ID_S1)); + request.add(new Item(SONG_INDEX_NAME, ID_S2)); + + MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); + + assertThat(response, is(notNullValue())); + MultiGetItemResponse[] responses = response.getResponses(); + assertThat(responses, arrayWithSize(2)); + Matcher withNullFailureProperty = hasProperty("failure", nullValue()); + assertThat(responses, arrayContaining(withNullFailureProperty, withNullFailureProperty)); + + assertThat(responses[0].getResponse(), allOf( + containDocument(SONG_INDEX_NAME, ID_S1), + documentContainField(FIELD_TITLE, TITLE_MAGNUM_OPUS)) + ); + assertThat(responses[1].getResponse(), allOf( + containDocument(SONG_INDEX_NAME, ID_S2), + documentContainField(FIELD_TITLE, TITLE_SONG_1_PLUS_1)) + ); + } + } + + @Test + public void shouldPerformMultiGetDocuments_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { + MultiGetRequest request = new MultiGetRequest(); + request.add(new Item(SONG_INDEX_NAME, ID_S1)); + + assertThatThrownBy(() -> restHighLevelClient.mget(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldPerformMultiGetDocuments_partiallyPositive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + MultiGetRequest request = new MultiGetRequest(); + request.add(new Item(SONG_INDEX_NAME, ID_S1)); + request.add(new Item(PROHIBITED_SONG_INDEX_NAME, ID_P4)); + + MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); + + assertThat(request, notNullValue()); + MultiGetItemResponse[] responses = response.getResponses(); + assertThat(responses, arrayWithSize(2)); + assertThat(responses, arrayContaining( + hasProperty("failure", nullValue()), + hasProperty("failure", notNullValue()) + )); + assertThat(responses[1].getFailure().getFailure(), statusException(INTERNAL_SERVER_ERROR)); + assertThat(responses[1].getFailure().getFailure(), errorMessageContain("security_exception")); + } + } + + @Test + public void shouldBeAllowedToPerformMulitSearch_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + MultiSearchRequest request = new MultiSearchRequest(); + request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS)); + request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_NEXT_SONG)); + + MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); + + assertThat(response, notNullValue()); + MultiSearchResponse.Item[] responses = response.getResponses(); + assertThat(responses, Matchers.arrayWithSize(2)); + assertThat(responses, arrayContaining( + notNullValue(), + notNullValue() + )); + assertThat(responses[0].getFailure(), nullValue()); + assertThat(responses[1].getFailure(), nullValue()); + + assertThat(responses[0].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat(responses[0].getResponse(), searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); + assertThat(responses[1].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); + assertThat(responses[1].getResponse(), searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S3)); + } + } + + @Test + public void shouldBeAllowedToPerformMulitSearch_partiallyPositive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + MultiSearchRequest request = new MultiSearchRequest(); + request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS)); + request.add(queryStringQueryRequest(PROHIBITED_SONG_INDEX_NAME, QUERY_TITLE_POISON)); + + MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); + + assertThat(response, notNullValue()); + MultiSearchResponse.Item[] responses = response.getResponses(); + assertThat(responses, Matchers.arrayWithSize(2)); + assertThat(responses, arrayContaining(notNullValue(), notNullValue())); + assertThat(responses[0].getFailure(), nullValue()); + assertThat(responses[1].getFailure(), statusException(INTERNAL_SERVER_ERROR)); + assertThat(responses[1].getFailure(), errorMessageContain("security_exception")); + assertThat(responses[1].getResponse(), nullValue()); + } + } + + @Test + public void shouldBeAllowedToPerformMulitSearch_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { + MultiSearchRequest request = new MultiSearchRequest(); + request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS)); + request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_NEXT_SONG)); + + assertThatThrownBy(() -> restHighLevelClient.msearch(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldAggregateDataAndComputeAverage_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + final String aggregationName = "averageStars"; + SearchRequest searchRequest = averageAggregationRequest(SONG_INDEX_NAME, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + } + } + + @Test + public void shouldAggregateDataAndComputeAverage_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = averageAggregationRequest(PROHIBITED_SONG_INDEX_NAME, "averageStars", FIELD_STARS); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldPerformStatAggregation_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + final String aggregationName = "statsStars"; + SearchRequest searchRequest = statsAggregationRequest(SONG_INDEX_NAME, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "stats")); + } + } + + @Test + public void shouldPerformStatAggregation_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = statsAggregationRequest(PROHIBITED_SONG_INDEX_NAME, "statsStars", FIELD_STARS); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldIndexDocumentInBulkRequest_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1])); + bulkRequest.setRefreshPolicy(IMMEDIATE); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat(response, successBulkResponse()); + assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, "one")); + assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, "two")); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "one", FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); + } + } + + @Test + public void shouldIndexDocumentInBulkRequest_partiallyPositive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("one").source(SONGS[0])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1])); + bulkRequest.setRefreshPolicy(IMMEDIATE); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat(response, bulkResponseContainExceptions(0, allOf( + statusException(INTERNAL_SERVER_ERROR), + errorMessageContain("security_exception") + ))); + assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, "two")); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); + } + } + + @Test + public void shouldIndexDocumentInBulkRequest_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("one").source(SONGS[0])); + bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("two").source(SONGS[1])); + bulkRequest.setRefreshPolicy(IMMEDIATE); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat(response, allOf( + failureBulkResponse(), + bulkResponseContainExceptions(statusException(INTERNAL_SERVER_ERROR)), + bulkResponseContainExceptions(errorMessageContain("security_exception")) + )); + assertThat(internalClient, not(clusterContainsDocument(SONG_INDEX_NAME, "one"))); + assertThat(internalClient, not(clusterContainsDocument(SONG_INDEX_NAME, "two"))); + } + } + + @Test + public void shouldUpdateDocumentsInBulk_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + final String titleOne = "shape of my mind"; + final String titleTwo = "forgiven"; + BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1])); + restHighLevelClient.bulk(bulkRequest, DEFAULT); + bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new UpdateRequest(WRITE_SONG_INDEX_NAME, "one").doc(Map.of(FIELD_TITLE, titleOne))); + bulkRequest.add(new UpdateRequest(WRITE_SONG_INDEX_NAME, "two").doc(Map.of(FIELD_TITLE, titleTwo))); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat(response, successBulkResponse()); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "one", FIELD_TITLE, titleOne)); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, titleTwo)); + } + } + + @Test + public void shouldUpdateDocumentsInBulk_partiallyPositive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + final String titleOne = "shape of my mind"; + BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0])); + restHighLevelClient.bulk(bulkRequest, DEFAULT); + bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new UpdateRequest(WRITE_SONG_INDEX_NAME, "one").doc(Map.of(FIELD_TITLE, titleOne))); + bulkRequest.add(new UpdateRequest(SONG_INDEX_NAME, ID_S2).doc(Map.of(FIELD_TITLE, "forgiven"))); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat(response, bulkResponseContainExceptions(1, allOf( + statusException(INTERNAL_SERVER_ERROR), + errorMessageContain("security_exception") + ))); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "one", FIELD_TITLE, titleOne)); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S2, FIELD_TITLE, TITLE_SONG_1_PLUS_1)); + } + } + + @Test + public void shouldUpdateDocumentsInBulk_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new UpdateRequest(SONG_INDEX_NAME, ID_S1).doc(Map.of(FIELD_TITLE, "shape of my mind"))); + bulkRequest.add(new UpdateRequest(SONG_INDEX_NAME, ID_S2).doc(Map.of(FIELD_TITLE, "forgiven"))); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat(response, allOf( + failureBulkResponse(), + bulkResponseContainExceptions(statusException(INTERNAL_SERVER_ERROR)), + bulkResponseContainExceptions(errorMessageContain("security_exception")) + )); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S1, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S2, FIELD_TITLE, TITLE_SONG_1_PLUS_1)); + } + } + + @Test + public void shouldDeleteDocumentInBulk_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("three").source(SONGS[2])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("four").source(SONGS[3])); + assertThat(restHighLevelClient.bulk(bulkRequest, DEFAULT), successBulkResponse()); + bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "one")); + bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "three")); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat(response, successBulkResponse()); + assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, "one"))); + assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, "three"))); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "four", FIELD_TITLE, TITLE_POISON)); + } + } + + @Test + public void shouldDeleteDocumentInBulk_partiallyPositive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1])); + assertThat(restHighLevelClient.bulk(bulkRequest, DEFAULT), successBulkResponse()); + bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "one")); + bulkRequest.add(new DeleteRequest(SONG_INDEX_NAME, ID_S3)); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, "one"))); + + assertThat(response, bulkResponseContainExceptions(1, allOf( + statusException(INTERNAL_SERVER_ERROR), + errorMessageContain("security_exception") + ))); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S3, FIELD_TITLE, TITLE_NEXT_SONG)); + } + } + + @Test + public void shouldDeleteDocumentInBulk_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new DeleteRequest(SONG_INDEX_NAME, ID_S1)); + bulkRequest.add(new DeleteRequest(SONG_INDEX_NAME, ID_S3)); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat(response, allOf( + failureBulkResponse(), + bulkResponseContainExceptions(statusException(INTERNAL_SERVER_ERROR)), + bulkResponseContainExceptions(errorMessageContain("security_exception")) + )); + assertThat(internalClient, clusterContainsDocument(SONG_INDEX_NAME, ID_S1)); + assertThat(internalClient, clusterContainsDocument(SONG_INDEX_NAME, ID_S3)); + } + } + + @Test + public void shouldReindexDocuments_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(REINDEXING_USER)) { + ReindexRequest reindexRequest = new ReindexRequest().setSourceIndices(SONG_INDEX_NAME).setDestIndex(WRITE_SONG_INDEX_NAME); + + BulkByScrollResponse response = restHighLevelClient.reindex(reindexRequest, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.getBulkFailures(), empty()); + assertThat(response.getSearchFailures(), empty()); + assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_S1)); + assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_S2)); + assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_S3)); + } + } + + @Test + public void shouldReindexDocuments_negativeSource() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(REINDEXING_USER)) { + ReindexRequest reindexRequest = new ReindexRequest().setSourceIndices(PROHIBITED_SONG_INDEX_NAME).setDestIndex(WRITE_SONG_INDEX_NAME); + + assertThatThrownBy(() -> restHighLevelClient.reindex(reindexRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_P4))); + } + } + + @Test + public void shouldReindexDocuments_negativeDestination() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(REINDEXING_USER)) { + ReindexRequest reindexRequest = new ReindexRequest().setSourceIndices(SONG_INDEX_NAME).setDestIndex(PROHIBITED_SONG_INDEX_NAME); + + assertThatThrownBy(() -> restHighLevelClient.reindex(reindexRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(internalClient, not(clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_S1))); + assertThat(internalClient, not(clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_S2))); + assertThat(internalClient, not(clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_S3))); + } + } + + @Test + public void shouldReindexDocuments_negativeSourceAndDestination() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(REINDEXING_USER)) { + ReindexRequest reindexRequest = new ReindexRequest().setSourceIndices(PROHIBITED_SONG_INDEX_NAME).setDestIndex(SONG_INDEX_NAME); + + assertThatThrownBy(() -> restHighLevelClient.reindex(reindexRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldCreateAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + AliasActions aliasAction = new AliasActions(ADD).indices(SONG_INDEX_NAME).alias(TEMPORARY_ALIAS_NAME); + IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); + + var response = restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, clusterContainsDocument(TEMPORARY_ALIAS_NAME, ID_S1)); + } + } + + @Test + public void shouldCreateAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + AliasActions aliasAction = new AliasActions(ADD).indices(PROHIBITED_SONG_INDEX_NAME).alias(TEMPORARY_ALIAS_NAME); + IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); + + assertThatThrownBy(() -> restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT), statusException(FORBIDDEN)); + + assertThat(internalClient, not(clusterContainsDocument(TEMPORARY_ALIAS_NAME, ID_P4))); + } + } + + @Test + public void shouldDeleteAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + AliasActions aliasAction = new AliasActions(ADD).indices(SONG_INDEX_NAME).alias(TEMPORARY_ALIAS_NAME); + IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); + restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT); + aliasAction = new AliasActions(REMOVE).indices(SONG_INDEX_NAME).alias(TEMPORARY_ALIAS_NAME); + indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); + + var response = restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, not(clusterContainsDocument(TEMPORARY_ALIAS_NAME, ID_S1))); + } + } + + @Test + public void shouldDeleteAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + AliasActions aliasAction = new AliasActions(REMOVE).indices(PROHIBITED_SONG_INDEX_NAME).alias(PROHIBITED_SONG_ALIAS); + IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); + + assertThatThrownBy(() -> restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT), statusException(FORBIDDEN)); + + assertThat(internalClient, clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_P4)); + } + } + + @Test + public void shouldCreateIndexTemplate_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + PutIndexTemplateRequest request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE) + .patterns(List.of(TEMPLATE_INDEX_PREFIX)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002)); + + var response = restHighLevelClient.indices().putTemplate(request, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE )); + String documentId = "0001"; + IndexRequest indexRequest = new IndexRequest(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ).id(documentId).source(SONGS[0]) + .setRefreshPolicy(IMMEDIATE); + restHighLevelClient.index(indexRequest, DEFAULT); + assertThat(internalClient, clusterContainsDocument(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ, documentId)); + assertThat(internalClient, clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, documentId)); + assertThat(internalClient, clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002, documentId)); + } + } + + @Test + public void shouldCreateIndexTemplate_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + PutIndexTemplateRequest request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE) + .patterns(List.of(TEMPLATE_INDEX_PREFIX)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002)); + + assertThatThrownBy(() -> restHighLevelClient.indices().putTemplate(request, DEFAULT), statusException(FORBIDDEN)); + assertThat(internalClient, not(clusterContainTemplate(MUSICAL_INDEX_TEMPLATE ))); + } + } + + @Test + public void shouldDeleteTemplate_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + PutIndexTemplateRequest request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE) + .patterns(List.of(TEMPLATE_INDEX_PREFIX)); + restHighLevelClient.indices().putTemplate(request, DEFAULT); + assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE)); + DeleteIndexTemplateRequest deleteRequest = new DeleteIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE); + + var response = restHighLevelClient.indices().deleteTemplate(deleteRequest, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, not(clusterContainTemplate(MUSICAL_INDEX_TEMPLATE))); + } + } + + @Test + public void shouldDeleteTemplate_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + DeleteIndexTemplateRequest deleteRequest = new DeleteIndexTemplateRequest(UNDELETABLE_TEMPLATE_NAME); + + assertThatThrownBy(() -> restHighLevelClient.indices().deleteTemplate(deleteRequest, DEFAULT), statusException(FORBIDDEN)); + + assertThat(internalClient, clusterContainTemplate(UNDELETABLE_TEMPLATE_NAME)); + } + } + + @Test + public void shouldUpdateTemplate_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + PutIndexTemplateRequest request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE) + .patterns(List.of(TEMPLATE_INDEX_PREFIX)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002)); + restHighLevelClient.indices().putTemplate(request, DEFAULT); + assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE)); + request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE) + .patterns(List.of(TEMPLATE_INDEX_PREFIX)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003)); + + var response = restHighLevelClient.indices().putTemplate(request, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + String documentId = "000one"; + IndexRequest indexRequest = new IndexRequest(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ).id(documentId).source(SONGS[0]) + .setRefreshPolicy(IMMEDIATE); + restHighLevelClient.index(indexRequest, DEFAULT); + assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE)); + assertThat(internalClient, clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003, documentId)); + assertThat(internalClient, not(clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, documentId))); + assertThat(internalClient, not(clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002, documentId))); + } + } + @Test + public void shouldUpdateTemplate_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + PutIndexTemplateRequest request = new PutIndexTemplateRequest(UNDELETABLE_TEMPLATE_NAME) + .patterns(List.of(TEMPLATE_INDEX_PREFIX)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003)); + + assertThatThrownBy(() -> restHighLevelClient.indices().putTemplate(request, DEFAULT), statusException(FORBIDDEN)); + assertThat(internalClient, clusterContainTemplateWithAlias(UNDELETABLE_TEMPLATE_NAME, ALIAS_FROM_UNDELETABLE_TEMPLATE)); + assertThat(internalClient, not(clusterContainTemplateWithAlias(UNDELETABLE_TEMPLATE_NAME, ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003))); + } + } + + @Test + public void shouldGetFieldCapabilitiesForAllIndexes_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().fields(FIELD_TITLE); + + FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.get(), aMapWithSize(1)); + assertThat(response.getIndices(), arrayWithSize(2)); + assertThat(response.getField(FIELD_TITLE), hasKey("text")); + assertThat(response.getIndices(), arrayContainingInAnyOrder(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME)); + } + } + + @Test + public void shouldGetFieldCapabilitiesForAllIndexes_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().fields(FIELD_TITLE); + + assertThatThrownBy(() -> restHighLevelClient.fieldCaps(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldGetFieldCapabilitiesForParticularIndex_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(SONG_INDEX_NAME).fields(FIELD_TITLE); + + FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.get(), aMapWithSize(1)); + assertThat(response.getIndices(), arrayWithSize(1)); + assertThat(response.getField(FIELD_TITLE), hasKey("text")); + assertThat(response.getIndices(), arrayContainingInAnyOrder(SONG_INDEX_NAME)); + } + } + + @Test + public void shouldGetFieldCapabilitiesForParticularIndex_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(PROHIBITED_SONG_INDEX_NAME).fields(FIELD_TITLE); + + assertThatThrownBy(() -> restHighLevelClient.fieldCaps(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldCreateSnapshotRepository_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + String snapshotDirPath = cluster.getSnapshotDirPath(); + + var response = steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotDirPath, "fs"); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME)); + } + } + + @Test + public void shouldCreateSnapshotRepository_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + String snapshotDirPath = cluster.getSnapshotDirPath(); + + assertThatThrownBy(() -> steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotDirPath, "fs"), statusException(FORBIDDEN)); + assertThat(internalClient, not(clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME))); + } + } + + @Test + public void shouldDeleteSnapshotRepository_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + assertThat(internalClient, clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME)); + + var response = steps.deleteSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, not(clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME))); + } + } + + @Test + public void shouldDeleteSnapshotRepository_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + + assertThatThrownBy(() -> steps.deleteSnapshotRepository(UNUSED_SNAPSHOT_REPOSITORY_NAME), statusException(FORBIDDEN)); + assertThat(internalClient, clusterContainsSnapshotRepository(UNUSED_SNAPSHOT_REPOSITORY_NAME)); + } + } + + @Test + public void shouldCreateSnapshot_positive() throws IOException { + final String snapshotName = "snapshot-positive-test"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + + CreateSnapshotResponse response = steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, SONG_INDEX_NAME); + + assertThat(response, notNullValue()); + assertThat(response.status(), equalTo(RestStatus.ACCEPTED)); + steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + assertThat(internalClient, clusterContainSuccessSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName)); + } + } + + @Test + public void shouldCreateSnapshot_negative() throws IOException { + final String snapshotName = "snapshot-negative-test"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + + assertThatThrownBy(() -> steps.createSnapshot(UNUSED_SNAPSHOT_REPOSITORY_NAME, snapshotName, SONG_INDEX_NAME), statusException(FORBIDDEN)); + + assertThat(internalClient, snapshotInClusterDoesNotExists(UNUSED_SNAPSHOT_REPOSITORY_NAME, snapshotName)); + } + } + + @Test + public void shouldDeleteSnapshot_positive() throws IOException { + String snapshotName = "delete-snapshot-positive"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + restHighLevelClient.snapshot(); + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, SONG_INDEX_NAME); + steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + + var response = steps.deleteSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, snapshotInClusterDoesNotExists(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName)); + } + } + + @Test + public void shouldDeleteSnapshot_negative() throws IOException { + String snapshotName = "delete-snapshot-negative"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, SONG_INDEX_NAME); + steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + } + try(RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + assertThatThrownBy(() -> steps.deleteSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName), statusException(FORBIDDEN)); + + assertThat(internalClient, clusterContainSuccessSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName)); + } + } + + @Test + public void shouldRestoreSnapshot_positive() throws IOException { + final String snapshotName = "restore-snapshot-positive"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + // 1. create some documents + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1])); + bulkRequest.setRefreshPolicy(IMMEDIATE); + restHighLevelClient.bulk(bulkRequest, DEFAULT); + + //2. create snapshot repository + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + + // 3. create snapshot + steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, WRITE_SONG_INDEX_NAME) ; + + // 4. wait till snapshot is ready + steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + + // 5. introduce some changes + bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Drei").source(SONGS[2])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Vier").source(SONGS[3])); + bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "Eins")); + bulkRequest.setRefreshPolicy(IMMEDIATE); + restHighLevelClient.bulk(bulkRequest, DEFAULT); + + // 6. restore the snapshot + var response = steps.restoreSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, "(.+)", "restored_$1"); + + assertThat(response, notNullValue()); + assertThat(response.status(), equalTo(ACCEPTED)); + + // 7. wait until snapshot is restored + CountRequest countRequest = new CountRequest(RESTORED_SONG_INDEX_NAME); + Awaitility.await().until(() -> restHighLevelClient.count(countRequest, DEFAULT).getCount() == 2); + + //8. verify that document are present in restored index + assertThat(internalClient, clusterContainsDocumentWithFieldValue(RESTORED_SONG_INDEX_NAME, "Eins", FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(RESTORED_SONG_INDEX_NAME, "Zwei", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); + assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Drei"))); + assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Vier"))); + } + } + + @Test + public void shouldRestoreSnapshot_failureForbiddenIndex() throws IOException { + final String snapshotName = "restore-snapshot-negative-forbidden-index"; + String restoreToIndex = "forbidden_index"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + // 1. create some documents + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1])); + bulkRequest.setRefreshPolicy(IMMEDIATE); + restHighLevelClient.bulk(bulkRequest, DEFAULT); + + //2. create snapshot repository + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + + // 3. create snapshot + steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, WRITE_SONG_INDEX_NAME); + + // 4. wait till snapshot is ready + steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + + // 5. restore the snapshot + assertThatThrownBy(() -> steps.restoreSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, "(.+)", restoreToIndex), + statusException(FORBIDDEN)); + + + //6. verify that document are not present in restored index + assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Eins"))); + assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Zwei"))); + } + } + + @Test + public void shouldRestoreSnapshot_failureOperationForbidden() throws IOException { + String snapshotName = "restore-snapshot-negative-forbidden-operation"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + // 1. create some documents + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1])); + bulkRequest.setRefreshPolicy(IMMEDIATE); + restHighLevelClient.bulk(bulkRequest, DEFAULT); + + //2. create snapshot repository + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + + // 3. create snapshot + steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, WRITE_SONG_INDEX_NAME); + + // 4. wait till snapshot is ready + steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + } + // 5. restore the snapshot + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + assertThatThrownBy( () -> steps.restoreSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, "(.+)", "restored_$1"), + statusException(FORBIDDEN)); + + // 6. verify that documents does not exist + assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Eins"))); + assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Zwei"))); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java b/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java new file mode 100644 index 0000000000..04155e762f --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java @@ -0,0 +1,87 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.io.IOException; +import java.util.Map; + +import org.awaitility.Awaitility; + +import org.opensearch.action.admin.cluster.repositories.delete.DeleteRepositoryRequest; +import org.opensearch.action.admin.cluster.repositories.put.PutRepositoryRequest; +import org.opensearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; +import org.opensearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; +import org.opensearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; +import org.opensearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; +import org.opensearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse; +import org.opensearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest; +import org.opensearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse; +import org.opensearch.client.RestHighLevelClient; +import org.opensearch.client.SnapshotClient; +import org.opensearch.snapshots.SnapshotInfo; +import org.opensearch.snapshots.SnapshotState; + +import static java.util.Objects.requireNonNull; +import static org.opensearch.client.RequestOptions.DEFAULT; + +class SnapshotSteps { + + private final SnapshotClient snapshotClient; + + public SnapshotSteps(RestHighLevelClient restHighLevelClient) { + this.snapshotClient = requireNonNull(restHighLevelClient, "Rest high level client is required.").snapshot(); + } + + // CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here + public org.opensearch.action.support.master.AcknowledgedResponse createSnapshotRepository(String repositoryName, String snapshotDirPath, String type) + //CS-ENFORCE-SINGLE + throws IOException { + PutRepositoryRequest createRepositoryRequest = new PutRepositoryRequest().name(repositoryName).type(type) + .settings(Map.of("location", snapshotDirPath)); + return snapshotClient.createRepository(createRepositoryRequest, DEFAULT); + } + + public CreateSnapshotResponse createSnapshot(String repositoryName, String snapshotName, String...indices) throws IOException { + CreateSnapshotRequest createSnapshotRequest = new CreateSnapshotRequest(repositoryName, snapshotName) + .indices(indices); + return snapshotClient.create(createSnapshotRequest, DEFAULT); + } + + public void waitForSnapshotCreation(String repositoryName, String snapshotName) { + GetSnapshotsRequest getSnapshotsRequest = new GetSnapshotsRequest(repositoryName, new String[] { snapshotName }); + Awaitility.await().until(() -> { + GetSnapshotsResponse snapshotsResponse = snapshotClient.get(getSnapshotsRequest, DEFAULT); + SnapshotInfo snapshotInfo = snapshotsResponse.getSnapshots().get(0); + return SnapshotState.SUCCESS.equals(snapshotInfo.state()); + }); + } + + //CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here + public org.opensearch.action.support.master.AcknowledgedResponse deleteSnapshotRepository(String repositoryName) throws IOException { + //CS-ENFORCE-SINGLE + DeleteRepositoryRequest request = new DeleteRepositoryRequest(repositoryName); + return snapshotClient.deleteRepository(request, DEFAULT); + } + + //CS-SUPPRESS-SINGLE: RegexpSingleline: It is not possible to use phrase "cluster manager" instead of master here + public org.opensearch.action.support.master.AcknowledgedResponse deleteSnapshot(String repositoryName, String snapshotName) throws IOException { + //CS-ENFORCE-SINGLE + return snapshotClient.delete(new DeleteSnapshotRequest(repositoryName, snapshotName), DEFAULT); + } + + public RestoreSnapshotResponse restoreSnapshot( + String repositoryName, String snapshotName, String renamePattern, + String renameReplacement) throws IOException { + RestoreSnapshotRequest restoreSnapshotRequest = new RestoreSnapshotRequest(repositoryName, snapshotName) + .renamePattern(renamePattern) + .renameReplacement(renameReplacement); + return snapshotClient.restore(restoreSnapshotRequest, DEFAULT); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/Song.java b/src/integrationTest/java/org/opensearch/security/Song.java new file mode 100644 index 0000000000..e71a258169 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/Song.java @@ -0,0 +1,44 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + + +class Song { + + static final String FIELD_TITLE = "title"; + static final String FIELD_ARTIST = "artist"; + static final String FIELD_LYRICS = "lyrics"; + + static final String FIELD_STARS = "stars"; + static final String ARTIST_FIRST = "First artist"; + static final String ARTIST_STRING = "String"; + static final String ARTIST_TWINS = "Twins"; + static final String TITLE_MAGNUM_OPUS = "Magnum Opus"; + static final String TITLE_SONG_1_PLUS_1 = "Song 1+1"; + static final String TITLE_NEXT_SONG = "Next song"; + static final String ARTIST_NO = "No!"; + static final String TITLE_POISON = "Poison"; + + public static final String LYRICS_1 = "Very deep subject"; + public static final String LYRICS_2 = "Once upon a time"; + public static final String LYRICS_3 = "giant nonsense"; + public static final String LYRICS_4 = "Much too much"; + + static final String QUERY_TITLE_NEXT_SONG = FIELD_TITLE + ":" + "\"" + TITLE_NEXT_SONG + "\""; + static final String QUERY_TITLE_POISON = FIELD_TITLE + ":" + TITLE_POISON; + static final String QUERY_TITLE_MAGNUM_OPUS = FIELD_TITLE + ":" + TITLE_MAGNUM_OPUS; + + static final Object[][] SONGS = { + {FIELD_ARTIST, ARTIST_FIRST, FIELD_TITLE, TITLE_MAGNUM_OPUS ,FIELD_LYRICS, LYRICS_1, FIELD_STARS, 1}, + {FIELD_ARTIST, ARTIST_STRING, FIELD_TITLE, TITLE_SONG_1_PLUS_1, FIELD_LYRICS, LYRICS_2, FIELD_STARS, 2}, + {FIELD_ARTIST, ARTIST_TWINS, FIELD_TITLE, TITLE_NEXT_SONG, FIELD_LYRICS, LYRICS_3, FIELD_STARS, 3}, + {FIELD_ARTIST, ARTIST_NO, FIELD_TITLE, TITLE_POISON, FIELD_LYRICS, LYRICS_4, FIELD_STARS, 4} + }; +} diff --git a/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java b/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java index 70a368fd73..0868431716 100644 --- a/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java @@ -46,7 +46,7 @@ public class BasicAuthTests { public static final String INVALID_PASSWORD = "secret-password"; public static final AuthcDomain AUTHC_DOMAIN = new AuthcDomain("basic", 0) - .challengingAuthenticator("basic").backend("internal"); + .httpAuthenticatorWithChallenge("basic").backend("internal"); @ClassRule public static final LocalCluster cluster = new LocalCluster.Builder() @@ -95,7 +95,7 @@ public void shouldRespondWith200WhenCredentialsAreCorrect() { } @Test - public void browserShouldRequestUserForCredentials() { + public void testBrowserShouldRequestForCredentials() { try (TestRestClient client = cluster.getRestClient()) { HttpResponse response = client.getAuthInfo(); @@ -122,7 +122,7 @@ public void testUserShouldNotHaveAssignedCustomAttributes() { } @Test - public void userShouldHaveAssignedCustomAttributes() { + public void testUserShouldHaveAssignedCustomAttributes() { try (TestRestClient client = cluster.getRestClient(SUPER_USER)) { HttpResponse response = client.getAuthInfo(); diff --git a/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java b/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java index b3ba22dc0b..3a960f0d35 100644 --- a/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java @@ -45,7 +45,7 @@ public void browserShouldNotRequestUserForCredentials() { } private void assertThatBrowserDoesNotAskUserForCredentials(HttpResponse response) { - String reason = "Browser ask user for credentials what is not expected"; + String reason = "Browser asked user for credentials which is not expected"; assertThat(reason, response.containHeader(HttpHeaders.WWW_AUTHENTICATE), equalTo(false)); } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index c5536efc35..b0eea6c336 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -340,7 +340,7 @@ public static class AuthcDomain implements ToXContentObject { private static String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqZbjLUAWc+DZTkinQAdvy1GFjPHPnxheU89hSiWoDD3NOW76H3u3T7cCDdOah2msdxSlBmCBH6wik8qLYkcV8owWukQg3PQmbEhrdPaKo0QCgomWs4nLgtmEYqcZ+QQldd82MdTlQ1QmoQmI9Uxqs1SuaKZASp3Gy19y8su5CV+FZ6BruUw9HELK055sAwl3X7j5ouabXGbcib2goBF3P52LkvbJLuWr5HDZEOeSkwIeqSeMojASM96K5SdotD+HwEyjaTjzRPL2Aa1BEQFWOQ6CFJLyLH7ZStDuPM1mJU1VxIVfMbZrhsUBjAnIhRynmWxML7YlNqkP9j6jyOIYQIDAQAB"; public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL = new TestSecurityConfig.AuthcDomain("basic", 0) - .challengingAuthenticator("basic").backend("internal"); + .httpAuthenticatorWithChallenge("basic").backend("internal"); public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE = new TestSecurityConfig.AuthcDomain("basic", 0) .httpAuthenticator("basic").backend("internal"); @@ -380,7 +380,7 @@ public AuthcDomain jwtHttpAuthenticator(String headerName, String signingKey) { return this; } - public AuthcDomain challengingAuthenticator(String type) { + public AuthcDomain httpAuthenticatorWithChallenge(String type) { this.httpAuthenticator = new HttpAuthenticator(type).challenge(true); return this; } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index 1170a25906..dbdb6a0cee 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -98,6 +98,10 @@ private LocalCluster(String clusterName, TestSecurityConfig testSgConfig, Settin this.testIndices = testIndices; } + public String getSnapshotDirPath() { + return localOpenSearchCluster.getSnapshotDirPath(); + } + @Override public void before() throws Throwable { if (localOpenSearchCluster == null) { diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java index 2750080225..e591d9f03c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java @@ -102,6 +102,8 @@ public class LocalOpenSearchCluster { private boolean started; private Random random = new Random(); + private File snapshotDir; + public LocalOpenSearchCluster(String clusterName, ClusterManager clusterManager, NodeSettingsSupplier nodeSettingsSupplier, List> additionalPlugins, TestCertificates testCertificates) { this.clusterName = clusterName; @@ -110,12 +112,23 @@ public LocalOpenSearchCluster(String clusterName, ClusterManager clusterManager, this.additionalPlugins = additionalPlugins; this.testCertificates = testCertificates; try { - this.clusterHomeDir = Files.createTempDirectory("local_cluster_" + clusterName).toFile(); + createClusterDirectory(clusterName); } catch (IOException e) { throw new IllegalStateException(e); } } + public String getSnapshotDirPath() { + return snapshotDir.getAbsolutePath(); + } + + private void createClusterDirectory(String clusterName) throws IOException { + this.clusterHomeDir = Files.createTempDirectory("local_cluster_" + clusterName).toFile(); + log.debug("Cluster home directory '{}'.", clusterHomeDir.getAbsolutePath()); + this.snapshotDir = new File(this.clusterHomeDir, "snapshots"); + this.snapshotDir.mkdir(); + } + private List getNodesByType(NodeType nodeType) { return nodes.stream() .filter(currentNode -> currentNode.hasAssignedType(nodeType)) @@ -230,8 +243,7 @@ private void retry() throws Exception { this.nodes.clear(); this.seedHosts = null; this.initialClusterManagerHosts = null; - this.clusterHomeDir = Files.createTempDirectory("local_cluster_" + clusterName + "_retry_" + retry).toFile(); - + createClusterDirectory("local_cluster_" + clusterName + "_retry_" + retry); start(); } @@ -458,13 +470,15 @@ public InetSocketAddress getTransportAddress() { } private Settings getOpenSearchSettings() { - Settings settings = getMinimalOpenSearchSettings(); + Settings settings = Settings.builder() + .put(getMinimalOpenSearchSettings()) + .putList("path.repo", List.of(getSnapshotDirPath())) + .build(); if (nodeSettingsSupplier != null) { // TODO node number return Settings.builder().put(settings).put(nodeSettingsSupplier.get(0)).build(); } - return settings; } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java index d70afbaa1a..54e4894a78 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -44,8 +44,18 @@ import javax.net.ssl.TrustManagerFactory; import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.message.BasicHeader; +import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; +import org.opensearch.client.RestClient; +import org.opensearch.client.RestClientBuilder; +import org.opensearch.client.RestHighLevelClient; import org.opensearch.security.support.PemKeyReader; import org.opensearch.test.framework.certificate.TestCertificates; @@ -82,6 +92,25 @@ default TestRestClient getRestClient(UserCredentialsHolder user, Header... heade return getRestClient(user.getName(), user.getPassword(), headers); } + default RestHighLevelClient getRestHighLevelClient(UserCredentialsHolder user) { + InetSocketAddress httpAddress = getHttpAddress(); + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(user.getName(), user.getPassword())); + + RestClientBuilder.HttpClientConfigCallback configCallback = httpClientBuilder -> { + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider).setSSLStrategy( + new SSLIOSessionStrategy(getSSLContext(), null, null, NoopHostnameVerifier.INSTANCE)); + + return httpClientBuilder; + }; + + RestClientBuilder builder = RestClient.builder(new HttpHost(httpAddress.getHostString(), httpAddress.getPort(), "https")) + .setHttpClientConfigCallback(configCallback); + + + return new RestHighLevelClient(builder); + } + /** * Returns a REST client that sends requests with basic authentication for the specified user name and password. Optionally, * additional HTTP headers can be specified which will be sent with each request. diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java new file mode 100644 index 0000000000..5c1d25911a --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java @@ -0,0 +1,79 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.cluster; + +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.action.search.SearchScrollRequest; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.search.aggregations.AggregationBuilders; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.search.sort.FieldSortBuilder; +import org.opensearch.search.sort.SortOrder; + +import static java.util.concurrent.TimeUnit.MINUTES; + +public final class SearchRequestFactory { + + private SearchRequestFactory() { + + } + public static SearchRequest queryStringQueryRequest(String indexName, String queryString) { + SearchRequest searchRequest = new SearchRequest(indexName); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.queryStringQuery(queryString)); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } + + public static SearchRequest queryStringQueryRequest(String queryString) { + SearchRequest searchRequest = new SearchRequest(); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.queryStringQuery(queryString)); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } + + public static SearchRequest searchRequestWithScroll(String indexName, int pageSize) { + SearchRequest searchRequest = new SearchRequest(indexName); + searchRequest.scroll(new TimeValue(1, MINUTES)); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.matchAllQuery()); + searchSourceBuilder.sort(new FieldSortBuilder("_id").order(SortOrder.ASC)); + searchSourceBuilder.size(pageSize); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } + + public static SearchScrollRequest getSearchScrollRequest(SearchResponse searchResponse) { + SearchScrollRequest scrollRequest = new SearchScrollRequest(searchResponse.getScrollId()); + scrollRequest.scroll(new TimeValue(1, MINUTES)); + return scrollRequest; + } + + public static SearchRequest averageAggregationRequest(String indexName, String aggregationName, String fieldName) { + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.aggregation(AggregationBuilders.avg(aggregationName).field(fieldName)); + searchSourceBuilder.size(0); + SearchRequest searchRequest = new SearchRequest(indexName); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } + + public static SearchRequest statsAggregationRequest(String indexName, String aggregationName, String fieldName) { + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.aggregation(AggregationBuilders.stats(aggregationName).field(fieldName)); + searchSourceBuilder.size(0); + SearchRequest searchRequest = new SearchRequest(indexName); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java index e16d29b9e1..0db80ee72f 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -32,6 +32,8 @@ import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -46,6 +48,7 @@ import org.apache.commons.io.IOUtils; import org.apache.http.Header; import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; @@ -56,6 +59,7 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.utils.URIBuilder; import org.apache.http.config.SocketConfig; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; @@ -101,8 +105,17 @@ public TestRestClient(InetSocketAddress nodeHttpAddress, List
headers, S this.sslContext = sslContext; } + public HttpResponse get(String path, List queryParameters, Header... headers) { + try { + URI uri = new URIBuilder(getHttpServerUri()).setPath(path).addParameters(queryParameters).build(); + return executeRequest(new HttpGet(uri), headers); + } catch (URISyntaxException ex) { + throw new RuntimeException("Incorrect URI syntax", ex); + } + } + public HttpResponse get(String path, Header... headers) { - return executeRequest(new HttpGet(getHttpServerUri() + "/" + path), headers); + return get(path, Collections.emptyList(), headers); } public HttpResponse getAuthInfo( Header... headers) { diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsAtIndexMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsAtIndexMatcher.java new file mode 100644 index 0000000000..d0b7e64673 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsAtIndexMatcher.java @@ -0,0 +1,70 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.bulk.BulkItemResponse; +import org.opensearch.action.bulk.BulkResponse; + +import static java.util.Objects.requireNonNull; + +class BulkResponseContainExceptionsAtIndexMatcher extends TypeSafeDiagnosingMatcher { + + private final int errorIndex; + private final Matcher exceptionMatcher; + + public BulkResponseContainExceptionsAtIndexMatcher(int errorIndex, Matcher exceptionMatcher) { + this.errorIndex = errorIndex; + this.exceptionMatcher = requireNonNull(exceptionMatcher, "Exception matcher is required."); + } + + @Override + protected boolean matchesSafely(BulkResponse response, Description mismatchDescription) { + if(response.hasFailures() == false) { + mismatchDescription.appendText("received successful bulk response what is not expected."); + return false; + } + BulkItemResponse[] items = response.getItems(); + if((items == null) || (items.length == 0) || (errorIndex >= items.length)) { + mismatchDescription.appendText("bulk response does not contain item with index ").appendValue(errorIndex); + return false; + } + BulkItemResponse item = items[errorIndex]; + if(item == null) { + mismatchDescription.appendText("bulk item response with index ").appendValue(errorIndex).appendText(" is null."); + return false; + } + BulkItemResponse.Failure failure = item.getFailure(); + if(failure == null) { + mismatchDescription.appendText("bulk response item with index ").appendValue(errorIndex).appendText(" does not contain failure"); + return false; + } + Exception exception = failure.getCause(); + if(exception == null) { + mismatchDescription.appendText("bulk response item with index ").appendValue(errorIndex).appendText(" does not contain exception."); + return false; + } + if(exceptionMatcher.matches(exception) == false) { + mismatchDescription.appendText("bulk response item with index ").appendValue(errorIndex) + .appendText(" contains incorrect exception which is ").appendValue(exception); + return false; + } + + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("bulk response should contain exceptions which indicate failure"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsMatcher.java new file mode 100644 index 0000000000..646a2aad93 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsMatcher.java @@ -0,0 +1,69 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.bulk.BulkItemResponse; +import org.opensearch.action.bulk.BulkResponse; + +import static java.util.Objects.requireNonNull; + +class BulkResponseContainExceptionsMatcher extends TypeSafeDiagnosingMatcher { + + private final Matcher exceptionMatcher; + + public BulkResponseContainExceptionsMatcher(Matcher exceptionMatcher) { + this.exceptionMatcher = requireNonNull(exceptionMatcher, "Exception matcher is required."); + } + + @Override + protected boolean matchesSafely(BulkResponse response, Description mismatchDescription) { + if(response.hasFailures() == false) { + mismatchDescription.appendText("received successful bulk response what is not expected."); + return false; + } + BulkItemResponse[] items = response.getItems(); + if((items == null) || (items.length == 0)) { + mismatchDescription.appendText("bulk response does not contain items ").appendValue(items); + return false; + } + for(int i = 0 ; i < items.length; ++i) { + BulkItemResponse item = items[i]; + if(item == null) { + mismatchDescription.appendText("bulk item response with index ").appendValue(i).appendText(" is null."); + return false; + } + BulkItemResponse.Failure failure = item.getFailure(); + if(failure == null) { + mismatchDescription.appendText("bulk response item with index ").appendValue(i).appendText(" does not contain failure"); + return false; + } + Exception exception = failure.getCause(); + if(exception == null) { + mismatchDescription.appendText("bulk response item with index ").appendValue(i).appendText(" does not contain exception."); + return false; + } + if(exceptionMatcher.matches(exception) == false) { + mismatchDescription.appendText("bulk response item with index ").appendValue(i) + .appendText(" contains incorrect exception which is ").appendValue(exception); + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("bulk response should contain exceptions which indicate failure"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseMatchers.java new file mode 100644 index 0000000000..4a72a67aba --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseMatchers.java @@ -0,0 +1,37 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Matcher; + +import org.opensearch.action.bulk.BulkResponse; + +public class BulkResponseMatchers { + + private BulkResponseMatchers(){ + + } + + public static Matcher successBulkResponse() { + return new SuccessBulkResponseMatcher(); + } + + public static Matcher failureBulkResponse() { + return new FailureBulkResponseMatcher(); + } + + public static Matcher bulkResponseContainExceptions(Matcher exceptionMatcher) { + return new BulkResponseContainExceptionsMatcher(exceptionMatcher); + } + + public static Matcher bulkResponseContainExceptions(int index, Matcher exceptionMatcher) { + return new BulkResponseContainExceptionsAtIndexMatcher(index, exceptionMatcher); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainSuccessSnapshotMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainSuccessSnapshotMatcher.java new file mode 100644 index 0000000000..f710e2c1fa --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainSuccessSnapshotMatcher.java @@ -0,0 +1,66 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.stream.Collectors; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; +import org.opensearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse; +import org.opensearch.client.Client; +import org.opensearch.snapshots.SnapshotMissingException; +import org.opensearch.snapshots.SnapshotState; + +import static java.util.Objects.requireNonNull; + +class ClusterContainSuccessSnapshotMatcher extends TypeSafeDiagnosingMatcher { + + private final String repositoryName; + private final String snapshotName; + + public ClusterContainSuccessSnapshotMatcher(String repositoryName, String snapshotName) { + this.repositoryName = requireNonNull(repositoryName, "Snapshot repository name is required."); + this.snapshotName = requireNonNull(snapshotName, "Snapshot name is required."); + } + + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + try { + GetSnapshotsRequest request = new GetSnapshotsRequest(repositoryName, new String[] { snapshotName }); + GetSnapshotsResponse response = client.admin().cluster().getSnapshots(request).actionGet(); + long count = response.getSnapshots() + .stream() + .map(snapshot -> snapshot.state()) + .filter(status -> SnapshotState.SUCCESS.equals(status)) + .count(); + if(count != 1){ + String snapshotStatuses = response.getSnapshots() + .stream() + .map(info -> String.format("%s %s", info.snapshotId().getName(), info.state())) + .collect(Collectors.joining(", ")); + mismatchDescription.appendText("snapshot is not present or has incorrect state, snapshots statuses ") + .appendValue(snapshotStatuses); + return false; + } + }catch (SnapshotMissingException e) { + mismatchDescription.appendText(" snapshot does not exist"); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Cluster contain snapshot ").appendValue(snapshotName).appendText(" in repository ") + .appendValue(repositoryName).appendText(" with success status"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateMatcher.java new file mode 100644 index 0000000000..2d64a95c65 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateMatcher.java @@ -0,0 +1,44 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.indices.template.get.GetIndexTemplatesRequest; +import org.opensearch.client.Client; + +import static java.util.Objects.requireNonNull; + +class ClusterContainTemplateMatcher extends TypeSafeDiagnosingMatcher { + + private final String templateName; + + public ClusterContainTemplateMatcher(String templateName) { + this.templateName = requireNonNull(templateName, "Index template name is required."); + + } + + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + var response = client.admin().indices().getTemplates(new GetIndexTemplatesRequest(templateName)).actionGet(); + if(response.getIndexTemplates().isEmpty()) { + mismatchDescription.appendText("But template does not exists"); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("template ").appendValue(templateName).appendText(" exists"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java new file mode 100644 index 0000000000..907fb30257 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java @@ -0,0 +1,71 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.indices.template.get.GetIndexTemplatesRequest; +import org.opensearch.action.admin.indices.template.get.GetIndexTemplatesResponse; +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.AliasMetadata; +import org.opensearch.common.collect.ImmutableOpenMap; + +import static java.util.Objects.requireNonNull; + +class ClusterContainTemplateWithAliasMatcher extends TypeSafeDiagnosingMatcher { + + private final String templateName; + private final String aliasName; + + public ClusterContainTemplateWithAliasMatcher(String templateName, String aliasName) { + this.templateName = requireNonNull(templateName, "Index template name is required."); + this.aliasName = requireNonNull(aliasName, "Alias name is required."); + } + + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + var response = client.admin().indices().getTemplates(new GetIndexTemplatesRequest(templateName)).actionGet(); + if(response.getIndexTemplates().isEmpty()) { + mismatchDescription.appendText("but template does not exists"); + return false; + } + Set aliases = getAliases(response); + if(aliases.contains(aliasName) == false) { + mismatchDescription.appendText("alias ").appendValue(aliasName) + .appendText(" is not present in template, other aliases in template ") + .appendValue(aliases.stream().collect(Collectors.joining(", "))); + return false; + } + return true; + } + + private Set getAliases(GetIndexTemplatesResponse response) { + return response.getIndexTemplates() + .stream() + .map(metadata -> metadata.getAliases()) + .flatMap(aliasMap -> aliasNames(aliasMap)) + .collect(Collectors.toSet()); + } + + private Stream aliasNames(ImmutableOpenMap aliasMap) { + return StreamSupport.stream(aliasMap.keys().spliterator(), false).map(objectCursor -> objectCursor.value); + } + + @Override + public void describeTo(Description description) { + description.appendText("template ").appendValue(templateName).appendText(" exists and "); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentMatcher.java new file mode 100644 index 0000000000..ffcceaa9cb --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentMatcher.java @@ -0,0 +1,58 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.concurrent.ExecutionException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.get.GetRequest; +import org.opensearch.action.get.GetResponse; +import org.opensearch.client.Client; + +import static java.util.Objects.requireNonNull; + +class ClusterContainsDocumentMatcher extends TypeSafeDiagnosingMatcher { + + private static final Logger log = LogManager.getLogger(ClusterContainsDocumentMatcher.class); + + private final String indexName; + private final String documentId; + + ClusterContainsDocumentMatcher(String indexName, String documentId) { + this.indexName = requireNonNull(indexName, "Index name is required."); + this.documentId = requireNonNull(documentId, "Document id is required."); + } + + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + try{ + GetResponse response = client.get(new GetRequest(indexName, documentId)).get(); + if(response.isExists() == false) { + mismatchDescription.appendText("Document does not exists"); + return false; + } + } catch (InterruptedException | ExecutionException e) { + log.error("Cannot verify if cluster contains document '{}' in index '{}'.", documentId, indexName, e); + mismatchDescription.appendText("Exception occured during verification if cluster contain document").appendValue(e); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Cluster contain document in index ").appendValue(indexName).appendText(" with id ") + .appendValue(documentId); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentWithFieldValueMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentWithFieldValueMatcher.java new file mode 100644 index 0000000000..df67a1669a --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentWithFieldValueMatcher.java @@ -0,0 +1,80 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.get.GetRequest; +import org.opensearch.action.get.GetResponse; +import org.opensearch.client.Client; + +import static java.util.Objects.requireNonNull; + +class ClusterContainsDocumentWithFieldValueMatcher extends TypeSafeDiagnosingMatcher { + + private static final Logger log = LogManager.getLogger(ClusterContainsDocumentWithFieldValueMatcher.class); + + private final String indexName; + private final String documentId; + + private final String fieldName; + + private final Object fieldValue; + + ClusterContainsDocumentWithFieldValueMatcher(String indexName, String documentId, String fieldName, Object fieldValue) { + this.indexName = requireNonNull(indexName, "Index name is required."); + this.documentId = requireNonNull(documentId, "Document id is required."); + this.fieldName = requireNonNull(fieldName, "Field name is required."); + this.fieldValue = requireNonNull(fieldValue, "Field value is required."); + } + + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + try { + GetResponse response = client.get(new GetRequest(indexName, documentId)).get(); + if(response.isExists() == false) { + mismatchDescription.appendText("Document does not exists"); + return false; + } + Map source = response.getSource(); + if(source == null) { + mismatchDescription.appendText("Cannot retrieve document source"); + return false; + } + if(source.containsKey(fieldName) == false) { + mismatchDescription.appendText("document does not contain field ").appendValue(fieldName); + return false; + } + Object actualFieldValue = source.get(fieldName); + if(fieldValue.equals(actualFieldValue) == false) { + mismatchDescription.appendText(" document contain incorrect field value ").appendValue(actualFieldValue); + return false; + } + } catch (InterruptedException | ExecutionException e) { + log.error("Cannot verify if cluster contains document '{}' in index '{}'.", documentId, indexName, e); + mismatchDescription.appendText("Exception occured during verification if cluster contain document").appendValue(e); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Cluster contain document in index ").appendValue(indexName).appendText(" with id ") + .appendValue(documentId).appendText(" with field ").appendValue(fieldName).appendText(" which is equal to ") + .appendValue(fieldValue); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsSnapshotRepositoryMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsSnapshotRepositoryMatcher.java new file mode 100644 index 0000000000..8b7dc777e4 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsSnapshotRepositoryMatcher.java @@ -0,0 +1,66 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.cluster.repositories.get.GetRepositoriesRequest; +import org.opensearch.action.admin.cluster.repositories.get.GetRepositoriesResponse; +import org.opensearch.client.Client; +import org.opensearch.client.ClusterAdminClient; +import org.opensearch.repositories.RepositoryMissingException; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; + +class ClusterContainsSnapshotRepositoryMatcher extends TypeSafeDiagnosingMatcher { + + private final String repositoryName; + + public ClusterContainsSnapshotRepositoryMatcher(String repositoryName) { + this.repositoryName = requireNonNull(repositoryName, "Repository name is required."); + } + + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + try { + ClusterAdminClient adminClient = client.admin().cluster(); + GetRepositoriesRequest request = new GetRepositoriesRequest(new String[]{"*"}); + GetRepositoriesResponse response = adminClient.getRepositories(request).actionGet(); + if(response == null) { + mismatchDescription.appendText("Cannot check if cluster contain repository"); + return false; + } + Set actualRepositoryNames = response.repositories() + .stream() + .map(metadata -> metadata.name()) + .collect(Collectors.toSet()); + if(actualRepositoryNames.contains(repositoryName) == false) { + mismatchDescription.appendText("Cluster does not contain snapshot repository ").appendValue(repositoryName) + .appendText(", but the following repositories are defined in the cluster ") + .appendValue(actualRepositoryNames.stream().collect(joining(", "))); + return false; + } + } catch (RepositoryMissingException e) { + mismatchDescription.appendText(" cluster does not contain any repository."); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Cluster contain snapshot repository with name ").appendValue(repositoryName); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java new file mode 100644 index 0000000000..2281026593 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java @@ -0,0 +1,49 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Matcher; + +import org.opensearch.client.Client; + +public class ClusterMatchers { + + private ClusterMatchers() { + + } + + public static Matcher clusterContainsDocument(String indexName, String documentId) { + return new ClusterContainsDocumentMatcher(indexName, documentId); + } + + public static Matcher clusterContainsDocumentWithFieldValue(String indexName, String documentId, String fieldName, Object fieldValue) { + return new ClusterContainsDocumentWithFieldValueMatcher(indexName, documentId, fieldName, fieldValue); + } + + public static Matcher clusterContainTemplate(String templateName) { + return new ClusterContainTemplateMatcher(templateName); + } + + public static Matcher clusterContainTemplateWithAlias(String templateName, String aliasName) { + return new ClusterContainTemplateWithAliasMatcher(templateName, aliasName); + } + + public static Matcher clusterContainsSnapshotRepository(String repositoryName) { + return new ClusterContainsSnapshotRepositoryMatcher(repositoryName); + } + + public static Matcher clusterContainSuccessSnapshot(String repositoryName, String snapshotName) { + return new ClusterContainSuccessSnapshotMatcher(repositoryName, snapshotName); + } + + public static Matcher snapshotInClusterDoesNotExists(String repositoryName, String snapshotName) { + return new SnapshotInClusterDoesNotExist(repositoryName, snapshotName); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainNotEmptyScrollingIdMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainNotEmptyScrollingIdMatcher.java new file mode 100644 index 0000000000..094b26f33f --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainNotEmptyScrollingIdMatcher.java @@ -0,0 +1,34 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.apache.commons.lang3.StringUtils; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.SearchResponse; + +class ContainNotEmptyScrollingIdMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + String scrollId = searchResponse.getScrollId(); + if(StringUtils.isEmpty(scrollId)) { + mismatchDescription.appendText("scrolling id is null or empty"); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Search response should contain scrolling id."); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsAggregationWithNameAndTypeMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsAggregationWithNameAndTypeMatcher.java new file mode 100644 index 0000000000..9f8fbd6dba --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsAggregationWithNameAndTypeMatcher.java @@ -0,0 +1,55 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.search.aggregations.Aggregation; +import org.opensearch.search.aggregations.Aggregations; + +import static java.util.Objects.requireNonNull; + +class ContainsAggregationWithNameAndTypeMatcher extends TypeSafeDiagnosingMatcher { + + private final String expectedAggregationName; + private final String expectedAggregationType; + + public ContainsAggregationWithNameAndTypeMatcher(String expectedAggregationName, String expectedAggregationType) { + this.expectedAggregationName = requireNonNull(expectedAggregationName, "Aggregation name is required"); + this.expectedAggregationType = requireNonNull(expectedAggregationType, "Expected aggregation type is required."); + } + + @Override + protected boolean matchesSafely(SearchResponse response, Description mismatchDescription) { + Aggregations aggregations = response.getAggregations(); + if(aggregations == null) { + mismatchDescription.appendText("search response does not contain aggregations"); + return false; + } + Aggregation aggregation = aggregations.get(expectedAggregationName); + if(aggregation == null) { + mismatchDescription.appendText("Response does not contain aggregation with name ").appendValue(expectedAggregationName); + return false; + } + if(expectedAggregationType.equals(aggregation.getType()) == false) { + mismatchDescription.appendText("Aggregation contain incorrect type which is ").appendValue(aggregation.getType()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Search response should contains aggregation results with name ").appendValue(expectedAggregationName) + .appendText(" and type ").appendValue(expectedAggregationType); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionErrorMessageMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionErrorMessageMatcher.java new file mode 100644 index 0000000000..e0e7b11be3 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionErrorMessageMatcher.java @@ -0,0 +1,41 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import static java.util.Objects.requireNonNull; + +class ExceptionErrorMessageMatcher extends TypeSafeDiagnosingMatcher { + + private final Matcher errorMessageMatcher; + + public ExceptionErrorMessageMatcher(Matcher errorMessageMatcher) { + this.errorMessageMatcher = requireNonNull(errorMessageMatcher, "Error message matcher is required"); + } + + @Override + protected boolean matchesSafely(Throwable ex, Description mismatchDescription) { + boolean matches = errorMessageMatcher.matches(ex.getMessage()); + if(matches == false) { + mismatchDescription.appendText("Exception of class ").appendValue(ex.getClass().getCanonicalName()) + .appendText("contains unexpected error message which is ").appendValue(ex.getMessage()); + } + return matches; + + } + + @Override + public void describeTo(Description description) { + description.appendText("Error message in exception matches").appendValue(errorMessageMatcher); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionMatcherAssert.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionMatcherAssert.java new file mode 100644 index 0000000000..35f9a32b62 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionMatcherAssert.java @@ -0,0 +1,40 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Matcher; + +import static java.util.Objects.requireNonNull; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; + +public class ExceptionMatcherAssert { + + @FunctionalInterface + public interface ThrowingCallable { + void call() throws Exception; + } + + public static void assertThatThrownBy(ThrowingCallable throwingCallable, Matcher matcher) { + Throwable expectedException = catchThrowable(throwingCallable); + assertThat("Expected exception was not thrown", expectedException, notNullValue()); + assertThat(expectedException, matcher); + } + + public static Throwable catchThrowable(ThrowingCallable throwingCallable) { + Throwable expectedException = null; + try { + requireNonNull(throwingCallable, "ThrowingCallable must not be null.").call(); + } catch (Throwable e) { + expectedException = e; + } + return expectedException; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/FailureBulkResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/FailureBulkResponseMatcher.java new file mode 100644 index 0000000000..a018c1c924 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/FailureBulkResponseMatcher.java @@ -0,0 +1,32 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.bulk.BulkResponse; + +class FailureBulkResponseMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(BulkResponse response, Description mismatchDescription) { + if(response.hasFailures() == false) { + mismatchDescription.appendText(" bulk operation was executed correctly what is not expected."); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("bulk operation failure"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentFieldValueMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentFieldValueMatcher.java new file mode 100644 index 0000000000..54b29949c1 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentFieldValueMatcher.java @@ -0,0 +1,51 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Map; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.get.GetResponse; + +import static java.util.Objects.requireNonNull; + +class GetResponseDocumentFieldValueMatcher extends TypeSafeDiagnosingMatcher { + + private final String fieldName; + private final Object fieldValue; + + public GetResponseDocumentFieldValueMatcher(String fieldName, Object fieldValue) { + this.fieldName = requireNonNull(fieldName, "Field name is required."); + this.fieldValue = requireNonNull(fieldValue, "Field value is required."); + } + + @Override + protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { + Map source = response.getSource(); + if(source.containsKey(fieldName) == false) { + mismatchDescription.appendText("Document does not contain field ").appendValue(fieldName); + return false; + } + Object actualFieldValue = source.get(fieldName); + if(fieldValue.equals(actualFieldValue) == false) { + mismatchDescription.appendText("Field ").appendValue(fieldName).appendText(" has incorrect value ") + .appendValue(actualFieldValue); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Document contain field ").appendValue(fieldName).appendText(" with value ").appendValue(fieldValue); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentIdMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentIdMatcher.java new file mode 100644 index 0000000000..64a64f93dd --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentIdMatcher.java @@ -0,0 +1,47 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.get.GetResponse; + +import static java.util.Objects.requireNonNull; + +class GetResponseDocumentIdMatcher extends TypeSafeDiagnosingMatcher { + + private final String indexName; + private final String documentId; + + public GetResponseDocumentIdMatcher(String indexName, String documentId) { + this.indexName = requireNonNull(indexName, "Index name is required"); + this.documentId = requireNonNull(documentId, "Document id is required"); + } + + @Override + protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { + if(indexName.equals(response.getIndex()) == false ) { + mismatchDescription.appendText("Document should not belong to index ").appendValue(response.getIndex()); + return false; + } + if(documentId.equals(response.getId()) == false) { + mismatchDescription.appendText("Document contain incorrect id which is ").appendValue(response.getId()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response should contain document from index ").appendValue(indexName).appendText(" with id ") + .appendValue(documentId); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java new file mode 100644 index 0000000000..04cdcb1508 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java @@ -0,0 +1,27 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Matcher; + +import org.opensearch.action.get.GetResponse; + +public class GetResponseMatchers { + + private GetResponseMatchers() {} + + public static Matcher containDocument(String indexName, String documentId) { + return new GetResponseDocumentIdMatcher(indexName, documentId); + } + + public static Matcher documentContainField(String fieldName, Object fieldValue) { + return new GetResponseDocumentFieldValueMatcher(fieldName, fieldValue); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfHitsInPageIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfHitsInPageIsEqualToMatcher.java new file mode 100644 index 0000000000..b8671bb885 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfHitsInPageIsEqualToMatcher.java @@ -0,0 +1,45 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.search.SearchHits; + +class NumberOfHitsInPageIsEqualToMatcher extends TypeSafeDiagnosingMatcher { + + private final int expectedNumberOfHits; + + public NumberOfHitsInPageIsEqualToMatcher(int expectedNumberOfHits) { + this.expectedNumberOfHits = expectedNumberOfHits; + } + + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + SearchHits hits = searchResponse.getHits(); + if((hits == null) || (hits.getHits() == null)) { + mismatchDescription.appendText("contains null hits"); + return false; + } + int actualNumberOfHits = hits.getHits().length; + if(expectedNumberOfHits != actualNumberOfHits) { + mismatchDescription.appendText("actual number of hits is equal to ").appendValue(actualNumberOfHits); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Number of hits on current page should be equal to ").appendValue(expectedNumberOfHits); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfTotalHitsIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfTotalHitsIsEqualToMatcher.java new file mode 100644 index 0000000000..3045da00b1 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfTotalHitsIsEqualToMatcher.java @@ -0,0 +1,57 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import org.apache.lucene.search.TotalHits; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.search.SearchHits; + +class NumberOfTotalHitsIsEqualToMatcher extends TypeSafeDiagnosingMatcher { + + private final int expectedNumberOfHits; + + NumberOfTotalHitsIsEqualToMatcher(int expectedNumberOfHits) { + this.expectedNumberOfHits = expectedNumberOfHits; + } + + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + SearchHits hits = searchResponse.getHits(); + if(hits == null) { + mismatchDescription.appendText("contains null hits"); + return false; + } + TotalHits totalHits = hits.getTotalHits(); + if(totalHits == null) { + mismatchDescription.appendText("Total hits number is null."); + return false; + } + if(expectedNumberOfHits != totalHits.value) { + String documentIds = Arrays.stream(searchResponse.getHits().getHits()) + .map(hit -> hit.getIndex() + "/" + hit.getId()) + .collect(Collectors.joining(",")); + mismatchDescription.appendText( "contains ").appendValue(hits.getHits().length).appendText(" hits, found document ids ") + .appendValue(documentIds); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Search response should contains ").appendValue(expectedNumberOfHits).appendText(" hits"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchExceptionMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchExceptionMatchers.java new file mode 100644 index 0000000000..4c2df2c3b5 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchExceptionMatchers.java @@ -0,0 +1,33 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Matcher; + +import org.opensearch.rest.RestStatus; + +import static org.hamcrest.Matchers.containsString; + +public class OpenSearchExceptionMatchers { + + private OpenSearchExceptionMatchers() {} + + public static Matcher statusException(RestStatus expectedRestStatus) { + return new OpenSearchStatusExceptionMatcher(expectedRestStatus); + } + + public static Matcher errorMessage(Matcher errorMessageMatcher) { + return new ExceptionErrorMessageMatcher(errorMessageMatcher); + } + + public static Matcher errorMessageContain(String errorMessage) { + return errorMessage(containsString(errorMessage)); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchStatusExceptionMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchStatusExceptionMatcher.java new file mode 100644 index 0000000000..863f1e52a1 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchStatusExceptionMatcher.java @@ -0,0 +1,48 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.OpenSearchException; +import org.opensearch.rest.RestStatus; + +import static java.util.Objects.requireNonNull; + +class OpenSearchStatusExceptionMatcher extends TypeSafeDiagnosingMatcher { + + private final RestStatus expectedRestStatus; + + public OpenSearchStatusExceptionMatcher(RestStatus expectedRestStatus) { + this.expectedRestStatus = requireNonNull(expectedRestStatus, "Expected rest status is required."); + } + + @Override + protected boolean matchesSafely(Throwable throwable, Description mismatchDescription) { + if((throwable instanceof OpenSearchException) == false) { + mismatchDescription.appendText("actual exception type is ").appendValue(throwable.getClass().getCanonicalName()) + .appendText(", error message ").appendValue(throwable.getMessage()); + return false; + } + OpenSearchException openSearchException = (OpenSearchException) throwable; + if(expectedRestStatus.equals(openSearchException.status()) == false) { + mismatchDescription.appendText("actual status code is ").appendValue(openSearchException.status()) + .appendText(", error message ").appendValue(throwable.getMessage()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("OpenSearchException with status code ").appendValue(expectedRestStatus); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitContainsFieldWithValueMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitContainsFieldWithValueMatcher.java new file mode 100644 index 0000000000..2a8e866901 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitContainsFieldWithValueMatcher.java @@ -0,0 +1,69 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Map; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.search.SearchHit; + +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.readTotalHits; + +class SearchHitContainsFieldWithValueMatcher extends TypeSafeDiagnosingMatcher { + + private final int hitIndex; + + private final String fieldName; + + private final T expectedValue; + + SearchHitContainsFieldWithValueMatcher(int hitIndex, String fieldName, T expectedValue) { + this.hitIndex = hitIndex; + this.fieldName = fieldName; + this.expectedValue = expectedValue; + } + + @Override protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + Long numberOfHits = readTotalHits(searchResponse); + if(numberOfHits == null) { + mismatchDescription.appendText("Total number of hits is unknown."); + return false; + } + if(hitIndex >= numberOfHits) { + mismatchDescription.appendText("Search result contain only ").appendValue(numberOfHits).appendText(" hits"); + return false; + } + SearchHit searchHit = searchResponse.getHits().getAt(hitIndex); + Map source = searchHit.getSourceAsMap(); + if(source == null){ + mismatchDescription.appendText("Source document is null, is fetch source option set to true?"); + return false; + } + if(!source.containsKey(fieldName)) { + mismatchDescription.appendText("Document does not contain field ").appendValue(fieldName); + return false; + } + Object actualValue = source.get(fieldName); + if(!expectedValue.equals(actualValue)) { + mismatchDescription.appendText("Field value is equal to ").appendValue(actualValue); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Search hit with index ").appendValue(hitIndex).appendText(" should contain field ").appendValue(fieldName) + .appendValue(" with value equal to ").appendValue(expectedValue); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentWithIdMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentWithIdMatcher.java new file mode 100644 index 0000000000..c3a7528432 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentWithIdMatcher.java @@ -0,0 +1,60 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.search.SearchHit; + +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.readTotalHits; + +class SearchHitsContainDocumentWithIdMatcher extends TypeSafeDiagnosingMatcher { + + private final int hitIndex; + private final String indexName; + private final String id; + + public SearchHitsContainDocumentWithIdMatcher(int hitIndex, String indexName, String id) { + this.hitIndex = hitIndex; + this.indexName = indexName; + this.id = id; + } + + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + Long numberOfHits = readTotalHits(searchResponse); + if(numberOfHits == null) { + mismatchDescription.appendText("Number of total hits is unknown."); + return false; + } + if(hitIndex >= numberOfHits) { + mismatchDescription.appendText("Search result contain only ").appendValue(numberOfHits).appendText(" hits"); + return false; + } + SearchHit searchHit = searchResponse.getHits().getAt(hitIndex); + if(indexName.equals(searchHit.getIndex()) == false) { + mismatchDescription.appendText("document is part of another index ").appendValue(indexName); + return false; + } + if(id.equals(searchHit.getId()) == false) { + mismatchDescription.appendText("Document has another id which is ").appendValue(searchHit.getId()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Search hit with index ").appendValue(hitIndex).appendText(" should contains document which is part of index ") + .appendValue(indexName).appendValue(" and has id ").appendValue(id); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java new file mode 100644 index 0000000000..efd7da8cf9 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java @@ -0,0 +1,63 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Optional; + +import org.hamcrest.Matcher; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.rest.RestStatus; +import org.opensearch.search.SearchHits; + +public class SearchResponseMatchers { + + private SearchResponseMatchers() {} + + public static Matcher isSuccessfulSearchResponse() { + return new SuccessfulSearchResponseMatcher(); + } + + public static Matcher numberOfTotalHitsIsEqualTo(int expectedNumberOfHits) { + return new NumberOfTotalHitsIsEqualToMatcher(expectedNumberOfHits); + } + + public static Matcher numberOfHitsInPageIsEqualTo(int expectedNumberOfHits) { + return new NumberOfHitsInPageIsEqualToMatcher(expectedNumberOfHits); + } + + public static Matcher searchHitContainsFieldWithValue(int hitIndex, String fieldName, T expectedValue) { + return new SearchHitContainsFieldWithValueMatcher<>(hitIndex, fieldName, expectedValue); + } + + public static Matcher searchHitsContainDocumentWithId(int hitIndex, String indexName, String documentId) { + return new SearchHitsContainDocumentWithIdMatcher(hitIndex, indexName, documentId); + } + + public static Matcher restStatusIs(RestStatus expectedRestStatus) { + return new SearchResponseWithStatusCodeMatcher(expectedRestStatus); + } + + public static Matcher containNotEmptyScrollingId() { + return new ContainNotEmptyScrollingIdMatcher(); + } + + public static Matcher containAggregationWithNameAndType(String expectedAggregationName, String expectedAggregationType) { + return new ContainsAggregationWithNameAndTypeMatcher(expectedAggregationName, expectedAggregationType); + } + + static Long readTotalHits(SearchResponse searchResponse) { + return Optional.ofNullable(searchResponse) + .map(SearchResponse::getHits) + .map(SearchHits::getTotalHits) + .map(totalHits -> totalHits.value) + .orElse(null); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseWithStatusCodeMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseWithStatusCodeMatcher.java new file mode 100644 index 0000000000..8316cc3425 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseWithStatusCodeMatcher.java @@ -0,0 +1,39 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.rest.RestStatus; + +class SearchResponseWithStatusCodeMatcher extends TypeSafeDiagnosingMatcher { + + private final RestStatus expectedRestStatus; + + public SearchResponseWithStatusCodeMatcher(RestStatus expectedRestStatus) { + this.expectedRestStatus = expectedRestStatus; + } + + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + if(expectedRestStatus.equals(searchResponse.status()) == false) { + mismatchDescription.appendText("actual response status is ").appendValue(searchResponse.status()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Expected response status is ").appendValue(expectedRestStatus); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SnapshotInClusterDoesNotExist.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SnapshotInClusterDoesNotExist.java new file mode 100644 index 0000000000..c2626669be --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SnapshotInClusterDoesNotExist.java @@ -0,0 +1,47 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; +import org.opensearch.client.Client; +import org.opensearch.snapshots.SnapshotMissingException; + +import static java.util.Objects.requireNonNull; + +class SnapshotInClusterDoesNotExist extends TypeSafeDiagnosingMatcher { + private final String repositoryName; + private final String snapshotName; + + public SnapshotInClusterDoesNotExist(String repositoryName, String snapshotName) { + this.repositoryName = requireNonNull(repositoryName, "Snapshot repository name is required."); + this.snapshotName = requireNonNull(snapshotName, "Snapshot name is required."); + } + + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + try { + GetSnapshotsRequest request = new GetSnapshotsRequest(repositoryName, new String[] { snapshotName }); + client.admin().cluster().getSnapshots(request).actionGet(); + mismatchDescription.appendText("snapshot exists"); + return false; + }catch (SnapshotMissingException e) { + return true; + } + } + + @Override + public void describeTo(Description description) { + description.appendText("Snapshot ").appendValue(snapshotName).appendText(" does not exist in repository ") + .appendValue(repositoryName); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessBulkResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessBulkResponseMatcher.java new file mode 100644 index 0000000000..72cc83491e --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessBulkResponseMatcher.java @@ -0,0 +1,47 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.bulk.BulkItemResponse; +import org.opensearch.action.bulk.BulkResponse; +import org.opensearch.rest.RestStatus; + +class SuccessBulkResponseMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(BulkResponse response, Description mismatchDescription) { + RestStatus status = response.status(); + if(RestStatus.OK.equals(status) == false){ + mismatchDescription.appendText("incorrect response status ").appendValue(status); + return false; + } + if(response.hasFailures()) { + String failureDescription = Arrays.stream(response.getItems()) + .filter(BulkItemResponse::isFailed) + .map(BulkItemResponse::getFailure) + .map(Object::toString) + .collect(Collectors.joining(",\n")); + mismatchDescription.appendText("bulk response contains failures ").appendValue(failureDescription); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("success bulk response"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulSearchResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulSearchResponseMatcher.java new file mode 100644 index 0000000000..6d59780798 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulSearchResponseMatcher.java @@ -0,0 +1,37 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.rest.RestStatus; + +class SuccessfulSearchResponseMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + if(RestStatus.OK.equals(searchResponse.status()) == false) { + mismatchDescription.appendText("has status ").appendValue(searchResponse.status()).appendText(" which denotes failure."); + return false; + } + if(searchResponse.getShardFailures().length != 0) { + mismatchDescription.appendText("contains ").appendValue(searchResponse.getShardFailures().length).appendText(" shard failures"); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful search response"); + } +} diff --git a/src/integrationTest/resources/log4j2-test.properties b/src/integrationTest/resources/log4j2-test.properties index 58bf82daf3..d9e87408a4 100644 --- a/src/integrationTest/resources/log4j2-test.properties +++ b/src/integrationTest/resources/log4j2-test.properties @@ -17,3 +17,5 @@ rootLogger.appenderRef.stdout.ref = consoleAppender logger.testsecconfig.name=org.opensearch.test.framework.TestSecurityConfig logger.testsecconfig.level = info +logger.localopensearchcluster.name=org.opensearch.test.framework.cluster.LocalOpenSearchCluster +logger.localopensearchcluster.level = info From 16b16765b2940ffb5ac4dcbfc35db959ff4411ee Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 12 Oct 2022 13:56:12 -0400 Subject: [PATCH 053/369] Remove references to LegacyESVersion.V_7x constants (#2151) * Remove references to LegacyESVersion.V_7x constants Signed-off-by: Craig Perkins --- .../TransportConfigUpdateAction.java | 4 +- .../configuration/ClusterInfoHolder.java | 49 ------------------- .../ConfigurationLoaderSecurity7.java | 18 ------- .../dlic/rest/api/MigrateApiAction.java | 9 ---- 4 files changed, 2 insertions(+), 78 deletions(-) diff --git a/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java b/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java index d72cfdac1c..42879a8b6c 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java +++ b/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java @@ -34,7 +34,6 @@ import org.opensearch.action.FailedNodeException; import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.nodes.BaseNodeRequest; import org.opensearch.action.support.nodes.TransportNodesAction; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; @@ -47,6 +46,7 @@ import org.opensearch.security.securityconf.DynamicConfigFactory; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportRequest; import org.opensearch.transport.TransportService; public class TransportConfigUpdateAction @@ -72,7 +72,7 @@ public TransportConfigUpdateAction(final Settings settings, this.dynamicConfigFactory = dynamicConfigFactory; } - public static class NodeConfigUpdateRequest extends BaseNodeRequest { + public static class NodeConfigUpdateRequest extends TransportRequest { ConfigUpdateRequest request; diff --git a/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java b/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java index c0569e8390..61877f2bf2 100644 --- a/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java +++ b/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java @@ -26,49 +26,23 @@ package org.opensearch.security.configuration; -import java.util.Iterator; -import java.util.List; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.LegacyESVersion; import org.opensearch.cluster.ClusterChangedEvent; -import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.ClusterStateListener; -import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodes; -import org.opensearch.index.Index; public class ClusterInfoHolder implements ClusterStateListener { protected final Logger log = LogManager.getLogger(this.getClass()); - private volatile Boolean has6xNodes = null; - private volatile Boolean has6xIndices = null; private volatile DiscoveryNodes nodes = null; private volatile Boolean isLocalNodeElectedClusterManager = null; private volatile boolean initialized; @Override public void clusterChanged(ClusterChangedEvent event) { - final boolean isTraceEnabled = log.isTraceEnabled(); - if(has6xNodes == null || event.nodesChanged()) { - has6xNodes = Boolean.valueOf(clusterHas6xNodes(event.state())); - if (isTraceEnabled) { - log.trace("has6xNodes: {}", has6xNodes); - } - } - - final List indicesCreated = event.indicesCreated(); - final List indicesDeleted = event.indicesDeleted(); - if(has6xIndices == null || !indicesCreated.isEmpty() || !indicesDeleted.isEmpty()) { - has6xIndices = Boolean.valueOf(clusterHas6xIndices(event.state())); - if (isTraceEnabled) { - log.trace("has6xIndices: {}", has6xIndices); - } - } - if(nodes == null || event.nodesChanged()) { nodes = event.state().nodes(); if (log.isDebugEnabled()) { @@ -80,14 +54,6 @@ public void clusterChanged(ClusterChangedEvent event) { isLocalNodeElectedClusterManager = event.localNodeClusterManager()?Boolean.TRUE:Boolean.FALSE; } - public Boolean getHas6xNodes() { - return has6xNodes; - } - - public Boolean getHas6xIndices() { - return has6xIndices; - } - public Boolean isLocalNodeElectedClusterManager() { return isLocalNodeElectedClusterManager; } @@ -106,19 +72,4 @@ public Boolean hasNode(DiscoveryNode node) { return nodes.nodeExists(node)?Boolean.TRUE:Boolean.FALSE; } - - private static boolean clusterHas6xNodes(ClusterState state) { - return state.nodes().getMinNodeVersion().before(LegacyESVersion.V_7_0_0); - } - - private static boolean clusterHas6xIndices(ClusterState state) { - final Iterator indices = state.metadata().indices().valuesIt(); - while (indices.hasNext()) { - final IndexMetadata indexMetadata = indices.next(); - if (indexMetadata.getCreationVersion().before(LegacyESVersion.V_7_0_0)) { - return true; - } - } - return false; - } } diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java b/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java index 218c507d82..e7b759aefd 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java @@ -39,7 +39,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.LegacyESVersion; import org.opensearch.action.ActionListener; import org.opensearch.action.get.GetResponse; import org.opensearch.action.get.MultiGetItemResponse; @@ -125,23 +124,6 @@ public void singleFailure(Failure failure) { public void noData(String id) { CType cType = CType.fromString(id); - //when index was created with ES 6 there are no separate tenants. So we load just empty ones. - //when index was created with ES 7 and type not "security" (ES 6 type) there are no rolemappings anymore. - if(cs.state().metadata().index(securityIndex).getCreationVersion().before(LegacyESVersion.V_7_0_0)) { - //created with SG 6 - //skip tenants - - if (isDebugEnabled) { - log.debug("Skip tenants because we not yet migrated to ES 7 (index was created with ES 6)"); - } - - if(cType == CType.TENANTS) { - rs.put(cType, SecurityDynamicConfiguration.empty()); - latch.countDown(); - return; - } - } - // Since NODESDN is newly introduced data-type applying for existing clusters as well, we make it backward compatible by returning valid empty // SecurityDynamicConfiguration. // Same idea for new setting WHITELIST/ALLOWLIST diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java index 3403dc1ee8..02bb9f61b2 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java @@ -20,8 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; -import org.opensearch.LegacyESVersion; -import org.opensearch.Version; import org.opensearch.action.ActionListener; import org.opensearch.action.admin.indices.create.CreateIndexResponse; import org.opensearch.action.bulk.BulkRequestBuilder; @@ -98,13 +96,6 @@ protected Endpoint getEndpoint() { @Override protected void handlePost(RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { - final Version oldestNodeVersion = cs.state().getNodes().getMinNodeVersion(); - - if(oldestNodeVersion.before(LegacyESVersion.V_7_0_0)) { - badRequestResponse(channel, "Can not migrate configuration because cluster is not fully migrated."); - return; - } - final SecurityDynamicConfiguration loadedConfig = load(CType.CONFIG, true); if (loadedConfig.getVersion() != 1) { From 41d68ccb30b52f285ea350038437871d72d59c83 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 18 Oct 2022 13:39:58 -0400 Subject: [PATCH 054/369] Migrate to Apache HttpClient / Core 5.x (#2166) This PR contains changes related to migration to Apache HttpClient and Core 5.x from 4.x with changes that fix build failures. The security plugin needs to retain support for v4 http client for SAML because of the class SamlHTTPMetadataResolver which extends OpenSAML and OpenSAML has a dependency on v4. Signed-off-by: Craig Perkins Signed-off-by: Darshit Chanpura Signed-off-by: Andriy Redko --- build.gradle | 12 +- .../security/SecurityRolesTests.java | 2 +- .../security/http/BasicAuthTests.java | 6 +- .../http/BasicAuthWithoutChallengeTests.java | 2 +- .../security/http/DisabledBasicAuthTests.java | 2 +- .../privileges/PrivilegesEvaluatorTest.java | 2 +- .../cluster/OpenSearchClientProvider.java | 52 +- .../framework/cluster/TestRestClient.java | 77 ++- .../jwt/AbstractHTTPJwtAuthenticator.java | 2 +- .../auth/http/jwt/HTTPJwtAuthenticator.java | 2 +- .../http/jwt/keybyoidc/KeySetRetriever.java | 54 +- .../http/saml/SamlHTTPMetadataResolver.java | 8 +- .../util/SettingsBasedSSLConfigurator.java | 23 +- .../util/SettingsBasedSSLConfiguratorV4.java | 561 ++++++++++++++++++ .../security/auditlog/impl/AuditMessage.java | 2 +- .../security/auditlog/sink/WebhookSink.java | 88 +-- .../security/httpclient/HttpClient.java | 71 ++- .../security/ssl/util/SSLConfigConstants.java | 3 - .../security/tools/SecurityAdmin.java | 52 +- .../http/jwt/HTTPJwtAuthenticatorTest.java | 2 +- .../jwt/keybyoidc/KeySetRetrieverTest.java | 34 +- .../http/jwt/keybyoidc/MockIpdServer.java | 91 +-- .../auth/http/saml/MockSamlIdpServer.java | 149 ++--- .../dlic/auth/ldap/LdapBackendIntegTest.java | 4 +- .../auth/ldap2/LdapBackendIntegTest2.java | 4 +- .../AdvancedSecurityMigrationTests.java | 4 +- .../opensearch/security/AggregationTests.java | 2 +- .../security/DataStreamIntegrationTests.java | 2 +- .../EncryptionInTransitMigrationTests.java | 2 +- .../org/opensearch/security/HealthTests.java | 2 +- .../security/HttpIntegrationTests.java | 6 +- .../security/IndexIntegrationTests.java | 4 +- ...exTemplateClusterPermissionsCheckTest.java | 2 +- .../InitializationIntegrationTests.java | 6 +- .../opensearch/security/IntegrationTests.java | 4 +- .../security/PitIntegrationTests.java | 2 +- .../opensearch/security/ResolveAPITests.java | 2 +- .../SecurityAdminIEndpointsTests.java | 2 +- .../SecurityAdminInvalidConfigsTests.java | 2 +- .../security/SecurityAdminTests.java | 2 +- .../security/SecurityRolesTests.java | 4 +- .../security/SlowIntegrationTests.java | 2 +- .../security/SnapshotRestoreTests.java | 2 +- .../security/SystemIntegratorsTests.java | 4 +- .../org/opensearch/security/TaskTests.java | 4 +- .../org/opensearch/security/TracingTests.java | 2 +- .../auditlog/AbstractAuditlogiUnitTest.java | 2 +- .../security/auditlog/AuditTestUtils.java | 2 +- .../compliance/ComplianceAuditlogTest.java | 4 +- .../RestApiComplianceAuditlogTest.java | 2 +- .../auditlog/helper/ErroneousHttpHandler.java | 18 +- .../auditlog/helper/TestHttpHandler.java | 30 +- .../security/auditlog/impl/TracingTests.java | 2 +- .../integration/BasicAuditlogTest.java | 10 +- .../auditlog/integration/SSLAuditlogTest.java | 2 +- .../auditlog/sink/SinkProviderTLSTest.java | 7 +- .../auditlog/sink/WebhookAuditLogTest.java | 55 +- .../security/cache/CachingTest.java | 4 +- .../ccstest/CrossClusterSearchTests.java | 2 +- .../security/ccstest/RemoteReindexTests.java | 2 +- .../CustomFieldMaskedComplexMappingTest.java | 2 +- .../dlic/dlsfls/CustomFieldMaskedTest.java | 2 +- .../security/dlic/dlsfls/DateMathTest.java | 2 +- .../security/dlic/dlsfls/DlsDateMathTest.java | 2 +- .../dlsfls/DlsFlsCrossClusterSearchTest.java | 2 +- .../security/dlic/dlsfls/DlsNestedTest.java | 2 +- .../dlic/dlsfls/DlsPropsReplaceTest.java | 2 +- .../security/dlic/dlsfls/DlsScrollTest.java | 2 +- .../security/dlic/dlsfls/DlsTest.java | 2 +- .../security/dlic/dlsfls/FieldMaskedTest.java | 2 +- .../security/dlic/dlsfls/Fls983Test.java | 2 +- .../security/dlic/dlsfls/FlsDlsTestAB.java | 2 +- .../dlic/dlsfls/FlsDlsTestForbiddenField.java | 2 +- .../security/dlic/dlsfls/FlsDlsTestMulti.java | 2 +- .../dlic/dlsfls/FlsExistsFieldsTest.java | 2 +- .../security/dlic/dlsfls/FlsFieldsTest.java | 2 +- .../security/dlic/dlsfls/FlsFieldsWcTest.java | 2 +- .../dlic/dlsfls/FlsIndexingTests.java | 4 +- .../security/dlic/dlsfls/FlsPerfTest.java | 2 +- .../security/dlic/dlsfls/FlsTest.java | 2 +- .../dlic/dlsfls/IndexPatternTest.java | 2 +- .../security/dlic/dlsfls/MFlsTest.java | 2 +- .../rest/api/AbstractRestApiUnitTest.java | 4 +- .../dlic/rest/api/AccountApiTest.java | 4 +- .../dlic/rest/api/ActionGroupsApiTest.java | 4 +- .../dlic/rest/api/AllowlistApiTest.java | 4 +- .../dlic/rest/api/AuditApiActionTest.java | 4 +- .../rest/api/DashboardsInfoActionTest.java | 2 +- .../dlic/rest/api/FlushCacheApiTest.java | 4 +- .../rest/api/GetConfigurationApiTest.java | 2 +- .../dlic/rest/api/IndexMissingTest.java | 4 +- .../dlic/rest/api/NodesDnApiTest.java | 4 +- .../dlic/rest/api/RoleBasedAccessTest.java | 2 +- .../security/dlic/rest/api/RolesApiTest.java | 6 +- .../dlic/rest/api/RolesMappingApiTest.java | 4 +- .../dlic/rest/api/SecurityApiAccessTest.java | 2 +- .../dlic/rest/api/SecurityConfigApiTest.java | 4 +- .../rest/api/SecurityHealthActionTest.java | 2 +- .../dlic/rest/api/SecurityInfoActionTest.java | 2 +- .../dlic/rest/api/TenantInfoActionTest.java | 4 +- .../security/dlic/rest/api/UserApiTest.java | 6 +- .../dlic/rest/api/WhitelistApiTest.java | 4 +- .../filter/SecurityRestFilterTest.java | 4 +- .../multitenancy/test/MultitenancyTests.java | 6 +- .../privileges/PrivilegesEvaluatorTest.java | 4 +- .../ProtectedIndicesTests.java | 4 +- .../sanity/tests/SecurityRestTestCase.java | 3 +- .../org/opensearch/security/ssl/SSLTest.java | 2 +- .../system_indices/SystemIndicesTests.java | 4 +- .../test/AbstractSecurityUnitTest.java | 47 +- .../security/test/helper/rest/RestHelper.java | 133 +++-- ...> SettingsBasedSSLConfiguratorV4Test.java} | 24 +- 112 files changed, 1212 insertions(+), 646 deletions(-) create mode 100644 src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java rename src/test/java/org/opensearch/security/util/{SettingsBasedSSLConfiguratorTest.java => SettingsBasedSSLConfiguratorV4Test.java} (94%) diff --git a/build.gradle b/build.gradle index 7d6b4dc299..c0e8befb15 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ buildscript { version_tokens = opensearch_version.tokenize('-') opensearch_build = version_tokens[0] + '.0' - common_utils_version = System.getProperty("common_utils.version", '2.1.0.0') + common_utils_version = System.getProperty("common_utils.version", '3.0.0.0-SNAPSHOT') kafka_version = '3.0.2' if (buildVersionQualifier) { @@ -296,12 +296,15 @@ dependencies { implementation 'jakarta.annotation:jakarta.annotation-api:1.3.5' implementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}" implementation "org.opensearch.client:opensearch-rest-high-level-client:${opensearch_version}" + implementation "org.apache.httpcomponents.client5:httpclient5-cache:${versions.httpclient5}" + implementation "org.apache.httpcomponents:httpclient:${versions.httpclient}" + implementation "org.apache.httpcomponents:httpcore:${versions.httpcore}" + implementation "org.apache.httpcomponents:httpasyncclient:${versions.httpasyncclient}" implementation 'com.google.guava:guava:30.0-jre' implementation 'org.greenrobot:eventbus:3.2.0' implementation 'commons-cli:commons-cli:1.3.1' implementation "org.bouncycastle:bcprov-jdk15on:${versions.bouncycastle}" implementation 'org.ldaptive:ldaptive:1.2.3' - implementation 'org.apache.httpcomponents:httpclient-cache:4.5.13' implementation 'io.jsonwebtoken:jjwt-api:0.10.8' implementation('org.apache.cxf:cxf-rt-rs-security-jose:3.4.5') { exclude(group: 'jakarta.activation', module: 'jakarta.activation-api') @@ -348,8 +351,6 @@ dependencies { implementation 'commons-lang:commons-lang:2.4' implementation 'commons-collections:commons-collections:3.2.2' implementation 'com.jayway.jsonpath:json-path:2.4.0' - implementation 'org.apache.httpcomponents:httpclient:4.5.13' - implementation 'org.apache.httpcomponents:httpclient:4.5.13' implementation 'net.minidev:json-smart:2.4.7' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.10.8' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.10.8' @@ -373,6 +374,7 @@ dependencies { implementation 'org.apache.commons:commons-lang3:3.4' + testImplementation "org.opensearch:common-utils:${common_utils_version}" testImplementation "org.opensearch.plugin:reindex-client:${opensearch_version}" testImplementation "org.opensearch:opensearch-ssl-config:${opensearch_version}" testImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}" @@ -387,6 +389,7 @@ dependencies { testImplementation 'com.unboundid:unboundid-ldapsdk:4.0.9' testImplementation 'javax.servlet:servlet-api:2.5' testImplementation 'org.apache.httpcomponents:fluent-hc:4.5.13' + testImplementation "org.apache.httpcomponents.client5:httpclient5-fluent:${versions.httpclient5}" testImplementation "org.apache.kafka:kafka_2.13:${kafka_version}" testImplementation "org.apache.kafka:kafka_2.13:${kafka_version}:test" testImplementation "org.apache.kafka:kafka-clients:${kafka_version}:test" @@ -394,7 +397,6 @@ dependencies { testImplementation 'org.springframework:spring-beans:5.3.20' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' - testImplementation "org.opensearch:common-utils:${common_utils_version}" // JUnit build requirement testCompileOnly 'org.apiguardian:apiguardian-api:1.0.0' // Kafka test execution diff --git a/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java b/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java index 30c7ed46de..d93a168341 100644 --- a/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java +++ b/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java @@ -12,7 +12,7 @@ package org.opensearch.security; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java b/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java index 0868431716..dafedcdf38 100644 --- a/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java @@ -12,7 +12,7 @@ import java.util.List; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; -import org.apache.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHeaders; import org.hamcrest.Matchers; import org.junit.ClassRule; import org.junit.Test; @@ -25,8 +25,8 @@ import org.opensearch.test.framework.cluster.TestRestClient; import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; -import static org.apache.http.HttpStatus.SC_OK; -import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; +import static org.apache.hc.core5.http.HttpStatus.SC_OK; +import static org.apache.hc.core5.http.HttpStatus.SC_UNAUTHORIZED; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.equalTo; diff --git a/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java b/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java index 3a960f0d35..4af5563e53 100644 --- a/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java @@ -10,7 +10,7 @@ package org.opensearch.security.http; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; -import org.apache.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHeaders; import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/integrationTest/java/org/opensearch/security/http/DisabledBasicAuthTests.java b/src/integrationTest/java/org/opensearch/security/http/DisabledBasicAuthTests.java index 2249083ab8..5398ea77f7 100644 --- a/src/integrationTest/java/org/opensearch/security/http/DisabledBasicAuthTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/DisabledBasicAuthTests.java @@ -19,7 +19,7 @@ import org.opensearch.test.framework.cluster.TestRestClient; import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; -import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; +import static org.apache.hc.core5.http.HttpStatus.SC_UNAUTHORIZED; import static org.opensearch.security.http.BasicAuthTests.TEST_USER; import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.DISABLED_AUTHC_HTTPBASIC_INTERNAL; import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.JWT_AUTH_DOMAIN; diff --git a/src/integrationTest/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java b/src/integrationTest/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java index c3ea872537..9fd3765ea6 100644 --- a/src/integrationTest/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java +++ b/src/integrationTest/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java @@ -12,7 +12,7 @@ package org.opensearch.security.privileges; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java index 54e4894a78..cfb3efab7c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -41,17 +41,22 @@ import java.util.stream.Stream; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManagerFactory; -import org.apache.http.Header; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.message.BasicHeader; -import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; +import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.core5.function.Factory; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.message.BasicHeader; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.opensearch.client.RestClient; import org.opensearch.client.RestClientBuilder; @@ -94,17 +99,32 @@ default TestRestClient getRestClient(UserCredentialsHolder user, Header... heade default RestHighLevelClient getRestHighLevelClient(UserCredentialsHolder user) { InetSocketAddress httpAddress = getHttpAddress(); - CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(user.getName(), user.getPassword())); - + BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(new AuthScope(null, -1), new UsernamePasswordCredentials(user.getName(), user.getPassword().toCharArray())); RestClientBuilder.HttpClientConfigCallback configCallback = httpClientBuilder -> { - httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider).setSSLStrategy( - new SSLIOSessionStrategy(getSSLContext(), null, null, NoopHostnameVerifier.INSTANCE)); - + TlsStrategy tlsStrategy = ClientTlsStrategyBuilder + .create() + .setSslContext(getSSLContext()) + .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) + // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 + .setTlsDetailsFactory(new Factory() { + @Override + public TlsDetails create(final SSLEngine sslEngine) { + return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); + } + }) + .build(); + + final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(tlsStrategy) + .build(); + + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + httpClientBuilder.setConnectionManager(cm); return httpClientBuilder; }; - RestClientBuilder builder = RestClient.builder(new HttpHost(httpAddress.getHostString(), httpAddress.getPort(), "https")) + RestClientBuilder builder = RestClient.builder(new HttpHost("https", httpAddress.getHostString(), httpAddress.getPort())) .setHttpClientConfigCallback(configCallback); diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java index 0db80ee72f..c578ca2e77 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -29,8 +29,6 @@ package org.opensearch.test.framework.cluster; import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; @@ -40,34 +38,37 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import org.apache.commons.io.IOUtils; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.NameValuePair; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpOptions; -import org.apache.http.client.methods.HttpPatch; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.config.SocketConfig; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.message.BasicHeader; +import org.apache.hc.client5.http.classic.methods.HttpDelete; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.client5.http.classic.methods.HttpOptions; +import org.apache.hc.client5.http.classic.methods.HttpPatch; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.io.SocketConfig; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicHeader; +import org.apache.hc.core5.net.URIBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -137,11 +138,7 @@ public HttpResponse putJson(String path, String body, Header... headers) { } private StringEntity toStringEntity(String body) { - try { - return new StringEntity(body); - } catch (UnsupportedEncodingException e) { - throw new RestClientException("Cannot create string entity", e); - } + return new StringEntity(body); } public HttpResponse putJson(String path, ToXContentObject body) { @@ -215,9 +212,11 @@ protected final CloseableHttpClient getHTTPClient() { final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(this.sslContext, protocols, null, NoopHostnameVerifier.INSTANCE); - hcb.setSSLSocketFactory(sslsf); - - hcb.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60 * 1000).build()); + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslsf) + .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60, TimeUnit.SECONDS).build()) + .build(); + hcb.setConnectionManager(cm); if (requestConfig != null) { hcb.setDefaultRequestConfig(requestConfig); @@ -254,9 +253,9 @@ public HttpResponse(CloseableHttpResponse inner) throws IllegalStateException, I } else { this.body = IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8); } - this.header = inner.getAllHeaders(); - this.statusCode = inner.getStatusLine().getStatusCode(); - this.statusReason = inner.getStatusLine().getReasonPhrase(); + this.header = inner.getHeaders(); + this.statusCode = inner.getCode(); + this.statusReason = inner.getReasonPhrase(); inner.close(); } @@ -381,14 +380,6 @@ public void setRequestConfig(RequestConfig requestConfig) { this.requestConfig = requestConfig; } - public void setLocalAddress(InetAddress inetAddress) { - if (requestConfig == null) { - requestConfig = RequestConfig.custom().setLocalAddress(inetAddress).build(); - } else { - requestConfig = RequestConfig.copy(requestConfig).setLocalAddress(inetAddress).build(); - } - } - public boolean isSendHTTPClientCertificate() { return sendHTTPClientCertificate; } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java index d2e14d6145..02919c186f 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java @@ -21,7 +21,7 @@ import com.google.common.annotations.VisibleForTesting; import org.apache.cxf.rs.security.jose.jwt.JwtClaims; import org.apache.cxf.rs.security.jose.jwt.JwtToken; -import org.apache.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHeaders; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticator.java index 21e740e04a..16cc71ffbd 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticator.java @@ -29,7 +29,7 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.WeakKeyException; -import org.apache.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHeaders; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java index 7731bd8084..50be122aec 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java @@ -12,22 +12,24 @@ package com.amazon.dlic.auth.http.jwt.keybyoidc; import java.io.IOException; +import java.util.concurrent.TimeUnit; import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys; import org.apache.cxf.rs.security.jose.jwk.JwkUtils; -import org.apache.http.HttpEntity; -import org.apache.http.StatusLine; -import org.apache.http.client.cache.HttpCacheContext; -import org.apache.http.client.cache.HttpCacheStorage; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.client.cache.BasicHttpCacheStorage; -import org.apache.http.impl.client.cache.CacheConfig; -import org.apache.http.impl.client.cache.CachingHttpClients; +import org.apache.hc.client5.http.cache.HttpCacheContext; +import org.apache.hc.client5.http.cache.HttpCacheStorage; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.cache.BasicHttpCacheStorage; +import org.apache.hc.client5.http.impl.cache.CacheConfig; +import org.apache.hc.client5.http.impl.cache.CachingHttpClients; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.core5.http.HttpEntity; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -70,16 +72,14 @@ public JsonWebKeys get() throws AuthenticatorUnavailableException { HttpGet httpGet = new HttpGet(uri); - RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(getRequestTimeoutMs()) - .setConnectTimeout(getRequestTimeoutMs()).setSocketTimeout(getRequestTimeoutMs()).build(); + RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(getRequestTimeoutMs(), TimeUnit.MILLISECONDS) + .setConnectTimeout(getRequestTimeoutMs(), TimeUnit.MILLISECONDS).build(); httpGet.setConfig(requestConfig); try (CloseableHttpResponse response = httpClient.execute(httpGet)) { - StatusLine statusLine = response.getStatusLine(); - - if (statusLine.getStatusCode() < 200 || statusLine.getStatusCode() >= 300) { - throw new AuthenticatorUnavailableException("Error while getting " + uri + ": " + statusLine); + if (response.getCode() < 200 || response.getCode() >= 300) { + throw new AuthenticatorUnavailableException("Error while getting " + uri + ": " + response.getReasonPhrase()); } HttpEntity httpEntity = response.getEntity(); @@ -105,8 +105,8 @@ String getJwksUri() throws AuthenticatorUnavailableException { HttpGet httpGet = new HttpGet(openIdConnectEndpoint); - RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(getRequestTimeoutMs()) - .setConnectTimeout(getRequestTimeoutMs()).setSocketTimeout(getRequestTimeoutMs()).build(); + RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(getRequestTimeoutMs(), TimeUnit.MILLISECONDS) + .setConnectTimeout(getRequestTimeoutMs(), TimeUnit.MILLISECONDS).build(); httpGet.setConfig(requestConfig); @@ -121,11 +121,9 @@ String getJwksUri() throws AuthenticatorUnavailableException { logCacheResponseStatus(httpContext); } - StatusLine statusLine = response.getStatusLine(); - - if (statusLine.getStatusCode() < 200 || statusLine.getStatusCode() >= 300) { + if (response.getCode() < 200 || response.getCode() >= 300) { throw new AuthenticatorUnavailableException( - "Error while getting " + openIdConnectEndpoint + ": " + statusLine); + "Error while getting " + openIdConnectEndpoint + ": " + response.getReasonPhrase()); } HttpEntity httpEntity = response.getEntity(); @@ -196,7 +194,11 @@ private CloseableHttpClient createHttpClient(HttpCacheStorage httpCacheStorage) builder.useSystemProperties(); if (sslConfig != null) { - builder.setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()); + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()) + .build(); + + builder.setConnectionManager(cm); } return builder.build(); diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/SamlHTTPMetadataResolver.java b/src/main/java/com/amazon/dlic/auth/http/saml/SamlHTTPMetadataResolver.java index a1269a4817..ef1ede7549 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/SamlHTTPMetadataResolver.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/SamlHTTPMetadataResolver.java @@ -22,7 +22,7 @@ import org.apache.http.impl.client.HttpClients; import org.opensaml.saml.metadata.resolver.impl.HTTPMetadataResolver; -import com.amazon.dlic.util.SettingsBasedSSLConfigurator; +import com.amazon.dlic.util.SettingsBasedSSLConfiguratorV4; import org.opensearch.SpecialPermission; import org.opensearch.common.settings.Settings; @@ -56,9 +56,9 @@ public byte[] run() throws ResolverException { } } - private static SettingsBasedSSLConfigurator.SSLConfig getSSLConfig(Settings settings, Path configPath) + private static SettingsBasedSSLConfiguratorV4.SSLConfig getSSLConfig(Settings settings, Path configPath) throws Exception { - return new SettingsBasedSSLConfigurator(settings, configPath, "idp").buildSSLConfig(); + return new SettingsBasedSSLConfiguratorV4(settings, configPath, "idp").buildSSLConfig(); } @SuppressWarnings("removal") @@ -91,7 +91,7 @@ private static HttpClient createHttpClient0(Settings settings, Path configPath) builder.useSystemProperties(); - SettingsBasedSSLConfigurator.SSLConfig sslConfig = getSSLConfig(settings, configPath); + SettingsBasedSSLConfiguratorV4.SSLConfig sslConfig = getSSLConfig(settings, configPath); if (sslConfig != null) { builder.setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()); diff --git a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfigurator.java b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfigurator.java index dd605ef087..03800feace 100644 --- a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfigurator.java +++ b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfigurator.java @@ -11,7 +11,6 @@ package com.amazon.dlic.util; -import java.net.Socket; import java.nio.file.Path; import java.security.KeyManagementException; import java.security.KeyStore; @@ -30,18 +29,18 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import com.google.common.collect.ImmutableList; -import org.apache.http.conn.ssl.DefaultHostnameVerifier; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; -import org.apache.http.ssl.PrivateKeyDetails; -import org.apache.http.ssl.PrivateKeyStrategy; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.ssl.SSLContexts; +import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.ssl.PrivateKeyDetails; +import org.apache.hc.core5.ssl.PrivateKeyStrategy; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.ssl.SSLContexts; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -196,7 +195,7 @@ private void configureWithSettings() throws SSLConfigException, NoSuchAlgorithmE new PrivateKeyStrategy() { @Override - public String chooseAlias(Map aliases, Socket socket) { + public String chooseAlias(Map aliases, SSLParameters sslParameters) { if (aliases == null || aliases.isEmpty()) { return effectiveKeyAlias; } @@ -430,10 +429,6 @@ public HostnameVerifier getHostnameVerifier() { return hostnameVerifier; } - public SSLIOSessionStrategy toSSLIOSessionStrategy() { - return new SSLIOSessionStrategy(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier); - } - public SSLConnectionSocketFactory toSSLConnectionSocketFactory() { return new SSLConnectionSocketFactory(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier); diff --git a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java new file mode 100644 index 0000000000..4f34b04499 --- /dev/null +++ b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java @@ -0,0 +1,561 @@ +/* + * 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 com.amazon.dlic.util; + +import java.net.Socket; +import java.nio.file.Path; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import com.google.common.collect.ImmutableList; +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; +import org.apache.http.ssl.PrivateKeyDetails; +import org.apache.http.ssl.PrivateKeyStrategy; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.SSLContexts; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.common.settings.Settings; +import org.opensearch.security.ssl.util.SSLConfigConstants; +import org.opensearch.security.support.PemKeyReader; + +public class SettingsBasedSSLConfiguratorV4 { + private static final Logger log = LogManager.getLogger(SettingsBasedSSLConfigurator.class); + + public static final String CERT_ALIAS = "cert_alias"; + public static final String CA_ALIAS = "ca_alias"; + public static final String ENABLE_SSL = "enable_ssl"; + + /** + * Shall STARTTLS shall be used? + *

+ * NOTE: The setting of this option is only reflected by the startTlsEnabled + * attribute of the returned SSLConfig object. Clients of this class need to + * take further measures to enable STARTTLS. It does not affect the + * SSLIOSessionStrategy and SSLConnectionSocketFactory objects returned from + * this class. + */ + public static final String ENABLE_START_TLS = "enable_start_tls"; + public static final String ENABLE_SSL_CLIENT_AUTH = "enable_ssl_client_auth"; + public static final String PEMKEY_FILEPATH = "pemkey_filepath"; + public static final String PEMKEY_CONTENT = "pemkey_content"; + public static final String PEMKEY_PASSWORD = "pemkey_password"; + public static final String PEMCERT_FILEPATH = "pemcert_filepath"; + public static final String PEMCERT_CONTENT = "pemcert_content"; + public static final String PEMTRUSTEDCAS_CONTENT = "pemtrustedcas_content"; + public static final String PEMTRUSTEDCAS_FILEPATH = "pemtrustedcas_filepath"; + public static final String VERIFY_HOSTNAMES = "verify_hostnames"; + public static final String TRUST_ALL = "trust_all"; + + private static final List DEFAULT_TLS_PROTOCOLS = ImmutableList.of("TLSv1.2", "TLSv1.1"); + + private SSLContextBuilder sslContextBuilder; + private final Settings settings; + private final String settingsKeyPrefix; + private final Path configPath; + private final String clientName; + + private boolean enabled; + private boolean enableSslClientAuth; + private KeyStore effectiveTruststore; + private KeyStore effectiveKeystore; + private char[] effectiveKeyPassword; + private String effectiveKeyAlias; + private List effectiveTruststoreAliases; + + public SettingsBasedSSLConfiguratorV4(Settings settings, Path configPath, String settingsKeyPrefix, + String clientName) { + this.settings = settings; + this.configPath = configPath; + this.settingsKeyPrefix = normalizeSettingsKeyPrefix(settingsKeyPrefix); + this.clientName = clientName != null ? clientName : this.settingsKeyPrefix; + } + + public SettingsBasedSSLConfiguratorV4(Settings settings, Path configPath, String settingsKeyPrefix) { + this(settings, configPath, settingsKeyPrefix, null); + } + + SSLContext buildSSLContext() throws SSLConfigException { + try { + if (isTrustAllEnabled()) { + sslContextBuilder = new OverlyTrustfulSSLContextBuilder(); + } else { + sslContextBuilder = SSLContexts.custom(); + } + + configureWithSettings(); + + if (!this.enabled) { + return null; + } + + return sslContextBuilder.build(); + + } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { + throw new SSLConfigException("Error while initializing SSL configuration for " + this.clientName, e); + } + } + + public SSLConfig buildSSLConfig() throws SSLConfigException { + SSLContext sslContext = buildSSLContext(); + + if (sslContext == null) { + // disabled + return null; + } + + return new SSLConfig(sslContext, getSupportedProtocols(), getSupportedCipherSuites(), getHostnameVerifier(), + isHostnameVerificationEnabled(), isTrustAllEnabled(), isStartTlsEnabled(), this.effectiveTruststore, + this.effectiveTruststoreAliases, this.effectiveKeystore, this.effectiveKeyPassword, + this.effectiveKeyAlias); + } + + private boolean isHostnameVerificationEnabled() { + return getSettingAsBoolean(VERIFY_HOSTNAMES, true) && !isTrustAllEnabled(); + } + + private HostnameVerifier getHostnameVerifier() { + if (isHostnameVerificationEnabled()) { + return new DefaultHostnameVerifier(); + } else { + return NoopHostnameVerifier.INSTANCE; + } + } + + private String[] getSupportedProtocols() { + return getSettingAsArray("enabled_ssl_protocols", DEFAULT_TLS_PROTOCOLS); + } + + private String[] getSupportedCipherSuites() { + return getSettingAsArray("enabled_ssl_ciphers", null); + + } + + private boolean isStartTlsEnabled() { + return getSettingAsBoolean(ENABLE_START_TLS, false); + } + + private boolean isTrustAllEnabled() { + return getSettingAsBoolean(TRUST_ALL, false); + } + + private void configureWithSettings() throws SSLConfigException, NoSuchAlgorithmException, KeyStoreException { + this.enabled = getSettingAsBoolean(ENABLE_SSL, false); + + if (!this.enabled) { + return; + } + + this.enableSslClientAuth = getSettingAsBoolean(ENABLE_SSL_CLIENT_AUTH, false); + + if (settings.get(settingsKeyPrefix + PEMTRUSTEDCAS_FILEPATH, null) != null + || settings.get(settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT, null) != null) { + initFromPem(); + } else { + initFromKeyStore(); + } + + if (effectiveTruststore != null) { + sslContextBuilder.loadTrustMaterial(effectiveTruststore, null); + } + + if (enableSslClientAuth) { + if (effectiveKeystore != null) { + try { + sslContextBuilder.loadKeyMaterial(effectiveKeystore, effectiveKeyPassword, + new PrivateKeyStrategy() { + + @Override + public String chooseAlias(Map aliases, Socket socket) { + if (aliases == null || aliases.isEmpty()) { + return effectiveKeyAlias; + } + + if (effectiveKeyAlias == null || effectiveKeyAlias.isEmpty()) { + return aliases.keySet().iterator().next(); + } + + return effectiveKeyAlias; + } + }); + } catch (UnrecoverableKeyException e) { + throw new RuntimeException(e); + } + } + } + + } + + private void initFromPem() throws SSLConfigException { + X509Certificate[] trustCertificates; + + try { + trustCertificates = PemKeyReader.loadCertificatesFromStream( + PemKeyReader.resolveStream(settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT, settings)); + } catch (Exception e) { + throw new SSLConfigException( + "Error loading PEM from " + settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT + " for " + this.clientName, + e); + } + + if (trustCertificates == null) { + String path = PemKeyReader.resolve(settingsKeyPrefix + PEMTRUSTEDCAS_FILEPATH, settings, configPath, + !isTrustAllEnabled()); + + try { + trustCertificates = PemKeyReader.loadCertificatesFromFile(path); + } catch (Exception e) { + throw new SSLConfigException("Error loading PEM from " + path + " (" + settingsKeyPrefix + + PEMTRUSTEDCAS_FILEPATH + ") for " + this.clientName, e); + } + } + + // for client authentication + X509Certificate[] authenticationCertificate; + + try { + authenticationCertificate = PemKeyReader.loadCertificatesFromStream( + PemKeyReader.resolveStream(settingsKeyPrefix + PEMCERT_CONTENT, settings)); + } catch (Exception e) { + throw new SSLConfigException( + "Error loading PEM from " + settingsKeyPrefix + PEMCERT_CONTENT + " for " + this.clientName, e); + } + + if (authenticationCertificate == null) { + String path = PemKeyReader.resolve(settingsKeyPrefix + PEMCERT_FILEPATH, settings, configPath, + enableSslClientAuth); + + try { + authenticationCertificate = PemKeyReader.loadCertificatesFromFile(path); + } catch (Exception e) { + throw new SSLConfigException("Error loading PEM from " + path + " (" + settingsKeyPrefix + + PEMCERT_FILEPATH + ") for " + this.clientName, e); + } + + } + + PrivateKey authenticationKey; + + try { + authenticationKey = PemKeyReader.loadKeyFromStream(getSetting(PEMKEY_PASSWORD), + PemKeyReader.resolveStream(settingsKeyPrefix + PEMKEY_CONTENT, settings)); + } catch (Exception e) { + throw new SSLConfigException( + "Error loading PEM from " + settingsKeyPrefix + PEMKEY_CONTENT + " for " + this.clientName, e); + } + + if (authenticationKey == null) { + String path = PemKeyReader.resolve(settingsKeyPrefix + PEMKEY_FILEPATH, settings, configPath, + enableSslClientAuth); + + try { + authenticationKey = PemKeyReader.loadKeyFromFile(getSetting(PEMKEY_PASSWORD), path); + } catch (Exception e) { + throw new SSLConfigException("Error loading PEM from " + path + " (" + settingsKeyPrefix + + PEMKEY_FILEPATH + ") for " + this.clientName, e); + } + } + + try { + effectiveKeyPassword = PemKeyReader.randomChars(12); + effectiveKeyAlias = "al"; + effectiveTruststore = PemKeyReader.toTruststore(effectiveKeyAlias, trustCertificates); + effectiveKeystore = PemKeyReader.toKeystore(effectiveKeyAlias, effectiveKeyPassword, + authenticationCertificate, authenticationKey); + } catch (Exception e) { + throw new SSLConfigException("Error initializing SSLConfig for " + this.clientName, e); + } + + } + + private void initFromKeyStore() throws SSLConfigException { + KeyStore trustStore; + KeyStore keyStore; + + try { + trustStore = PemKeyReader.loadKeyStore( + PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, + configPath, !isTrustAllEnabled()), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, + SSLConfigConstants.DEFAULT_STORE_PASSWORD), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); + } catch (Exception e) { + throw new SSLConfigException("Error loading trust store from " + + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH), e); + } + + effectiveTruststoreAliases = getSettingAsList(CA_ALIAS, null); + + // for client authentication + + try { + keyStore = PemKeyReader.loadKeyStore( + PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, settings, + configPath, enableSslClientAuth), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, + SSLConfigConstants.DEFAULT_STORE_PASSWORD), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE)); + } catch (Exception e) { + throw new SSLConfigException("Error loading key store from " + + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH), e); + } + + String keyStorePassword = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, + SSLConfigConstants.DEFAULT_STORE_PASSWORD); + effectiveKeyPassword = keyStorePassword == null || keyStorePassword.isEmpty() ? null + : keyStorePassword.toCharArray(); + effectiveKeyAlias = getSetting(CERT_ALIAS); + + if (enableSslClientAuth && effectiveKeyAlias == null) { + throw new IllegalArgumentException(settingsKeyPrefix + CERT_ALIAS + " not given"); + } + + effectiveTruststore = trustStore; + effectiveKeystore = keyStore; + + } + + private String getSetting(String key) { + return settings.get(settingsKeyPrefix + key); + } + + private Boolean getSettingAsBoolean(String key, Boolean defaultValue) { + return settings.getAsBoolean(settingsKeyPrefix + key, defaultValue); + } + + private List getSettingAsList(String key, List defaultValue) { + return settings.getAsList(settingsKeyPrefix + key, defaultValue); + } + + private String[] getSettingAsArray(String key, List defaultValue) { + List list = getSettingAsList(key, defaultValue); + + if (list == null) { + return null; + } + + return list.toArray(new String[list.size()]); + } + + private static String normalizeSettingsKeyPrefix(String settingsKeyPrefix) { + if (settingsKeyPrefix == null || settingsKeyPrefix.length() == 0) { + return ""; + } else if (!settingsKeyPrefix.endsWith(".")) { + return settingsKeyPrefix + "."; + } else { + return settingsKeyPrefix; + } + } + + public static class SSLConfig { + + private final SSLContext sslContext; + private final String[] supportedProtocols; + private final String[] supportedCipherSuites; + private final HostnameVerifier hostnameVerifier; + private final boolean startTlsEnabled; + private final boolean hostnameVerificationEnabled; + private final boolean trustAll; + private final KeyStore effectiveTruststore; + private final List effectiveTruststoreAliases; + private final KeyStore effectiveKeystore; + private final char[] effectiveKeyPassword; + private final String effectiveKeyAlias; + + public SSLConfig(SSLContext sslContext, String[] supportedProtocols, String[] supportedCipherSuites, + HostnameVerifier hostnameVerifier, boolean hostnameVerificationEnabled, boolean trustAll, + boolean startTlsEnabled, KeyStore effectiveTruststore, List effectiveTruststoreAliases, + KeyStore effectiveKeystore, char[] effectiveKeyPassword, String effectiveKeyAlias) { + this.sslContext = sslContext; + this.supportedProtocols = supportedProtocols; + this.supportedCipherSuites = supportedCipherSuites; + this.hostnameVerifier = hostnameVerifier; + this.hostnameVerificationEnabled = hostnameVerificationEnabled; + this.trustAll = trustAll; + this.startTlsEnabled = startTlsEnabled; + this.effectiveTruststore = effectiveTruststore; + this.effectiveTruststoreAliases = effectiveTruststoreAliases; + this.effectiveKeystore = effectiveKeystore; + this.effectiveKeyPassword = effectiveKeyPassword; + this.effectiveKeyAlias = effectiveKeyAlias; + + if (log.isDebugEnabled()) { + log.debug("Created SSLConfig: {}", this); + } + } + + public SSLContext getSslContext() { + return sslContext; + } + + public String[] getSupportedProtocols() { + return supportedProtocols; + } + + public String[] getSupportedCipherSuites() { + return supportedCipherSuites; + } + + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; + } + + public SSLIOSessionStrategy toSSLIOSessionStrategy() { + return new SSLIOSessionStrategy(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier); + } + + public SSLConnectionSocketFactory toSSLConnectionSocketFactory() { + return new SSLConnectionSocketFactory(sslContext, supportedProtocols, supportedCipherSuites, + hostnameVerifier); + } + + public boolean isStartTlsEnabled() { + return startTlsEnabled; + } + + public boolean isHostnameVerificationEnabled() { + return hostnameVerificationEnabled; + } + + public KeyStore getEffectiveTruststore() { + return effectiveTruststore; + } + + public KeyStore getEffectiveKeystore() { + return effectiveKeystore; + } + + public char[] getEffectiveKeyPassword() { + return effectiveKeyPassword; + } + + public String getEffectiveKeyPasswordString() { + if (this.effectiveKeyPassword == null) { + return null; + } else { + return new String(this.effectiveKeyPassword); + } + } + + public String getEffectiveKeyAlias() { + return effectiveKeyAlias; + } + + public List getEffectiveTruststoreAliases() { + return effectiveTruststoreAliases; + } + + public String[] getEffectiveTruststoreAliasesArray() { + if (this.effectiveTruststoreAliases == null) { + return null; + } else { + return this.effectiveTruststoreAliases.toArray(new String[this.effectiveTruststoreAliases.size()]); + } + } + + public String[] getEffectiveKeyAliasesArray() { + if (this.effectiveKeyAlias == null) { + return null; + } else { + return new String[] { this.effectiveKeyAlias }; + } + } + + @Override + public String toString() { + return "SSLConfig [sslContext=" + sslContext + ", supportedProtocols=" + Arrays.toString(supportedProtocols) + + ", supportedCipherSuites=" + Arrays.toString(supportedCipherSuites) + ", hostnameVerifier=" + + hostnameVerifier + ", startTlsEnabled=" + startTlsEnabled + ", hostnameVerificationEnabled=" + + hostnameVerificationEnabled + ", trustAll=" + trustAll + ", effectiveTruststore=" + + effectiveTruststore + ", effectiveTruststoreAliases=" + effectiveTruststoreAliases + + ", effectiveKeystore=" + effectiveKeystore + ", effectiveKeyAlias=" + effectiveKeyAlias + "]"; + } + + public boolean isTrustAllEnabled() { + return trustAll; + } + } + + public static class SSLConfigException extends Exception { + + private static final long serialVersionUID = 5827273100470174111L; + + public SSLConfigException() { + super(); + } + + public SSLConfigException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public SSLConfigException(String message, Throwable cause) { + super(message, cause); + } + + public SSLConfigException(String message) { + super(message); + } + + public SSLConfigException(Throwable cause) { + super(cause); + } + + } + + private static class OverlyTrustfulSSLContextBuilder extends SSLContextBuilder { + @Override + protected void initSSLContext(SSLContext sslContext, Collection keyManagers, + Collection trustManagers, SecureRandom secureRandom) throws KeyManagementException { + sslContext.init(!keyManagers.isEmpty() ? keyManagers.toArray(new KeyManager[keyManagers.size()]) : null, + new TrustManager[] { new OverlyTrustfulTrustManager() }, secureRandom); + } + } + + private static class OverlyTrustfulTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(final X509Certificate[] chain, final String authType) + throws CertificateException { + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, final String authType) + throws CertificateException { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } +} 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 12ca085f37..88d05d0f2a 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java @@ -26,7 +26,7 @@ import com.google.common.annotations.VisibleForTesting; import org.apache.commons.codec.digest.DigestUtils; -import org.apache.http.client.utils.URIBuilder; +import org.apache.hc.core5.net.URIBuilder; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; diff --git a/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java b/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java index 35479016ae..af4525fcba 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java @@ -20,21 +20,28 @@ import java.security.KeyStore; import java.security.PrivilegedAction; import java.security.cert.X509Certificate; - -import org.apache.http.HttpStatus; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.conn.ssl.DefaultHostnameVerifier; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.ssl.TrustStrategy; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLContext; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.SocketConfig; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.ssl.TrustStrategy; import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; @@ -217,7 +224,7 @@ protected boolean doGet(String url) { CloseableHttpResponse serverResponse = null; try { serverResponse = httpClient.execute(httpGet); - int responseCode = serverResponse.getStatusLine().getStatusCode(); + int responseCode = serverResponse.getCode(); if (responseCode != HttpStatus.SC_OK) { log.error("Cannot GET to webhook URL '{}', server returned status {}", webhookUrl, responseCode); return false; @@ -269,14 +276,13 @@ protected boolean doPost(String url, String payload) { HttpPost postRequest = new HttpPost(url); - StringEntity input = new StringEntity(payload, StandardCharsets.UTF_8); - input.setContentType(webhookFormat.contentType.toString()); + StringEntity input = new StringEntity(payload, webhookFormat.contentType.withCharset(StandardCharsets.UTF_8)); postRequest.setEntity(input); CloseableHttpResponse serverResponse = null; try { serverResponse = httpClient.execute(postRequest); - int responseCode = serverResponse.getStatusLine().getStatusCode(); + int responseCode = serverResponse.getCode(); if (responseCode != HttpStatus.SC_OK) { log.error("Cannot POST to webhook URL '{}', server returned status {}", webhookUrl, responseCode); return false; @@ -339,9 +345,8 @@ CloseableHttpClient getHttpClient() { int timeout = 5; RequestConfig config = RequestConfig.custom() - .setConnectTimeout(timeout * 1000) - .setConnectionRequestTimeout(timeout * 1000) - .setSocketTimeout(timeout * 1000).build(); + .setConnectTimeout(timeout, TimeUnit.SECONDS) + .setConnectionRequestTimeout(timeout, TimeUnit.SECONDS).build(); final TrustStrategy trustAllStrategy = new TrustStrategy() { @Override @@ -352,16 +357,18 @@ public boolean isTrusted(X509Certificate[] chain, String authType) { try { + HttpClientBuilder hcb = HttpClients.custom().setDefaultRequestConfig(config); if(!verifySSL) { - return HttpClients.custom() - .setSSLSocketFactory( - new SSLConnectionSocketFactory( - new SSLContextBuilder() - .loadTrustMaterial(trustAllStrategy) - .build(), - NoopHostnameVerifier.INSTANCE)) - .setDefaultRequestConfig(config) - .build(); + SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(trustAllStrategy).build(); + final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, null, null, + NoopHostnameVerifier.INSTANCE); + + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslsf) + .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeout, TimeUnit.SECONDS).build()) + .build(); + hcb.setConnectionManager(cm); + return hcb.build(); } if(effectiveTruststore == null) { @@ -369,16 +376,17 @@ public boolean isTrusted(X509Certificate[] chain, String authType) { .setDefaultRequestConfig(config) .build(); } + SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(effectiveTruststore, null).build(); + final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, null, null, + new DefaultHostnameVerifier()); + + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslsf) + .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeout, TimeUnit.SECONDS).build()) + .build(); + hcb.setConnectionManager(cm); - return HttpClients.custom() - .setSSLSocketFactory( - new SSLConnectionSocketFactory( - new SSLContextBuilder() - .loadTrustMaterial(effectiveTruststore, null) - .build(), - new DefaultHostnameVerifier())) - .setDefaultRequestConfig(config) - .build(); + return hcb.build(); } catch(Exception ex) { diff --git a/src/main/java/org/opensearch/security/httpclient/HttpClient.java b/src/main/java/org/opensearch/security/httpclient/HttpClient.java index 281235f5e0..d032ca3544 100644 --- a/src/main/java/org/opensearch/security/httpclient/HttpClient.java +++ b/src/main/java/org/opensearch/security/httpclient/HttpClient.java @@ -13,7 +13,6 @@ import java.io.Closeable; import java.io.IOException; -import java.net.Socket; import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; import java.security.KeyStore; @@ -25,24 +24,32 @@ import java.util.Base64; import java.util.Map; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; import com.google.common.collect.Lists; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpHost; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.conn.ssl.DefaultHostnameVerifier; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; -import org.apache.http.message.BasicHeader; -import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; -import org.apache.http.ssl.PrivateKeyDetails; -import org.apache.http.ssl.PrivateKeyStrategy; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.ssl.SSLContexts; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; +import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; +import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.core5.function.Factory; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.message.BasicHeader; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.reactor.ssl.TlsDetails; +import org.apache.hc.core5.ssl.PrivateKeyDetails; +import org.apache.hc.core5.ssl.PrivateKeyStrategy; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.ssl.SSLContexts; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -153,7 +160,7 @@ private HttpClient(final KeyStore trustStore, final String basicCredentials, fin HttpHost[] hosts = Arrays.stream(servers) .map(s->s.split(":")) - .map(s->new HttpHost(s[0], Integer.parseInt(s[1]),ssl?"https":"http")) + .map(s->new HttpHost(ssl?"https":"http", s[0], Integer.parseInt(s[1]))) .collect(Collectors.toList()).toArray(new HttpHost[0]); @@ -223,7 +230,7 @@ private final HttpAsyncClientBuilder asyncClientBuilder(HttpAsyncClientBuilder h sslContextBuilder.loadKeyMaterial(keystore, keyPassword, new PrivateKeyStrategy() { @Override - public String chooseAlias(Map aliases, Socket socket) { + public String chooseAlias(Map aliases, SSLParameters sslParameters) { if(aliases == null || aliases.isEmpty()) { return keystoreAlias; } @@ -232,19 +239,32 @@ public String chooseAlias(Map aliases, Socket socket) return aliases.keySet().iterator().next(); } - return keystoreAlias; } + return keystoreAlias; + } }); } - final HostnameVerifier hnv = verifyHostnames?new DefaultHostnameVerifier():NoopHostnameVerifier.INSTANCE; + final HostnameVerifier hnv = verifyHostnames ? new DefaultHostnameVerifier() : NoopHostnameVerifier.INSTANCE; final SSLContext sslContext = sslContextBuilder.build(); - httpClientBuilder.setSSLStrategy(new SSLIOSessionStrategy( - sslContext, - supportedProtocols, - supportedCipherSuites, - hnv - )); + TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() + .setSslContext(sslContext) + .setTlsVersions(supportedProtocols) + .setCiphers(supportedCipherSuites) + .setHostnameVerifier(hnv) + // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 + .setTlsDetailsFactory(new Factory() { + @Override + public TlsDetails create(final SSLEngine sslEngine) { + return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); + } + }) + .build(); + + final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(tlsStrategy) + .build(); + httpClientBuilder.setConnectionManager(cm); } if (basicCredentials != null) { @@ -255,9 +275,8 @@ public String chooseAlias(Map aliases, Socket socket) int timeout = 5; RequestConfig config = RequestConfig.custom() - .setConnectTimeout(timeout * 1000) - .setConnectionRequestTimeout(timeout * 1000) - .setSocketTimeout(timeout * 1000).build(); + .setConnectTimeout(timeout, TimeUnit.SECONDS) + .setConnectionRequestTimeout(timeout, TimeUnit.SECONDS).build(); httpClientBuilder.setDefaultRequestConfig(config); diff --git a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java index 57a1df8ba4..287152d9dc 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -180,13 +180,10 @@ public static final String[] getSecureSSLProtocols(Settings settings, boolean ht "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 2553a13677..4e89fd32de 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -54,6 +54,7 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import com.fasterxml.jackson.databind.InjectableValues; import com.fasterxml.jackson.databind.JsonNode; @@ -70,12 +71,17 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.apache.http.HttpHost; -import org.apache.http.conn.ssl.DefaultHostnameVerifier; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.ssl.SSLContexts; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; +import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; +import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.core5.function.Factory; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.reactor.ssl.TlsDetails; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.ssl.SSLContexts; import org.opensearch.ExceptionsHelper; import org.opensearch.OpenSearchException; @@ -1394,19 +1400,31 @@ private static RestHighLevelClient getRestHighLevelClient(SSLContext sslContext, String[] supportedProtocols = enabledProtocols.length > 0 ? enabledProtocols : null; String[] supportedCipherSuites = enabledCiphers.length > 0 ? enabledCiphers : null; - HttpHost httpHost = new HttpHost(hostname, port, "https"); + HttpHost httpHost = new HttpHost("https", hostname, port); RestClientBuilder restClientBuilder = RestClient.builder(httpHost) - .setHttpClientConfigCallback( - builder -> builder.setSSLStrategy( - new SSLIOSessionStrategy( - sslContext, - supportedProtocols, - supportedCipherSuites, - hnv - ) - ) - ); + .setHttpClientConfigCallback( + builder -> { + TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() + .setSslContext(sslContext) + .setTlsVersions(supportedProtocols) + .setCiphers(supportedCipherSuites) + // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 + .setTlsDetailsFactory(new Factory() { + @Override + public TlsDetails create(final SSLEngine sslEngine) { + return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); + } + }) + .build(); + + final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(tlsStrategy) + .build(); + + builder.setConnectionManager(cm); + return builder; + }); return new RestHighLevelClient(restClientBuilder); } diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticatorTest.java index 2e4b659841..0aeb4df082 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticatorTest.java @@ -29,7 +29,7 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; -import org.apache.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHeaders; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetrieverTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetrieverTest.java index bdf367ead5..b30a6326b6 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetrieverTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetrieverTest.java @@ -14,22 +14,24 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.net.Socket; import java.security.KeyStore; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Map; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; + import com.google.common.hash.Hashing; -import org.apache.http.HttpException; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.protocol.HttpContext; -import org.apache.http.protocol.HttpCoreContext; -import org.apache.http.ssl.PrivateKeyDetails; -import org.apache.http.ssl.PrivateKeyStrategy; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.ssl.SSLContexts; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.http.protocol.HttpCoreContext; +import org.apache.hc.core5.ssl.PrivateKeyDetails; +import org.apache.hc.core5.ssl.PrivateKeyStrategy; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.ssl.SSLContexts; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -79,13 +81,13 @@ public void clientCertTest() throws Exception { try (MockIpdServer sslMockIdpServer = new MockIpdServer(TestJwk.Jwks.ALL, SocketUtils.findAvailableTcpPort(), true) { @Override - protected void handleDiscoverRequest(HttpRequest request, HttpResponse response, HttpContext context) - throws HttpException, IOException { + protected void handleDiscoverRequest(HttpRequest request, ClassicHttpResponse response, HttpContext context) + throws IOException, HttpException { + - MockIpdServer.SSLTestHttpServerConnection connection = (MockIpdServer.SSLTestHttpServerConnection) ((HttpCoreContext) context) - .getConnection(); + SSLSession sslSession = ((HttpCoreContext) context).getSSLSession(); - X509Certificate peerCert = (X509Certificate) connection.getPeerCertificates()[0]; + X509Certificate peerCert = (X509Certificate) sslSession.getPeerCertificates()[0]; try { String sha256Fingerprint = Hashing.sha256().hashBytes(peerCert.getEncoded()).toString(); @@ -118,7 +120,7 @@ protected void handleDiscoverRequest(HttpRequest request, HttpResponse response, sslContextBuilder.loadKeyMaterial(keyStore, "changeit".toCharArray(), new PrivateKeyStrategy() { @Override - public String chooseAlias(Map aliases, Socket socket) { + public String chooseAlias(Map aliases, SSLParameters sslParameters) { return "spock"; } }); diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java index 580e2bcc17..21a9d239c3 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java @@ -16,38 +16,28 @@ import java.io.IOException; import java.io.InputStream; import java.net.Socket; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CharsetEncoder; import java.security.GeneralSecurityException; import java.security.KeyStore; -import java.security.cert.Certificate; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLParameters; import javax.net.ssl.TrustManagerFactory; import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys; -import org.apache.http.HttpConnectionFactory; -import org.apache.http.HttpException; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.config.ConnectionConfig; -import org.apache.http.config.MessageConstraints; -import org.apache.http.entity.ContentLengthStrategy; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.ConnSupport; -import org.apache.http.impl.DefaultBHttpServerConnection; -import org.apache.http.impl.bootstrap.HttpServer; -import org.apache.http.impl.bootstrap.SSLServerSetupHandler; -import org.apache.http.impl.bootstrap.ServerBootstrap; -import org.apache.http.io.HttpMessageParserFactory; -import org.apache.http.io.HttpMessageWriterFactory; -import org.apache.http.protocol.HttpContext; -import org.apache.http.protocol.HttpRequestHandler; +import org.apache.hc.core5.function.Callback; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.config.Http1Config; +import org.apache.hc.core5.http.impl.bootstrap.HttpServer; +import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; +import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnection; +import org.apache.hc.core5.http.io.HttpConnectionFactory; +import org.apache.hc.core5.http.io.HttpRequestHandler; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.protocol.HttpContext; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.network.SocketUtils; @@ -75,44 +65,31 @@ class MockIpdServer implements Closeable { this.jwks = jwks; ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap().setListenerPort(port) - .registerHandler(CTX_DISCOVER, new HttpRequestHandler() { + .register(CTX_DISCOVER, new HttpRequestHandler() { @Override - public void handle(HttpRequest request, HttpResponse response, HttpContext context) - throws HttpException, IOException { - + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { handleDiscoverRequest(request, response, context); - } - }).registerHandler(CTX_KEYS, new HttpRequestHandler() { + }).register(CTX_KEYS, new HttpRequestHandler() { @Override - public void handle(HttpRequest request, HttpResponse response, HttpContext context) - throws HttpException, IOException { - + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { handleKeysRequest(request, response, context); - } }); if (ssl) { serverBootstrap = serverBootstrap.setSslContext(createSSLContext()) - .setSslSetupHandler(new SSLServerSetupHandler() { - + .setSslSetupHandler(new Callback() { @Override - public void initialize(SSLServerSocket socket) throws SSLException { - socket.setNeedClientAuth(true); + public void execute(SSLParameters object) { + object.setNeedClientAuth(true); } }).setConnectionFactory(new HttpConnectionFactory() { - - private ConnectionConfig cconfig = ConnectionConfig.DEFAULT; - @Override public DefaultBHttpServerConnection createConnection(final Socket socket) throws IOException { - final SSLTestHttpServerConnection conn = new SSLTestHttpServerConnection( - this.cconfig.getBufferSize(), this.cconfig.getFragmentSizeHint(), - ConnSupport.createDecoder(this.cconfig), ConnSupport.createEncoder(this.cconfig), - this.cconfig.getMessageConstraints(), null, null, null, null); + final DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection(ssl ? "https" : "http", Http1Config.DEFAULT); conn.bind(socket); return conn; } @@ -145,17 +122,17 @@ public int getPort() { return port; } - protected void handleDiscoverRequest(HttpRequest request, HttpResponse response, HttpContext context) + protected void handleDiscoverRequest(HttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { - response.setStatusCode(200); + response.setCode(200); response.setHeader("Cache-Control", "public, max-age=31536000"); response.setEntity(new StringEntity("{\"jwks_uri\": \"" + uri + CTX_KEYS + "\",\n" + "\"issuer\": \"" + uri + "\", \"unknownPropertyToBeIgnored\": 42}")); } - protected void handleKeysRequest(HttpRequest request, HttpResponse response, HttpContext context) + protected void handleKeysRequest(HttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { - response.setStatusCode(200); + response.setCode(200); response.setEntity(new StringEntity(toJson(jwks))); } @@ -187,20 +164,4 @@ private SSLContext createSSLContext() { throw new RuntimeException(e); } } - - static class SSLTestHttpServerConnection extends DefaultBHttpServerConnection { - public SSLTestHttpServerConnection(final int buffersize, final int fragmentSizeHint, - final CharsetDecoder chardecoder, final CharsetEncoder charencoder, - final MessageConstraints constraints, final ContentLengthStrategy incomingContentStrategy, - final ContentLengthStrategy outgoingContentStrategy, - final HttpMessageParserFactory requestParserFactory, - final HttpMessageWriterFactory responseWriterFactory) { - super(buffersize, fragmentSizeHint, chardecoder, charencoder, constraints, incomingContentStrategy, - outgoingContentStrategy, requestParserFactory, responseWriterFactory); - } - - public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { - return ((SSLSocket) getSocket()).getSession().getPeerCertificates(); - } - } } diff --git a/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java b/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java index 9b2a2f1854..4f4a8c9640 100644 --- a/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java +++ b/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java @@ -30,7 +30,6 @@ import java.security.Principal; import java.security.PrivateKey; import java.security.UnrecoverableKeyException; -import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; @@ -43,10 +42,7 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLParameters; import javax.net.ssl.TrustManagerFactory; import javax.servlet.RequestDispatcher; import javax.servlet.ServletInputStream; @@ -65,28 +61,27 @@ import net.shibboleth.utilities.java.support.codec.Base64Support; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; -import org.apache.http.Header; -import org.apache.http.HttpConnectionFactory; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpException; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.config.ConnectionConfig; -import org.apache.http.config.MessageConstraints; -import org.apache.http.entity.ContentLengthStrategy; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.ConnSupport; -import org.apache.http.impl.DefaultBHttpServerConnection; -import org.apache.http.impl.bootstrap.HttpServer; -import org.apache.http.impl.bootstrap.SSLServerSetupHandler; -import org.apache.http.impl.bootstrap.ServerBootstrap; -import org.apache.http.io.HttpMessageParserFactory; -import org.apache.http.io.HttpMessageWriterFactory; -import org.apache.http.message.BasicHttpRequest; -import org.apache.http.protocol.HttpContext; -import org.apache.http.protocol.HttpRequestHandler; +import org.apache.hc.core5.function.Callback; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentLengthStrategy; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.config.Http1Config; +import org.apache.hc.core5.http.impl.bootstrap.HttpServer; +import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; +import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnection; +import org.apache.hc.core5.http.io.HttpConnectionFactory; +import org.apache.hc.core5.http.io.HttpMessageParserFactory; +import org.apache.hc.core5.http.io.HttpMessageWriterFactory; +import org.apache.hc.core5.http.io.HttpRequestHandler; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicHttpRequest; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.net.URIBuilder; import org.joda.time.DateTime; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilderFactory; @@ -199,53 +194,41 @@ class MockSamlIdpServer implements Closeable { this.loadSigningKeys("saml/kirk-keystore.jks", "kirk"); ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap().setListenerPort(port) - .registerHandler(CTX_METADATA, new HttpRequestHandler() { + .register(CTX_METADATA, new HttpRequestHandler() { @Override - public void handle(HttpRequest request, HttpResponse response, HttpContext context) - throws HttpException, IOException { + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { handleMetadataRequest(request, response, context); } - }).registerHandler(CTX_SAML_SSO, new HttpRequestHandler() { + }).register(CTX_SAML_SSO, new HttpRequestHandler() { @Override - public void handle(HttpRequest request, HttpResponse response, HttpContext context) - throws HttpException, IOException { - + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { handleSsoRequest(request, response, context); - } - }).registerHandler(CTX_SAML_SLO, new HttpRequestHandler() { + }).register(CTX_SAML_SLO, new HttpRequestHandler() { @Override - public void handle(HttpRequest request, HttpResponse response, HttpContext context) - throws HttpException, IOException { - + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { handleSloRequest(request, response, context); - } }); if (ssl) { - serverBootstrap = serverBootstrap.setSslContext(createSSLContext()) - .setSslSetupHandler(new SSLServerSetupHandler() { + serverBootstrap = serverBootstrap.setSslContext(createSSLContext()) + .setSslSetupHandler(new Callback() { @Override - public void initialize(SSLServerSocket socket) throws SSLException { - socket.setNeedClientAuth(true); + public void execute(SSLParameters object) { + object.setNeedClientAuth(true); } - }).setConnectionFactory(new HttpConnectionFactory() { - - private ConnectionConfig cconfig = ConnectionConfig.DEFAULT; - + }) + .setConnectionFactory(new HttpConnectionFactory() { @Override public DefaultBHttpServerConnection createConnection(final Socket socket) throws IOException { - final SSLTestHttpServerConnection conn = new SSLTestHttpServerConnection( - this.cconfig.getBufferSize(), this.cconfig.getFragmentSizeHint(), - ConnSupport.createDecoder(this.cconfig), ConnSupport.createEncoder(this.cconfig), - this.cconfig.getMessageConstraints(), null, null, null, null); + final DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection(ssl ? "https" : "http", Http1Config.DEFAULT); conn.bind(socket); return conn; } @@ -306,9 +289,9 @@ public int getPort() { return port; } - protected void handleMetadataRequest(HttpRequest request, HttpResponse response, HttpContext context) + protected void handleMetadataRequest(HttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { - response.setStatusCode(200); + response.setCode(200); response.setHeader("Cache-Control", "public, max-age=31536000"); response.setHeader("Content-Type", "application/xml"); response.setEntity(new StringEntity(createMetadata())); @@ -317,10 +300,10 @@ protected void handleMetadataRequest(HttpRequest request, HttpResponse response, protected void handleSsoRequest(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { - if ("GET".equalsIgnoreCase(request.getRequestLine().getMethod())) { + if ("GET".equalsIgnoreCase(request.getMethod())) { handleSsoGetRequestBase(request); } else { - response.setStatusCode(405); + response.setCode(405); } } @@ -328,10 +311,10 @@ protected void handleSsoRequest(HttpRequest request, HttpResponse response, Http protected void handleSloRequest(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { - if ("GET".equalsIgnoreCase(request.getRequestLine().getMethod())) { + if ("GET".equalsIgnoreCase(request.getMethod())) { handleSloGetRequestBase(request); } else { - response.setStatusCode(405); + response.setCode(405); } } @@ -726,19 +709,15 @@ private String nextId() { } static class SSLTestHttpServerConnection extends DefaultBHttpServerConnection { - public SSLTestHttpServerConnection(final int buffersize, final int fragmentSizeHint, - final CharsetDecoder chardecoder, final CharsetEncoder charencoder, - final MessageConstraints constraints, final ContentLengthStrategy incomingContentStrategy, - final ContentLengthStrategy outgoingContentStrategy, - final HttpMessageParserFactory requestParserFactory, - final HttpMessageWriterFactory responseWriterFactory) { - super(buffersize, fragmentSizeHint, chardecoder, charencoder, constraints, incomingContentStrategy, + public SSLTestHttpServerConnection(final String scheme, Http1Config http1Config, + final CharsetDecoder charDecoder, final CharsetEncoder charEncoder, + final ContentLengthStrategy incomingContentStrategy, + final ContentLengthStrategy outgoingContentStrategy, + final HttpMessageParserFactory requestParserFactory, + final HttpMessageWriterFactory responseWriterFactory) { + super(scheme, http1Config, charDecoder, charEncoder, incomingContentStrategy, outgoingContentStrategy, requestParserFactory, responseWriterFactory); } - - public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { - return ((SSLSocket) getSocket()).getSession().getPeerCertificates(); - } } static class FakeHttpServletRequest implements HttpServletRequest { @@ -748,7 +727,7 @@ static class FakeHttpServletRequest implements HttpServletRequest { FakeHttpServletRequest(HttpRequest delegate) throws URISyntaxException { this.delegate = delegate; - String uri = delegate.getRequestLine().getUri(); + String uri = delegate.getRequestUri(); this.uriBuilder = new URIBuilder(uri); this.queryParams = uriBuilder.getQueryParams().stream() .collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue)); @@ -767,8 +746,8 @@ public Enumeration getAttributeNames() { @Override public String getCharacterEncoding() { - if (delegate instanceof HttpEntityEnclosingRequest) { - return ((HttpEntityEnclosingRequest) delegate).getEntity().getContentEncoding().getValue(); + if (delegate instanceof ClassicHttpRequest) { + return ((ClassicHttpRequest) delegate).getEntity().getContentEncoding(); } else { return null; } @@ -776,8 +755,8 @@ public String getCharacterEncoding() { @Override public int getContentLength() { - if (delegate instanceof HttpEntityEnclosingRequest) { - return (int) ((HttpEntityEnclosingRequest) delegate).getEntity().getContentLength(); + if (delegate instanceof ClassicHttpRequest) { + return (int) ((ClassicHttpRequest) delegate).getEntity().getContentLength(); } else { return 0; } @@ -785,8 +764,8 @@ public int getContentLength() { @Override public String getContentType() { - if (delegate instanceof HttpEntityEnclosingRequest) { - return ((HttpEntityEnclosingRequest) delegate).getEntity().getContentType().getValue(); + if (delegate instanceof ClassicHttpRequest) { + return ((ClassicHttpRequest) delegate).getEntity().getContentType(); } else { return null; } @@ -794,8 +773,8 @@ public String getContentType() { @Override public ServletInputStream getInputStream() throws IOException { - if (delegate instanceof HttpEntityEnclosingRequest) { - final InputStream in = ((HttpEntityEnclosingRequest) delegate).getEntity().getContent(); + if (delegate instanceof ClassicHttpRequest) { + final InputStream in = ((ClassicHttpRequest) delegate).getEntity().getContent(); return new ServletInputStream() { @@ -877,8 +856,8 @@ public String getProtocol() { @Override public BufferedReader getReader() throws IOException { - if (delegate instanceof HttpEntityEnclosingRequest) { - final InputStream in = ((HttpEntityEnclosingRequest) delegate).getEntity().getContent(); + if (delegate instanceof ClassicHttpRequest) { + final InputStream in = ((ClassicHttpRequest) delegate).getEntity().getContent(); return new BufferedReader(new InputStreamReader(in)); } else { @@ -981,7 +960,7 @@ public String getHeader(String name) { @Override public Enumeration getHeaderNames() { return Collections.enumeration( - Arrays.asList(delegate.getAllHeaders()).stream().map(Header::getName).collect(Collectors.toSet())); + Arrays.asList(delegate.getHeaders()).stream().map(Header::getName).collect(Collectors.toSet())); } @SuppressWarnings("rawtypes") @@ -1010,7 +989,7 @@ public int getIntHeader(String name) { @Override public String getMethod() { - return delegate.getRequestLine().getMethod(); + return delegate.getMethod(); } @Override @@ -1025,7 +1004,7 @@ public String getPathTranslated() { @Override public String getQueryString() { - return this.delegate.getRequestLine().getUri().replaceAll("^.*\\?", ""); + return this.delegate.getRequestUri().replaceAll("^.*\\?", ""); } @Override @@ -1035,12 +1014,12 @@ public String getRemoteUser() { @Override public String getRequestURI() { - return delegate.getRequestLine().getUri(); + return delegate.getRequestUri(); } @Override public StringBuffer getRequestURL() { - return new StringBuffer(delegate.getRequestLine().getUri()); + return new StringBuffer(delegate.getRequestUri()); } @Override diff --git a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendIntegTest.java b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendIntegTest.java index e5a39cf3e9..81654d4c19 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendIntegTest.java +++ b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendIntegTest.java @@ -11,8 +11,8 @@ package com.amazon.dlic.auth.ldap; -import org.apache.http.HttpStatus; -import org.apache.http.message.BasicHeader; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; diff --git a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendIntegTest2.java b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendIntegTest2.java index 17df201599..0ce9d0c857 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendIntegTest2.java +++ b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendIntegTest2.java @@ -11,8 +11,8 @@ package com.amazon.dlic.auth.ldap2; -import org.apache.http.HttpStatus; -import org.apache.http.message.BasicHeader; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; diff --git a/src/test/java/org/opensearch/security/AdvancedSecurityMigrationTests.java b/src/test/java/org/opensearch/security/AdvancedSecurityMigrationTests.java index e4711bb504..3b069a2339 100644 --- a/src/test/java/org/opensearch/security/AdvancedSecurityMigrationTests.java +++ b/src/test/java/org/opensearch/security/AdvancedSecurityMigrationTests.java @@ -14,8 +14,8 @@ import java.io.File; import java.util.Arrays; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.After; import org.junit.Assert; import org.junit.Before; diff --git a/src/test/java/org/opensearch/security/AggregationTests.java b/src/test/java/org/opensearch/security/AggregationTests.java index b003643209..c2feddd6b6 100644 --- a/src/test/java/org/opensearch/security/AggregationTests.java +++ b/src/test/java/org/opensearch/security/AggregationTests.java @@ -26,7 +26,7 @@ package org.opensearch.security; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java b/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java index c88c0c99a6..cc37a6d1d4 100644 --- a/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java +++ b/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java @@ -11,7 +11,7 @@ package org.opensearch.security; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/EncryptionInTransitMigrationTests.java b/src/test/java/org/opensearch/security/EncryptionInTransitMigrationTests.java index b9679c217b..5535d8a924 100644 --- a/src/test/java/org/opensearch/security/EncryptionInTransitMigrationTests.java +++ b/src/test/java/org/opensearch/security/EncryptionInTransitMigrationTests.java @@ -10,7 +10,7 @@ */ package org.opensearch.security; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/HealthTests.java b/src/test/java/org/opensearch/security/HealthTests.java index a4ff381e04..4cba4030e6 100644 --- a/src/test/java/org/opensearch/security/HealthTests.java +++ b/src/test/java/org/opensearch/security/HealthTests.java @@ -26,7 +26,7 @@ package org.opensearch.security; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/HttpIntegrationTests.java b/src/test/java/org/opensearch/security/HttpIntegrationTests.java index c45d9f332c..d9ed9c34df 100644 --- a/src/test/java/org/opensearch/security/HttpIntegrationTests.java +++ b/src/test/java/org/opensearch/security/HttpIntegrationTests.java @@ -31,9 +31,9 @@ import com.fasterxml.jackson.databind.JsonNode; import org.apache.commons.io.FileUtils; -import org.apache.http.HttpStatus; -import org.apache.http.NoHttpResponseException; -import org.apache.http.message.BasicHeader; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.NoHttpResponseException; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/IndexIntegrationTests.java b/src/test/java/org/opensearch/security/IndexIntegrationTests.java index a88821078e..8f2ee960bd 100644 --- a/src/test/java/org/opensearch/security/IndexIntegrationTests.java +++ b/src/test/java/org/opensearch/security/IndexIntegrationTests.java @@ -31,7 +31,7 @@ import java.util.Date; import java.util.TimeZone; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -124,7 +124,7 @@ public void testBulkShards() throws Exception { System.out.println("############ _bulk"); HttpResponse res = rh.executePostRequest("_bulk?refresh=true&pretty=true", bulkBody, encodeBasicHeader("worf", "worf")); System.out.println(res.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("\"errors\" : true")); Assert.assertTrue(res.getBody().contains("\"status\" : 201")); Assert.assertTrue(res.getBody().contains("no permissions for")); diff --git a/src/test/java/org/opensearch/security/IndexTemplateClusterPermissionsCheckTest.java b/src/test/java/org/opensearch/security/IndexTemplateClusterPermissionsCheckTest.java index 26aec2481f..7bdbc57cf5 100644 --- a/src/test/java/org/opensearch/security/IndexTemplateClusterPermissionsCheckTest.java +++ b/src/test/java/org/opensearch/security/IndexTemplateClusterPermissionsCheckTest.java @@ -11,7 +11,7 @@ package org.opensearch.security; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/InitializationIntegrationTests.java b/src/test/java/org/opensearch/security/InitializationIntegrationTests.java index 090b1ece75..ef8ef9bf86 100644 --- a/src/test/java/org/opensearch/security/InitializationIntegrationTests.java +++ b/src/test/java/org/opensearch/security/InitializationIntegrationTests.java @@ -30,9 +30,9 @@ import java.util.Iterator; import com.fasterxml.jackson.databind.JsonNode; -import org.apache.http.Header; -import org.apache.http.HttpStatus; -import org.apache.http.client.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/IntegrationTests.java b/src/test/java/org/opensearch/security/IntegrationTests.java index 985ea826b6..226551a5ae 100644 --- a/src/test/java/org/opensearch/security/IntegrationTests.java +++ b/src/test/java/org/opensearch/security/IntegrationTests.java @@ -30,8 +30,8 @@ import com.fasterxml.jackson.databind.JsonNode; import io.netty.handler.ssl.OpenSsl; -import org.apache.http.HttpStatus; -import org.apache.http.message.BasicHeader; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Assert; import org.junit.Assume; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/PitIntegrationTests.java b/src/test/java/org/opensearch/security/PitIntegrationTests.java index b31450dcf7..baab586beb 100644 --- a/src/test/java/org/opensearch/security/PitIntegrationTests.java +++ b/src/test/java/org/opensearch/security/PitIntegrationTests.java @@ -13,7 +13,7 @@ import java.util.ArrayList; import java.util.List; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/ResolveAPITests.java b/src/test/java/org/opensearch/security/ResolveAPITests.java index 4915cec4d7..2c297e3bbe 100644 --- a/src/test/java/org/opensearch/security/ResolveAPITests.java +++ b/src/test/java/org/opensearch/security/ResolveAPITests.java @@ -15,7 +15,7 @@ package org.opensearch.security; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.Assert; diff --git a/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java b/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java index 6446e9eac7..bc5d174739 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java @@ -11,7 +11,7 @@ package org.opensearch.security; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/SecurityAdminInvalidConfigsTests.java b/src/test/java/org/opensearch/security/SecurityAdminInvalidConfigsTests.java index f377dfa7bc..18f5c06529 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminInvalidConfigsTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminInvalidConfigsTests.java @@ -30,7 +30,7 @@ import java.util.ArrayList; import java.util.List; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/SecurityAdminTests.java b/src/test/java/org/opensearch/security/SecurityAdminTests.java index c2f6b9ab27..e7953c508a 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminTests.java @@ -23,7 +23,7 @@ import java.util.ArrayList; import java.util.List; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/SecurityRolesTests.java b/src/test/java/org/opensearch/security/SecurityRolesTests.java index 4b5f45d544..ee8e1ea150 100644 --- a/src/test/java/org/opensearch/security/SecurityRolesTests.java +++ b/src/test/java/org/opensearch/security/SecurityRolesTests.java @@ -26,8 +26,8 @@ package org.opensearch.security; -import org.apache.http.HttpStatus; -import org.apache.http.message.BasicHeader; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/SlowIntegrationTests.java b/src/test/java/org/opensearch/security/SlowIntegrationTests.java index c08e3e3fd6..6a90ef8e71 100644 --- a/src/test/java/org/opensearch/security/SlowIntegrationTests.java +++ b/src/test/java/org/opensearch/security/SlowIntegrationTests.java @@ -28,7 +28,7 @@ import java.io.IOException; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/SnapshotRestoreTests.java b/src/test/java/org/opensearch/security/SnapshotRestoreTests.java index 03d1128bfe..ef7189d4b4 100644 --- a/src/test/java/org/opensearch/security/SnapshotRestoreTests.java +++ b/src/test/java/org/opensearch/security/SnapshotRestoreTests.java @@ -26,7 +26,7 @@ package org.opensearch.security; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/SystemIntegratorsTests.java b/src/test/java/org/opensearch/security/SystemIntegratorsTests.java index 0bb8e26569..4e647a6324 100644 --- a/src/test/java/org/opensearch/security/SystemIntegratorsTests.java +++ b/src/test/java/org/opensearch/security/SystemIntegratorsTests.java @@ -27,8 +27,8 @@ package org.opensearch.security; import com.google.common.collect.Lists; -import org.apache.http.HttpStatus; -import org.apache.http.message.BasicHeader; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/TaskTests.java b/src/test/java/org/opensearch/security/TaskTests.java index d3ad0cd03a..0ec671af27 100644 --- a/src/test/java/org/opensearch/security/TaskTests.java +++ b/src/test/java/org/opensearch/security/TaskTests.java @@ -17,8 +17,8 @@ package org.opensearch.security; -import org.apache.http.HttpStatus; -import org.apache.http.message.BasicHeader; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/TracingTests.java b/src/test/java/org/opensearch/security/TracingTests.java index 4c7e0472ce..10372cf73b 100644 --- a/src/test/java/org/opensearch/security/TracingTests.java +++ b/src/test/java/org/opensearch/security/TracingTests.java @@ -26,7 +26,7 @@ package org.opensearch.security; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/auditlog/AbstractAuditlogiUnitTest.java b/src/test/java/org/opensearch/security/auditlog/AbstractAuditlogiUnitTest.java index dc0cd8d4cb..14ae6aa81e 100644 --- a/src/test/java/org/opensearch/security/auditlog/AbstractAuditlogiUnitTest.java +++ b/src/test/java/org/opensearch/security/auditlog/AbstractAuditlogiUnitTest.java @@ -15,7 +15,7 @@ import java.util.Collection; import com.fasterxml.jackson.databind.JsonNode; -import org.apache.http.Header; +import org.apache.hc.core5.http.Header; import org.opensearch.common.settings.Settings; import org.opensearch.security.DefaultObjectMapper; diff --git a/src/test/java/org/opensearch/security/auditlog/AuditTestUtils.java b/src/test/java/org/opensearch/security/auditlog/AuditTestUtils.java index 33a42ca10f..507ebc1409 100644 --- a/src/test/java/org/opensearch/security/auditlog/AuditTestUtils.java +++ b/src/test/java/org/opensearch/security/auditlog/AuditTestUtils.java @@ -15,7 +15,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.opensearch.client.Client; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; 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 6436f9436d..6cb51ff441 100644 --- a/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java @@ -17,8 +17,8 @@ import java.util.stream.Collectors; import com.google.common.collect.ImmutableMap; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/auditlog/compliance/RestApiComplianceAuditlogTest.java b/src/test/java/org/opensearch/security/auditlog/compliance/RestApiComplianceAuditlogTest.java index 7d9ca05c2f..0a90f2f396 100644 --- a/src/test/java/org/opensearch/security/auditlog/compliance/RestApiComplianceAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/compliance/RestApiComplianceAuditlogTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.auditlog.compliance; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/auditlog/helper/ErroneousHttpHandler.java b/src/test/java/org/opensearch/security/auditlog/helper/ErroneousHttpHandler.java index 29a9ea9432..120232825b 100644 --- a/src/test/java/org/opensearch/security/auditlog/helper/ErroneousHttpHandler.java +++ b/src/test/java/org/opensearch/security/auditlog/helper/ErroneousHttpHandler.java @@ -11,18 +11,14 @@ package org.opensearch.security.auditlog.helper; -import java.io.IOException; - -import org.apache.http.HttpException; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.protocol.HttpContext; -import org.apache.http.protocol.HttpRequestHandler; - -public class ErroneousHttpHandler implements HttpRequestHandler{ +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.io.HttpRequestHandler; +import org.apache.hc.core5.http.protocol.HttpContext; +public class ErroneousHttpHandler implements HttpRequestHandler { @Override - public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { - response.setStatusCode(404); + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) { + response.setCode(404); } } diff --git a/src/test/java/org/opensearch/security/auditlog/helper/TestHttpHandler.java b/src/test/java/org/opensearch/security/auditlog/helper/TestHttpHandler.java index 69f667e8cb..d888949e46 100644 --- a/src/test/java/org/opensearch/security/auditlog/helper/TestHttpHandler.java +++ b/src/test/java/org/opensearch/security/auditlog/helper/TestHttpHandler.java @@ -14,15 +14,13 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpException; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.RequestLine; -import org.apache.http.protocol.HttpContext; -import org.apache.http.protocol.HttpRequestHandler; -import org.apache.http.util.EntityUtils; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.io.HttpRequestHandler; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.protocol.HttpContext; public class TestHttpHandler implements HttpRequestHandler { public String method; @@ -30,16 +28,12 @@ public class TestHttpHandler implements HttpRequestHandler { public String body; @Override - public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { - RequestLine requestLine = request.getRequestLine(); - this.method = requestLine.getMethod(); - this.uri = requestLine.getUri(); + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { + this.method = request.getMethod(); + this.uri = request.getRequestUri(); - HttpEntity entity = null; - if (request instanceof HttpEntityEnclosingRequest) { - entity = ((HttpEntityEnclosingRequest) request).getEntity(); - body = EntityUtils.toString(entity, StandardCharsets.UTF_8); - } + HttpEntity entity = request.getEntity(); + body = EntityUtils.toString(entity, StandardCharsets.UTF_8); } public void reset() { diff --git a/src/test/java/org/opensearch/security/auditlog/impl/TracingTests.java b/src/test/java/org/opensearch/security/auditlog/impl/TracingTests.java index 49dd3b38b2..2764ae8eb7 100644 --- a/src/test/java/org/opensearch/security/auditlog/impl/TracingTests.java +++ b/src/test/java/org/opensearch/security/auditlog/impl/TracingTests.java @@ -11,7 +11,7 @@ package org.opensearch.security.auditlog.impl; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; 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 02cde14159..ac8df9cc72 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java @@ -15,9 +15,9 @@ import java.util.List; import com.google.common.collect.ImmutableMap; -import org.apache.http.Header; -import org.apache.http.HttpStatus; -import org.apache.http.message.BasicHeader; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Assert; import org.junit.Test; @@ -132,8 +132,8 @@ public void testSSLPlainText() throws Exception { 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); + Assert.assertEquals("org.apache.hc.core5.http.NoHttpResponseException", ex.getCause().getClass().getName()); + }, 1); // All of the messages should be the same as the http client is attempting multiple times. messages.stream().forEach((message) -> { diff --git a/src/test/java/org/opensearch/security/auditlog/integration/SSLAuditlogTest.java b/src/test/java/org/opensearch/security/auditlog/integration/SSLAuditlogTest.java index 74e6b0f383..56fd17ddff 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/SSLAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/SSLAuditlogTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.auditlog.integration; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.After; import org.junit.Assert; import org.junit.Test; 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 2aa7c86be7..fb0f665b16 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java @@ -21,8 +21,9 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; -import org.apache.http.impl.bootstrap.HttpServer; -import org.apache.http.impl.bootstrap.ServerBootstrap; +import org.apache.hc.core5.http.impl.HttpProcessors; +import org.apache.hc.core5.http.impl.bootstrap.HttpServer; +import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -58,7 +59,7 @@ public void testTlsConfigurationNoFallback() throws Exception { TestHttpHandler handler = new TestHttpHandler(); int port = findFreePort(); - server = ServerBootstrap.bootstrap().setListenerPort(port).setServerInfo("Test/1.1").setSslContext(createSSLContext()).registerHandler("*", handler).create(); + server = ServerBootstrap.bootstrap().setListenerPort(port).setHttpProcessor(HttpProcessors.server("Test/1.1")).setSslContext(createSSLContext()).register("*", handler).create(); server.start(); 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 8446e38b44..1e327750b6 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java @@ -18,15 +18,16 @@ import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.security.KeyStore; -import java.util.concurrent.TimeUnit; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; -import org.apache.http.entity.ContentType; -import org.apache.http.impl.bootstrap.HttpServer; -import org.apache.http.impl.bootstrap.ServerBootstrap; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.impl.HttpProcessors; +import org.apache.hc.core5.http.impl.bootstrap.HttpServer; +import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; +import org.apache.hc.core5.util.TimeValue; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -223,8 +224,8 @@ public void postGetHttpTest() throws Exception { int port = findFreePort(); server = ServerBootstrap.bootstrap() .setListenerPort(port) - .setServerInfo("Test/1.1") - .registerHandler("*", handler) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .register("*", handler) .create(); server.start(); @@ -318,7 +319,7 @@ public void postGetHttpTest() throws Exception { Assert.assertTrue(handler.method.equals("GET")); Assert.assertEquals(null, handler.body); assertStringContainsAllKeysAndValues(URLDecoder.decode(handler.uri, StandardCharsets.UTF_8.displayName())); - server.shutdown(3l, TimeUnit.SECONDS); + server.awaitTermination(TimeValue.ofSeconds(3)); } @Test @@ -329,8 +330,8 @@ public void httpsTestWithoutTLSServer() throws Exception { int port = findFreePort(); server = ServerBootstrap.bootstrap() .setListenerPort(port) - .setServerInfo("Test/1.1") - .registerHandler("*", handler) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .register("*", handler) .create(); server.start(); @@ -355,7 +356,7 @@ public void httpsTestWithoutTLSServer() throws Exception { // ... so message must be stored in fallback Assert.assertEquals(1, fallback.messages.size()); Assert.assertEquals(msg, fallback.messages.get(0)); - server.shutdown(3l, TimeUnit.SECONDS); + server.awaitTermination(TimeValue.ofSeconds(3)); } @@ -366,9 +367,9 @@ public void httpsTest() throws Exception { int port = findFreePort(); server = ServerBootstrap.bootstrap() .setListenerPort(port) - .setServerInfo("Test/1.1") - .setSslContext(createSSLContext()) - .registerHandler("*", handler) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .setSslContext(createSSLContext()) + .register("*", handler) .create(); server.start(); @@ -440,7 +441,7 @@ public void httpsTest() throws Exception { Assert.assertNull(handler.body); Assert.assertNull(handler.body); - server.shutdown(3l, TimeUnit.SECONDS); + server.awaitTermination(TimeValue.ofSeconds(3)); } @Test @@ -450,9 +451,9 @@ public void httpsTestPemDefault() throws Exception { server = ServerBootstrap.bootstrap() .setListenerPort(port) - .setServerInfo("Test/1.1") - .setSslContext(createSSLContext()) - .registerHandler("*", handler) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .setSslContext(createSSLContext()) + .register("*", handler) .create(); server.start(); @@ -554,7 +555,7 @@ public void httpsTestPemDefault() throws Exception { Assert.assertNull(handler.method); Assert.assertNull(handler.body); Assert.assertNull(handler.body); - server.shutdown(3l, TimeUnit.SECONDS); + server.awaitTermination(TimeValue.ofSeconds(3)); } @Test @@ -565,9 +566,9 @@ public void httpsTestPemEndpoint() throws Exception { server = ServerBootstrap.bootstrap() .setListenerPort(port) - .setServerInfo("Test/1.1") - .setSslContext(createSSLContext()) - .registerHandler("*", handler) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .setSslContext(createSSLContext()) + .register("*", handler) .create(); server.start(); @@ -652,7 +653,7 @@ public void httpsTestPemEndpoint() throws Exception { Assert.assertNull(handler.body); Assert.assertNull(handler.body); - server.shutdown(3l, TimeUnit.SECONDS); + server.awaitTermination(TimeValue.ofSeconds(3)); } @Test @@ -663,9 +664,9 @@ public void httpsTestPemContentEndpoint() throws Exception { server = ServerBootstrap.bootstrap() .setListenerPort(port) - .setServerInfo("Test/1.1") - .setSslContext(createSSLContext()) - .registerHandler("*", handler) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .setSslContext(createSSLContext()) + .register("*", handler) .create(); server.start(); @@ -691,9 +692,7 @@ public void httpsTestPemContentEndpoint() throws Exception { Assert.assertTrue(handler.body.contains("{")); assertStringContainsAllKeysAndValues(handler.body); - - - server.shutdown(3l, TimeUnit.SECONDS); + server.awaitTermination(TimeValue.ofSeconds(3)); } // for TLS support on our in-memory server diff --git a/src/test/java/org/opensearch/security/cache/CachingTest.java b/src/test/java/org/opensearch/security/cache/CachingTest.java index 4288a735cf..5276196856 100644 --- a/src/test/java/org/opensearch/security/cache/CachingTest.java +++ b/src/test/java/org/opensearch/security/cache/CachingTest.java @@ -11,8 +11,8 @@ package org.opensearch.security.cache; -import org.apache.http.HttpStatus; -import org.apache.http.message.BasicHeader; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Assert; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java index 69141be6e6..64e73202c7 100644 --- a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java +++ b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java @@ -26,7 +26,7 @@ package org.opensearch.security.ccstest; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.After; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/ccstest/RemoteReindexTests.java b/src/test/java/org/opensearch/security/ccstest/RemoteReindexTests.java index f7626ced60..0d6efe1bb9 100644 --- a/src/test/java/org/opensearch/security/ccstest/RemoteReindexTests.java +++ b/src/test/java/org/opensearch/security/ccstest/RemoteReindexTests.java @@ -26,7 +26,7 @@ package org.opensearch.security.ccstest; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.After; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedComplexMappingTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedComplexMappingTest.java index 75655ac927..bbc7bd5479 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedComplexMappingTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedComplexMappingTest.java @@ -13,7 +13,7 @@ import java.nio.charset.StandardCharsets; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedTest.java index 18577cefcc..9d48e0309f 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DateMathTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DateMathTest.java index 4a470990f5..54110e911f 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DateMathTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DateMathTest.java @@ -15,7 +15,7 @@ import java.util.Date; import java.util.TimeZone; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsDateMathTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsDateMathTest.java index 441eaca870..c4105c11e9 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsDateMathTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsDateMathTest.java @@ -15,7 +15,7 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java index 3fd7d0a406..4e8351d7b3 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.After; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsNestedTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsNestedTest.java index bd64d0c011..a89d12770d 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsNestedTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsNestedTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsPropsReplaceTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsPropsReplaceTest.java index b929a6146b..43d5ecfc5f 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsPropsReplaceTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsPropsReplaceTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsScrollTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsScrollTest.java index 76762269f8..b1d87734e5 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsScrollTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsScrollTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; 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 c863e3364b..cb2fa254b9 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FieldMaskedTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FieldMaskedTest.java index 57cae053df..40542e76b7 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FieldMaskedTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FieldMaskedTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/Fls983Test.java b/src/test/java/org/opensearch/security/dlic/dlsfls/Fls983Test.java index a100fa91cb..6f00dfd348 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/Fls983Test.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/Fls983Test.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestAB.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestAB.java index dd2f0028db..9cc5cc8b3b 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestAB.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestAB.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestForbiddenField.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestForbiddenField.java index 001bd1858d..6df02c2e22 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestForbiddenField.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestForbiddenField.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestMulti.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestMulti.java index 4af0c9104e..b177f1d346 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestMulti.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestMulti.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsExistsFieldsTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsExistsFieldsTest.java index 50a19c4372..8f056c8244 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsExistsFieldsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsExistsFieldsTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsTest.java index 22a46029ff..d4826222fa 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsTest.java @@ -13,7 +13,7 @@ import java.io.IOException; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsWcTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsWcTest.java index fb652ff5da..951e786891 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsWcTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsWcTest.java @@ -13,7 +13,7 @@ import java.io.IOException; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java index 73a152eca4..bbac74e6eb 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java @@ -11,8 +11,8 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Test; import org.opensearch.action.index.IndexRequest; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsPerfTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsPerfTest.java index 2a8a6a1885..ca1e297f6c 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsPerfTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsPerfTest.java @@ -15,7 +15,7 @@ import java.util.HashMap; import java.util.Map; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsTest.java index 011afed4cf..c31650e734 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/IndexPatternTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/IndexPatternTest.java index 6d15d97c2e..7348b11341 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/IndexPatternTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/IndexPatternTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/MFlsTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/MFlsTest.java index 6ff1ad7105..b7305ee48c 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/MFlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/MFlsTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.dlsfls; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java index 59e8feb198..6e775bbc62 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java @@ -20,8 +20,8 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonMappingException; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.opensearch.common.settings.Settings; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java index c1840524c9..0b91aa35af 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java @@ -11,8 +11,8 @@ package org.opensearch.security.dlic.rest.api; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java index 09efae9fbe..6323746a7f 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java @@ -13,8 +13,8 @@ import java.util.List; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java index 1c97d138da..3d9e2dfc66 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java @@ -17,8 +17,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java index c5e0a61d2f..450a5de83b 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java @@ -24,8 +24,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Streams; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.After; import org.junit.Rule; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java index c6af253f95..c17e997dc3 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.rest.api; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java index ad0a4eea14..c998bf5a19 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java @@ -11,8 +11,8 @@ package org.opensearch.security.dlic.rest.api; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java index 237e75a79a..ea5e96d37e 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java @@ -12,7 +12,7 @@ package org.opensearch.security.dlic.rest.api; import com.fasterxml.jackson.databind.JsonNode; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java index 257732f129..c2313fe434 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java @@ -11,8 +11,8 @@ package org.opensearch.security.dlic.rest.api; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java index f72375600c..ba46781e7e 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java @@ -19,8 +19,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java index 96027e6f8d..5adac7ca78 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.rest.api; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java index 8dc18f5043..01fa5b4baf 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java @@ -14,8 +14,8 @@ import java.util.List; import com.fasterxml.jackson.databind.JsonNode; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; @@ -491,7 +491,7 @@ public void testRolesApiForNonSuperAdmin() throws Exception { // put hidden role String body = FileHelper.loadFile("restapi/roles_captains.json"); response = rh.executePutRequest( ENDPOINT+ "/roles/opendistro_security_internal", body, new Header[0]); - Assert.assertEquals(org.apache.http.HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch single hidden roles response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_internal", "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java index 2d1f10736d..168f15dc43 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java @@ -13,8 +13,8 @@ import java.util.List; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java index 4e8808e811..83630c036a 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.rest.api; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java index f5742cfecd..d717dcbf6c 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java @@ -11,8 +11,8 @@ package org.opensearch.security.dlic.rest.api; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java index 54aeb6d8a1..13dc4ee885 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.rest.api; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java index 0743cd4d95..506ea3bdd2 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java @@ -11,7 +11,7 @@ package org.opensearch.security.dlic.rest.api; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java index e6864b8244..ab7e807153 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java @@ -11,8 +11,8 @@ package org.opensearch.security.dlic.rest.api; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index e81e42c25c..715c256cb7 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -14,9 +14,9 @@ import java.net.URLEncoder; import java.util.List; -import org.apache.http.Header; -import org.apache.http.HttpStatus; -import org.apache.http.message.BasicHeader; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java index b5c0d98fc2..e4fca1e99b 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java @@ -16,8 +16,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/filter/SecurityRestFilterTest.java b/src/test/java/org/opensearch/security/filter/SecurityRestFilterTest.java index 574a35b6a7..1a087887d8 100644 --- a/src/test/java/org/opensearch/security/filter/SecurityRestFilterTest.java +++ b/src/test/java/org/opensearch/security/filter/SecurityRestFilterTest.java @@ -11,8 +11,8 @@ package org.opensearch.security.filter; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Test; import org.opensearch.security.dlic.rest.api.AbstractRestApiUnitTest; diff --git a/src/test/java/org/opensearch/security/multitenancy/test/MultitenancyTests.java b/src/test/java/org/opensearch/security/multitenancy/test/MultitenancyTests.java index 4964c940b9..7bfb5f9e87 100644 --- a/src/test/java/org/opensearch/security/multitenancy/test/MultitenancyTests.java +++ b/src/test/java/org/opensearch/security/multitenancy/test/MultitenancyTests.java @@ -14,9 +14,9 @@ import java.util.HashMap; import java.util.Map; -import org.apache.http.Header; -import org.apache.http.HttpStatus; -import org.apache.http.message.BasicHeader; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java b/src/test/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java index 79f23037f5..ba265bcf2e 100644 --- a/src/test/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java +++ b/src/test/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java @@ -11,8 +11,8 @@ package org.opensearch.security.privileges; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/opensearch/security/protected_indices/ProtectedIndicesTests.java b/src/test/java/org/opensearch/security/protected_indices/ProtectedIndicesTests.java index b86055f7e9..60a19d4210 100644 --- a/src/test/java/org/opensearch/security/protected_indices/ProtectedIndicesTests.java +++ b/src/test/java/org/opensearch/security/protected_indices/ProtectedIndicesTests.java @@ -30,8 +30,8 @@ import java.util.Arrays; import java.util.List; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Test; import org.opensearch.action.admin.cluster.repositories.put.PutRepositoryRequest; diff --git a/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java b/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java index 2418bd2194..c6d61bf617 100644 --- a/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java +++ b/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java @@ -16,7 +16,7 @@ import java.nio.file.Path; import java.util.Map; -import org.apache.http.HttpHost; +import org.apache.hc.core5.http.HttpHost; import org.opensearch.client.Request; import org.opensearch.client.Response; @@ -82,6 +82,7 @@ protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOE // create client with passed user String userName = System.getProperty("user"); String password = System.getProperty("password"); + return new SecureRestClientBuilder(hosts, isHttps(), userName, password).setSocketTimeout(60000).build(); } else { diff --git a/src/test/java/org/opensearch/security/ssl/SSLTest.java b/src/test/java/org/opensearch/security/ssl/SSLTest.java index e028ac82e3..331abdc414 100644 --- a/src/test/java/org/opensearch/security/ssl/SSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/SSLTest.java @@ -31,7 +31,7 @@ import javax.net.ssl.SSLHandshakeException; import io.netty.util.internal.PlatformDependent; -import org.apache.http.NoHttpResponseException; +import org.apache.hc.core5.http.NoHttpResponseException; import org.apache.lucene.util.Constants; import org.junit.Assert; import org.junit.Assume; diff --git a/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java b/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java index d569ee0880..11bd4738e6 100644 --- a/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java +++ b/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java @@ -15,8 +15,8 @@ import java.util.Arrays; import java.util.List; -import org.apache.http.Header; -import org.apache.http.HttpStatus; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; import org.junit.Test; import org.opensearch.action.admin.cluster.repositories.put.PutRepositoryRequest; diff --git a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java index b95104dd9f..592433d5e9 100644 --- a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java +++ b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java @@ -38,6 +38,7 @@ import java.util.concurrent.atomic.AtomicLong; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import com.carrotsearch.randomizedtesting.RandomizedTest; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; @@ -45,13 +46,18 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import io.netty.handler.ssl.OpenSsl; -import org.apache.http.Header; -import org.apache.http.HttpHost; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.message.BasicHeader; -import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.ssl.SSLContexts; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; +import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.core5.function.Factory; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.message.BasicHeader; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.reactor.ssl.TlsDetails; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.ssl.SSLContexts; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.Assert; @@ -155,15 +161,30 @@ protected RestHighLevelClient getRestClient(ClusterInfo info, String keyStoreNam sslContextBuilder.loadTrustMaterial(trustStore, null); SSLContext sslContext = sslContextBuilder.build(); - HttpHost httpHost = new HttpHost(info.httpHost, info.httpPort, "https"); + HttpHost httpHost = new HttpHost("https", info.httpHost, info.httpPort); RestClientBuilder restClientBuilder = RestClient.builder(httpHost) .setHttpClientConfigCallback( - builder -> builder.setSSLStrategy( - new SSLIOSessionStrategy(sslContext, - new String[] { "TLSv1", "TLSv1.1", "TLSv1.2", "SSLv3"}, - null, - NoopHostnameVerifier.INSTANCE))); + builder -> { + TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() + .setSslContext(sslContext) + .setTlsVersions(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2", "SSLv3"}) + .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) + // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 + .setTlsDetailsFactory(new Factory() { + @Override + public TlsDetails create(final SSLEngine sslEngine) { + return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); + } + }) + .build(); + + final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(tlsStrategy) + .build(); + builder.setConnectionManager(cm); + return builder; + }); return new RestHighLevelClient(restClientBuilder); } catch (Exception e) { log.error("Cannot create client", e); 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 49d498833e..30b549dfa1 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 @@ -28,7 +28,6 @@ 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; @@ -38,6 +37,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -45,32 +45,33 @@ import com.fasterxml.jackson.databind.JsonNode; import org.apache.commons.io.IOUtils; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHeaders; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpOptions; -import org.apache.http.client.methods.HttpPatch; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.methods.RequestBuilder; -import org.apache.http.config.SocketConfig; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.ssl.SSLContexts; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.classic.methods.HttpDelete; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.client5.http.classic.methods.HttpOptions; +import org.apache.hc.client5.http.classic.methods.HttpPatch; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.io.SocketConfig; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.ssl.SSLContexts; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -109,12 +110,13 @@ public String executeSimpleRequest(final String request) throws Exception { CloseableHttpClient httpClient = null; CloseableHttpResponse response = null; + try { httpClient = getHTTPClient(); - response = httpClient.execute(new HttpGet(getHttpServerUri() + "/" + request)); + response = httpClient.execute(new HttpGet(getRequestUri(request))); - if (response.getStatusLine().getStatusCode() >= 300) { - throw new Exception("Statuscode " + response.getStatusLine().getStatusCode()); + if (response.getCode() >= 300) { + throw new Exception("Statuscode " + response.getCode()); } return IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); @@ -143,27 +145,26 @@ public HttpResponse[] executeMultipleAsyncPutRequest(final int numOfRequests, fi } public HttpResponse executeGetRequest(final String request, Header... header) { - return executeRequest(new HttpGet(getHttpServerUri() + "/" + request), header); + return executeRequest(new HttpGet(getRequestUri(request)), header); } public HttpResponse executeGetRequest(final String request, String body, Header... header) { - HttpUriRequest uriRequest = RequestBuilder.get(getHttpServerUri() + "/" + request) - .setEntity(createStringEntity(body)) - .setHeader(HttpHeaders.CONTENT_TYPE, "application/json") - .build(); - return executeRequest(uriRequest, header); + HttpGet getRequest = new HttpGet(getRequestUri(request)); + getRequest.setEntity(createStringEntity(body)); + getRequest.addHeader(HttpHeaders.CONTENT_TYPE, "application/json"); + return executeRequest(getRequest, header); } public HttpResponse executeHeadRequest(final String request, Header... header) { - return executeRequest(new HttpHead(getHttpServerUri() + "/" + request), header); + return executeRequest(new HttpHead(getRequestUri(request)), header); } public HttpResponse executeOptionsRequest(final String request) { - return executeRequest(new HttpOptions(getHttpServerUri() + "/" + request)); + return executeRequest(new HttpOptions(getRequestUri(request))); } public HttpResponse executePutRequest(final String request, String body, Header... header) { - HttpPut uriRequest = new HttpPut(getHttpServerUri() + "/" + request); + HttpPut uriRequest = new HttpPut(getRequestUri(request)); if (body != null && !body.isEmpty()) { uriRequest.setEntity(createStringEntity(body)); } @@ -171,20 +172,19 @@ public HttpResponse executePutRequest(final String request, String body, Header. } public HttpResponse executeDeleteRequest(final String request, Header... header) { - return executeRequest(new HttpDelete(getHttpServerUri() + "/" + request), header); + return executeRequest(new HttpDelete(getRequestUri(request)), header); } public HttpResponse executeDeleteRequest(final String request, String body, Header... header) { - HttpUriRequest uriRequest = RequestBuilder.delete(getHttpServerUri() + "/" + request) - .setEntity(createStringEntity(body)) - .setHeader(HttpHeaders.CONTENT_TYPE, "application/json") - .build(); - return executeRequest(uriRequest, header); + HttpDelete delRequest = new HttpDelete(getRequestUri(request)); + delRequest.setEntity(createStringEntity(body)); + delRequest.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); + return executeRequest(delRequest, header); } public HttpResponse executePostRequest(final String request, String body, Header... header) { - HttpPost uriRequest = new HttpPost(getHttpServerUri() + "/" + request); + HttpPost uriRequest = new HttpPost(getRequestUri(request)); if (body != null && !body.isEmpty()) { uriRequest.setEntity(createStringEntity(body)); } @@ -193,7 +193,7 @@ public HttpResponse executePostRequest(final String request, String body, Header } public HttpResponse executePatchRequest(final String request, String body, Header... header) { - HttpPatch uriRequest = new HttpPatch(getHttpServerUri() + "/" + request); + HttpPatch uriRequest = new HttpPatch(getRequestUri(request)); if (body != null && !body.isEmpty()) { uriRequest.setEntity(createStringEntity(body)); } @@ -234,12 +234,8 @@ public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... header) } } - private StringEntity createStringEntity(String body) { - try { - return new StringEntity(body); - } catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + private HttpEntity createStringEntity(String body) { + return new StringEntity(body); } protected final String getHttpServerUri() { @@ -247,16 +243,20 @@ protected final String getHttpServerUri() { log.debug("Connect to {}", address); return address; } + + protected final String getRequestUri(String request) { + return getHttpServerUri() + "/" + StringUtils.strip(request, "/"); + } protected final CloseableHttpClient getHTTPClient() throws Exception { final HttpClientBuilder hcb = HttpClients.custom(); if (sendHTTPClientCredentials) { - CredentialsProvider provider = new BasicCredentialsProvider(); - UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("sarek", "sarek"); - provider.setCredentials(AuthScope.ANY, credentials); - hcb.setDefaultCredentialsProvider(provider); + UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("sarek", "sarek".toCharArray()); + BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(new AuthScope(null, -1), credentials); + hcb.setDefaultCredentialsProvider(credentialsProvider); } if (enableHTTPClientSSL) { @@ -296,17 +296,16 @@ protected final CloseableHttpClient getHTTPClient() throws Exception { protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" }; } - final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( - sslContext, - protocols, - null, + final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, protocols, null, NoopHostnameVerifier.INSTANCE); - hcb.setSSLSocketFactory(sslsf); + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslsf) + .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60, TimeUnit.SECONDS).build()) + .build(); + hcb.setConnectionManager(cm); } - hcb.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60 * 1000).build()); - return hcb.build(); } @@ -327,9 +326,9 @@ public HttpResponse(CloseableHttpResponse inner) throws IllegalStateException, I } else { this.body = IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8); } - this.header = inner.getAllHeaders(); - this.statusCode = inner.getStatusLine().getStatusCode(); - this.statusReason = inner.getStatusLine().getReasonPhrase(); + this.header = inner.getHeaders(); + this.statusCode = inner.getCode(); + this.statusReason = inner.getReasonPhrase(); inner.close(); } diff --git a/src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorTest.java b/src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorV4Test.java similarity index 94% rename from src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorTest.java rename to src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorV4Test.java index 377809023d..afcd9549a3 100644 --- a/src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorTest.java +++ b/src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorV4Test.java @@ -63,8 +63,8 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -import com.amazon.dlic.util.SettingsBasedSSLConfigurator; -import com.amazon.dlic.util.SettingsBasedSSLConfigurator.SSLConfig; +import com.amazon.dlic.util.SettingsBasedSSLConfiguratorV4; +import com.amazon.dlic.util.SettingsBasedSSLConfiguratorV4.SSLConfig; import org.opensearch.common.settings.Settings; import org.opensearch.security.ssl.util.SSLConfigConstants; @@ -74,7 +74,7 @@ import static org.hamcrest.CoreMatchers.either; import static org.hamcrest.CoreMatchers.instanceOf; -public class SettingsBasedSSLConfiguratorTest { +public class SettingsBasedSSLConfiguratorV4Test { @Rule public ExpectedException thrown = ExpectedException.none(); @@ -93,7 +93,7 @@ public void testPemTrust() throws Exception { .put("prefix.enable_ssl", "true").put("path.home", rootCaPemPath.getParent().toString()).build(); Path configPath = rootCaPemPath.getParent(); - SettingsBasedSSLConfigurator sbsc = new SettingsBasedSSLConfigurator(settings, configPath, "prefix"); + SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); @@ -120,7 +120,7 @@ public void testPemWrongTrust() throws Exception { .put("prefix.enable_ssl", "true").put("path.home", rootCaPemPath.getParent().toString()).build(); Path configPath = rootCaPemPath.getParent(); - SettingsBasedSSLConfigurator sbsc = new SettingsBasedSSLConfigurator(settings, configPath, "prefix"); + SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); @@ -151,7 +151,7 @@ public void testPemClientAuth() throws Exception { .put("prefix.pemkey_filepath", "kirk.key").put("prefix.pemkey_password", "secret").build(); Path configPath = rootCaPemPath.getParent(); - SettingsBasedSSLConfigurator sbsc = new SettingsBasedSSLConfigurator(settings, configPath, "prefix"); + SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); @@ -181,7 +181,7 @@ public void testPemClientAuthFailure() throws Exception { .build(); Path configPath = rootCaPemPath.getParent(); - SettingsBasedSSLConfigurator sbsc = new SettingsBasedSSLConfigurator(settings, configPath, "prefix"); + SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); @@ -215,7 +215,7 @@ public void testPemHostnameVerificationFailure() throws Exception { .put("path.home", rootCaPemPath.getParent().toString()).build(); Path configPath = rootCaPemPath.getParent(); - SettingsBasedSSLConfigurator sbsc = new SettingsBasedSSLConfigurator(settings, configPath, "prefix"); + SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); @@ -244,7 +244,7 @@ public void testPemHostnameVerificationOff() throws Exception { .put("path.home", rootCaPemPath.getParent().toString()).build(); Path configPath = rootCaPemPath.getParent(); - SettingsBasedSSLConfigurator sbsc = new SettingsBasedSSLConfigurator(settings, configPath, "prefix"); + SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); @@ -271,7 +271,7 @@ public void testJksTrust() throws Exception { .put("path.home", rootCaJksPath.getParent().toString()).build(); Path configPath = rootCaJksPath.getParent(); - SettingsBasedSSLConfigurator sbsc = new SettingsBasedSSLConfigurator(settings, configPath, "prefix"); + SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); @@ -299,7 +299,7 @@ public void testJksWrongTrust() throws Exception { .put("path.home", rootCaJksPath.getParent().toString()).build(); Path configPath = rootCaJksPath.getParent(); - SettingsBasedSSLConfigurator sbsc = new SettingsBasedSSLConfigurator(settings, configPath, "prefix"); + SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); @@ -325,7 +325,7 @@ public void testTrustAll() throws Exception { .put("path.home", rootCaJksPath.getParent().toString()).build(); Path configPath = rootCaJksPath.getParent(); - SettingsBasedSSLConfigurator sbsc = new SettingsBasedSSLConfigurator(settings, configPath, "prefix"); + SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); From e3a7b32c0c86a6d4391cca3b48df27e932dd77cf Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Wed, 19 Oct 2022 11:05:45 -0500 Subject: [PATCH 055/369] Add repository health badges (#2167) Adding more detailed badges generated with https://github.com/peternied/opensearch-build-badger - Related https://github.com/opensearch-project/opensearch-build/issues/2629 Signed-off-by: Peter Nied --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7283705a4c..120523c2b0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![CI](https://github.com/opensearch-project/security/workflows/CI/badge.svg?branch=main)](https://github.com/opensearch-project/security/actions)[![codecov](https://codecov.io/gh/opensearch-project/security/branch/main/graph/badge.svg)](https://codecov.io/gh/opensearch-project/security) +[![CI](https://github.com/opensearch-project/security/workflows/CI/badge.svg?branch=main)](https://github.com/opensearch-project/security/actions) [![](https://img.shields.io/github/issues/opensearch-project/security/untriaged?labelColor=red)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"untriaged") [![](https://img.shields.io/github/issues/opensearch-project/security/security%20vulnerability?labelColor=red)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"security%20vulnerability") [![](https://img.shields.io/github/issues/opensearch-project/security)](https://github.com/opensearch-project/security/issues) [![](https://img.shields.io/github/issues-pr/opensearch-project/security)](https://github.com/opensearch-project/security/pulls) +[![](https://img.shields.io/codecov/c/gh/opensearch-project/security)](https://app.codecov.io/gh/opensearch-project/security) [![](https://img.shields.io/github/issues/opensearch-project/security/v2.4.0)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"v2.4.0") [![](https://img.shields.io/github/issues/opensearch-project/security/v3.0.0)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"v3.0.0") From b836040c577894a08db9377d46a3d4eaca426fb8 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Wed, 19 Oct 2022 12:16:38 -0400 Subject: [PATCH 056/369] Address CVE-2022-42889 by updating commons-text (#2177) Signed-off-by: Andriy Redko --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c0e8befb15..a7845c439d 100644 --- a/build.gradle +++ b/build.gradle @@ -355,7 +355,7 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.10.8' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.10.8' runtimeOnly 'com.google.guava:failureaccess:1.0.1' - runtimeOnly 'org.apache.commons:commons-text:1.2' + runtimeOnly 'org.apache.commons:commons-text:1.10.0' runtimeOnly 'org.glassfish.jaxb:jaxb-runtime:2.3.4' runtimeOnly 'com.google.j2objc:j2objc-annotations:1.3' runtimeOnly 'com.google.code.findbugs:jsr305:3.0.2' From 48463965c2fdfe30a7a815817eaf7d83d1af760c Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Thu, 20 Oct 2022 14:45:33 -0400 Subject: [PATCH 057/369] Allows for configuration of LDAP referral following (#2135) * Correct Java Native Access Description in THIRD-PARTY.txt Signed-off-by: Stephen Crawford --- .../backend/LDAPAuthenticationBackend.java | 20 +++++++++-------- .../backend/LDAPAuthorizationBackend.java | 16 ++++++++------ .../dlic/auth/ldap/util/ConfigConstants.java | 3 +++ .../dlic/auth/ldap/util/LdapHelper.java | 17 +++++++++----- .../ldap2/LDAPAuthenticationBackend2.java | 6 +++-- .../auth/ldap2/LDAPAuthorizationBackend2.java | 14 +++++++----- .../dlic/auth/ldap2/LDAPUserSearcher.java | 14 ++++++------ .../security/support/ConfigConstants.java | 1 + .../dlic/auth/ldap/LdapBackendTest.java | 22 +++++++++++++++++-- .../ldap/LdapBackendTestNewStyleConfig.java | 22 ++++++++++++++++++- .../ldap2/LdapBackendTestNewStyleConfig2.java | 22 ++++++++++++++++++- .../ldap2/LdapBackendTestOldStyleConfig2.java | 22 ++++++++++++++++++- 12 files changed, 137 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java index fac06dc9a3..1ce545e8da 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java @@ -61,12 +61,14 @@ public class LDAPAuthenticationBackend implements AuthenticationBackend { private final WildcardMatcher allowlistedCustomLdapAttrMatcher; private final String[] returnAttributes; + private final boolean shouldFollowReferrals; public LDAPAuthenticationBackend(final Settings settings, final Path configPath) { this.settings = settings; this.configPath = configPath; this.userBaseSettings = getUserBaseSettings(settings); this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); + this.shouldFollowReferrals = settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT); customAttrMaxValueLen = settings.getAsInt(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 36); checkForDeprecatedSetting(settings, ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, ConfigConstants.LDAP_CUSTOM_ATTR_ALLOWLIST); @@ -90,7 +92,7 @@ public User authenticate(final AuthCredentials credentials) throws OpenSearchSec try { ldapConnection = LDAPAuthorizationBackend.getConnection(settings, configPath); - entry = exists(user, ldapConnection, settings, userBaseSettings, this.returnAttributes); + entry = exists(user, ldapConnection, settings, userBaseSettings, this.returnAttributes, this.shouldFollowReferrals); // fake a user that no exists // makes guessing if a user exists or not harder when looking on the @@ -164,7 +166,7 @@ public boolean exists(final User user) { try { ldapConnection = LDAPAuthorizationBackend.getConnection(settings, configPath); - LdapEntry userEntry = exists(userName, ldapConnection, settings, userBaseSettings, this.returnAttributes); + LdapEntry userEntry = exists(userName, ldapConnection, settings, userBaseSettings, this.returnAttributes, this.shouldFollowReferrals); boolean exists = userEntry != null; if(exists) { @@ -205,19 +207,19 @@ static List> getUserBaseSettings(Settings settings) } static LdapEntry exists(final String user, Connection ldapConnection, Settings settings, - List> userBaseSettings, String[] returnAttributes) throws Exception { + List> userBaseSettings, String[] returnAttributes, final boolean shouldFollowReferrals) throws Exception { if (settings.getAsBoolean(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, false) || settings.getAsBoolean(ConfigConstants.LDAP_SEARCH_ALL_BASES, false) || settings.hasValue(ConfigConstants.LDAP_AUTHC_USERBASE)) { - return existsSearchingAllBases(user, ldapConnection, userBaseSettings, returnAttributes); + return existsSearchingAllBases(user, ldapConnection, userBaseSettings, returnAttributes, shouldFollowReferrals); } else { - return existsSearchingUntilFirstHit(user, ldapConnection, userBaseSettings, returnAttributes); + return existsSearchingUntilFirstHit(user, ldapConnection, userBaseSettings, returnAttributes, shouldFollowReferrals); } } private static LdapEntry existsSearchingUntilFirstHit(final String user, Connection ldapConnection, - List> userBaseSettings, final String[] returnAttributes) throws Exception { + List> userBaseSettings, final String[] returnAttributes, final boolean shouldFollowReferrals) throws Exception { final String username = user; final boolean isDebugEnabled = log.isDebugEnabled(); @@ -232,7 +234,7 @@ private static LdapEntry existsSearchingUntilFirstHit(final String user, Connect baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), f, SearchScope.SUBTREE, - returnAttributes); + returnAttributes, shouldFollowReferrals); if (isDebugEnabled) { log.debug("Results for LDAP search for {} in base {} is {}", user, entry.getKey(), result); @@ -247,7 +249,7 @@ private static LdapEntry existsSearchingUntilFirstHit(final String user, Connect } private static LdapEntry existsSearchingAllBases(final String user, Connection ldapConnection, - List> userBaseSettings, final String[] returnAttributes) throws Exception { + List> userBaseSettings, final String[] returnAttributes, final boolean shouldFollowReferrals) throws Exception { final String username = user; Set result = new HashSet<>(); @@ -263,7 +265,7 @@ private static LdapEntry existsSearchingAllBases(final String user, Connection l baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), f, SearchScope.SUBTREE, - returnAttributes); + returnAttributes, shouldFollowReferrals); if (isDebugEnabled) { log.debug("Results for LDAP search for " + user + " in base " + entry.getKey() + ":\n" + result); diff --git a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java index 7146d0f678..e6adea241a 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java @@ -99,12 +99,12 @@ public class LDAPAuthorizationBackend implements AuthorizationBackend { private final Settings settings; private final WildcardMatcher skipUsersMatcher; private final WildcardMatcher nestedRoleMatcher; - private final Path configPath; private final List> roleBaseSettings; private final List> userBaseSettings; private final String[] returnAttributes; + private final boolean shouldFollowReferrals; public LDAPAuthorizationBackend(final Settings settings, final Path configPath) { this.settings = settings; @@ -115,6 +115,8 @@ public LDAPAuthorizationBackend(final Settings settings, final Path configPath) this.roleBaseSettings = getRoleSearchSettings(settings); this.userBaseSettings = LDAPAuthenticationBackend.getUserBaseSettings(settings); this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); + this.shouldFollowReferrals = settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT); + } @SuppressWarnings("removal") @@ -728,7 +730,7 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) log.debug("DBGTRACE (4): authenticatedUser="+authenticatedUser+" -> "+Arrays.toString(authenticatedUser.getBytes(StandardCharsets.UTF_8))); } - entry = LdapHelper.lookup(connection, authenticatedUser, this.returnAttributes); + entry = LdapHelper.lookup(connection, authenticatedUser, this.returnAttributes, this.shouldFollowReferrals); if (entry == null) { throw new OpenSearchSecurityException("No user '" + authenticatedUser + "' found"); @@ -739,7 +741,7 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) if (isDebugEnabled) log.debug("DBGTRACE (5): authenticatedUser="+user.getName()+" -> "+Arrays.toString(user.getName().getBytes(StandardCharsets.UTF_8))); - entry = LDAPAuthenticationBackend.exists(user.getName(), connection, settings, userBaseSettings, this.returnAttributes); + entry = LDAPAuthenticationBackend.exists(user.getName(), connection, settings, userBaseSettings, this.returnAttributes, this.shouldFollowReferrals); if (isTraceEnabled) { log.trace("{} is not a valid DN and was resolved to {}", authenticatedUser, entry); @@ -852,7 +854,7 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) List rolesResult = LdapHelper.search(connection, roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), f, - SearchScope.SUBTREE, this.returnAttributes); + SearchScope.SUBTREE, this.returnAttributes, this.shouldFollowReferrals); if (isTraceEnabled) { log.trace("Results for LDAP group search for {} in base {}:\n{}", escapedDn, roleSearchSettingsEntry.getKey(), rolesResult); @@ -970,7 +972,7 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti final Set result = new HashSet<>(20); final HashMultimap> resultRoleSearchBaseKeys = HashMultimap.create(); - final LdapEntry e0 = LdapHelper.lookup(ldapConnection, roleDn.toString(), this.returnAttributes); + final LdapEntry e0 = LdapHelper.lookup(ldapConnection, roleDn.toString(), this.returnAttributes, this.shouldFollowReferrals); if (e0.getAttribute(userRoleName) != null) { final Collection userRoles = e0.getAttribute(userRoleName).getStringValues(); @@ -1023,7 +1025,7 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), f, SearchScope.SUBTREE, - this.returnAttributes); + this.returnAttributes, this.shouldFollowReferrals); if (isTraceEnabled) { log.trace("Results for LDAP group search for {} in base {}:\n{}", escapedDn, roleSearchBaseSettingsEntry.getKey(), foundEntries); @@ -1101,7 +1103,7 @@ private String getRoleFromEntry(final Connection ldapConnection, final LdapName } try { - final LdapEntry roleEntry = LdapHelper.lookup(ldapConnection, ldapName.toString(), this.returnAttributes); + final LdapEntry roleEntry = LdapHelper.lookup(ldapConnection, ldapName.toString(), this.returnAttributes, this.shouldFollowReferrals); if(roleEntry != null) { final LdapAttribute roleAttribute = roleEntry.getAttribute(role); diff --git a/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java b/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java index 25c3a89fb4..bdd9f39a8e 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java @@ -35,6 +35,9 @@ public final class ConfigConstants { public static final String LDAP_AUTHZ_MAX_NESTED_DEPTH = "max_nested_depth"; public static final int LDAP_AUTHZ_MAX_NESTED_DEPTH_DEFAULT = 30; + public static final String FOLLOW_REFERRALS = "follow_referrals"; + public static final boolean FOLLOW_REFERRALS_DEFAULT = true; + public static final String LDAP_HOSTS = "hosts"; public static final String LDAP_BIND_DN = "bind_dn"; public static final String LDAP_PASSWORD = "password"; diff --git a/src/main/java/com/amazon/dlic/auth/ldap/util/LdapHelper.java b/src/main/java/com/amazon/dlic/auth/ldap/util/LdapHelper.java index 39da2dea88..db737ca5c5 100644 --- a/src/main/java/com/amazon/dlic/auth/ldap/util/LdapHelper.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/util/LdapHelper.java @@ -40,14 +40,13 @@ public class LdapHelper { private static SearchFilter ALL = new SearchFilter("(objectClass=*)"); @SuppressWarnings("removal") public static List search(final Connection conn, final String unescapedDn, SearchFilter filter, - final SearchScope searchScope, final String[] returnAttributes) throws LdapException { + final SearchScope searchScope, final String[] returnAttributes, boolean shouldFollowReferrals) throws LdapException { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new SpecialPermission()); } - try { final String baseDn = escapeDn(unescapedDn); return AccessController.doPrivileged(new PrivilegedExceptionAction>() { @@ -55,15 +54,21 @@ public static List search(final Connection conn, final String unescap public List run() throws Exception { final List entries = new ArrayList<>(); final SearchRequest request = new SearchRequest(baseDn, filter); - request.setReferralHandler(new SearchReferralHandler()); + request.setSearchScope(searchScope); request.setDerefAliases(DerefAliases.ALWAYS); request.setReturnAttributes(returnAttributes); final SearchOperation search = new SearchOperation(conn); - // referrals will be followed to build the response + + if (shouldFollowReferrals) { + // referrals will be followed to build the response + request.setReferralHandler(new SearchReferralHandler()); + } + final Response r = search.execute(request); final org.ldaptive.SearchResult result = r.getResult(); entries.addAll(result.getEntries()); + return entries; } }); @@ -80,9 +85,9 @@ public List run() throws Exception { } } - public static LdapEntry lookup(final Connection conn, final String unescapedDn, final String[] returnAttributes) throws LdapException { + public static LdapEntry lookup(final Connection conn, final String unescapedDn, final String[] returnAttributes, boolean shouldFollowReferrals) throws LdapException { - final List entries = search(conn, unescapedDn, ALL, SearchScope.OBJECT, returnAttributes); + final List entries = search(conn, unescapedDn, ALL, SearchScope.OBJECT, returnAttributes, shouldFollowReferrals); if (entries.size() == 1) { return entries.get(0); diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthenticationBackend2.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthenticationBackend2.java index fecaab3467..f64f3fc0b4 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthenticationBackend2.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthenticationBackend2.java @@ -60,6 +60,7 @@ public class LDAPAuthenticationBackend2 implements AuthenticationBackend, Destro private final int customAttrMaxValueLen; private final WildcardMatcher whitelistedCustomLdapAttrMatcher; private final String[] returnAttributes; + private final boolean shouldFollowReferrals; public LDAPAuthenticationBackend2(final Settings settings, final Path configPath) throws SSLConfigException { this.settings = settings; @@ -78,6 +79,7 @@ public LDAPAuthenticationBackend2(final Settings settings, final Path configPath this.userSearcher = new LDAPUserSearcher(settings); this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); + this.shouldFollowReferrals = settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT); customAttrMaxValueLen = settings.getAsInt(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 36); whitelistedCustomLdapAttrMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, Collections.singletonList("*"))); @@ -122,7 +124,7 @@ private User authenticate0(final AuthCredentials credentials) throws OpenSearchS ldapConnection = connectionFactory.getConnection(); ldapConnection.open(); - LdapEntry entry = userSearcher.exists(ldapConnection, user, this.returnAttributes); + LdapEntry entry = userSearcher.exists(ldapConnection, user, this.returnAttributes, this.shouldFollowReferrals); // fake a user that no exists // makes guessing if a user exists or not harder when looking on the @@ -214,7 +216,7 @@ private boolean exists0(final User user) { try { ldapConnection = this.connectionFactory.getConnection(); ldapConnection.open(); - LdapEntry userEntry = this.userSearcher.exists(ldapConnection, userName, this.returnAttributes); + LdapEntry userEntry = this.userSearcher.exists(ldapConnection, userName, this.returnAttributes, this.shouldFollowReferrals); boolean exists = userEntry != null; diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java index eb3efb5d10..01d422ea6c 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java @@ -76,6 +76,7 @@ public class LDAPAuthorizationBackend2 implements AuthorizationBackend, Destroya private ConnectionFactory connectionFactory; private LDAPUserSearcher userSearcher; private final String[] returnAttributes; + private final boolean shouldFollowReferrals; public LDAPAuthorizationBackend2(final Settings settings, final Path configPath) throws SSLConfigException { this.settings = settings; @@ -91,6 +92,7 @@ public LDAPAuthorizationBackend2(final Settings settings, final Path configPath) this.connectionFactory = ldapConnectionFactoryFactory.createConnectionFactory(this.connectionPool); this.userSearcher = new LDAPUserSearcher(settings); this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); + this.shouldFollowReferrals = settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT); } private static List> getRoleSearchSettings(Settings settings) { @@ -207,14 +209,14 @@ private void fillRoles0(final User user, final AuthCredentials optionalAuthCreds log.trace("{} is a valid DN", authenticatedUser); } - entry = LdapHelper.lookup(connection, authenticatedUser, this.returnAttributes); + entry = LdapHelper.lookup(connection, authenticatedUser, this.returnAttributes, this.shouldFollowReferrals); if (entry == null) { throw new OpenSearchSecurityException("No user '" + authenticatedUser + "' found"); } } else { - entry = this.userSearcher.exists(connection, user.getName(), this.returnAttributes); + entry = this.userSearcher.exists(connection, user.getName(), this.returnAttributes, this.shouldFollowReferrals); if (isTraceEnabled) { log.trace("{} is not a valid DN and was resolved to {}", authenticatedUser, entry); @@ -314,7 +316,7 @@ private void fillRoles0(final User user, final AuthCredentials optionalAuthCreds roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), f, SearchScope.SUBTREE, - this.returnAttributes); + this.returnAttributes, this.shouldFollowReferrals); if (isTraceEnabled) { log.trace("Results for LDAP group search for {} in base {}:\n{}", escapedDn, roleSearchSettingsEntry.getKey(), rolesResult); @@ -430,7 +432,7 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti final Set result = new HashSet<>(20); final HashMultimap> resultRoleSearchBaseKeys = HashMultimap.create(); - final LdapEntry e0 = LdapHelper.lookup(ldapConnection, roleDn.toString(), this.returnAttributes); + final LdapEntry e0 = LdapHelper.lookup(ldapConnection, roleDn.toString(), this.returnAttributes, this.shouldFollowReferrals); final boolean isDebugEnabled = log.isDebugEnabled(); if (e0.getAttribute(userRoleName) != null) { @@ -472,7 +474,7 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), f, SearchScope.SUBTREE, - this.returnAttributes); + this.returnAttributes, this.shouldFollowReferrals); if (isTraceEnabled) { log.trace("Results for LDAP group search for {} in base {}:\n{}", escapedDn, roleSearchBaseSettingsEntry.getKey(), foundEntries); @@ -550,7 +552,7 @@ private String getRoleFromEntry(final Connection ldapConnection, final LdapName } try { - final LdapEntry roleEntry = LdapHelper.lookup(ldapConnection, ldapName.toString(), this.returnAttributes); + final LdapEntry roleEntry = LdapHelper.lookup(ldapConnection, ldapName.toString(), this.returnAttributes, this.shouldFollowReferrals); if(roleEntry != null) { final LdapAttribute roleAttribute = roleEntry.getAttribute(role); diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPUserSearcher.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPUserSearcher.java index ccd60f450d..81da81647f 100644 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPUserSearcher.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPUserSearcher.java @@ -70,19 +70,19 @@ static List> getUserBaseSettings(Settings settings) } } - LdapEntry exists(Connection ldapConnection, String user, final String[] returnAttributes) throws Exception { + LdapEntry exists(Connection ldapConnection, String user, final String[] returnAttributes, final boolean shouldFollowReferrals) throws Exception { if (settings.getAsBoolean(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, false) || settings.getAsBoolean(ConfigConstants.LDAP_SEARCH_ALL_BASES, false) || settings.hasValue(ConfigConstants.LDAP_AUTHC_USERBASE)) { - return existsSearchingAllBases(ldapConnection, user, returnAttributes); + return existsSearchingAllBases(ldapConnection, user, returnAttributes, shouldFollowReferrals); } else { - return existsSearchingUntilFirstHit(ldapConnection, user, returnAttributes); + return existsSearchingUntilFirstHit(ldapConnection, user, returnAttributes, shouldFollowReferrals); } } - private LdapEntry existsSearchingUntilFirstHit(Connection ldapConnection, String user, final String[] returnAttributes) throws Exception { + private LdapEntry existsSearchingUntilFirstHit(Connection ldapConnection, String user, final String[] returnAttributes, final boolean shouldFollowReferrals) throws Exception { final String username = user; final boolean isDebugEnabled = log.isDebugEnabled(); for (Map.Entry entry : userBaseSettings) { @@ -96,7 +96,7 @@ private LdapEntry existsSearchingUntilFirstHit(Connection ldapConnection, String baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), f, SearchScope.SUBTREE, - returnAttributes); + returnAttributes, shouldFollowReferrals); if (isDebugEnabled) { log.debug("Results for LDAP search for {} in base {}:\n{}", user, entry.getKey(), result); @@ -110,7 +110,7 @@ private LdapEntry existsSearchingUntilFirstHit(Connection ldapConnection, String return null; } - private LdapEntry existsSearchingAllBases(Connection ldapConnection, String user, final String[] returnAttributes) throws Exception { + private LdapEntry existsSearchingAllBases(Connection ldapConnection, String user, final String[] returnAttributes, final boolean shouldFollowReferrals) throws Exception { final String username = user; Set result = new HashSet<>(); final boolean isDebugEnabled = log.isDebugEnabled(); @@ -125,7 +125,7 @@ private LdapEntry existsSearchingAllBases(Connection ldapConnection, String user baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), f, SearchScope.SUBTREE, - returnAttributes); + returnAttributes, shouldFollowReferrals); if (isDebugEnabled) { log.debug("Results for LDAP search for {} in base {}:\n{}", user, entry.getKey(), result); diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index 386a85d684..ee83284ca4 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -162,6 +162,7 @@ public class ConfigConstants { public static final String SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX = "plugins.security.audit.config."; + // Internal / External OpenSearch public static final String SECURITY_AUDIT_OPENSEARCH_INDEX = "index"; public static final String SECURITY_AUDIT_OPENSEARCH_TYPE = "type"; diff --git a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTest.java b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTest.java index 080dee1b1f..db961eb9a4 100755 --- a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTest.java +++ b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTest.java @@ -408,14 +408,32 @@ public void testLdapAuthenticationReferral() throws Exception { final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); try { - final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value()); + final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), true); Assert.assertEquals("cn=refsolved,ou=people,o=TEST", ref1.getDn()); } finally { con.close(); } - } + @Test + public void testLdapDontFollowReferrals() throws Exception { + + + final Settings settings = Settings.builder() + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.FOLLOW_REFERRALS, false).build(); + + + final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); + try { + //If following is off then should fail to return the result provided by following + final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT)); + Assert.assertNull(ref1); + } finally { + con.close(); + } + } @Test public void testLdapEscape() throws Exception { diff --git a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestNewStyleConfig.java b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestNewStyleConfig.java index 4e40e2d4d6..3e21a223e2 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestNewStyleConfig.java +++ b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestNewStyleConfig.java @@ -341,7 +341,7 @@ public void testLdapAuthenticationReferral() throws Exception { final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); try { - final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value()); + final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), true); Assert.assertEquals("cn=refsolved,ou=people,o=TEST", ref1.getDn()); } finally { con.close(); @@ -349,6 +349,26 @@ public void testLdapAuthenticationReferral() throws Exception { } + @Test + public void testLdapDontFollowReferrals() throws Exception { + + + final Settings settings = Settings.builder() + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.FOLLOW_REFERRALS, false).build(); + + + final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); + try { + //If following is off then should fail to return the result provided by following + final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT)); + Assert.assertNull(ref1); + } finally { + con.close(); + } + } + @Test public void testLdapEscape() throws Exception { diff --git a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestNewStyleConfig2.java b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestNewStyleConfig2.java index 7389f46d06..dfa3296a25 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestNewStyleConfig2.java +++ b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestNewStyleConfig2.java @@ -402,7 +402,7 @@ public void testLdapAuthenticationReferral() throws Exception { .getConnection(); try { con.open(); - final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value()); + final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), true); Assert.assertEquals("cn=refsolved,ou=people,o=TEST", ref1.getDn()); } finally { con.close(); @@ -410,6 +410,26 @@ public void testLdapAuthenticationReferral() throws Exception { } + @Test + public void testLdapDontFollowReferrals() throws Exception { + + + final Settings settings = Settings.builder() + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.FOLLOW_REFERRALS, false).build(); + + + final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); + try { + //If following is off then should fail to return the result provided by following + final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT)); + Assert.assertNull(ref1); + } finally { + con.close(); + } + } + @Test public void testLdapEscape() throws Exception { diff --git a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestOldStyleConfig2.java b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestOldStyleConfig2.java index 5c40dd6b7d..8a4afee5da 100755 --- a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestOldStyleConfig2.java +++ b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestOldStyleConfig2.java @@ -454,7 +454,7 @@ public void testLdapAuthenticationReferral() throws Exception { .getConnection(); try { con.open(); - final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value()); + final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), true); Assert.assertEquals("cn=refsolved,ou=people,o=TEST", ref1.getDn()); } finally { con.close(); @@ -462,6 +462,26 @@ public void testLdapAuthenticationReferral() throws Exception { } + @Test + public void testLdapDontFollowReferrals() throws Exception { + + + final Settings settings = Settings.builder() + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.FOLLOW_REFERRALS, false).build(); + + + final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); + try { + //If following is off then should fail to return the result provided by following + final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT)); + Assert.assertNull(ref1); + } finally { + con.close(); + } + } + @Test public void testLdapEscape() throws Exception { From 7b81e55daf99a53a3c709c825df11582d1c88967 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Thu, 20 Oct 2022 17:45:05 -0400 Subject: [PATCH 058/369] Patch bump for scala dependency (#2163) * Patch bump for scala dependency Signed-off-by: Stephen Crawford --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index a7845c439d..e3e6ffc3aa 100644 --- a/build.gradle +++ b/build.gradle @@ -241,7 +241,7 @@ configurations { resolutionStrategy { force 'commons-codec:commons-codec:1.14' force 'org.slf4j:slf4j-api:1.7.30' - force 'org.scala-lang:scala-library:2.13.8' + force 'org.scala-lang:scala-library:2.13.9' force 'commons-io:commons-io:2.11.0' force "com.fasterxml.jackson:jackson-bom:${versions.jackson}" force "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" @@ -404,7 +404,7 @@ dependencies { testRuntimeOnly ('org.springframework:spring-core:5.3.21') { exclude(group:'org.springframework', module: 'spring-jcl' ) } - testRuntimeOnly 'org.scala-lang:scala-library:2.13.8' + testRuntimeOnly 'org.scala-lang:scala-library:2.13.9' testRuntimeOnly 'com.yammer.metrics:metrics-core:2.2.0' testRuntimeOnly 'com.typesafe.scala-logging:scala-logging_3:3.9.5' testRuntimeOnly 'org.apache.zookeeper:zookeeper:3.7.1' From 49e73003b9809181fb37b860e34750d392cd9f89 Mon Sep 17 00:00:00 2001 From: Prudhvi Godithi Date: Thu, 20 Oct 2022 16:50:43 -0700 Subject: [PATCH 059/369] add group = org.opensearch.plugin (#2158) Signed-off-by: prudhvigodithi --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index e3e6ffc3aa..2bd2d4c0b0 100644 --- a/build.gradle +++ b/build.gradle @@ -179,6 +179,7 @@ publishing { pom { name = "opensearch-security" description = "Provide access control related features for OpenSearch" + groupId = "org.opensearch.plugin" licenses { license { name = "The Apache License, Version 2.0" From 5072d0516f80e55d2000fe6a24f946d603461af3 Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Fri, 21 Oct 2022 19:20:48 +0200 Subject: [PATCH 060/369] TLS related tests. (#2156) This PR introduced negative test cases related to TLS. Almost all integration tests use TLS so this feature is already pretty well tested. Signed-off-by: Lukasz Soszynski Signed-off-by: Lukasz Soszynski <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> --- .../org/opensearch/security/SslOnlyTests.java | 69 ++++++++++++++ .../org/opensearch/security/TlsTests.java | 90 +++++++++++++++++++ .../cluster/CloseableHttpClientFactory.java | 60 +++++++++++++ .../test/framework/cluster/LocalCluster.java | 43 ++++++--- .../cluster/LocalOpenSearchCluster.java | 2 - ...inimumSecuritySettingsSupplierFactory.java | 18 ++-- .../cluster/OpenSearchClientProvider.java | 6 ++ .../cluster/RestClientException.java | 4 +- .../framework/cluster/TestRestClient.java | 31 +------ .../matcher/ExceptionHasCauseMatcher.java | 43 +++++++++ .../matcher/ExceptionMatcherAssert.java | 2 +- .../matcher/OpenSearchExceptionMatchers.java | 4 + 12 files changed, 320 insertions(+), 52 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/SslOnlyTests.java create mode 100644 src/integrationTest/java/org/opensearch/security/TlsTests.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/CloseableHttpClientFactory.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionHasCauseMatcher.java diff --git a/src/integrationTest/java/org/opensearch/security/SslOnlyTests.java b/src/integrationTest/java/org/opensearch/security/SslOnlyTests.java new file mode 100644 index 0000000000..b258f0fed2 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/SslOnlyTests.java @@ -0,0 +1,69 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.util.Map; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; + +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; + +/** +* Test related to SSL-only mode of security plugin. In this mode, the security plugin is responsible only for TLS/SSL encryption. +* Therefore, the plugin does not perform authentication and authorization. Moreover, the REST resources (e.g. /_plugins/_security/whoami, +* /_plugins/_security/authinfo, etc.) provided by the plugin are not available. +*/ +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class SslOnlyTests { + + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) + .loadConfigurationIntoIndex(false) + .nodeSettings(Map.of(ConfigConstants.SECURITY_SSL_ONLY, true)) + .sslOnly(true) + .authc(AUTHC_HTTPBASIC_INTERNAL).build(); + + @Test + public void shouldNotLoadSecurityPluginResources() { + try(TestRestClient client = cluster.getRestClient()) { + + HttpResponse response = client.getAuthInfo(); + + // in SSL only mode the security plugin does not register a handler for resource /_plugins/_security/whoami. Therefore error + // response is returned. + response.assertStatusCode(400); + } + } + + @Test + public void shouldGetIndicesWithoutAuthentication() { + try(TestRestClient client = cluster.getRestClient()) { + + // request does not contains credential + HttpResponse response = client.get("/_cat/indices"); + + // successful response is returned because the security plugin in SSL only mode + // does not perform authentication and authorization + response.assertStatusCode(200); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/TlsTests.java b/src/integrationTest/java/org/opensearch/security/TlsTests.java new file mode 100644 index 0000000000..1bc0e47252 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/TlsTests.java @@ -0,0 +1,90 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.SSLHandshakeException; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.NoHttpResponseException; +import org.apache.hc.core5.http.message.BasicHeader; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; +import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class TlsTests { + + private static final User USER_ADMIN = new User("admin").roles(ALL_ACCESS); + + public static final String SUPPORTED_CIPHER_SUIT = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; + public static final String NOT_SUPPORTED_CIPHER_SUITE = "TLS_RSA_WITH_AES_128_CBC_SHA"; + public static final String AUTH_INFO_ENDPOINT = "/_opendistro/_security/authinfo?pretty"; + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) + .nodeSettings(Map.of(SECURITY_SSL_HTTP_ENABLED_CIPHERS, List.of(SUPPORTED_CIPHER_SUIT))) + .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_ADMIN).build(); + + @Test + public void shouldCreateAuditOnIncomingNonTlsConnection() throws IOException { + try(CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpGet request = new HttpGet("http://localhost:" + cluster.getHttpPort()); + + assertThatThrownBy(() -> httpClient.execute(request), instanceOf(NoHttpResponseException.class)); + } + //TODO check if audit is created, audit_category = SSL_EXCEPTION + } + + @Test + public void shouldSupportClientCipherSuite_positive() throws IOException { + try(CloseableHttpClient client = cluster.getClosableHttpClient(new String[] { SUPPORTED_CIPHER_SUIT })) { + HttpGet httpGet = new HttpGet("https://localhost:" + cluster.getHttpPort() + AUTH_INFO_ENDPOINT); + BasicHeader header = cluster.getBasicAuthHeader(USER_ADMIN.getName(), USER_ADMIN.getPassword()); + httpGet.addHeader(header); + + try(CloseableHttpResponse response = client.execute(httpGet)) { + + int responseStatusCode = response.getCode(); + assertThat(responseStatusCode, equalTo(200)); + } + } + } + + @Test + public void shouldSupportClientCipherSuite_negative() throws IOException { + try(CloseableHttpClient client = cluster.getClosableHttpClient(new String[]{ NOT_SUPPORTED_CIPHER_SUITE })) { + HttpGet httpGet = new HttpGet("https://localhost:" + cluster.getHttpPort() + AUTH_INFO_ENDPOINT); + + assertThatThrownBy(() -> client.execute(httpGet), instanceOf(SSLHandshakeException.class)); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/CloseableHttpClientFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/CloseableHttpClientFactory.java new file mode 100644 index 0000000000..b6980ededf --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/CloseableHttpClientFactory.java @@ -0,0 +1,60 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.cluster; + +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLContext; + +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.io.SocketConfig; + +class CloseableHttpClientFactory { + + private final SSLContext sslContext; + + private final RequestConfig requestConfig; + + private final String[] supportedCipherSuit; + + public CloseableHttpClientFactory(SSLContext sslContext, RequestConfig requestConfig, String[] supportedCipherSuit) { + this.sslContext = Objects.requireNonNull(sslContext, "SSL context is required."); + this.requestConfig = requestConfig; + this.supportedCipherSuit = supportedCipherSuit; + } + + public CloseableHttpClient getHTTPClient() { + + final HttpClientBuilder hcb = HttpClients.custom(); + + final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(this.sslContext, null, supportedCipherSuit, + NoopHostnameVerifier.INSTANCE); + + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslsf) + .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60, TimeUnit.SECONDS).build()) + .build(); + hcb.setConnectionManager(cm); + + if (requestConfig != null) { + hcb.setDefaultRequestConfig(requestConfig); + } + + return hcb.build(); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index dbdb6a0cee..f6f4610121 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -71,6 +71,8 @@ public class LocalCluster extends ExternalResource implements AutoCloseable, Ope protected static final AtomicLong num = new AtomicLong(); + private boolean sslOnly = false; + private final List> plugins; private final ClusterManager clusterManager; private final TestSecurityConfig testSecurityConfig; @@ -83,19 +85,24 @@ public class LocalCluster extends ExternalResource implements AutoCloseable, Ope private volatile LocalOpenSearchCluster localOpenSearchCluster; private final List testIndices; - private LocalCluster(String clusterName, TestSecurityConfig testSgConfig, Settings nodeOverride, + private boolean loadConfigurationIntoIndex; + + private LocalCluster(String clusterName, TestSecurityConfig testSgConfig, boolean sslOnly, Settings nodeOverride, ClusterManager clusterManager, List> plugins, TestCertificates testCertificates, - List clusterDependencies, Map remotes, List testIndices) { + List clusterDependencies, Map remotes, List testIndices, + boolean loadConfigurationIntoIndex) { this.plugins = plugins; this.testCertificates = testCertificates; this.clusterManager = clusterManager; this.testSecurityConfig = testSgConfig; + this.sslOnly = sslOnly; this.nodeOverride = nodeOverride; this.clusterName = clusterName; this.minimumOpenSearchSettingsSupplierFactory = new MinimumSecuritySettingsSupplierFactory(testCertificates); this.remotes = remotes; this.clusterDependencies = clusterDependencies; this.testIndices = testIndices; + this.loadConfigurationIntoIndex = loadConfigurationIntoIndex; } public String getSnapshotDirPath() { @@ -151,6 +158,10 @@ public InetSocketAddress getHttpAddress() { return localOpenSearchCluster.clientNode().getHttpAddress(); } + public int getHttpPort() { + return getHttpAddress().getPort(); + } + @Override public InetSocketAddress getTransportAddress() { return localOpenSearchCluster.clientNode().getTransportAddress(); @@ -193,13 +204,13 @@ public Random getRandom() { private void start() { try { - localOpenSearchCluster = new LocalOpenSearchCluster(clusterName, clusterManager, - minimumOpenSearchSettingsSupplierFactory.minimumOpenSearchSettings(nodeOverride), plugins, testCertificates); + NodeSettingsSupplier nodeSettingsSupplier = minimumOpenSearchSettingsSupplierFactory.minimumOpenSearchSettings(sslOnly, nodeOverride); + localOpenSearchCluster = new LocalOpenSearchCluster(clusterName, clusterManager, nodeSettingsSupplier, plugins, testCertificates); localOpenSearchCluster.start(); - if (testSecurityConfig != null) { + if (loadConfigurationIntoIndex) { initSecurityIndex(testSecurityConfig); } @@ -225,6 +236,8 @@ private void initSecurityIndex(TestSecurityConfig testSecurityConfig) { public static class Builder { private final Settings.Builder nodeOverrideSettingsBuilder = Settings.builder(); + + private boolean sslOnly = false; private final List> plugins = new ArrayList<>(); private Map remoteClusters = new HashMap<>(); private List clusterDependencies = new ArrayList<>(); @@ -234,6 +247,8 @@ public static class Builder { private String clusterName = "local_cluster"; private TestCertificates testCertificates; + private boolean loadConfigurationIntoIndex = true; + public Builder() { this.testCertificates = new TestCertificates(); } @@ -269,6 +284,11 @@ public Builder config(TestSecurityConfig testSecurityConfig) { return this; } + public Builder sslOnly(boolean sslOnly) { + this.sslOnly = sslOnly; + return this; + } + public Builder nodeSettings(Map settings) { settings.forEach((key, value) -> { if (value instanceof List) { @@ -343,15 +363,18 @@ public Builder anonymousAuth(boolean anonAuthEnabled) { return this; } + public Builder loadConfigurationIntoIndex(boolean loadConfigurationIntoIndex) { + this.loadConfigurationIntoIndex = loadConfigurationIntoIndex; + return this; + } + public LocalCluster build() { try { clusterName += "_" + num.incrementAndGet(); - Settings settings = nodeOverrideSettingsBuilder - .put(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, false) - .build(); - return new LocalCluster(clusterName, testSecurityConfig, settings, clusterManager, plugins, - testCertificates, clusterDependencies, remoteClusters, testIndices); + Settings settings = nodeOverrideSettingsBuilder.build(); + return new LocalCluster(clusterName, testSecurityConfig, sslOnly, settings, clusterManager, plugins, + testCertificates, clusterDependencies, remoteClusters, testIndices, loadConfigurationIntoIndex); } catch (Exception e) { log.error("Failed to build LocalCluster", e); throw new RuntimeException(e); diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java index e591d9f03c..66f4f095cb 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java @@ -489,8 +489,6 @@ private Settings getMinimalOpenSearchSettings() { .put("discovery.initial_state_timeout", "8s").putList("discovery.seed_hosts", seedHosts).put("transport.tcp.port", transportPort) .put("http.port", httpPort).put("cluster.routing.allocation.disk.threshold_enabled", false) .put("discovery.probe.connect_timeout", "10s").put("discovery.probe.handshake_timeout", "10s").put("http.cors.enabled", true) - .put("plugins.security.compliance.salt", "1234567890123456") - .put("plugins.security.audit.type", "noop") .put("gateway.auto_import_dangling_indices", "true") .build(); } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java index b2660c87e9..cf35962565 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java @@ -29,6 +29,7 @@ package org.opensearch.test.framework.cluster; import org.opensearch.common.settings.Settings; +import org.opensearch.security.support.ConfigConstants; import org.opensearch.test.framework.certificate.TestCertificates; public class MinimumSecuritySettingsSupplierFactory { @@ -46,12 +47,8 @@ public MinimumSecuritySettingsSupplierFactory(TestCertificates testCertificates) } - public NodeSettingsSupplier minimumOpenSearchSettings(Settings other) { - return i -> minimumOpenSearchSettingsBuilder(i, false).put(other).build(); - } - - public NodeSettingsSupplier minimumOpenSearchSettingsSslOnly(Settings other) { - return i -> minimumOpenSearchSettingsBuilder(i, true).put(other).build(); + public NodeSettingsSupplier minimumOpenSearchSettings(boolean sslOnly, Settings other) { + return i -> minimumOpenSearchSettingsBuilder(i, sslOnly).put(other).build(); } private Settings.Builder minimumOpenSearchSettingsBuilder(int node, boolean sslOnly) { @@ -68,9 +65,12 @@ private Settings.Builder minimumOpenSearchSettingsBuilder(int node, boolean sslO builder.put("plugins.security.ssl.http.pemcert_filepath", testCertificates.getNodeCertificate(node).getAbsolutePath()); builder.put("plugins.security.ssl.http.pemkey_filepath", testCertificates.getNodeKey(node, PRIVATE_KEY_HTTP_PASSWORD).getAbsolutePath()); builder.put("plugins.security.ssl.http.pemkey_password", PRIVATE_KEY_HTTP_PASSWORD); - - builder.putList("plugins.security.authcz.admin_dn", testCertificates.getAdminDNs()); - + if(sslOnly == false) { + builder.put(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, false); + builder.putList("plugins.security.authcz.admin_dn", testCertificates.getAdminDNs()); + builder.put("plugins.security.compliance.salt", "1234567890123456"); + builder.put("plugins.security.audit.type", "noop"); + } return builder; } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java index cfb3efab7c..3c6c375c38 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -47,6 +47,7 @@ import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; @@ -131,6 +132,11 @@ public TlsDetails create(final SSLEngine sslEngine) { return new RestHighLevelClient(builder); } + default CloseableHttpClient getClosableHttpClient(String[] supportedCipherSuit) { + CloseableHttpClientFactory factory = new CloseableHttpClientFactory(getSSLContext(), null, supportedCipherSuit); + return factory.getHTTPClient(); + } + /** * Returns a REST client that sends requests with basic authentication for the specified user name and password. Optionally, * additional HTTP headers can be specified which will be sent with each request. diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/RestClientException.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/RestClientException.java index 527fe1cb2f..d53258f020 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/RestClientException.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/RestClientException.java @@ -9,8 +9,8 @@ */ package org.opensearch.test.framework.cluster; -class RestClientException extends RuntimeException { - public RestClientException(String message, Throwable cause) { +public class RestClientException extends RuntimeException { + RestClientException(String message, Throwable cause) { super(message, cause); } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java index c578ca2e77..b4b2ca0583 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -38,7 +38,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; @@ -56,16 +55,9 @@ import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; -import org.apache.hc.client5.http.io.HttpClientConnectionManager; -import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.NameValuePair; -import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.net.URIBuilder; @@ -199,30 +191,13 @@ public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... requestS } } - protected final String getHttpServerUri() { + public final String getHttpServerUri() { return "http" + (enableHTTPClientSSL ? "s" : "") + "://" + nodeHttpAddress.getHostString() + ":" + nodeHttpAddress.getPort(); } protected final CloseableHttpClient getHTTPClient() { - - final HttpClientBuilder hcb = HttpClients.custom(); - - String[] protocols = null; - - final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(this.sslContext, protocols, null, - NoopHostnameVerifier.INSTANCE); - - final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslsf) - .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60, TimeUnit.SECONDS).build()) - .build(); - hcb.setConnectionManager(cm); - - if (requestConfig != null) { - hcb.setDefaultRequestConfig(requestConfig); - } - - return hcb.build(); + var factory = new CloseableHttpClientFactory(sslContext, requestConfig, null); + return factory.getHTTPClient(); } private Header[] mergeHeaders(Header header, Header... headers) { diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionHasCauseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionHasCauseMatcher.java new file mode 100644 index 0000000000..3a200d8849 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionHasCauseMatcher.java @@ -0,0 +1,43 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Objects; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +class ExceptionHasCauseMatcher extends TypeSafeDiagnosingMatcher { + + private final Class expectedCauseType; + + public ExceptionHasCauseMatcher(Class expectedCauseType) { + this.expectedCauseType = Objects.requireNonNull(expectedCauseType, "Exception cause type is required"); + } + + @Override + protected boolean matchesSafely(Throwable throwable, Description mismatchDescription) { + Throwable cause = throwable.getCause(); + if(cause == null) { + mismatchDescription.appendText("exception cause is null"); + return false; + } + if(expectedCauseType.isInstance(cause) == false) { + mismatchDescription.appendText(" cause is instance of ").appendValue(cause.getClass()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Exception cause is instance of ").appendValue(expectedCauseType); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionMatcherAssert.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionMatcherAssert.java index 35f9a32b62..d46107b0fb 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionMatcherAssert.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionMatcherAssert.java @@ -22,7 +22,7 @@ public interface ThrowingCallable { void call() throws Exception; } - public static void assertThatThrownBy(ThrowingCallable throwingCallable, Matcher matcher) { + public static void assertThatThrownBy(ThrowingCallable throwingCallable, Matcher matcher) { Throwable expectedException = catchThrowable(throwingCallable); assertThat("Expected exception was not thrown", expectedException, notNullValue()); assertThat(expectedException, matcher); diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchExceptionMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchExceptionMatchers.java index 4c2df2c3b5..008922b4e8 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchExceptionMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchExceptionMatchers.java @@ -30,4 +30,8 @@ public static Matcher errorMessage(Matcher errorMessageMatche public static Matcher errorMessageContain(String errorMessage) { return errorMessage(containsString(errorMessage)); } + + public static Matcher hasCause(Class clazz) { + return new ExceptionHasCauseMatcher(clazz); + } } From 1d7ab5fa2d66da703cfec4a0a12807c54816e3bc Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Fri, 21 Oct 2022 19:55:51 +0200 Subject: [PATCH 061/369] Add index, update, delete operation tests (#2152) The PR adds tests related to the following operations: index, update and delete. Ignored test cases are related to bugs which have been already reported Signed-off-by: Kacper Trochimiak Signed-off-by: Lukasz Soszynski <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> --- .../security/IndexOperationsHelper.java | 66 ++ .../security/SearchOperationTest.java | 663 +++++++++++++++++- .../framework/matcher/ClusterMatchers.java | 21 + .../matcher/DeleteResponseMatchers.java | 23 + ...etIndexResponseContainsIndicesMatcher.java | 51 ++ ...appingsResponseContainsIndicesMatcher.java | 51 ++ ...ettingsResponseContainsIndicesMatcher.java | 50 ++ .../framework/matcher/IndexExistsMatcher.java | 46 ++ .../matcher/IndexMappingIsEqualToMatcher.java | 66 ++ .../matcher/IndexResponseMatchers.java | 57 ++ .../IndexSettingsContainValuesMatcher.java | 68 ++ .../matcher/IndexStateIsEqualToMatcher.java | 58 ++ ...ssfulClearIndicesCacheResponseMatcher.java | 37 + .../SuccessfulCloseIndexResponseMatcher.java | 36 + .../SuccessfulCreateIndexResponseMatcher.java | 49 ++ .../SuccessfulDeleteResponseMatcher.java | 37 + .../SuccessfulOpenIndexResponseMatcher.java | 36 + .../SuccessfulResizeResponseMatcher.java | 49 ++ .../SuccessfulUpdateResponseMatcher.java | 37 + .../matcher/UpdateResponseMatchers.java | 23 + 20 files changed, 1520 insertions(+), 4 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/IndexOperationsHelper.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/DeleteResponseMatchers.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/GetIndexResponseContainsIndicesMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/GetMappingsResponseContainsIndicesMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/IndexExistsMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/IndexMappingIsEqualToMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/IndexResponseMatchers.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/IndexSettingsContainValuesMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/IndexStateIsEqualToMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulClearIndicesCacheResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCloseIndexResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreateIndexResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeleteResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulOpenIndexResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulResizeResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulUpdateResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/UpdateResponseMatchers.java diff --git a/src/integrationTest/java/org/opensearch/security/IndexOperationsHelper.java b/src/integrationTest/java/org/opensearch/security/IndexOperationsHelper.java new file mode 100644 index 0000000000..c1001e5fc5 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/IndexOperationsHelper.java @@ -0,0 +1,66 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.util.Map; + +import org.opensearch.action.admin.indices.close.CloseIndexRequest; +import org.opensearch.action.admin.indices.close.CloseIndexResponse; +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.admin.indices.create.CreateIndexResponse; +import org.opensearch.action.admin.indices.mapping.put.PutMappingRequest; +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.common.settings.Settings; +import org.opensearch.test.framework.cluster.LocalCluster; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.opensearch.test.framework.matcher.ClusterMatchers.indexExists; +import static org.opensearch.test.framework.matcher.ClusterMatchers.indexStateIsEqualTo; + +public class IndexOperationsHelper { + + public static void createIndex(LocalCluster cluster, String indexName) { + createIndex(cluster, indexName, Settings.EMPTY); + } + + public static void createIndex(LocalCluster cluster, String indexName, Settings settings) { + try(Client client = cluster.getInternalNodeClient()) { + CreateIndexResponse createIndexResponse = client.admin().indices() + .create(new CreateIndexRequest(indexName).settings(settings)) + .actionGet(); + + assertThat(createIndexResponse.isAcknowledged(), is(true)); + assertThat(createIndexResponse.isShardsAcknowledged(), is(true)); + assertThat(cluster, indexExists(indexName)); + } + } + + public static void closeIndex(LocalCluster cluster, String indexName) { + try(Client client = cluster.getInternalNodeClient()) { + CloseIndexRequest closeIndexRequest = new CloseIndexRequest(indexName); + CloseIndexResponse response = client.admin().indices().close(closeIndexRequest).actionGet(); + + assertThat(response.isAcknowledged(), is(true)); + assertThat(response.isShardsAcknowledged(), is(true)); + assertThat(cluster, indexStateIsEqualTo(indexName, IndexMetadata.State.CLOSE)); + } + } + + public static void createMapping(LocalCluster cluster, String indexName, Map indexMapping) { + try(Client client = cluster.getInternalNodeClient()) { + var response = client.admin().indices() + .putMapping(new PutMappingRequest(indexName).source(indexMapping)).actionGet(); + + assertThat(response.isAcknowledged(), is(true)); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java index 63dc01738b..b51280efed 100644 --- a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java +++ b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java @@ -25,6 +25,7 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,14 +35,22 @@ import org.opensearch.action.admin.indices.alias.Alias; import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest; import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions; +import org.opensearch.action.admin.indices.cache.clear.ClearIndicesCacheRequest; +import org.opensearch.action.admin.indices.cache.clear.ClearIndicesCacheResponse; import org.opensearch.action.admin.indices.delete.DeleteIndexRequest; import org.opensearch.action.admin.indices.exists.indices.IndicesExistsRequest; +import org.opensearch.action.admin.indices.open.OpenIndexRequest; +import org.opensearch.action.admin.indices.open.OpenIndexResponse; +import org.opensearch.action.admin.indices.settings.get.GetSettingsRequest; +import org.opensearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.opensearch.action.admin.indices.settings.put.UpdateSettingsRequest; import org.opensearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest; import org.opensearch.action.admin.indices.template.get.GetIndexTemplatesRequest; import org.opensearch.action.admin.indices.template.get.GetIndexTemplatesResponse; import org.opensearch.action.bulk.BulkRequest; import org.opensearch.action.bulk.BulkResponse; import org.opensearch.action.delete.DeleteRequest; +import org.opensearch.action.delete.DeleteResponse; import org.opensearch.action.fieldcaps.FieldCapabilitiesRequest; import org.opensearch.action.fieldcaps.FieldCapabilitiesResponse; import org.opensearch.action.get.GetRequest; @@ -57,13 +66,27 @@ import org.opensearch.action.search.SearchResponse; import org.opensearch.action.search.SearchScrollRequest; import org.opensearch.action.update.UpdateRequest; +import org.opensearch.action.update.UpdateResponse; import org.opensearch.client.Client; import org.opensearch.client.ClusterAdminClient; import org.opensearch.client.IndicesAdminClient; import org.opensearch.client.RestHighLevelClient; import org.opensearch.client.core.CountRequest; +import org.opensearch.client.indices.CloseIndexRequest; +import org.opensearch.client.indices.CloseIndexResponse; +import org.opensearch.client.indices.CreateIndexRequest; +import org.opensearch.client.indices.CreateIndexResponse; +import org.opensearch.client.indices.GetIndexRequest; +import org.opensearch.client.indices.GetIndexResponse; +import org.opensearch.client.indices.GetMappingsRequest; +import org.opensearch.client.indices.GetMappingsResponse; import org.opensearch.client.indices.PutIndexTemplateRequest; +import org.opensearch.client.indices.PutMappingRequest; +import org.opensearch.client.indices.ResizeRequest; +import org.opensearch.client.indices.ResizeResponse; +import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexTemplateMetadata; +import org.opensearch.common.settings.Settings; import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.MatchQueryBuilder; import org.opensearch.index.query.QueryBuilders; @@ -125,10 +148,23 @@ import static org.opensearch.test.framework.matcher.ClusterMatchers.clusterContainsDocument; import static org.opensearch.test.framework.matcher.ClusterMatchers.clusterContainsDocumentWithFieldValue; import static org.opensearch.test.framework.matcher.ClusterMatchers.clusterContainsSnapshotRepository; +import static org.opensearch.test.framework.matcher.ClusterMatchers.indexExists; +import static org.opensearch.test.framework.matcher.ClusterMatchers.indexMappingIsEqualTo; +import static org.opensearch.test.framework.matcher.ClusterMatchers.indexSettingsContainValues; +import static org.opensearch.test.framework.matcher.ClusterMatchers.indexStateIsEqualTo; import static org.opensearch.test.framework.matcher.ClusterMatchers.snapshotInClusterDoesNotExists; +import static org.opensearch.test.framework.matcher.DeleteResponseMatchers.isSuccessfulDeleteResponse; import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy; import static org.opensearch.test.framework.matcher.GetResponseMatchers.containDocument; import static org.opensearch.test.framework.matcher.GetResponseMatchers.documentContainField; +import static org.opensearch.test.framework.matcher.IndexResponseMatchers.getIndexResponseContainsIndices; +import static org.opensearch.test.framework.matcher.IndexResponseMatchers.getMappingsResponseContainsIndices; +import static org.opensearch.test.framework.matcher.IndexResponseMatchers.getSettingsResponseContainsIndices; +import static org.opensearch.test.framework.matcher.IndexResponseMatchers.isSuccessfulClearIndicesCacheResponse; +import static org.opensearch.test.framework.matcher.IndexResponseMatchers.isSuccessfulCloseIndexResponse; +import static org.opensearch.test.framework.matcher.IndexResponseMatchers.isSuccessfulCreateIndexResponse; +import static org.opensearch.test.framework.matcher.IndexResponseMatchers.isSuccessfulOpenIndexResponse; +import static org.opensearch.test.framework.matcher.IndexResponseMatchers.isSuccessfulResizeResponse; import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.errorMessageContain; import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.statusException; import static org.opensearch.test.framework.matcher.SearchResponseMatchers.containAggregationWithNameAndType; @@ -138,6 +174,7 @@ import static org.opensearch.test.framework.matcher.SearchResponseMatchers.numberOfTotalHitsIsEqualTo; import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitContainsFieldWithValue; import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitsContainDocumentWithId; +import static org.opensearch.test.framework.matcher.UpdateResponseMatchers.isSuccessfulUpdateResponse; @RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) @ThreadLeakScope(ThreadLeakScope.Scope.NONE) @@ -171,6 +208,10 @@ public class SearchOperationTest { public static final String RESTORED_SONG_INDEX_NAME = "restored_" + WRITE_SONG_INDEX_NAME; + public static final String UPDATE_DELETE_OPERATION_INDEX_NAME = "update_delete_index"; + + public static final String DOCUMENT_TO_UPDATE_ID = "doc_to_update"; + public static final String ID_P4 = "4"; public static final String ID_S3 = "3"; public static final String ID_S2 = "2"; @@ -219,17 +260,50 @@ public class SearchOperationTest { .on(SONG_INDEX_NAME)); private Client internalClient; + /** + * User who is allowed to update and delete documents on index {@link #UPDATE_DELETE_OPERATION_INDEX_NAME} + */ + static final User UPDATE_DELETE_USER = new User("update_delete_user") + .roles(new Role("document-updater") + .clusterPermissions("indices:data/write/bulk") + .indexPermissions("indices:data/write/update","indices:data/write/index", "indices:data/write/bulk[s]", "indices:admin/mapping/put") + .on(UPDATE_DELETE_OPERATION_INDEX_NAME), + new Role("document-remover") + .indexPermissions("indices:data/write/delete") + .on(UPDATE_DELETE_OPERATION_INDEX_NAME)) + ; + + static final String INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX = "index_operations_"; + + /** + * User who is allowed to perform index-related operations on + * indices with names prefixed by the {@link #INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX} + */ + static final User USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES = new User("index-operation-tester") + .roles(new Role("index-manager") + .indexPermissions( + "indices:admin/create", "indices:admin/get", "indices:admin/delete", "indices:admin/close", + "indices:admin/close*", "indices:admin/open", "indices:admin/resize", "indices:monitor/stats", + "indices:monitor/settings/get", "indices:admin/settings/update", "indices:admin/mapping/put", + "indices:admin/mappings/get", "indices:admin/cache/clear" + ) + .on(INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("*"))); @ClassRule public static final LocalCluster cluster = new LocalCluster.Builder() .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) - .authc(AUTHC_HTTPBASIC_INTERNAL).users(ADMIN_USER, LIMITED_READ_USER, LIMITED_WRITE_USER, DOUBLE_READER_USER, REINDEXING_USER) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users( + ADMIN_USER, LIMITED_READ_USER, LIMITED_WRITE_USER, DOUBLE_READER_USER, REINDEXING_USER, UPDATE_DELETE_USER, + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) .build(); @BeforeClass public static void createTestData() { try(Client client = cluster.getInternalNodeClient()){ client.prepareIndex(SONG_INDEX_NAME).setId(ID_S1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0]).get(); + client.prepareIndex(UPDATE_DELETE_OPERATION_INDEX_NAME).setId(DOCUMENT_TO_UPDATE_ID).setRefreshPolicy(IMMEDIATE).setSource("field", "value").get(); client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new AliasActions(ADD).indices(SONG_INDEX_NAME).alias(SONG_LYRICS_ALIAS))).actionGet(); client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SONG_INDEX_NAME).id(ID_S2).source(SONGS[1])).actionGet(); client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SONG_INDEX_NAME).id(ID_S3).source(SONGS[2])).actionGet(); @@ -256,7 +330,11 @@ public void retrieveClusterClient() { public void cleanData() throws ExecutionException, InterruptedException { Stopwatch stopwatch = Stopwatch.createStarted(); IndicesAdminClient indices = internalClient.admin().indices(); - for(String indexToBeDeleted : List.of(WRITE_SONG_INDEX_NAME, INDEX_NAME_SONG_TRANSCRIPTION_JAZZ, RESTORED_SONG_INDEX_NAME)) { + List indicesToBeDeleted = List.of( + WRITE_SONG_INDEX_NAME, INDEX_NAME_SONG_TRANSCRIPTION_JAZZ, RESTORED_SONG_INDEX_NAME, + INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("*") + ); + for(String indexToBeDeleted : indicesToBeDeleted) { IndicesExistsRequest indicesExistsRequest = new IndicesExistsRequest(indexToBeDeleted); var indicesExistsResponse = indices.exists(indicesExistsRequest).get(); if (indicesExistsResponse.isExists()) { @@ -914,6 +992,56 @@ public void shouldReindexDocuments_negativeSourceAndDestination() throws IOExcep } } + @Test + public void shouldUpdateDocument_positive() throws IOException { + String newField = "newField"; + String newValue = "newValue"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(UPDATE_DELETE_USER)) { + UpdateRequest updateRequest = new UpdateRequest(UPDATE_DELETE_OPERATION_INDEX_NAME, DOCUMENT_TO_UPDATE_ID) + .doc(newField, newValue).setRefreshPolicy(IMMEDIATE); + + UpdateResponse response = restHighLevelClient.update(updateRequest, DEFAULT); + + assertThat(response, isSuccessfulUpdateResponse()); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(UPDATE_DELETE_OPERATION_INDEX_NAME, DOCUMENT_TO_UPDATE_ID, newField, newValue)); + } + } + @Test + public void shouldUpdateDocument_negative() throws IOException { + String newField = "newField"; + String newValue = "newValue"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(UPDATE_DELETE_USER)) { + UpdateRequest updateRequest = new UpdateRequest(PROHIBITED_SONG_INDEX_NAME, DOCUMENT_TO_UPDATE_ID).doc(newField, newValue).setRefreshPolicy(IMMEDIATE); + + assertThatThrownBy(() -> restHighLevelClient.update(updateRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldDeleteDocument_positive() throws IOException { + String docId = "shouldDeleteDocument_positive"; + try(Client client = cluster.getInternalNodeClient()){ + client.index(new IndexRequest(UPDATE_DELETE_OPERATION_INDEX_NAME).id(docId).source("field", "value").setRefreshPolicy(IMMEDIATE)).actionGet(); + assertThat(internalClient, clusterContainsDocument(UPDATE_DELETE_OPERATION_INDEX_NAME, docId)); + } + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(UPDATE_DELETE_USER)) { + DeleteRequest deleteRequest = new DeleteRequest(UPDATE_DELETE_OPERATION_INDEX_NAME, docId).setRefreshPolicy(IMMEDIATE); + + DeleteResponse response = restHighLevelClient.delete(deleteRequest, DEFAULT); + + assertThat(response, isSuccessfulDeleteResponse()); + assertThat(internalClient, not(clusterContainsDocument(UPDATE_DELETE_OPERATION_INDEX_NAME, docId))); + } + } + @Test + public void shouldDeleteDocument_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(UPDATE_DELETE_USER)) { + DeleteRequest deleteRequest = new DeleteRequest(PROHIBITED_SONG_INDEX_NAME, ID_S1).setRefreshPolicy(IMMEDIATE); + + assertThatThrownBy(() -> restHighLevelClient.delete(deleteRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + @Test public void shouldCreateAlias_positive() throws IOException { try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { @@ -1082,9 +1210,9 @@ public void shouldGetFieldCapabilitiesForAllIndexes_positive() throws IOExceptio assertThat(response, notNullValue()); assertThat(response.get(), aMapWithSize(1)); - assertThat(response.getIndices(), arrayWithSize(2)); + assertThat(response.getIndices(), arrayWithSize(3)); assertThat(response.getField(FIELD_TITLE), hasKey("text")); - assertThat(response.getIndices(), arrayContainingInAnyOrder(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME)); + assertThat(response.getIndices(), arrayContainingInAnyOrder(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME, UPDATE_DELETE_OPERATION_INDEX_NAME)); } } @@ -1345,4 +1473,531 @@ public void shouldRestoreSnapshot_failureOperationForbidden() throws IOException assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Zwei"))); } } + + @Test + //required permissions: "indices:admin/create" + public void createIndex_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("create_index_positive"); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName); + CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, DEFAULT); + + assertThat(createIndexResponse, isSuccessfulCreateIndexResponse(indexName)); + assertThat(cluster, indexExists(indexName)); + } + } + + @Test + public void createIndex_negative() throws IOException { + String indexName = "create_index_negative"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName); + + assertThatThrownBy(() -> restHighLevelClient.indices().create(createIndexRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(indexName))); + } + } + + @Test + //required permissions: "indices:admin/get" + public void checkIfIndexExists_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("index_exists_positive"); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + boolean exists = restHighLevelClient.indices().exists(new GetIndexRequest(indexName), DEFAULT); + + assertThat(exists, is(false)); + } + } + + @Test + public void checkIfIndexExists_negative() throws IOException { + String indexThatUserHasNoAccessTo = "index_exists_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + assertThatThrownBy(() -> + restHighLevelClient.indices().exists(new GetIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().exists(new GetIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().exists(new GetIndexRequest("*"), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + //required permissions: "indices:admin/delete" + public void deleteIndex_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("delete_index_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName); + var response = restHighLevelClient.indices().delete(deleteIndexRequest, DEFAULT); + + assertThat(response.isAcknowledged(), is(true)); + assertThat(cluster, not(indexExists(indexName))); + } + } + + @Test + public void deleteIndex_negative() throws IOException { + String indexThatUserHasNoAccessTo = "delete_index_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + + assertThatThrownBy(() -> + restHighLevelClient.indices().delete(new DeleteIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().delete(new DeleteIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().delete(new DeleteIndexRequest("*"), DEFAULT), statusException(FORBIDDEN) + ); + } + } + + @Test + //required permissions: "indices:admin/get" + public void getIndex_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("get_index_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + GetIndexRequest getIndexRequest = new GetIndexRequest(indexName); + GetIndexResponse response = restHighLevelClient.indices().get(getIndexRequest, DEFAULT); + + assertThat(response, getIndexResponseContainsIndices(indexName)); + } + } + + @Test + public void getIndex_negative() throws IOException { + String indexThatUserHasNoAccessTo = "get_index_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + + assertThatThrownBy(() -> + restHighLevelClient.indices().get(new GetIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().get(new GetIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().get(new GetIndexRequest("*"), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + //required permissions: "indices:admin/close", "indices:admin/close*" + public void closeIndex_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("close_index_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + CloseIndexRequest closeIndexRequest = new CloseIndexRequest(indexName); + CloseIndexResponse response = restHighLevelClient.indices().close(closeIndexRequest, DEFAULT); + + assertThat(response, isSuccessfulCloseIndexResponse()); + assertThat(cluster, indexStateIsEqualTo(indexName, IndexMetadata.State.CLOSE)); + } + } + + @Test + public void closeIndex_negative() throws IOException { + String indexThatUserHasNoAccessTo = "close_index_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + + assertThatThrownBy(() -> + restHighLevelClient.indices().close(new CloseIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().close(new CloseIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().close(new CloseIndexRequest("*"), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + //required permissions: "indices:admin/open" + public void openIndex_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("open_index_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + IndexOperationsHelper.closeIndex(cluster, indexName); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + OpenIndexRequest closeIndexRequest = new OpenIndexRequest(indexName); + OpenIndexResponse response = restHighLevelClient.indices().open(closeIndexRequest, DEFAULT); + + assertThat(response, isSuccessfulOpenIndexResponse()); + assertThat(cluster, indexStateIsEqualTo(indexName, IndexMetadata.State.OPEN)); + } + } + + @Test + public void openIndex_negative() throws IOException { + String indexThatUserHasNoAccessTo = "open_index_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + + assertThatThrownBy(() -> + restHighLevelClient.indices().open(new OpenIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().open(new OpenIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().open(new OpenIndexRequest("*"), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + @Ignore + //required permissions: "indices:admin/resize", "indices:monitor/stats + // todo even when I assign the `indices:admin/resize` and `indices:monitor/stats` permissions to test user, this test fails. + // Issue: https://github.com/opensearch-project/security/issues/2141 + public void shrinkIndex_positive() throws IOException { + String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("shrink_index_positive_source"); + Settings sourceIndexSettings = Settings.builder() + .put("index.blocks.write", true) + .put("index.number_of_shards", 2) + .build(); + String targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("shrink_index_positive_target"); + IndexOperationsHelper.createIndex(cluster, sourceIndexName, sourceIndexSettings); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + ResizeResponse response = restHighLevelClient.indices().shrink(resizeRequest, DEFAULT); + + assertThat(response, isSuccessfulResizeResponse(targetIndexName)); + assertThat(cluster, indexExists(targetIndexName)); + } + } + + @Test + public void shrinkIndex_negative() throws IOException { + //user cannot access target index + String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("shrink_index_negative_source"); + String targetIndexName = "shrink_index_negative_target"; + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + + assertThatThrownBy(() -> restHighLevelClient.indices().shrink(resizeRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(targetIndexName))); + } + + //user cannot access source index + sourceIndexName = "shrink_index_negative_source"; + targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("shrink_index_negative_target"); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + + assertThatThrownBy(() -> restHighLevelClient.indices().shrink(resizeRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(targetIndexName))); + } + } + + @Test + @Ignore + //required permissions: "indices:admin/resize", "indices:monitor/stats + // todo even when I assign the `indices:admin/resize` and `indices:monitor/stats` permissions to test user, this test fails. + // Issue: https://github.com/opensearch-project/security/issues/2141 + public void cloneIndex_positive() throws IOException { + String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clone_index_positive_source"); + Settings sourceIndexSettings = Settings.builder() + .put("index.blocks.write", true) + .build(); + String targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clone_index_positive_target"); + IndexOperationsHelper.createIndex(cluster, sourceIndexName, sourceIndexSettings); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + ResizeResponse response = restHighLevelClient.indices().clone(resizeRequest, DEFAULT); + + assertThat(response, isSuccessfulResizeResponse(targetIndexName)); + assertThat(cluster, indexExists(targetIndexName)); + } + } + + @Test + public void cloneIndex_negative() throws IOException { + //user cannot access target index + String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clone_index_negative_source"); + String targetIndexName = "clone_index_negative_target"; + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + + assertThatThrownBy(() -> restHighLevelClient.indices().clone(resizeRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(targetIndexName))); + } + + //user cannot access source index + sourceIndexName = "clone_index_negative_source"; + targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clone_index_negative_target"); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + + assertThatThrownBy(() -> restHighLevelClient.indices().clone(resizeRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(targetIndexName))); + } + } + + @Test + @Ignore + //required permissions: "indices:admin/resize", "indices:monitor/stats + // todo even when I assign the `indices:admin/resize` and `indices:monitor/stats` permissions to test user, this test fails. + // Issue: https://github.com/opensearch-project/security/issues/2141 + public void splitIndex_positive() throws IOException { + String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("split_index_positive_source"); + Settings sourceIndexSettings = Settings.builder() + .put("index.blocks.write", true) + .build(); + String targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("split_index_positive_target"); + IndexOperationsHelper.createIndex(cluster, sourceIndexName, sourceIndexSettings); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + resizeRequest.setSettings(Settings.builder().put("index.number_of_shards", 2).build()); + ResizeResponse response = restHighLevelClient.indices().split(resizeRequest, DEFAULT); + + assertThat(response, isSuccessfulResizeResponse(targetIndexName)); + assertThat(cluster, indexExists(targetIndexName)); + } + } + + @Test + public void splitIndex_negative() throws IOException { + //user cannot access target index + String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("split_index_negative_source"); + String targetIndexName = "split_index_negative_target"; + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + resizeRequest.setSettings(Settings.builder().put("index.number_of_shards", 2).build()); + + assertThatThrownBy(() -> restHighLevelClient.indices().split(resizeRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(targetIndexName))); + } + + //user cannot access source index + sourceIndexName = "split_index_negative_source"; + targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("split_index_negative_target"); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + resizeRequest.setSettings(Settings.builder().put("index.number_of_shards", 2).build()); + + assertThatThrownBy(() -> restHighLevelClient.indices().split(resizeRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(targetIndexName))); + } + } + + @Test + //required permissions: "indices:monitor/settings/get" + public void getIndexSettings_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("get_index_settings_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + GetSettingsRequest getSettingsRequest = new GetSettingsRequest().indices(indexName); + GetSettingsResponse response = restHighLevelClient.indices().getSettings(getSettingsRequest, DEFAULT); + + assertThat(response, getSettingsResponseContainsIndices(indexName)); + } + } + + @Test + public void getIndexSettings_negative() throws IOException { + String indexThatUserHasNoAccessTo = "get_index_settings_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + assertThatThrownBy(() -> + restHighLevelClient.indices().getSettings(new GetSettingsRequest().indices(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().getSettings(new GetSettingsRequest().indices(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().getSettings(new GetSettingsRequest().indices("*"), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + //required permissions: "indices:admin/settings/update" + public void updateIndexSettings_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("update_index_settings_positive"); + Settings initialSettings = Settings.builder().put("index.number_of_replicas", "2").build(); + Settings updatedSettings = Settings.builder().put("index.number_of_replicas", "4").build(); + IndexOperationsHelper.createIndex(cluster, indexName, initialSettings); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + UpdateSettingsRequest updateSettingsRequest = new UpdateSettingsRequest(indexName) + .settings(updatedSettings); + var response = restHighLevelClient.indices().putSettings(updateSettingsRequest, DEFAULT); + + assertThat(response.isAcknowledged(), is(true)); + assertThat(cluster, indexSettingsContainValues(indexName, updatedSettings)); + } + } + + @Test + public void updateIndexSettings_negative() throws IOException { + String indexThatUserHasNoAccessTo = "update_index_settings_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + Settings settingsToUpdate = Settings.builder().put("index.number_of_replicas", 2).build(); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + + assertThatThrownBy(() -> + restHighLevelClient.indices().putSettings(new UpdateSettingsRequest(indexThatUserHasNoAccessTo).settings(settingsToUpdate), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().putSettings(new UpdateSettingsRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo).settings(settingsToUpdate), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().putSettings(new UpdateSettingsRequest("*").settings(settingsToUpdate), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + //required permissions: indices:admin/mapping/put + public void createIndexMappings_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("create_index_mappings_positive"); + Map indexMapping = Map.of("properties", Map.of("message", Map.of("type", "text"))); + IndexOperationsHelper.createIndex(cluster, indexName); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + PutMappingRequest putMappingRequest = new PutMappingRequest(indexName).source(indexMapping); + var response = restHighLevelClient.indices().putMapping(putMappingRequest, DEFAULT); + + assertThat(response.isAcknowledged(), is(true)); + assertThat(cluster, indexMappingIsEqualTo(indexName, indexMapping)); + } + } + + @Test + public void createIndexMappings_negative() throws IOException { + String indexThatUserHasNoAccessTo = "create_index_mappings_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + Map indexMapping = Map.of("properties", Map.of("message", Map.of("type", "text"))); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + + assertThatThrownBy(() -> + restHighLevelClient.indices().putMapping(new PutMappingRequest(indexThatUserHasNoAccessTo).source(indexMapping), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().putMapping(new PutMappingRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo).source(indexMapping), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().putMapping(new PutMappingRequest("*").source(indexMapping), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + //required permissions: indices:admin/mappings/get + public void getIndexMappings_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("get_index_mappings_positive"); + Map indexMapping = Map.of("properties", Map.of("message", Map.of("type", "text"))); + IndexOperationsHelper.createIndex(cluster, indexName); + IndexOperationsHelper.createMapping(cluster, indexName, indexMapping); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + GetMappingsRequest getMappingsRequest = new GetMappingsRequest().indices(indexName); + GetMappingsResponse response = restHighLevelClient.indices().getMapping(getMappingsRequest, DEFAULT); + + assertThat(response, getMappingsResponseContainsIndices(indexName)); + } + } + + @Test + public void getIndexMappings_negative() throws IOException { + String indexThatUserHasNoAccessTo = "get_index_mappings_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + + assertThatThrownBy(() -> + restHighLevelClient.indices().getMapping(new GetMappingsRequest().indices(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().getMapping(new GetMappingsRequest().indices(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().getMapping(new GetMappingsRequest().indices("*"), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + //required permissions: "indices:admin/cache/clear" + public void clearIndexCache_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clear_index_cache_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + ClearIndicesCacheRequest clearIndicesCacheRequest = new ClearIndicesCacheRequest(indexName); + ClearIndicesCacheResponse response = restHighLevelClient.indices().clearCache(clearIndicesCacheRequest, DEFAULT); + + assertThat(response, isSuccessfulClearIndicesCacheResponse()); + } + } + + @Test + public void clearIndexCache_negative() throws IOException { + String indexThatUserHasNoAccessTo = "clear_index_cache_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + + assertThatThrownBy(() -> + restHighLevelClient.indices().clearCache(new ClearIndicesCacheRequest(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().clearCache(new ClearIndicesCacheRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> + restHighLevelClient.indices().clearCache(new ClearIndicesCacheRequest("*"), DEFAULT), + statusException(FORBIDDEN) + ); + } + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java index 2281026593..7023301f34 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java @@ -9,9 +9,14 @@ */ package org.opensearch.test.framework.matcher; +import java.util.Map; + import org.hamcrest.Matcher; import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.common.settings.Settings; +import org.opensearch.test.framework.cluster.LocalCluster; public class ClusterMatchers { @@ -46,4 +51,20 @@ public static Matcher clusterContainSuccessSnapshot(String repositoryNam public static Matcher snapshotInClusterDoesNotExists(String repositoryName, String snapshotName) { return new SnapshotInClusterDoesNotExist(repositoryName, snapshotName); } + + public static Matcher indexExists(String expectedIndexName) { + return new IndexExistsMatcher(expectedIndexName); + } + + public static Matcher indexStateIsEqualTo(String expectedIndexName, IndexMetadata.State expectedState) { + return new IndexStateIsEqualToMatcher(expectedIndexName, expectedState); + } + + public static Matcher indexSettingsContainValues(String expectedIndexName, Settings expectedSettings) { + return new IndexSettingsContainValuesMatcher(expectedIndexName, expectedSettings); + } + + public static Matcher indexMappingIsEqualTo(String expectedIndexName, Map expectedMapping) { + return new IndexMappingIsEqualToMatcher(expectedIndexName, expectedMapping); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/DeleteResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/DeleteResponseMatchers.java new file mode 100644 index 0000000000..435c2521c8 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/DeleteResponseMatchers.java @@ -0,0 +1,23 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Matcher; + +import org.opensearch.action.delete.DeleteResponse; + +public class DeleteResponseMatchers { + + private DeleteResponseMatchers() {} + + public static Matcher isSuccessfulDeleteResponse() { + return new SuccessfulDeleteResponseMatcher(); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetIndexResponseContainsIndicesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetIndexResponseContainsIndicesMatcher.java new file mode 100644 index 0000000000..43d1bce761 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetIndexResponseContainsIndicesMatcher.java @@ -0,0 +1,51 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Arrays; +import java.util.List; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.client.indices.GetIndexResponse; + +import static java.util.Objects.isNull; + +class GetIndexResponseContainsIndicesMatcher extends TypeSafeDiagnosingMatcher { + + private final String[] expectedIndices; + + GetIndexResponseContainsIndicesMatcher(String[] expectedIndices) { + if (isNull(expectedIndices) || 0 == expectedIndices.length) { + throw new IllegalArgumentException("expectedIndices cannot be null or empty"); + } + this.expectedIndices = expectedIndices; + } + + @Override + protected boolean matchesSafely(GetIndexResponse response, Description mismatchDescription) { + List actual = Arrays.asList(response.getIndices()); + for (String index : expectedIndices) { + if (!actual.contains(index)) { + mismatchDescription + .appendText("Actual indices: ") + .appendValue(response.getIndices()); + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response should contain indices: ").appendValue(expectedIndices); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetMappingsResponseContainsIndicesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetMappingsResponseContainsIndicesMatcher.java new file mode 100644 index 0000000000..a246ec14b3 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetMappingsResponseContainsIndicesMatcher.java @@ -0,0 +1,51 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Map; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.client.indices.GetMappingsResponse; +import org.opensearch.cluster.metadata.MappingMetadata; + +import static java.util.Objects.isNull; + +class GetMappingsResponseContainsIndicesMatcher extends TypeSafeDiagnosingMatcher { + + private final String[] expectedIndices; + + GetMappingsResponseContainsIndicesMatcher(String[] expectedIndices) { + if (isNull(expectedIndices) || 0 == expectedIndices.length) { + throw new IllegalArgumentException("expectedIndices cannot be null or empty"); + } + this.expectedIndices = expectedIndices; + } + + @Override + protected boolean matchesSafely(GetMappingsResponse response, Description mismatchDescription) { + Map indicesMappings = response.mappings(); + for (String index : expectedIndices) { + if (!indicesMappings.containsKey(index)) { + mismatchDescription + .appendText("Response contains mappings of indices: ") + .appendValue(indicesMappings.keySet()); + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response should contain mappings of indices: ").appendValue(expectedIndices); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java new file mode 100644 index 0000000000..15c558d32f --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java @@ -0,0 +1,50 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.opensearch.common.collect.ImmutableOpenMap; +import org.opensearch.common.settings.Settings; + +import static java.util.Objects.isNull; + +class GetSettingsResponseContainsIndicesMatcher extends TypeSafeDiagnosingMatcher { + + private final String[] expectedIndices; + + GetSettingsResponseContainsIndicesMatcher(String[] expectedIndices) { + if (isNull(expectedIndices) || 0 == expectedIndices.length) { + throw new IllegalArgumentException("expectedIndices cannot be null or empty"); + } + this.expectedIndices = expectedIndices; + } + + @Override + protected boolean matchesSafely(GetSettingsResponse response, Description mismatchDescription) { + ImmutableOpenMap indexToSettings = response.getIndexToSettings(); + for (String index : expectedIndices) { + if (!indexToSettings.containsKey(index)) { + mismatchDescription + .appendText("Response contains settings of indices: ") + .appendValue(indexToSettings.keys()); + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response should contain settings of indices: ").appendValue(expectedIndices); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexExistsMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexExistsMatcher.java new file mode 100644 index 0000000000..09e76c93e0 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexExistsMatcher.java @@ -0,0 +1,46 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.indices.exists.indices.IndicesExistsRequest; +import org.opensearch.action.admin.indices.exists.indices.IndicesExistsResponse; +import org.opensearch.client.Client; +import org.opensearch.test.framework.cluster.LocalCluster; + +import static java.util.Objects.requireNonNull; + +class IndexExistsMatcher extends TypeSafeDiagnosingMatcher { + + private final String expectedIndexName; + + IndexExistsMatcher(String expectedIndexName) { + this.expectedIndexName = requireNonNull(expectedIndexName); + } + + @Override + protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { + try(Client client = cluster.getInternalNodeClient()) { + IndicesExistsResponse indicesExistsResponse = client.admin().indices().exists(new IndicesExistsRequest(expectedIndexName)).actionGet(); + if (!indicesExistsResponse.isExists()) { + mismatchDescription.appendText("Index ").appendValue(expectedIndexName).appendValue(" does not exist"); + return false; + } + return true; + } + } + + @Override + public void describeTo(Description description) { + description.appendText("Index ").appendValue(expectedIndexName).appendText(" exists"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexMappingIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexMappingIsEqualToMatcher.java new file mode 100644 index 0000000000..9621aff449 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexMappingIsEqualToMatcher.java @@ -0,0 +1,66 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Map; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.indices.mapping.get.GetMappingsRequest; +import org.opensearch.action.admin.indices.mapping.get.GetMappingsResponse; +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.MappingMetadata; +import org.opensearch.common.collect.ImmutableOpenMap; +import org.opensearch.index.IndexNotFoundException; +import org.opensearch.test.framework.cluster.LocalCluster; + +import static java.util.Objects.isNull; +import static java.util.Objects.requireNonNull; + +class IndexMappingIsEqualToMatcher extends TypeSafeDiagnosingMatcher { + + private final String expectedIndexName; + private final Map expectedMapping; + + IndexMappingIsEqualToMatcher(String expectedIndexName, Map expectedMapping) { + this.expectedIndexName = requireNonNull(expectedIndexName); + if (isNull(expectedMapping) || expectedMapping.isEmpty()) { + throw new IllegalArgumentException("expectedMapping cannot be null or empty"); + } + this.expectedMapping = expectedMapping; + } + + @Override + protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { + try(Client client = cluster.getInternalNodeClient()) { + GetMappingsResponse response = client.admin().indices() + .getMappings(new GetMappingsRequest().indices(expectedIndexName)).actionGet(); + + ImmutableOpenMap actualMappings = response.mappings(); + Map actualIndexMapping = actualMappings.get(expectedIndexName).getSourceAsMap(); + + if (!expectedMapping.equals(actualIndexMapping)) { + mismatchDescription.appendText("Actual mapping ").appendValue(actualIndexMapping).appendText(" does not match expected"); + return false; + } + return true; + } catch (IndexNotFoundException e) { + mismatchDescription.appendText("Index: ").appendValue(expectedIndexName).appendText(" does not exist"); + return false; + } + } + + @Override + public void describeTo(Description description) { + description.appendText("Index ").appendValue(expectedIndexName) + .appendText(". Mapping should be equal to: ").appendValue(expectedMapping); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexResponseMatchers.java new file mode 100644 index 0000000000..80d90ecc6b --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexResponseMatchers.java @@ -0,0 +1,57 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Matcher; + +import org.opensearch.action.admin.indices.cache.clear.ClearIndicesCacheResponse; +import org.opensearch.action.admin.indices.open.OpenIndexResponse; +import org.opensearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.opensearch.client.indices.CloseIndexResponse; +import org.opensearch.client.indices.CreateIndexResponse; +import org.opensearch.client.indices.GetIndexResponse; +import org.opensearch.client.indices.GetMappingsResponse; +import org.opensearch.client.indices.ResizeResponse; + +public class IndexResponseMatchers { + + public static Matcher isSuccessfulCreateIndexResponse(String expectedIndexName) { + return new SuccessfulCreateIndexResponseMatcher(expectedIndexName); + } + + public static Matcher getIndexResponseContainsIndices(String... expectedIndices) { + return new GetIndexResponseContainsIndicesMatcher(expectedIndices); + } + + public static Matcher isSuccessfulCloseIndexResponse() { + return new SuccessfulCloseIndexResponseMatcher(); + } + + public static Matcher isSuccessfulOpenIndexResponse() { + return new SuccessfulOpenIndexResponseMatcher(); + } + + public static Matcher isSuccessfulResizeResponse(String expectedIndexName) { + return new SuccessfulResizeResponseMatcher(expectedIndexName); + } + + public static Matcher getSettingsResponseContainsIndices(String... expectedIndices) { + return new GetSettingsResponseContainsIndicesMatcher(expectedIndices); + } + + public static Matcher isSuccessfulClearIndicesCacheResponse() { + return new SuccessfulClearIndicesCacheResponseMatcher(); + } + + public static Matcher getMappingsResponseContainsIndices(String... expectedIndices) { + return new GetMappingsResponseContainsIndicesMatcher(expectedIndices); + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexSettingsContainValuesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexSettingsContainValuesMatcher.java new file mode 100644 index 0000000000..9af40fcd48 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexSettingsContainValuesMatcher.java @@ -0,0 +1,68 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.indices.settings.get.GetSettingsRequest; +import org.opensearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.opensearch.client.Client; +import org.opensearch.common.settings.Settings; +import org.opensearch.index.IndexNotFoundException; +import org.opensearch.test.framework.cluster.LocalCluster; + +import static java.util.Objects.isNull; +import static java.util.Objects.requireNonNull; + +class IndexSettingsContainValuesMatcher extends TypeSafeDiagnosingMatcher { + + private final String expectedIndexName; + private final Settings expectedSettings; + + IndexSettingsContainValuesMatcher(String expectedIndexName, Settings expectedSettings) { + this.expectedIndexName = requireNonNull(expectedIndexName); + if (isNull(expectedSettings) || expectedSettings.isEmpty()) { + throw new IllegalArgumentException("expectedSettings cannot be null or empty"); + } + this.expectedSettings = expectedSettings; + } + + @Override + protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { + try(Client client = cluster.getInternalNodeClient()) { + GetSettingsResponse response = client.admin().indices().getSettings(new GetSettingsRequest().indices(expectedIndexName)).actionGet(); + + Settings actualSettings = response.getIndexToSettings().get(expectedIndexName); + + for (String setting : expectedSettings.keySet()) { + if (isNull(actualSettings.get(setting))) { + mismatchDescription.appendValue("Value of ").appendValue(setting).appendText(" property is missing"); + return false; + } + if (!expectedSettings.get(setting).equals(actualSettings.get(setting))) { + mismatchDescription.appendText("Actual value of `").appendValue(setting) + .appendText("` property: ").appendValue(actualSettings.get(setting)); + return false; + } + } + return true; + } catch (IndexNotFoundException e) { + mismatchDescription.appendText("Index: ").appendValue(expectedIndexName).appendText(" does not exist"); + return false; + } + } + + @Override + public void describeTo(Description description) { + description.appendText("Settings of index ").appendValue(expectedIndexName) + .appendText(" should contain values: ").appendValue(expectedSettings); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexStateIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexStateIsEqualToMatcher.java new file mode 100644 index 0000000000..a9538b4b8b --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexStateIsEqualToMatcher.java @@ -0,0 +1,58 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.cluster.state.ClusterStateRequest; +import org.opensearch.action.admin.cluster.state.ClusterStateResponse; +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.common.collect.ImmutableOpenMap; +import org.opensearch.test.framework.cluster.LocalCluster; + +import static java.util.Objects.requireNonNull; + +class IndexStateIsEqualToMatcher extends TypeSafeDiagnosingMatcher { + + private final String expectedIndexName; + private final IndexMetadata.State expectedState; + + IndexStateIsEqualToMatcher(String expectedIndexName, IndexMetadata.State expectedState) { + this.expectedIndexName = requireNonNull(expectedIndexName); + this.expectedState = requireNonNull(expectedState); + } + + @Override + protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { + try(Client client = cluster.getInternalNodeClient()) { + ClusterStateRequest clusterStateRequest = new ClusterStateRequest().indices(expectedIndexName); + ClusterStateResponse clusterStateResponse = client.admin().cluster().state(clusterStateRequest).actionGet(); + + ImmutableOpenMap indicesMetadata = clusterStateResponse.getState().getMetadata().indices(); + if (!indicesMetadata.containsKey(expectedIndexName)) { + mismatchDescription.appendValue("Index does not exist"); + } + IndexMetadata indexMetadata = indicesMetadata.get(expectedIndexName); + if (expectedState != indexMetadata.getState()) { + mismatchDescription.appendValue("Actual index state is equal to ").appendValue(indexMetadata.getState().name()); + return false; + } + return true; + } + } + + @Override + public void describeTo(Description description) { + description.appendText("Index: ").appendValue(expectedIndexName) + .appendText(" . State should be equal to ").appendValue(expectedState.name()); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulClearIndicesCacheResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulClearIndicesCacheResponseMatcher.java new file mode 100644 index 0000000000..cc2a11d82b --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulClearIndicesCacheResponseMatcher.java @@ -0,0 +1,37 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.indices.cache.clear.ClearIndicesCacheResponse; +import org.opensearch.rest.RestStatus; + +class SuccessfulClearIndicesCacheResponseMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(ClearIndicesCacheResponse response, Description mismatchDescription) { + if(!RestStatus.OK.equals(response.getStatus())) { + mismatchDescription.appendText("Status is equal to ").appendValue(response.getStatus()); + return false; + } + if(response.getShardFailures().length != 0) { + mismatchDescription.appendText("Contains ").appendValue(response.getShardFailures().length).appendText(" shard failures"); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful clear index cache response"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCloseIndexResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCloseIndexResponseMatcher.java new file mode 100644 index 0000000000..beda676540 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCloseIndexResponseMatcher.java @@ -0,0 +1,36 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.client.indices.CloseIndexResponse; + +class SuccessfulCloseIndexResponseMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(CloseIndexResponse response, Description mismatchDescription) { + if (!response.isShardsAcknowledged()) { + mismatchDescription.appendText("shardsAcknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + if (!response.isAcknowledged()) { + mismatchDescription.appendText("acknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful close index response"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreateIndexResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreateIndexResponseMatcher.java new file mode 100644 index 0000000000..2a017e0ae0 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreateIndexResponseMatcher.java @@ -0,0 +1,49 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.client.indices.CreateIndexResponse; + +import static java.util.Objects.requireNonNull; + +class SuccessfulCreateIndexResponseMatcher extends TypeSafeDiagnosingMatcher { + + private final String expectedIndexName; + + SuccessfulCreateIndexResponseMatcher(String expectedIndexName) { + this.expectedIndexName = requireNonNull(expectedIndexName); + } + + @Override + protected boolean matchesSafely(CreateIndexResponse response, Description mismatchDescription) { + if (!expectedIndexName.equals(response.index())) { + mismatchDescription.appendText("Index name ").appendValue(response.index()) + .appendText(" does not match expected index name ").appendValue(expectedIndexName); + return false; + } + if (!response.isShardsAcknowledged()) { + mismatchDescription.appendText("shardsAcknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + if (!response.isAcknowledged()) { + mismatchDescription.appendText("acknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful create index response"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeleteResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeleteResponseMatcher.java new file mode 100644 index 0000000000..8d1df13ea9 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeleteResponseMatcher.java @@ -0,0 +1,37 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.delete.DeleteResponse; +import org.opensearch.rest.RestStatus; + +class SuccessfulDeleteResponseMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(DeleteResponse response, Description mismatchDescription) { + if(!RestStatus.OK.equals(response.status())) { + mismatchDescription.appendText("has status ").appendValue(response.status()).appendText(" which denotes failure."); + return false; + } + if(response.getShardInfo().getFailures().length != 0) { + mismatchDescription.appendText("contains ").appendValue(response.getShardInfo().getFailures().length).appendText(" shard failures"); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful delete response"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulOpenIndexResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulOpenIndexResponseMatcher.java new file mode 100644 index 0000000000..3147195474 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulOpenIndexResponseMatcher.java @@ -0,0 +1,36 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.indices.open.OpenIndexResponse; + +class SuccessfulOpenIndexResponseMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(OpenIndexResponse response, Description mismatchDescription) { + if (!response.isShardsAcknowledged()) { + mismatchDescription.appendText("shardsAcknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + if (!response.isAcknowledged()) { + mismatchDescription.appendText("acknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful open index response"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulResizeResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulResizeResponseMatcher.java new file mode 100644 index 0000000000..c9b5d92b94 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulResizeResponseMatcher.java @@ -0,0 +1,49 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.client.indices.ResizeResponse; + +import static java.util.Objects.requireNonNull; + +class SuccessfulResizeResponseMatcher extends TypeSafeDiagnosingMatcher { + + private final String expectedIndexName; + + SuccessfulResizeResponseMatcher(String expectedIndexName) { + this.expectedIndexName = requireNonNull(expectedIndexName); + } + + @Override + protected boolean matchesSafely(ResizeResponse response, Description mismatchDescription) { + if (!expectedIndexName.equals(response.index())) { + mismatchDescription.appendText("Index name ").appendValue(response.index()) + .appendText(" does not match expected index name ").appendValue(expectedIndexName); + return false; + } + if (!response.isShardsAcknowledged()) { + mismatchDescription.appendText("shardsAcknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + if (!response.isAcknowledged()) { + mismatchDescription.appendText("acknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful create index response"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulUpdateResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulUpdateResponseMatcher.java new file mode 100644 index 0000000000..35a9851055 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulUpdateResponseMatcher.java @@ -0,0 +1,37 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.update.UpdateResponse; +import org.opensearch.rest.RestStatus; + +class SuccessfulUpdateResponseMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(UpdateResponse response, Description mismatchDescription) { + if(!RestStatus.OK.equals(response.status())) { + mismatchDescription.appendText("has status ").appendValue(response.status()).appendText(" which denotes failure."); + return false; + } + if(response.getShardInfo().getFailures().length != 0) { + mismatchDescription.appendText("contains ").appendValue(response.getShardInfo().getFailures().length).appendText(" shard failures"); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful update response"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/UpdateResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/UpdateResponseMatchers.java new file mode 100644 index 0000000000..9062d9bb68 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/UpdateResponseMatchers.java @@ -0,0 +1,23 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Matcher; + +import org.opensearch.action.update.UpdateResponse; + +public class UpdateResponseMatchers { + + private UpdateResponseMatchers() {} + + public static Matcher isSuccessfulUpdateResponse() { + return new SuccessfulUpdateResponseMatcher(); + } +} From ba9d82ef6a2c6da137c19fcaaddce6dabcf7160f Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Mon, 24 Oct 2022 12:34:39 -0500 Subject: [PATCH 062/369] Prevent message collection from being updated after message count has been received (#2180) Also adds mechanism to detect if messages were missed so tests can be updated to appropriate counts. Signed-off-by: Peter Nied --- .github/workflows/ci.yml | 2 +- .../security/auditlog/impl/AuditMessage.java | 13 +++- .../compliance/ComplianceAuditlogTest.java | 78 +++++++++++-------- .../integration/BasicAuditlogTest.java | 2 +- .../integration/TestAuditlogImpl.java | 58 +++++++++++--- 5 files changed, 108 insertions(+), 45 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 082b722e0c..67ee7ada09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: run: ./gradlew clean build -Dbuild.snapshot=false -x test -x integrationTest - name: Test - run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew test integrationTest -i + run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew test integrationTest - name: Coverage uses: codecov/codecov-action@v1 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 88d05d0f2a..21aee075c5 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java @@ -84,7 +84,6 @@ public final class AuditMessage { public static final String REMOTE_ADDRESS = "audit_request_remote_address"; public static final String REST_REQUEST_PATH = "audit_rest_request_path"; - //public static final String REST_REQUEST_BODY = "audit_rest_request_body"; public static final String REST_REQUEST_PARAMS = "audit_rest_request_params"; public static final String REST_REQUEST_HEADERS = "audit_rest_request_headers"; public static final String REST_REQUEST_METHOD = "audit_rest_request_method"; @@ -449,6 +448,18 @@ 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/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java b/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java index 6cb51ff441..dd53cd16a8 100644 --- a/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java @@ -43,6 +43,8 @@ import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; 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; @@ -90,10 +92,11 @@ public void testSourceFilter() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); }); - 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")); + 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)); } @@ -200,17 +203,24 @@ public void testSourceFilterMsearch() throws Exception { " }" + "}"+System.lineSeparator(); - TestAuditlogImpl.doThenWaitForMessages(() -> { + 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); - System.out.println(TestAuditlogImpl.sb.toString()); - 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 AuditMessage desginationMsg = messages.stream().filter(msg -> msg.getRequestBody().contains("Designation")).findFirst().orElseThrow(); + 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().orElseThrow(); + assertThat(genderMsg.getCategory(), equalTo(AuditCategory.COMPLIANCE_DOC_READ)); + assertThat(genderMsg.getRequestBody(), containsString("Gender")); + assertThat(genderMsg.getRequestBody(), not(containsString("Salary"))); + + Assert.assertTrue(validateMsgs(messages)); } @Test @@ -230,6 +240,7 @@ public void testInternalConfig() throws Exception { setup(additionalSettings); + final List expectedDocumentsTypes = List.of("config", "actiongroups", "internalusers", "roles", "rolesmapping", "tenants", "audit"); final List messages = TestAuditlogImpl.doThenWaitForMessages(() -> { try (RestHighLevelClient restHighLevelClient = getRestClient(clusterInfo, "kirk-keystore.jks", "truststore.jks")) { for (IndexRequest ir: new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) { @@ -245,21 +256,19 @@ public void testInternalConfig() throws Exception { assertThat(response.getStatusCode(), equalTo(HttpStatus.SC_OK)); }, 14); - 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 read/write config message", + messagesByDocId.stream().map(AuditMessage::getCategory).collect(Collectors.toList()), + equalTo(List.of(AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE, AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ)) + ); + }); + + Assert.assertTrue(validateMsgs(messages)); } @Test @@ -276,7 +285,7 @@ public void testExternalConfig() throws Exception { .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") .build(); - TestAuditlogImpl.doThenWaitForMessages(() -> { + final List messages = TestAuditlogImpl.doThenWaitForMessages(() -> { try { setup(additionalSettings); } catch (final Exception ex) { @@ -293,10 +302,17 @@ public void testExternalConfig() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); }, 4); - 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)); + // 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 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 ac8df9cc72..30e3e53436 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java @@ -133,7 +133,7 @@ public void testSSLPlainText() throws Exception { final RuntimeException ex = Assert.assertThrows(RuntimeException.class, () -> nonSslRestHelper().executeGetRequest("_search", encodeBasicHeader("admin", "admin"))); Assert.assertEquals("org.apache.hc.core5.http.NoHttpResponseException", ex.getCause().getClass().getName()); - }, 1); + }, 2); // All of the messages should be the same as the http client is attempting multiple times. messages.stream().forEach((message) -> { 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 4677bc37a9..b91ddee9fe 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java @@ -58,30 +58,66 @@ public static synchronized void clear() { * 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 CountDownLatch latch = new CountDownLatch(expectedCount); + final List missedMessages = new ArrayList<>(); final List messages = new ArrayList<>(); - countDownRef.set(latch); - messagesRef.set(messages); - - TestAuditlogImpl.sb = new StringBuffer(); - TestAuditlogImpl.messages = messages; + final CountDownLatch latch = resetAuditStorage(expectedCount, messages); try { action.run(); final int maxSecondsToWaitForMessages = 1; - final boolean foundAll = latch.await(maxSecondsToWaitForMessages, TimeUnit.SECONDS); - if (!foundAll) { + 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); } - if (messages.size() != expectedCount) { - throw new RuntimeException("Unexpected number of messages, was expecting " + expectedCount + ", received " + messages.size()); - } } 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. */ From 9f9fddc3722d5cf86a7f35d4e7f8611ae4b44fe9 Mon Sep 17 00:00:00 2001 From: Hailong-amzn Date: Tue, 25 Oct 2022 07:08:24 +0800 Subject: [PATCH 063/369] fix duplicate check (#2183) Signed-off-by: Hailong Cui --- .../security/securityconf/ConfigModelV7.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index c7612cf0d8..eb42792c2a 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -27,6 +27,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -1116,21 +1117,21 @@ public Map mapTenants(final User user, Set roles) { if (rw || !result.containsKey(tenant)) { //RW outperforms RO - // We want to make sure that we add a tenant that exissts + // We want to make sure that we add a tenant that exists // Indeed, because we don't have control over what will be // passed on as values of users' attributes, we have to make // sure that we don't allow them to select tenants that do not exist. - if(ConfigModelV7.this.tenants.getCEntries().keySet().contains(tenant)) { + if(ConfigModelV7.this.tenants.getCEntries().containsKey(tenant)) { result.put(tenant, rw); } } }); - + + Set _roles = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + _roles.addAll(roles); if(!result.containsKey("global_tenant") && ( - roles.contains("kibana_user") - || roles.contains("kibana_user") - || roles.contains("all_access") - || roles.contains("ALL_ACCESS") + _roles.contains("kibana_user") + || _roles.contains("all_access") )) { result.put("global_tenant", true); } From d93c9eec7893861a278e5fb34f0701334007bad8 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Wed, 26 Oct 2022 10:55:19 -0400 Subject: [PATCH 064/369] [FasterXML/Woodstox] Woodstox Version Bump to 6.4.0 (#2197) Signed-off-by: Stephen Crawford --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2bd2d4c0b0..8ae6cc874a 100644 --- a/build.gradle +++ b/build.gradle @@ -367,7 +367,7 @@ dependencies { runtimeOnly 'org.xerial.snappy:snappy-java:1.1.8.1' runtimeOnly 'org.codehaus.woodstox:stax2-api:4.2.1' runtimeOnly 'org.glassfish.jaxb:txw2:2.3.4' - runtimeOnly 'com.fasterxml.woodstox:woodstox-core:6.2.6' + runtimeOnly 'com.fasterxml.woodstox:woodstox-core:6.4.0' runtimeOnly 'org.apache.ws.xmlschema:xmlschema-core:2.2.5' runtimeOnly 'org.apache.santuario:xmlsec:2.2.3' runtimeOnly 'com.github.luben:zstd-jni:1.5.0-2' From 966b3fcf68e3bc6f74a237228606aa4a68a6cdee Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 26 Oct 2022 14:30:12 -0400 Subject: [PATCH 065/369] Add bcpkix-jdk15on runtimeOnly dependency to read keys with bouncycastle (#2191) * Add bouncycastle bcpkix-jdk15on runtimeOnly dependency to read keys with bouncycastle Signed-off-by: Craig Perkins --- build.gradle | 1 + .../opensearch/security/ssl/OpenSSLTest.java | 2 +- .../org/opensearch/security/ssl/SSLTest.java | 35 ++++++++++++++++++- src/test/resources/ssl/node-0-pkcs1.key.pem | 27 ++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/ssl/node-0-pkcs1.key.pem diff --git a/build.gradle b/build.gradle index 8ae6cc874a..4d2a94b624 100644 --- a/build.gradle +++ b/build.gradle @@ -372,6 +372,7 @@ dependencies { runtimeOnly 'org.apache.santuario:xmlsec:2.2.3' runtimeOnly 'com.github.luben:zstd-jni:1.5.0-2' runtimeOnly 'org.checkerframework:checker-qual:3.5.0' + runtimeOnly "org.bouncycastle:bcpkix-jdk15on:${versions.bouncycastle}" implementation 'org.apache.commons:commons-lang3:3.4' diff --git a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java index 7b97112a27..e38e0beff1 100644 --- a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java @@ -175,7 +175,7 @@ public void testHttpsAndNodeSSLFailedCipher() throws Exception { @Test public void testHttpsAndNodeSSLPem() throws Exception { Assume.assumeTrue(OpenSearchSecuritySSLPlugin.OPENSSL_SUPPORTED && OpenSsl.isAvailable()); - super.testHttpsAndNodeSSLPem(); + super.testHttpsAndNodeSSLPKCS8Pem(); } @Test diff --git a/src/test/java/org/opensearch/security/ssl/SSLTest.java b/src/test/java/org/opensearch/security/ssl/SSLTest.java index 331abdc414..08bbbb15d3 100644 --- a/src/test/java/org/opensearch/security/ssl/SSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/SSLTest.java @@ -265,7 +265,7 @@ public void testHttpsAndNodeSSL() throws Exception { } @Test - public void testHttpsAndNodeSSLPem() throws Exception { + public void testHttpsAndNodeSSLPKCS8Pem() throws Exception { final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) @@ -301,6 +301,39 @@ public void testHttpsAndNodeSSLPem() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); } + @Test + public void testHttpsAndNodeSSLPKCS1Pem() throws Exception { + + final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-pkcs1.key.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .put("plugins.security.ssl.transport.enforce_hostname_verification", false) + .put("plugins.security.ssl.transport.resolve_hostname", false) + + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-pkcs1.key.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .build(); + + setupSslOnlyMode(settings); + + RestHelper rh = restHelper(); + rh.enableHTTPClientSSL = true; + rh.trustHTTPServerCertificate = true; + rh.sendAdminCertificate = true; + + Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); + Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); + Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); + Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + } + @Test public void testHttpsAndNodeSSLPemEnc() throws Exception { diff --git a/src/test/resources/ssl/node-0-pkcs1.key.pem b/src/test/resources/ssl/node-0-pkcs1.key.pem new file mode 100644 index 0000000000..3f17386738 --- /dev/null +++ b/src/test/resources/ssl/node-0-pkcs1.key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAh5j6Dy0HqaP5MZe+KLkby42pUJn7OaQfZ7AYHJXs8XQ0aB+3 +eHfl57ubGDlsbxZc9s88LuhPo1Y3Frgj7SbH/KVeD0nc04uSWDvhFlxbL27c+Top +Pg75YTC8KcXy4mK+Bt1od28yxno6z18XroOoqBEf7086MY7MyDnduuLp/B1nwGij +YM32k+KP9HJbRu9JMMt16GQYmbQtGJEqo5E8WAP/doTiH1f/cASSSoaiAR4Lzjhj +bf6BEvtEQK51BFvqhG/qN229joyiOOexiLZb/bCdRdGebOOpOb9uBjqI1FlVc4Tg +IfyCKm/QATinZMLCcSl6NcUTFgcJkUxZn9DaEwIDAQABAoIBAERT0a3UAwh4mBll +XW0n2dm4iJkU/oMNMMYO9hdxdPQ2mjHdyZXq1O7wmjaauQlBO5ci1jDM31RvHVNV +dsUJxbyJl9wcXCo9KoFLqyCZaVl9g9wScrXS1dcjt61VEg8Bsr/C0eFdJzjcXsYg +JM7LiTEnb/Am/Cv8yTQb9J83uLBRXfTG965R05voFV5OkGvto+YUJH8AkNB0J8sl +5xW/fWGSAVTGgZJ+qFJHW28J9gXR/gccqcGw0YAfxaEYcDCGofQgS8rqmbduiolS +lJ7HHpr7iPx97B9R9b3h9rIaaliHgyXDVrNiG0vJGxlu8A3OZa80aUy0olbOoO4C +JOykUAECgYEA9biE/Pt0J1maa9YvtXKGCc4BVpYISVRI/UqlVRbAvNmiw2Gk3dkv +OGpwmFfcg5zvObHuagO8/gxPowRekrstbuYhbFDsqxF3klyuOjrNLIPFfd3s+JnA +nw2pq106veUGcVlXiHFowfZsM6Ao/YC7RZHe+LVRwYC4WKmBOMdWuckCgYEAjUUf +VhtayWpwcPfHvi64gTewPe7YtdY/loHod9D7BDLZhbQR87VKDtQXl0suS+mUAA4w +ygxITwy88xbeojdkaspxt5yn+2cwVWF0xNAtLLs0RSxOAeCMDgS1GtCOdLXiDmr+ +u/qOLfypgs8fd0+zPmESwAE17kUNREfFBy48IvsCgYA3f295/AkmAhTgmkW4Q5+G +g2LF/ajtdv3tR3jEGRl7DeS3IEyuVqlVoqS/o8iIaV+WtltU0ndTIdCyzv/VQDVo +wM13u2dY098fzZMET9ebYD+wx/kHxSI+SkWyEKJ91UZ5P2aHyKWSeWfC2T+o0fR6 +KBImNj266Km6TL6E5nDuEQKBgCFv8vLWlq6F2wdiHo0NUe6D19tQ5Upk47gkF3oE +pOVhg8r5zCX7CwRnfw34ZYTiTH2W3kV0ksjjIvYGu7t5kPMV58Sl97yxt+b9oj3T +aF3mEYEt82jOVVgcFSG7q3xEcLUo7hJgQ7buztB/Zds/qhVhtKZtou46ueEne6Mv +dlxxAoGBALPpHtIUdfQtGxj63Bq/VKOSUrDCHKhEmLa5Cd2EetpPoLsw3OrdykSB +Q8y7/SOLOA6bLJ+OkCpzGFvylAcb+6R5HWVLSWoGD6njCCj1kflJ8LJgwxSfUf/V +Oz3//TDGkH+1OuH34d0MXaHfzBMiPLlMG7pd35kmuDgIx4UjT6Ep +-----END RSA PRIVATE KEY----- From 45c766f5b98bb88b642d0c7fbccb32653213c300 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 27 Oct 2022 18:48:11 -0400 Subject: [PATCH 066/369] Add install_demo_configuration Batch script for Windows (#2161) Signed-off-by: Craig Perkins Signed-off-by: Stephen Crawford Co-authored-by: Stephen Crawford Co-authored-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- .github/workflows/plugin_install.yml | 173 ++++++++----- tools/install_demo_configuration.bat | 360 +++++++++++++++++++++++++++ 2 files changed, 475 insertions(+), 58 deletions(-) create mode 100755 tools/install_demo_configuration.bat diff --git a/.github/workflows/plugin_install.yml b/.github/workflows/plugin_install.yml index c0c6df60a4..7e032b750f 100644 --- a/.github/workflows/plugin_install.yml +++ b/.github/workflows/plugin_install.yml @@ -3,63 +3,120 @@ name: Plugin Install on: [push, pull_request, workflow_dispatch] jobs: - plugin_install: - name: Plugin Install + + linux-install: + runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v2 - - - name: Set up JDK - uses: actions/setup-java@v1 - with: - java-version: 11 - - - name: Build - run: ./gradlew clean assemble -Dbuild.snapshot=false - - - name: Download OpenSearch Core - run: | - opensearch_version=`./gradlew properties -q | grep "opensearch_version:" | awk '{print $2}' | sed 's/-SNAPSHOT//g'` - wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/$opensearch_version/latest/linux/x64/tar/builds/opensearch/dist/opensearch-min-$opensearch_version-linux-x64.tar.gz - tar -xzf opensearch-*.tar.gz - rm -f opensearch-*.tar.gz - - - name: Move and rename security plugin for installation - run: mv build/distributions/opensearch-security-*.zip opensearch-security.zip - - - name: Run OpenSearch with plugin - run: | - cat > os-ep.sh < os-ep.sh <nul findstr /c:"plugins.security" "%OPENSEARCH_CONF_FILE%" && ( + echo %OPENSEARCH_CONF_FILE% seems to be already configured for Security. Quit. + exit /b %skip_updates% +) + +set LF=^ + + +:: two empty line required after LF +set ADMIN_CERT=-----BEGIN CERTIFICATE-----!LF!^ +MIIEdzCCA1+gAwIBAgIGAWLrc1O4MA0GCSqGSIb3DQEBCwUAMIGPMRMwEQYKCZIm!LF!^ +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ!LF!^ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290!LF!^ +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwHhcNMTgwNDIy!LF!^ +MDM0MzQ3WhcNMjgwNDE5MDM0MzQ3WjBNMQswCQYDVQQGEwJkZTENMAsGA1UEBwwE!LF!^ +dGVzdDEPMA0GA1UECgwGY2xpZW50MQ8wDQYDVQQLDAZjbGllbnQxDTALBgNVBAMM!LF!^ +BGtpcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCwgBOoO88uMM8!LF!^ +dREJsk58Yt4Jn0zwQ2wUThbvy3ICDiEWhiAhUbg6dTggpS5vWWJto9bvaaqgMVoh!LF!^ +ElfYHdTDncX3UQNBEP8tqzHON6BFEFSGgJRGLd6f5dri6rK32nCotYS61CFXBFxf!LF!^ +WumXjSukjyrcTsdkR3C5QDo2oN7F883MOQqRENPzAtZi9s3jNX48u+/e3yvJzXsB!LF!^ +GS9Qmsye6C71enbIujM4CVwDT/7a5jHuaUp6OuNCFbdRPnu/wLYwOS2/yOtzAqk7!LF!^ +/PFnPCe7YOa10ShnV/jx2sAHhp7ZQBJgFkkgnIERz9Ws74Au+EbptWnsWuB+LqRL!LF!^ +x5G02IzpAgMBAAGjggEYMIIBFDCBvAYDVR0jBIG0MIGxgBSSNQzgDx4rRfZNOfN7!LF!^ +X6LmEpdAc6GBlaSBkjCBjzETMBEGCgmSJomT8ixkARkWA2NvbTEXMBUGCgmSJomT!LF!^ +8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1wbGUgQ29tIEluYy4xITAfBgNV!LF!^ +BAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEhMB8GA1UEAwwYRXhhbXBsZSBD!LF!^ +b20gSW5jLiBSb290IENBggEBMB0GA1UdDgQWBBRsdhuHn3MGDvZxOe22+1wliCJB!LF!^ +mDAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF4DAWBgNVHSUBAf8EDDAKBggr!LF!^ +BgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAkPrUTKKn+/6g0CjhTPBFeX8mKXhG!LF!^ +zw5z9Oq+xnwefZwxV82E/tgFsPcwXcJIBg0f43BaVSygPiV7bXqWhxASwn73i24z!LF!^ +lveIR4+z56bKIhP6c3twb8WWR9yDcLu2Iroin7dYEm3dfVUrhz/A90WHr6ddwmLL!LF!^ +3gcFF2kBu3S3xqM5OmN/tqRXFmo+EvwrdJRiTh4Fsf0tX1ZT07rrGvBFYktK7Kma!LF!^ +lqDl4UDCF1UWkiiFubc0Xw+DR6vNAa99E0oaphzvCmITU1wITNnYZTKzVzQ7vUCq!LF!^ +kLmXOFLTcxTQpptxSo5xDD3aTpzWGCvjExCKpXQtsITUOYtZc02AGjjPOQ==!LF!^ +-----END CERTIFICATE-----!LF! + + +set ADMIN_CERT_KEY=-----BEGIN PRIVATE KEY-----!LF!^ +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCwgBOoO88uMM8!LF!^ +dREJsk58Yt4Jn0zwQ2wUThbvy3ICDiEWhiAhUbg6dTggpS5vWWJto9bvaaqgMVoh!LF!^ +ElfYHdTDncX3UQNBEP8tqzHON6BFEFSGgJRGLd6f5dri6rK32nCotYS61CFXBFxf!LF!^ +WumXjSukjyrcTsdkR3C5QDo2oN7F883MOQqRENPzAtZi9s3jNX48u+/e3yvJzXsB!LF!^ +GS9Qmsye6C71enbIujM4CVwDT/7a5jHuaUp6OuNCFbdRPnu/wLYwOS2/yOtzAqk7!LF!^ +/PFnPCe7YOa10ShnV/jx2sAHhp7ZQBJgFkkgnIERz9Ws74Au+EbptWnsWuB+LqRL!LF!^ +x5G02IzpAgMBAAECggEAEzwnMkeBbqqDgyRqFbO/PgMNvD7i0b/28V0dCtCPEVY6!LF!^ +klzrg3RCERP5V9AN8VVkppYjPkCzZ2A4b0JpMUu7ncOmr7HCnoSCj2IfEyePSVg+!LF!^ +4OHbbcBOAoDTHiI2myM/M9++8izNS34qGV4t6pfjaDyeQQ/5cBVWNBWnKjS34S5H!LF!^ +rJWpAcDgxYk5/ah2Xs2aULZlXDMxbSikjrv+n4JIYTKFQo8ydzL8HQDBRmXAFLjC!LF!^ +gNOSHf+5u1JdpY3uPIxK1ugVf8zPZ4/OEB23j56uu7c8+sZ+kZwfRWAQmMhFVG/y!LF!^ +OXxoT5mOruBsAw29m2Ijtxg252/YzSTxiDqFziB/eQKBgQDjeVAdi55GW/bvhuqn!LF!^ +xME/An8E3hI/FyaaITrMQJUBjiCUaStTEqUgQ6A7ZfY/VX6qafOX7sli1svihrXC!LF!^ +uelmKrdve/CFEEqzX9JWWRiPiQ0VZD+EQRsJvX85Tw2UGvVUh6dO3UGPS0BhplMD!LF!^ +jeVpyXgZ7Gy5we+DWjfwhYrCmwKBgQDbLmQhRy+IdVljObZmv3QtJ0cyxxZETWzU!LF!^ +MKmgBFvcRw+KvNwO+Iy0CHEbDu06Uj63kzI2bK3QdINaSrjgr8iftXIQpBmcgMF+!LF!^ +a1l5HtHlCp6RWd55nWQOEvn36IGN3cAaQkXuh4UYM7QfEJaAbzJhyJ+wXA3jWqUd!LF!^ +8bDTIAZ0ywKBgFuZ44gyTAc7S2JDa0Up90O/ZpT4NFLRqMrSbNIJg7d/m2EIRNkM!LF!^ +HhCzCthAg/wXGo3XYq+hCdnSc4ICCzmiEfoBY6LyPvXmjJ5VDOeWs0xBvVIK74T7!LF!^ +jr7KX2wdiHNGs9pZUidw89CXVhK8nptEzcheyA1wZowbK68yamph7HHXAoGBAK3x!LF!^ +7D9Iyl1mnDEWPT7f1Gh9UpDm1TIRrDvd/tBihTCVKK13YsFy2d+LD5Bk0TpGyUVR!LF!^ +STlOGMdloFUJFh4jA3pUOpkgUr8Uo/sbYN+x6Ov3+I3sH5aupRhSURVA7YhUIz/z!LF!^ +tqIt5R+m8Nzygi6dkQNvf+Qruk3jw0S3ahizwsvvAoGAL7do6dTLp832wFVxkEf4!LF!^ +gg1M6DswfkgML5V/7GQ3MkIX/Hrmiu+qSuHhDGrp9inZdCDDYg5+uy1+2+RBMRZ3!LF!^ +vDUUacvc4Fep05zp7NcjgU5y+/HWpuKVvLIlZAO1MBY4Xinqqii6RdxukIhxw7eT!LF!^ +C6TPL5KAcV1R/XAihDhI18Y=!LF!^ +-----END PRIVATE KEY-----!LF! + + +set NODE_CERT=-----BEGIN CERTIFICATE-----!LF!^ +MIIEyTCCA7GgAwIBAgIGAWLrc1O2MA0GCSqGSIb3DQEBCwUAMIGPMRMwEQYKCZIm!LF!^ +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ!LF!^ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290!LF!^ +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwHhcNMTgwNDIy!LF!^ +MDM0MzQ3WhcNMjgwNDE5MDM0MzQ3WjBeMRIwEAYKCZImiZPyLGQBGRYCZGUxDTAL!LF!^ +BgNVBAcMBHRlc3QxDTALBgNVBAoMBG5vZGUxDTALBgNVBAsMBG5vZGUxGzAZBgNV!LF!^ +BAMMEm5vZGUtMC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC!LF!^ +AQoCggEBAJa+f476vLB+AwK53biYByUwN+40D8jMIovGXm6wgT8+9Sbs899dDXgt!LF!^ +9CE1Beo65oP1+JUz4c7UHMrCY3ePiDt4cidHVzEQ2g0YoVrQWv0RedS/yx/DKhs8!LF!^ +Pw1O715oftP53p/2ijD5DifFv1eKfkhFH+lwny/vMSNxellpl6NxJTiJVnQ9HYOL!LF!^ +gf2t971ITJHnAuuxUF48HcuNovW4rhtkXef8kaAN7cE3LU+A9T474ULNCKkEFPIl!LF!^ +ZAKN3iJNFdVsxrTU+CUBHzk73Do1cCkEvJZ0ZFjp0Z3y8wLY/gqWGfGVyA9l2CUq!LF!^ +eIZNf55PNPtGzOrvvONiui48vBKH1LsCAwEAAaOCAVkwggFVMIG8BgNVHSMEgbQw!LF!^ +gbGAFJI1DOAPHitF9k0583tfouYSl0BzoYGVpIGSMIGPMRMwEQYKCZImiZPyLGQB!LF!^ +GRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQRXhhbXBs!LF!^ +ZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMSEw!LF!^ +HwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0GCAQEwHQYDVR0OBBYEFKyv!LF!^ +78ZmFjVKM9g7pMConYH7FVBHMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgXg!LF!^ +MCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA1BgNVHREELjAsiAUq!LF!^ +AwQFBYISbm9kZS0wLmV4YW1wbGUuY29tgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZI!LF!^ +hvcNAQELBQADggEBAIOKuyXsFfGv1hI/Lkpd/73QNqjqJdxQclX57GOMWNbOM5H0!LF!^ +5/9AOIZ5JQsWULNKN77aHjLRr4owq2jGbpc/Z6kAd+eiatkcpnbtbGrhKpOtoEZy!LF!^ +8KuslwkeixpzLDNISSbkeLpXz4xJI1ETMN/VG8ZZP1bjzlHziHHDu0JNZ6TnNzKr!LF!^ +XzCGMCohFfem8vnKNnKUneMQMvXd3rzUaAgvtf7Hc2LTBlf4fZzZF1EkwdSXhaMA!LF!^ +1lkfHiqOBxtgeDLxCHESZ2fqgVqsWX+t3qHQfivcPW6txtDyrFPRdJOGhiMGzT/t!LF!^ +e/9kkAtQRgpTb3skYdIOOUOV0WGQ60kJlFhAzIs=!LF!^ +-----END CERTIFICATE-----!LF! + + +set NODE_KEY=-----BEGIN PRIVATE KEY-----!LF!^ +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCWvn+O+rywfgMC!LF!^ +ud24mAclMDfuNA/IzCKLxl5usIE/PvUm7PPfXQ14LfQhNQXqOuaD9fiVM+HO1BzK!LF!^ +wmN3j4g7eHInR1cxENoNGKFa0Fr9EXnUv8sfwyobPD8NTu9eaH7T+d6f9oow+Q4n!LF!^ +xb9Xin5IRR/pcJ8v7zEjcXpZaZejcSU4iVZ0PR2Di4H9rfe9SEyR5wLrsVBePB3L!LF!^ +jaL1uK4bZF3n/JGgDe3BNy1PgPU+O+FCzQipBBTyJWQCjd4iTRXVbMa01PglAR85!LF!^ +O9w6NXApBLyWdGRY6dGd8vMC2P4KlhnxlcgPZdglKniGTX+eTzT7Rszq77zjYrou!LF!^ +PLwSh9S7AgMBAAECggEABwiohxFoEIwws8XcdKqTWsbfNTw0qFfuHLuK2Htf7IWR!LF!^ +htlzn66F3F+4jnwc5IsPCoVFriCXnsEC/usHHSMTZkL+gJqxlNaGdin6DXS/aiOQ!LF!^ +nb69SaQfqNmsz4ApZyxVDqsQGkK0vAhDAtQVU45gyhp/nLLmmqP8lPzMirOEodmp!LF!^ +U9bA8t/ttrzng7SVAER42f6IVpW0iTKTLyFii0WZbq+ObViyqib9hVFrI6NJuQS+!LF!^ +IelcZB0KsSi6rqIjXg1XXyMiIUcSlhq+GfEa18AYgmsbPwMbExate7/8Ci7ZtCbh!LF!^ +lx9bves2+eeqq5EMm3sMHyhdcg61yzd5UYXeZhwJkQKBgQDS9YqrAtztvLY2gMgv!LF!^ +d+wOjb9awWxYbQTBjx33kf66W+pJ+2j8bI/XX2CpZ98w/oq8VhMqbr9j5b8MfsrF!LF!^ +EoQvedA4joUo8sXd4j1mR2qKF4/KLmkgy6YYusNP2UrVSw7sh77bzce+YaVVoO/e!LF!^ +0wIVTHuD/QZ6fG6MasOqcbl6hwKBgQC27cQruaHFEXR/16LrMVAX+HyEEv44KOCZ!LF!^ +ij5OE4P7F0twb+okngG26+OJV3BtqXf0ULlXJ+YGwXCRf6zUZkld3NMy3bbKPgH6!LF!^ +H/nf3BxqS2tudj7+DV52jKtisBghdvtlKs56oc9AAuwOs37DvhptBKUPdzDDqfys!LF!^ +Qchv5JQdLQKBgERev+pcqy2Bk6xmYHrB6wdseS/4sByYeIoi0BuEfYH4eB4yFPx6!LF!^ +UsQCbVl6CKPgWyZe3ydJbU37D8gE78KfFagtWoZ56j4zMF2RDUUwsB7BNCDamce/!LF!^ +OL2bCeG/Erm98cBG3lxufOX+z47I8fTNfkdY2k8UmhzoZwurLm73HJ3RAoGBAKsp!LF!^ +6yamuXF2FbYRhUXgjHsBbTD/vJO72/yO2CGiLRpi/5mjfkjo99269trp0C8sJSub!LF!^ +5PBiSuADXFsoRgUv+HI1UAEGaCTwxFTQWrRWdtgW3d0sE2EQDVWL5kmfT9TwSeat!LF!^ +mSoyAYR5t3tCBNkPJhbgA7pm4mASzHQ50VyxWs25AoGBAKPFx9X2oKhYQa+mW541!LF!^ +bbqRuGFMoXIIcr/aeM3LayfLETi48o5NDr2NDP11j4yYuz26YLH0Dj8aKpWuehuH!LF!^ +uB27n6j6qu0SVhQi6mMJBe1JrKbzhqMKQjYOoy8VsC2gdj5pCUP/kLQPW7zm9diX!LF!^ +CiKTtKgPIeYdigor7V3AHcVT!LF!^ +-----END PRIVATE KEY-----!LF! + + +set ROOT_CA=-----BEGIN CERTIFICATE-----!LF!^ +MIID/jCCAuagAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjzETMBEGCgmSJomT8ixk!LF!^ +ARkWA2NvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1w!LF!^ +bGUgQ29tIEluYy4xITAfBgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEh!LF!^ +MB8GA1UEAwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMB4XDTE4MDQyMjAzNDM0!LF!^ +NloXDTI4MDQxOTAzNDM0NlowgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJ!LF!^ +kiaJk/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEw!LF!^ +HwYDVQQLDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1w!LF!^ +bGUgQ29tIEluYy4gUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC!LF!^ +ggEBAK/u+GARP5innhpXK0c0q7s1Su1VTEaIgmZr8VWI6S8amf5cU3ktV7WT9SuV!LF!^ +TsAm2i2A5P+Ctw7iZkfnHWlsC3HhPUcd6mvzGZ4moxnamM7r+a9otRp3owYoGStX!LF!^ +ylVTQusAjbq9do8CMV4hcBTepCd+0w0v4h6UlXU8xjhj1xeUIz4DKbRgf36q0rv4!LF!^ +VIX46X72rMJSETKOSxuwLkov1ZOVbfSlPaygXIxqsHVlj1iMkYRbQmaTib6XWHKf!LF!^ +MibDaqDejOhukkCjzpptGZOPFQ8002UtTTNv1TiaKxkjMQJNwz6jfZ53ws3fh1I0!LF!^ +RWT6WfM4oeFRFnyFRmc4uYTUgAkCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAf!LF!^ +BgNVHSMEGDAWgBSSNQzgDx4rRfZNOfN7X6LmEpdAczAdBgNVHQ4EFgQUkjUM4A8e!LF!^ +K0X2TTnze1+i5hKXQHMwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB!LF!^ +AQBoQHvwsR34hGO2m8qVR9nQ5Klo5HYPyd6ySKNcT36OZ4AQfaCGsk+SecTi35QF!LF!^ +RHL3g2qffED4tKR0RBNGQSgiLavmHGCh3YpDupKq2xhhEeS9oBmQzxanFwWFod4T!LF!^ +nnsG2cCejyR9WXoRzHisw0KJWeuNlwjUdJY0xnn16srm1zL/M/f0PvCyh9HU1mF1!LF!^ +ivnOSqbDD2Z7JSGyckgKad1Omsg/rr5XYtCeyJeXUPcmpeX6erWJJNTUh6yWC/hY!LF!^ +G/dFC4xrJhfXwz6Z0ytUygJO32bJG4Np2iGAwvvgI9EfxzEv/KP+FGrJOvQJAq4/!LF!^ +BU36ZAa80W/8TBnqZTkNnqZV!LF!^ +-----END CERTIFICATE-----!LF! + + +echo !ADMIN_CERT! > "%OPENSEARCH_CONF_DIR%kirk.pem" +echo !NODE_CERT! > "%OPENSEARCH_CONF_DIR%esnode.pem" +echo !ROOT_CA! > "%OPENSEARCH_CONF_DIR%root-ca.pem" +echo !NODE_KEY! > "%OPENSEARCH_CONF_DIR%esnode-key.pem" +echo !ADMIN_CERT_KEY! > "%OPENSEARCH_CONF_DIR%kirk-key.pem" + +echo. >> "%OPENSEARCH_CONF_FILE%" +echo ######## Start OpenSearch Security Demo Configuration ######## >> "%OPENSEARCH_CONF_FILE%" +echo # WARNING: revise all the lines below before you go into production >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.ssl.transport.pemcert_filepath: esnode.pem >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.ssl.transport.pemkey_filepath: esnode-key.pem >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.ssl.transport.pemtrustedcas_filepath: root-ca.pem >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.ssl.transport.enforce_hostname_verification: false >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.ssl.http.enabled: true >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.ssl.http.pemcert_filepath: esnode.pem >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.ssl.http.pemkey_filepath: esnode-key.pem >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.ssl.http.pemtrustedcas_filepath: root-ca.pem >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.allow_unsafe_democertificates: true >> "%OPENSEARCH_CONF_FILE%" +if %initsecurity% == 1 ( + echo plugins.security.allow_default_init_securityindex: true >> "%OPENSEARCH_CONF_FILE%" +) +echo plugins.security.authcz.admin_dn: >> "%OPENSEARCH_CONF_FILE%" +echo - CN=kirk,OU=client,O=client,L=test, C=de >> "%OPENSEARCH_CONF_FILE%" +echo. >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.audit.type: internal_opensearch >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.enable_snapshot_restore_privilege: true >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.check_snapshot_restore_write_privileges: true >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"] >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.system_indices.enabled: true >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.system_indices.indices: [".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".opendistro-asynchronous-search-response*", ".replication-metadata-store"] >> "%OPENSEARCH_CONF_FILE%" + +:: network.host +>nul findstr /b /c:"network.host" "%OPENSEARCH_CONF_FILE%" && ( + echo network.host already present +) || ( + if %cluster_mode% == 1 ( + echo network.host: 0.0.0.0 >> "%OPENSEARCH_CONF_FILE%" + echo node.name: smoketestnode >> "%OPENSEARCH_CONF_FILE%" + echo cluster.initial_cluster_manager_nodes: smoketestnode >> "%OPENSEARCH_CONF_FILE%" + ) +) + +>nul findstr /b /c:"node.max_local_storage_nodes" "%OPENSEARCH_CONF_FILE%" && ( + echo node.max_local_storage_nodes already present +) || ( + echo node.max_local_storage_nodes: 3 >> "%OPENSEARCH_CONF_FILE%" +) + +echo ######## End OpenSearch Security Demo Configuration ######## >> "%OPENSEARCH_CONF_FILE%" + +echo ### Success +echo ### Execute this script now on all your nodes and then start all nodes +:: Generate securityadmin_demo.bat +echo. > securityadmin_demo.bat +echo %OPENSEARCH_PLUGINS_DIR%opensearch-security\tools\securityadmin.bat -cd %OPENSEARCH_CONF_DIR%opensearch-security -icl -key %OPENSEARCH_CONF_DIR%kirk-key.pem -cert %OPENSEARCH_CONF_DIR%kirk.pem -cacert %OPENSEARCH_CONF_DIR%root-ca.pem -nhnv >> securityadmin_demo.bat + +if %initsecurity% == 0 ( + echo ### After the whole cluster is up execute: + type securityadmin_demo.bat + echo ### or run ./securityadmin_demo.bat + echo ### After that you can also use the Security Plugin ConfigurationGUI +) else ( + echo ### OpenSearch Security will be automatically initialized. + echo ### If you like to change the runtime configuration + echo ### change the files in ../../../config/opensearch-security and execute: + type securityadmin_demo.bat + echo ### or run ./securityadmin_demo.bat + echo ### To use the Security Plugin ConfigurationGUI +) + +echo ### To access your secured cluster open https://: and log in with admin/admin. +echo ### [Ignore the SSL certificate warning because we installed self-signed demo certificates] From a57fd0a745a398b1fb7ba005bda44d0e55f5fc88 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 28 Oct 2022 11:09:48 -0500 Subject: [PATCH 067/369] Add CI for Windows and MacOS platforms (#2190) Add CI for Windows and MacOS platforms Due to the increase in the number of platforms, I've separated the newer integration tests into their own workflow. Until retries have been enabled they will automatically pass - but still run and report logs. As soon as we have full confidence we will allow them to start blocking pull requests from merging. https://github.com/opensearch-project/security/issues/2184 Switch the gradle commands to be platform agnostic via the `gradle/gradle-build-action@v2`, dropping the 'clean' step to the build which allows us to reuse the gradle cache - if we see any problems pulling in more recent snapshots we can disable this setting quickly. Found and fixed an issued with config value replacement via environment variables, long story short Windows and MacOS allow for many more characters that are used in the unix environment variable landscape. Added new tests to cover these interesting scenarios as well. Found an encoding issue with user names from config files, still unclear of the source of this issue, be it test setup specific or a problem in the broader OpenSearch ecosystem, disabling the `testSpecialUsernames` until we can dive deeper. https://github.com/opensearch-project/security/issues/2194 Disabled the HeapBasedRateTrackerTests - it was depending on system timing and was very brittle if the system under test experienced any undo load, created follow up issue https://github.com/opensearch-project/security/issues/2193 Fixed a test issue in testDlsWithMinDocCountZeroAggregations where there was a random chance for a test failure, easier to find intermittent tests when they are run so often. OpenSSL has open questions - while it is supported for our Linux JDK11 builds, it seems like a stopgap measure. I've disabled the tests on windows environment while we determine if we should support OpenSSL at all on Windows JDK11. https://github.com/opensearch-project/security/issues/2195 Signed-off-by: Peter Nied --- .github/workflows/ci.yml | 61 +++++++++++----- .../security/support/SecurityUtils.java | 7 +- .../opensearch/security/IntegrationTests.java | 2 + .../limiting/HeapBasedRateTrackerTest.java | 3 + .../security/dlic/dlsfls/DlsTest.java | 12 +-- .../opensearch/security/ssl/OpenSSLTest.java | 15 +--- .../security/support/SecurityUtilsTest.java | 73 +++++++++++++++++++ 7 files changed, 131 insertions(+), 42 deletions(-) create mode 100644 src/test/java/org/opensearch/security/support/SecurityUtilsTest.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67ee7ada09..ad10298a61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,14 +8,14 @@ env: jobs: build: name: build - runs-on: ubuntu-latest strategy: - fail-fast: true + fail-fast: false matrix: jdk: [11, 17] + platform: ["ubuntu-latest", "windows-latest", "macos-latest"] + runs-on: ${{ matrix.platform }} steps: - - name: Set up JDK for build and test uses: actions/setup-java@v2 with: @@ -25,21 +25,15 @@ jobs: - name: Checkout security uses: actions/checkout@v2 - - name: Cache Gradle packages - uses: actions/cache@v2 + - name: Build and Test + uses: gradle/gradle-build-action@v2 with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Package - run: ./gradlew clean build -Dbuild.snapshot=false -x test -x integrationTest - - - name: Test - run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew test integrationTest + arguments: | + build test -Dbuild.snapshot=false + -x integrationTest + -x spotlessCheck + -x checkstyleMain + -x checkstyleTest - name: Coverage uses: codecov/codecov-action@v1 @@ -50,13 +44,42 @@ jobs: - uses: actions/upload-artifact@v3 if: always() with: - name: ${{ matrix.jdk }}-reports + name: ${{ matrix.platform }}-JDK${{ matrix.jdk }}-reports path: | ./build/reports/ - name: check archive for debugging if: always() - run: echo "Check the artifact ${{ matrix.jdk }}-reports.zip for detailed test results" + run: echo "Check the artifact ${{ matrix.platform }}-JDK${{ matrix.jdk }}-reports for detailed test results" + + integration-tests: + name: integration-tests + strategy: + fail-fast: false + matrix: + jdk: [17] + platform: ["ubuntu-latest", "windows-latest", "macos-latest"] + runs-on: ${{ matrix.platform }} + + steps: + - name: Set up JDK for build and test + uses: actions/setup-java@v2 + with: + distribution: temurin # Temurin is a distribution of adoptium + java-version: ${{ matrix.jdk }} + + - name: Checkout security + uses: actions/checkout@v2 + + - name: Build and Test + uses: gradle/gradle-build-action@v2 + continue-on-error: true # Until retries are enable do not fail the workflow https://github.com/opensearch-project/security/issues/2184 + with: + arguments: | + integrationTest -Dbuild.snapshot=false + -x spotlessCheck + -x checkstyleMain + -x checkstyleTest backward-compatibility: runs-on: ubuntu-latest diff --git a/src/main/java/org/opensearch/security/support/SecurityUtils.java b/src/main/java/org/opensearch/security/support/SecurityUtils.java index edf0e06207..5a9215497c 100644 --- a/src/main/java/org/opensearch/security/support/SecurityUtils.java +++ b/src/main/java/org/opensearch/security/support/SecurityUtils.java @@ -46,9 +46,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/test/java/org/opensearch/security/IntegrationTests.java b/src/test/java/org/opensearch/security/IntegrationTests.java index 226551a5ae..246fca03d9 100644 --- a/src/test/java/org/opensearch/security/IntegrationTests.java +++ b/src/test/java/org/opensearch/security/IntegrationTests.java @@ -34,6 +34,7 @@ import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Assert; import org.junit.Assume; +import org.junit.Ignore; import org.junit.Test; import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest; @@ -269,6 +270,7 @@ public void testSingle() throws Exception { } @Test + @Ignore // https://github.com/opensearch-project/security/issues/2194 public void testSpecialUsernames() throws Exception { setup(); 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 c605fe57b9..d3383f2dbe 100644 --- a/src/test/java/org/opensearch/security/auth/limiting/HeapBasedRateTrackerTest.java +++ b/src/test/java/org/opensearch/security/auth/limiting/HeapBasedRateTrackerTest.java @@ -17,6 +17,7 @@ package org.opensearch.security.auth.limiting; +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 cb2fa254b9..18bbef8fef 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java @@ -366,7 +366,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()); @@ -377,7 +377,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")); @@ -410,7 +410,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")); @@ -422,7 +422,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")); @@ -456,7 +456,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")); @@ -468,7 +468,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 e38e0beff1..6990df9ea7 100644 --- a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java @@ -66,23 +66,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/support/SecurityUtilsTest.java b/src/test/java/org/opensearch/security/support/SecurityUtilsTest.java new file mode 100644 index 0000000000..ed6a471421 --- /dev/null +++ b/src/test/java/org/opensearch/security/support/SecurityUtilsTest.java @@ -0,0 +1,73 @@ +/* + * 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.Collection; +import java.util.List; +import java.util.function.Predicate; + +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 = List.of( + "=ExitCode", + "=C:", + "ProgramFiles(x86)", + "INPUT_GRADLE-HOME-CACHE-CLEANUP", + "MYENV", + "MYENV:", + "MYENV::" + ); + private final Collection namesFromThisRuntimeEnvironment = System.getenv().keySet(); + + @Test + public void checkInterestingNamesForEnvPattern() { + checkKeysWithPredicate(interestingEnvKeyNames, "env", ENV_PATTERN.asMatchPredicate()); + } + + @Test + public void checkRuntimeKeyNamesForEnvPattern() { + checkKeysWithPredicate(namesFromThisRuntimeEnvironment, "env", ENV_PATTERN.asMatchPredicate()); + } + + @Test + public void checkInterestingNamesForEnvbcPattern() { + checkKeysWithPredicate(interestingEnvKeyNames, "envbc", ENVBC_PATTERN.asMatchPredicate()); + } + + @Test + public void checkInterestingNamesForEnvBase64Pattern() { + checkKeysWithPredicate(interestingEnvKeyNames, "envbase64", ENVBASE64_PATTERN.asMatchPredicate()); + } + + 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)); + }); + } +} From a040b86a48eebcbe2791ba6371772fb9d386d6f2 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Wed, 2 Nov 2022 10:56:12 -0500 Subject: [PATCH 068/369] Fix windows encoding issues (#2206) Adds spotbugs [1] to detect internalization before they are added to the codebase, also fixed several encoding bugs that impact windows users. [1] https://spotbugs.readthedocs.io/en/stable/index.html Closes https://github.com/opensearch-project/security/issues/2194 Signed-off-by: Peter Nied --- .github/workflows/ci.yml | 2 ++ .github/workflows/code-hygiene.yml | 15 +++++++++++++++ build.gradle | 9 +++++++++ spotbugs-include.xml | 5 +++++ .../auth/http/saml/AuthTokenProcessorHandler.java | 3 ++- .../security/auditlog/impl/AbstractAuditLog.java | 6 +++--- .../ConfigurationLoaderSecurity7.java | 3 ++- .../security/ssl/DefaultSecurityKeyStore.java | 3 ++- .../opensearch/security/support/ConfigHelper.java | 5 +++-- .../opensearch/security/tools/SecurityAdmin.java | 2 +- .../org/opensearch/security/IntegrationTests.java | 2 -- 11 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 spotbugs-include.xml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad10298a61..3e7fc78987 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,7 @@ jobs: -x spotlessCheck -x checkstyleMain -x checkstyleTest + -x spotbugsMain - name: Coverage uses: codecov/codecov-action@v1 @@ -80,6 +81,7 @@ jobs: -x spotlessCheck -x checkstyleMain -x checkstyleTest + -x spotbugsMain backward-compatibility: runs-on: ubuntu-latest diff --git a/.github/workflows/code-hygiene.yml b/.github/workflows/code-hygiene.yml index 6691ee8a15..f078cb2b56 100644 --- a/.github/workflows/code-hygiene.yml +++ b/.github/workflows/code-hygiene.yml @@ -42,3 +42,18 @@ jobs: - uses: gradle/gradle-build-action@v2 with: arguments: checkstyleMain checkstyleTest + + spotbugs: + runs-on: ubuntu-latest + name: Spotbugs scan + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v2 + with: + distribution: temurin # Temurin is a distribution of adoptium + java-version: 11 + + - uses: gradle/gradle-build-action@v2 + with: + arguments: spotbugsMain diff --git a/build.gradle b/build.gradle index 4d2a94b624..bafa7de4ac 100644 --- a/build.gradle +++ b/build.gradle @@ -57,6 +57,7 @@ plugins { id 'nebula.ospackage' version "8.3.0" id "org.gradle.test-retry" version "1.3.1" id 'eclipse' + id "com.github.spotbugs" version "5.0.13" } allprojects { @@ -85,6 +86,14 @@ spotless { } } +spotbugs { + includeFilter = file('spotbugs-include.xml') +} + +spotbugsTest { + enabled = false +} + java.sourceCompatibility = JavaVersion.VERSION_11 java.targetCompatibility = JavaVersion.VERSION_11 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 cb9f6e9a8b..4ab5673adb 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 @@ -14,6 +14,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; @@ -152,7 +153,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 d6f59028fa..3ebabb14bb 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java @@ -419,7 +419,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); } @@ -430,7 +430,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); } @@ -457,7 +457,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/configuration/ConfigurationLoaderSecurity7.java b/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java index e7b759aefd..785a923bf8 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java @@ -27,6 +27,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; @@ -253,7 +254,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 8562ea20e7..d186b26869 100644 --- a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java +++ b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java @@ -20,6 +20,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; @@ -646,7 +647,7 @@ private boolean areSameCerts(final X509Certificate[] currentX509Certs, final X50 final Function certificateSignature = cert -> { final byte[] signature = cert !=null && cert.getSignature() != null ? cert.getSignature() : null; - return new String(signature); + return new String(signature, StandardCharsets.UTF_8); }; final Set currentCertSignatureSet = Arrays.stream(currentX509Certs) diff --git a/src/main/java/org/opensearch/security/support/ConfigHelper.java b/src/main/java/org/opensearch/security/support/ConfigHelper.java index a12a8d5614..23f93e3672 100644 --- a/src/main/java/org/opensearch/security/support/ConfigHelper.java +++ b/src/main/java/org/opensearch/security/support/ConfigHelper.java @@ -31,6 +31,7 @@ import java.io.IOException; import java.io.Reader; import java.io.StringReader; +import java.nio.charset.StandardCharsets; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -91,7 +92,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 FileReader(filepath, StandardCharsets.UTF_8); } else { reader = new StringReader(createEmptySdcYaml(cType, configVersion)); } @@ -143,7 +144,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 FileReader(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/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 4e89fd32de..ee4d8e72d7 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -896,7 +896,7 @@ private static boolean retrieveFile(final RestHighLevelClient restHighLevelClien } System.out.println("Will retrieve '"+"/" +id+"' into "+filepath+" "+(legacy?"(legacy mode)":"")); - try (Writer writer = new FileWriter(filepath)) { + try (Writer writer = new FileWriter(filepath, StandardCharsets.UTF_8)) { final GetResponse response = restHighLevelClient.get(new GetRequest(index).id(id).refresh(true).realtime(false), RequestOptions.DEFAULT); diff --git a/src/test/java/org/opensearch/security/IntegrationTests.java b/src/test/java/org/opensearch/security/IntegrationTests.java index 246fca03d9..226551a5ae 100644 --- a/src/test/java/org/opensearch/security/IntegrationTests.java +++ b/src/test/java/org/opensearch/security/IntegrationTests.java @@ -34,7 +34,6 @@ import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Assert; import org.junit.Assume; -import org.junit.Ignore; import org.junit.Test; import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest; @@ -270,7 +269,6 @@ public void testSingle() throws Exception { } @Test - @Ignore // https://github.com/opensearch-project/security/issues/2194 public void testSpecialUsernames() throws Exception { setup(); From 89a11c5a165d9fc1a5412a3c2369d3b27869b305 Mon Sep 17 00:00:00 2001 From: RAJ CHAKRAVARTHI <49325334+raj-chak@users.noreply.github.com> Date: Wed, 2 Nov 2022 13:30:25 -0400 Subject: [PATCH 069/369] roles yml changes for security-analytics plugin (#2192) * roles yml changes for security-analytics plugin Signed-off-by: Raj Chakravarthi Signed-off-by: Raj Chakravarthi <49325334+raj-chak@users.noreply.github.com> --- config/roles.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/config/roles.yml b/config/roles.yml index c96e8b27e9..1d081a5fd0 100644 --- a/config/roles.yml +++ b/config/roles.yml @@ -255,3 +255,38 @@ point_in_time_full_access: - '*' allowed_actions: - 'manage_point_in_time' + +# Allows users to see security analytics detectors and others +security_analytics_read_access: + reserved: true + cluster_permissions: + - 'cluster:admin/opensearch/securityanalytics/alerts/get' + - 'cluster:admin/opensearch/securityanalytics/detector/get' + - 'cluster:admin/opensearch/securityanalytics/detector/search' + - 'cluster:admin/opensearch/securityanalytics/findings/get' + - 'cluster:admin/opensearch/securityanalytics/mapping/get' + - 'cluster:admin/opensearch/securityanalytics/mapping/view/get' + - 'cluster:admin/opensearch/securityanalytics/rule/get' + - 'cluster:admin/opensearch/securityanalytics/rule/search' + +# Allows users to use all security analytics functionality +security_analytics_full_access: + reserved: true + cluster_permissions: + - 'cluster:admin/opensearch/securityanalytics/alerts/*' + - 'cluster:admin/opensearch/securityanalytics/detector/*' + - 'cluster:admin/opensearch/securityanalytics/findings/*' + - 'cluster:admin/opensearch/securityanalytics/mapping/*' + - 'cluster:admin/opensearch/securityanalytics/rule/*' + index_permissions: + - index_patterns: + - '*' + allowed_actions: + - 'indices:admin/mapping/put' + - 'indices:admin/mappings/get' + +# Allows users to view and acknowledge alerts +security_analytics_ack_alerts: + reserved: true + cluster_permissions: + - 'cluster:admin/opensearch/securityanalytics/alerts/*' From bd437553cc5206d0611935ffffde7fb2235546a4 Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Fri, 4 Nov 2022 16:14:30 +0100 Subject: [PATCH 070/369] Test extended to verify audit logs. (#2153) * Test extended to verify audit logs. * Retries added to all tests in location ./src/integrationTest/java/org/opensearch/security/** * Snapshot tests stability improved. An exception is not able to interrupt active waiting. Signed-off-by: Lukasz Soszynski Signed-off-by: Lukasz Soszynski <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> --- build.gradle | 6 +- .../security/SearchOperationTest.java | 310 +++++++++++++++++- .../opensearch/security/SnapshotSteps.java | 2 +- .../test/framework/AuditCompliance.java | 106 ++++++ .../test/framework/AuditConfiguration.java | 55 ++++ .../test/framework/AuditFilters.java | 121 +++++++ .../test/framework/TestSecurityConfig.java | 30 +- .../test/framework/audit/AuditLogsRule.java | 129 ++++++++ .../audit/AuditMessagePredicate.java | 154 +++++++++ .../framework/audit/TestRuleAuditLogSink.java | 51 +++ .../test/framework/cluster/LocalCluster.java | 14 + ...NumberOfAuditsFulfillPredicateMatcher.java | 44 +++ .../matcher/AuditMessageMatchers.java | 32 ++ .../AuditsFulfillPredicateMatcher.java | 36 ++ ...usterContainDocumentCountIndexMatcher.java | 43 +++ ...NumberOfAuditsFulfillPredicateMatcher.java | 43 +++ .../resources/log4j2-test.properties | 3 + 17 files changed, 1151 insertions(+), 28 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/test/framework/AuditCompliance.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/AuditConfiguration.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/AuditFilters.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/audit/AuditLogsRule.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/audit/AuditMessagePredicate.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/audit/TestRuleAuditLogSink.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/AtLeastCertainNumberOfAuditsFulfillPredicateMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/AuditMessageMatchers.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/AuditsFulfillPredicateMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainDocumentCountIndexMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ExactNumberOfAuditsFulfillPredicateMatcher.java diff --git a/build.gradle b/build.gradle index bafa7de4ac..927124723c 100644 --- a/build.gradle +++ b/build.gradle @@ -294,7 +294,11 @@ task integrationTest(type: Test) { systemProperty "java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager" testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath - + retry { + failOnPassedAfterRetry = false + maxRetries = 2 + maxFailures = 10 + } //run the integrationTest task after the test task shouldRunAfter test } diff --git a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java index b51280efed..a6702344b8 100644 --- a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java +++ b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java @@ -26,6 +26,7 @@ import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -95,8 +96,12 @@ import org.opensearch.repositories.RepositoryMissingException; import org.opensearch.rest.RestStatus; import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.test.framework.AuditCompliance; +import org.opensearch.test.framework.AuditConfiguration; +import org.opensearch.test.framework.AuditFilters; import org.opensearch.test.framework.TestSecurityConfig.Role; import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.audit.AuditLogsRule; import org.opensearch.test.framework.cluster.ClusterManager; import org.opensearch.test.framework.cluster.LocalCluster; @@ -118,6 +123,10 @@ import static org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions.Type.REMOVE; import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; import static org.opensearch.client.RequestOptions.DEFAULT; +import static org.opensearch.rest.RestRequest.Method.DELETE; +import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.rest.RestRequest.Method.POST; +import static org.opensearch.rest.RestRequest.Method.PUT; import static org.opensearch.rest.RestStatus.ACCEPTED; import static org.opensearch.rest.RestStatus.FORBIDDEN; import static org.opensearch.rest.RestStatus.INTERNAL_SERVER_ERROR; @@ -132,8 +141,13 @@ import static org.opensearch.security.Song.TITLE_NEXT_SONG; import static org.opensearch.security.Song.TITLE_POISON; import static org.opensearch.security.Song.TITLE_SONG_1_PLUS_1; +import static org.opensearch.security.auditlog.impl.AuditCategory.INDEX_EVENT; import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; +import static org.opensearch.test.framework.audit.AuditMessagePredicate.auditPredicate; +import static org.opensearch.test.framework.audit.AuditMessagePredicate.grantedPrivilege; +import static org.opensearch.test.framework.audit.AuditMessagePredicate.missingPrivilege; +import static org.opensearch.test.framework.audit.AuditMessagePredicate.userAuthenticated; import static org.opensearch.test.framework.cluster.SearchRequestFactory.averageAggregationRequest; import static org.opensearch.test.framework.cluster.SearchRequestFactory.getSearchScrollRequest; import static org.opensearch.test.framework.cluster.SearchRequestFactory.queryStringQueryRequest; @@ -293,11 +307,15 @@ public class SearchOperationTest { public static final LocalCluster cluster = new LocalCluster.Builder() .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) .authc(AUTHC_HTTPBASIC_INTERNAL) - .users( - ADMIN_USER, LIMITED_READ_USER, LIMITED_WRITE_USER, DOUBLE_READER_USER, REINDEXING_USER, UPDATE_DELETE_USER, - USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES - ) - .build(); + .users(ADMIN_USER, LIMITED_READ_USER, LIMITED_WRITE_USER, DOUBLE_READER_USER, REINDEXING_USER, UPDATE_DELETE_USER, + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES) + .audit(new AuditConfiguration(true) + .compliance(new AuditCompliance().enabled(true)) + .filters(new AuditFilters().enabledRest(true).enabledTransport(true)) + ).build(); + + @Rule + public AuditLogsRule auditLogsRule = new AuditLogsRule(); @BeforeClass public static void createTestData() { @@ -340,7 +358,7 @@ public void cleanData() throws ExecutionException, InterruptedException { if (indicesExistsResponse.isExists()) { DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexToBeDeleted); indices.delete(deleteIndexRequest).actionGet(); - Awaitility.await().until(() -> indices.exists(indicesExistsRequest).get().isExists() == false); + Awaitility.await().ignoreExceptions().until(() -> indices.exists(indicesExistsRequest).get().isExists() == false); } } @@ -378,6 +396,8 @@ public void shouldSearchForDocuments_positive() throws IOException { assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); } @Test @@ -387,6 +407,8 @@ public void shouldSearchForDocuments_negative() throws IOException { assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); } @Test @@ -401,6 +423,8 @@ public void shouldSearchForDocumentsViaAlias_positive() throws IOException { assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics_index_alias/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); } @Test @@ -410,6 +434,8 @@ public void shouldSearchForDocumentsViaAlias_negative() throws IOException { assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics_index_alias/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); } @Test @@ -424,6 +450,19 @@ public void shouldBeAbleToSearchSongViaMultiIndexAlias_positive() throws IOExcep assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S3)); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); } + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/collective-index-alias/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(DOUBLE_READER_USER, "SearchRequest")); + } + + @Test + public void shouldBeAbleToSearchSongViaMultiIndexAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(COLLECTIVE_INDEX_ALIAS, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/collective-index-alias/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); } @Test @@ -438,15 +477,8 @@ public void shouldBeAbleToSearchAllIndexes_positive() throws IOException { assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); } - } - - @Test - public void shouldBeAbleToSearchSongViaMultiIndexAlias_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(COLLECTIVE_INDEX_ALIAS, QUERY_TITLE_POISON); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } + auditLogsRule.assertExactlyOne(userAuthenticated(ADMIN_USER).withRestRequest(POST, "/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(ADMIN_USER, "SearchRequest")); } @Test @@ -456,8 +488,9 @@ public void shouldBeAbleToSearchAllIndexes_negative() throws IOException { assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); } - @Test public void shouldBeAbleToSearchSongIndexesWithAsterisk_prohibitedSongIndex_positive() throws IOException { try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { @@ -470,6 +503,8 @@ public void shouldBeAbleToSearchSongIndexesWithAsterisk_prohibitedSongIndex_posi assertThat(searchResponse, searchHitsContainDocumentWithId(0, PROHIBITED_SONG_INDEX_NAME, ID_P4)); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_POISON)); } + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/*song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(DOUBLE_READER_USER, "SearchRequest")); } @Test @@ -484,6 +519,8 @@ public void shouldBeAbleToSearchSongIndexesWithAsterisk_singIndex_positive() thr assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S3)); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); } + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/*song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(DOUBLE_READER_USER, "SearchRequest")); } @Test @@ -493,6 +530,8 @@ public void shouldBeAbleToSearchSongIndexesWithAsterisk_negative() throws IOExce assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/*song_lyrics/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); } @Test @@ -513,6 +552,8 @@ public void shouldFindSongUsingDslQuery_positive() throws IOException { assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); } @Test @@ -528,6 +569,8 @@ public void shouldFindSongUsingDslQuery_negative() throws IOException { assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); } @Test @@ -542,6 +585,8 @@ public void shouldPerformSearchWithAllIndexAlias_positive() throws IOException { assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); } + auditLogsRule.assertExactlyOne(userAuthenticated(ADMIN_USER).withRestRequest(POST, "/_all/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(ADMIN_USER, "SearchRequest")); } @Test @@ -551,6 +596,8 @@ public void shouldPerformSearchWithAllIndexAlias_negative() throws IOException { assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_all/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); } @Test @@ -569,6 +616,10 @@ public void shouldScrollOverSearchResults_positive() throws IOException { assertThat(scrollResponse, numberOfTotalHitsIsEqualTo(3)); assertThat(scrollResponse, numberOfHitsInPageIsEqualTo(1)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_search/scroll")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchScrollRequest")); } @Test @@ -583,6 +634,10 @@ public void shouldScrollOverSearchResults_negative() throws IOException { assertThatThrownBy(() -> restHighLevelClient.scroll(scrollRequest, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(DOUBLE_READER_USER, "SearchRequest")); + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/_search/scroll")); + auditLogsRule.assertExactlyOne(missingPrivilege(DOUBLE_READER_USER, "SearchScrollRequest")); } @Test @@ -593,6 +648,8 @@ public void shouldGetDocument_positive() throws IOException { assertThat(response, containDocument(SONG_INDEX_NAME, ID_S1)); assertThat(response, documentContainField(FIELD_TITLE, TITLE_MAGNUM_OPUS)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/song_lyrics/_doc/1")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "GetRequest")); } @Test @@ -601,6 +658,8 @@ public void shouldGetDocument_negative() throws IOException { GetRequest getRequest = new GetRequest(PROHIBITED_SONG_INDEX_NAME, ID_P4); assertThatThrownBy(() -> restHighLevelClient.get(getRequest, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/prohibited_song_lyrics/_doc/4")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "GetRequest")); } @Test @@ -627,6 +686,9 @@ public void shouldPerformMultiGetDocuments_positive() throws IOException { documentContainField(FIELD_TITLE, TITLE_SONG_1_PLUS_1)) ); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_mget")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiGetRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiGetShardRequest")); } @Test @@ -637,6 +699,8 @@ public void shouldPerformMultiGetDocuments_negative() throws IOException { assertThatThrownBy(() -> restHighLevelClient.mget(request, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/_mget")); + auditLogsRule.assertExactlyOne(missingPrivilege(DOUBLE_READER_USER, "MultiGetRequest")); } @Test @@ -658,6 +722,10 @@ public void shouldPerformMultiGetDocuments_partiallyPositive() throws IOExceptio assertThat(responses[1].getFailure().getFailure(), statusException(INTERNAL_SERVER_ERROR)); assertThat(responses[1].getFailure().getFailure(), errorMessageContain("security_exception")); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_mget")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiGetRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiGetShardRequest").withIndex(SONG_INDEX_NAME)); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "MultiGetShardRequest").withIndex(PROHIBITED_SONG_INDEX_NAME)); } @Test @@ -684,6 +752,9 @@ public void shouldBeAllowedToPerformMulitSearch_positive() throws IOException { assertThat(responses[1].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); assertThat(responses[1].getResponse(), searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S3)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_msearch")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiSearchRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); } @Test @@ -704,6 +775,10 @@ public void shouldBeAllowedToPerformMulitSearch_partiallyPositive() throws IOExc assertThat(responses[1].getFailure(), errorMessageContain("security_exception")); assertThat(responses[1].getResponse(), nullValue()); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_msearch")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiSearchRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest").withIndex(SONG_INDEX_NAME)); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest").withIndex(PROHIBITED_SONG_INDEX_NAME)); } @Test @@ -715,6 +790,8 @@ public void shouldBeAllowedToPerformMulitSearch_negative() throws IOException { assertThatThrownBy(() -> restHighLevelClient.msearch(request, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/_msearch")); + auditLogsRule.assertExactlyOne(missingPrivilege(DOUBLE_READER_USER, "MultiSearchRequest")); } @Test @@ -728,6 +805,8 @@ public void shouldAggregateDataAndComputeAverage_positive() throws IOException { assertThat(searchResponse, isSuccessfulSearchResponse()); assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest").withIndex(SONG_INDEX_NAME)); } @Test @@ -737,6 +816,8 @@ public void shouldAggregateDataAndComputeAverage_negative() throws IOException { assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest").withIndex(PROHIBITED_SONG_INDEX_NAME)); } @Test @@ -750,6 +831,8 @@ public void shouldPerformStatAggregation_positive() throws IOException { assertThat(searchResponse, isSuccessfulSearchResponse()); assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "stats")); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); } @Test @@ -759,6 +842,8 @@ public void shouldPerformStatAggregation_negative() throws IOException { assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); } @Test @@ -777,6 +862,11 @@ public void shouldIndexDocumentInBulkRequest_positive() throws IOException { assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "one", FIELD_TITLE, TITLE_MAGNUM_OPUS)); assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertAtLeast(4, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER));//sometimes 4 or 6 + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest"));//sometimes 2 or 4 } @Test @@ -796,6 +886,12 @@ public void shouldIndexDocumentInBulkRequest_partiallyPositive() throws IOExcept assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, "two")); assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest").withIndex(SONG_INDEX_NAME)); } @Test @@ -816,6 +912,9 @@ public void shouldIndexDocumentInBulkRequest_negative() throws IOException { assertThat(internalClient, not(clusterContainsDocument(SONG_INDEX_NAME, "one"))); assertThat(internalClient, not(clusterContainsDocument(SONG_INDEX_NAME, "two"))); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest").withIndex(SONG_INDEX_NAME)); } @Test @@ -837,6 +936,12 @@ public void shouldUpdateDocumentsInBulk_positive() throws IOException { assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "one", FIELD_TITLE, titleOne)); assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, titleTwo)); } + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + } @Test @@ -859,6 +964,12 @@ public void shouldUpdateDocumentsInBulk_partiallyPositive() throws IOException { assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "one", FIELD_TITLE, titleOne)); assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S2, FIELD_TITLE, TITLE_SONG_1_PLUS_1)); } + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest").withIndex(SONG_INDEX_NAME)); } @Test @@ -878,6 +989,9 @@ public void shouldUpdateDocumentsInBulk_negative() throws IOException { assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S1, FIELD_TITLE, TITLE_MAGNUM_OPUS)); assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S2, FIELD_TITLE, TITLE_SONG_1_PLUS_1)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest")); } @Test @@ -901,6 +1015,11 @@ public void shouldDeleteDocumentInBulk_positive() throws IOException { assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "four", FIELD_TITLE, TITLE_POISON)); } + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); } @Test @@ -924,6 +1043,12 @@ public void shouldDeleteDocumentInBulk_partiallyPositive() throws IOException { assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S3, FIELD_TITLE, TITLE_NEXT_SONG)); } + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest")); } @Test @@ -943,6 +1068,10 @@ public void shouldDeleteDocumentInBulk_negative() throws IOException { assertThat(internalClient, clusterContainsDocument(SONG_INDEX_NAME, ID_S1)); assertThat(internalClient, clusterContainsDocument(SONG_INDEX_NAME, ID_S3)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest")); + } @Test @@ -959,6 +1088,15 @@ public void shouldReindexDocuments_positive() throws IOException { assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_S2)); assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_S3)); } + auditLogsRule.assertExactlyOne(userAuthenticated(REINDEXING_USER).withRestRequest(POST, "/_reindex")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "ReindexRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "SearchRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(REINDEXING_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(REINDEXING_USER, "PutMappingRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "SearchScrollRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(REINDEXING_USER)); + auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "ClearScrollRequest")); } @Test @@ -969,6 +1107,9 @@ public void shouldReindexDocuments_negativeSource() throws IOException { assertThatThrownBy(() -> restHighLevelClient.reindex(reindexRequest, DEFAULT), statusException(FORBIDDEN)); assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_P4))); } + auditLogsRule.assertExactlyOne(userAuthenticated(REINDEXING_USER).withRestRequest(POST, "/_reindex")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "ReindexRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "SearchRequest")); } @Test @@ -981,6 +1122,12 @@ public void shouldReindexDocuments_negativeDestination() throws IOException { assertThat(internalClient, not(clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_S2))); assertThat(internalClient, not(clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_S3))); } + auditLogsRule.assertExactlyOne(userAuthenticated(REINDEXING_USER).withRestRequest(POST, "/_reindex")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "ReindexRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "SearchRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "BulkRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "BulkShardRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "ClearScrollRequest")); } @Test @@ -990,6 +1137,9 @@ public void shouldReindexDocuments_negativeSourceAndDestination() throws IOExcep assertThatThrownBy(() -> restHighLevelClient.reindex(reindexRequest, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(REINDEXING_USER).withRestRequest(POST, "/_reindex")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "ReindexRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "SearchRequest")); } @Test @@ -1054,6 +1204,9 @@ public void shouldCreateAlias_positive() throws IOException { assertThat(response.isAcknowledged(), equalTo(true)); assertThat(internalClient, clusterContainsDocument(TEMPORARY_ALIAS_NAME, ID_S1)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_aliases")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_READ_USER, "IndicesAliasesRequest")); + auditLogsRule.assertExactly(2, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_READ_USER)); } @Test @@ -1066,6 +1219,8 @@ public void shouldCreateAlias_negative() throws IOException { assertThat(internalClient, not(clusterContainsDocument(TEMPORARY_ALIAS_NAME, ID_P4))); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_aliases")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "IndicesAliasesRequest")); } @Test @@ -1083,6 +1238,9 @@ public void shouldDeleteAlias_positive() throws IOException { assertThat(response.isAcknowledged(), equalTo(true)); assertThat(internalClient, not(clusterContainsDocument(TEMPORARY_ALIAS_NAME, ID_S1))); } + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_aliases")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_READ_USER, "IndicesAliasesRequest")); + auditLogsRule.assertExactly(4, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_READ_USER)); } @Test @@ -1095,6 +1253,8 @@ public void shouldDeleteAlias_negative() throws IOException { assertThat(internalClient, clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_P4)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_aliases")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "IndicesAliasesRequest")); } @Test @@ -1118,6 +1278,14 @@ public void shouldCreateIndexTemplate_positive() throws IOException { assertThat(internalClient, clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, documentId)); assertThat(internalClient, clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002, documentId)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_template/musical-index-template")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/song-transcription-jazz/_doc/0001")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutIndexTemplateRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "IndexRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(8, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); } @Test @@ -1131,6 +1299,8 @@ public void shouldCreateIndexTemplate_negative() throws IOException { assertThatThrownBy(() -> restHighLevelClient.indices().putTemplate(request, DEFAULT), statusException(FORBIDDEN)); assertThat(internalClient, not(clusterContainTemplate(MUSICAL_INDEX_TEMPLATE ))); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(PUT, "/_template/musical-index-template")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "PutIndexTemplateRequest")); } @Test @@ -1148,6 +1318,11 @@ public void shouldDeleteTemplate_positive() throws IOException { assertThat(response.isAcknowledged(), equalTo(true)); assertThat(internalClient, not(clusterContainTemplate(MUSICAL_INDEX_TEMPLATE))); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_template/musical-index-template")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(DELETE, "/_template/musical-index-template")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutIndexTemplateRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "DeleteIndexTemplateRequest")); + auditLogsRule.assertExactly(4, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); } @Test @@ -1159,6 +1334,8 @@ public void shouldDeleteTemplate_negative() throws IOException { assertThat(internalClient, clusterContainTemplate(UNDELETABLE_TEMPLATE_NAME)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(DELETE, "/_template/undeletable-template-name")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "DeleteIndexTemplateRequest")); } @Test @@ -1187,6 +1364,14 @@ public void shouldUpdateTemplate_positive() throws IOException { assertThat(internalClient, not(clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, documentId))); assertThat(internalClient, not(clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002, documentId))); } + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_template/musical-index-template")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/song-transcription-jazz/_doc/000one")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutIndexTemplateRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "IndexRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(10, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); } @Test public void shouldUpdateTemplate_negative() throws IOException { @@ -1199,6 +1384,8 @@ public void shouldUpdateTemplate_negative() throws IOException { assertThat(internalClient, clusterContainTemplateWithAlias(UNDELETABLE_TEMPLATE_NAME, ALIAS_FROM_UNDELETABLE_TEMPLATE)); assertThat(internalClient, not(clusterContainTemplateWithAlias(UNDELETABLE_TEMPLATE_NAME, ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003))); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(PUT, "/_template/undeletable-template-name")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "PutIndexTemplateRequest")); } @Test @@ -1214,6 +1401,9 @@ public void shouldGetFieldCapabilitiesForAllIndexes_positive() throws IOExceptio assertThat(response.getField(FIELD_TITLE), hasKey("text")); assertThat(response.getIndices(), arrayContainingInAnyOrder(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME, UPDATE_DELETE_OPERATION_INDEX_NAME)); } + auditLogsRule.assertExactlyOne(userAuthenticated(ADMIN_USER).withRestRequest(GET, "/_field_caps")); + auditLogsRule.assertExactlyOne(grantedPrivilege(ADMIN_USER, "FieldCapabilitiesRequest")); + auditLogsRule.assertExactly(3, grantedPrivilege(ADMIN_USER, "FieldCapabilitiesIndexRequest")); } @Test @@ -1223,6 +1413,8 @@ public void shouldGetFieldCapabilitiesForAllIndexes_negative() throws IOExceptio assertThatThrownBy(() -> restHighLevelClient.fieldCaps(request, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/_field_caps")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "FieldCapabilitiesRequest")); } @Test @@ -1238,6 +1430,9 @@ public void shouldGetFieldCapabilitiesForParticularIndex_positive() throws IOExc assertThat(response.getField(FIELD_TITLE), hasKey("text")); assertThat(response.getIndices(), arrayContainingInAnyOrder(SONG_INDEX_NAME)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/song_lyrics/_field_caps")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "FieldCapabilitiesRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "FieldCapabilitiesIndexRequest")); } @Test @@ -1247,6 +1442,8 @@ public void shouldGetFieldCapabilitiesForParticularIndex_negative() throws IOExc assertThatThrownBy(() -> restHighLevelClient.fieldCaps(request, DEFAULT), statusException(FORBIDDEN)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/prohibited_song_lyrics/_field_caps")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "FieldCapabilitiesRequest")); } @Test @@ -1261,6 +1458,8 @@ public void shouldCreateSnapshotRepository_positive() throws IOException { assertThat(response.isAcknowledged(), equalTo(true)); assertThat(internalClient, clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); } @Test @@ -1272,6 +1471,8 @@ public void shouldCreateSnapshotRepository_negative() throws IOException { assertThatThrownBy(() -> steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotDirPath, "fs"), statusException(FORBIDDEN)); assertThat(internalClient, not(clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME))); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "PutRepositoryRequest")); } @Test @@ -1287,6 +1488,10 @@ public void shouldDeleteSnapshotRepository_positive() throws IOException { assertThat(response.isAcknowledged(), equalTo(true)); assertThat(internalClient, not(clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME))); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(DELETE, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "DeleteRepositoryRequest")); } @Test @@ -1297,9 +1502,11 @@ public void shouldDeleteSnapshotRepository_negative() throws IOException { assertThatThrownBy(() -> steps.deleteSnapshotRepository(UNUSED_SNAPSHOT_REPOSITORY_NAME), statusException(FORBIDDEN)); assertThat(internalClient, clusterContainsSnapshotRepository(UNUSED_SNAPSHOT_REPOSITORY_NAME)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(DELETE, "/_snapshot/unused-snapshot-repository")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "DeleteRepositoryRequest")); } - @Test + @Test //Bug which can be reproduced with the below test: https://github.com/opensearch-project/security/issues/2169 public void shouldCreateSnapshot_positive() throws IOException { final String snapshotName = "snapshot-positive-test"; try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { @@ -1313,6 +1520,12 @@ public void shouldCreateSnapshot_positive() throws IOException { steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); assertThat(internalClient, clusterContainSuccessSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/snapshot-positive-test")); + auditLogsRule.assertAtLeast(1, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/snapshot-positive-test")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); } @Test @@ -1325,6 +1538,8 @@ public void shouldCreateSnapshot_negative() throws IOException { assertThat(internalClient, snapshotInClusterDoesNotExists(UNUSED_SNAPSHOT_REPOSITORY_NAME, snapshotName)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(PUT, "/_snapshot/unused-snapshot-repository/snapshot-negative-test")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "CreateSnapshotRequest")); } @Test @@ -1342,6 +1557,14 @@ public void shouldDeleteSnapshot_positive() throws IOException { assertThat(response.isAcknowledged(), equalTo(true)); assertThat(internalClient, snapshotInClusterDoesNotExists(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/delete-snapshot-positive")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(DELETE, "/_snapshot/test-snapshot-repository/delete-snapshot-positive")); + auditLogsRule.assertAtLeast(1, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/delete-snapshot-positive")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "DeleteSnapshotRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); } @Test @@ -1359,6 +1582,14 @@ public void shouldDeleteSnapshot_negative() throws IOException { assertThat(internalClient, clusterContainSuccessSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName)); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/delete-snapshot-negative")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(DELETE, "/_snapshot/test-snapshot-repository/delete-snapshot-negative")); + auditLogsRule.assertAtLeast(1, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/delete-snapshot-negative")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); + auditLogsRule.assertExactly(1, missingPrivilege(LIMITED_READ_USER, "DeleteSnapshotRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); } @Test @@ -1398,7 +1629,8 @@ public void shouldRestoreSnapshot_positive() throws IOException { // 7. wait until snapshot is restored CountRequest countRequest = new CountRequest(RESTORED_SONG_INDEX_NAME); - Awaitility.await().until(() -> restHighLevelClient.count(countRequest, DEFAULT).getCount() == 2); + Awaitility.await().ignoreExceptions().alias("Index contains proper number of documents restored from snapshot.") + .until(() -> restHighLevelClient.count(countRequest, DEFAULT).getCount() == 2); //8. verify that document are present in restored index assertThat(internalClient, clusterContainsDocumentWithFieldValue(RESTORED_SONG_INDEX_NAME, "Eins", FIELD_TITLE, TITLE_MAGNUM_OPUS)); @@ -1406,6 +1638,21 @@ public void shouldRestoreSnapshot_positive() throws IOException { assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Drei"))); assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Vier"))); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/restore-snapshot-positive")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_snapshot/test-snapshot-repository/restore-snapshot-positive/_restore")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/restored_write_song_index/_count")); + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertAtLeast(1, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/restore-snapshot-positive")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "RestoreSnapshotRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "SearchRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); } @Test @@ -1439,6 +1686,20 @@ public void shouldRestoreSnapshot_failureForbiddenIndex() throws IOException { assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Eins"))); assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Zwei"))); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-index")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-index/_restore")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertAtLeast(1, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-index")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "RestoreSnapshotRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "RestoreSnapshotRequest")); } @Test @@ -1472,6 +1733,19 @@ public void shouldRestoreSnapshot_failureOperationForbidden() throws IOException assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Eins"))); assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Zwei"))); } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-operation")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-operation/_restore")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertAtLeast(1, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-operation")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(1, missingPrivilege(LIMITED_READ_USER, "RestoreSnapshotRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); } @Test diff --git a/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java b/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java index 04155e762f..0fbaf78435 100644 --- a/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java +++ b/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java @@ -56,7 +56,7 @@ public CreateSnapshotResponse createSnapshot(String repositoryName, String snaps public void waitForSnapshotCreation(String repositoryName, String snapshotName) { GetSnapshotsRequest getSnapshotsRequest = new GetSnapshotsRequest(repositoryName, new String[] { snapshotName }); - Awaitility.await().until(() -> { + Awaitility.await().alias("wait for snapshot creation").ignoreExceptions().until(() -> { GetSnapshotsResponse snapshotsResponse = snapshotClient.get(getSnapshotsRequest, DEFAULT); SnapshotInfo snapshotInfo = snapshotsResponse.getSnapshots().get(0); return SnapshotState.SUCCESS.equals(snapshotInfo.state()); diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuditCompliance.java b/src/integrationTest/java/org/opensearch/test/framework/AuditCompliance.java new file mode 100644 index 0000000000..fdf3cdd6f1 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/AuditCompliance.java @@ -0,0 +1,106 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; + +public class AuditCompliance implements ToXContentObject { + + private boolean enabled = false; + + private Boolean writeLogDiffs; + + private List readIgnoreUsers; + + private List writeWatchedIndices; + + private List writeIgnoreUsers; + + private Boolean readMetadataOnly; + + private Boolean writeMetadataOnly; + + private Boolean externalConfig; + + private Boolean internalConfig; + + public AuditCompliance enabled(boolean enabled) { + this.enabled = enabled; + this.writeLogDiffs = false; + this.readIgnoreUsers = Collections.emptyList(); + this.writeWatchedIndices = Collections.emptyList(); + this.writeIgnoreUsers = Collections.emptyList(); + this.readMetadataOnly = false; + this.writeMetadataOnly = false; + this.externalConfig = false; + this.internalConfig = false; + return this; + } + + public AuditCompliance writeLogDiffs(boolean writeLogDiffs) { + this.writeLogDiffs = writeLogDiffs; + return this; + } + + public AuditCompliance readIgnoreUsers(List list) { + this.readIgnoreUsers = list; + return this; + } + + public AuditCompliance writeWatchedIndices(List list) { + this.writeWatchedIndices = list; + return this; + } + + public AuditCompliance writeIgnoreUsers(List list) { + this.writeIgnoreUsers = list; + return this; + } + + public AuditCompliance readMetadataOnly(boolean readMetadataOnly) { + this.readMetadataOnly = readMetadataOnly; + return this; + } + + public AuditCompliance writeMetadataOnly(boolean writeMetadataOnly) { + this.writeMetadataOnly = writeMetadataOnly; + return this; + } + + public AuditCompliance externalConfig(boolean externalConfig) { + this.externalConfig = externalConfig; + return this; + } + + public AuditCompliance internalConfig(boolean internalConfig) { + this.internalConfig = internalConfig; + return this; + } + + @Override public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("enabled", enabled); + xContentBuilder.field("write_log_diffs", writeLogDiffs); + xContentBuilder.field("read_ignore_users", readIgnoreUsers); + xContentBuilder.field("write_watched_indices", writeWatchedIndices); + xContentBuilder.field("write_ignore_users", writeIgnoreUsers); + xContentBuilder.field("read_metadata_only", readMetadataOnly); + xContentBuilder.field("write_metadata_only", writeMetadataOnly); + xContentBuilder.field("external_config", externalConfig); + xContentBuilder.field("internal_config", internalConfig); + xContentBuilder.endObject(); + return xContentBuilder; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuditConfiguration.java b/src/integrationTest/java/org/opensearch/test/framework/AuditConfiguration.java new file mode 100644 index 0000000000..762b27085a --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/AuditConfiguration.java @@ -0,0 +1,55 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework; + +import java.io.IOException; + +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; + +public class AuditConfiguration implements ToXContentObject { + private final boolean enabled; + + private AuditFilters filters; + + private AuditCompliance compliance; + + public AuditConfiguration(boolean enabled) { + this.filters = new AuditFilters(); + this.compliance = new AuditCompliance(); + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public AuditConfiguration filters(AuditFilters filters) { + this.filters = filters; + return this; + } + + public AuditConfiguration compliance(AuditCompliance auditCompliance) { + this.compliance = auditCompliance; + return this; + } + + @Override public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + // json built here must be deserialized to org.opensearch.security.auditlog.config.AuditConfig + xContentBuilder.startObject(); + xContentBuilder.field("enabled", enabled); + + xContentBuilder.field("audit", filters); + xContentBuilder.field("compliance", compliance); + + xContentBuilder.endObject(); + return xContentBuilder; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuditFilters.java b/src/integrationTest/java/org/opensearch/test/framework/AuditFilters.java new file mode 100644 index 0000000000..24dc3449b9 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/AuditFilters.java @@ -0,0 +1,121 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; + +public class AuditFilters implements ToXContentObject { + + private Boolean enabledRest; + + private Boolean enabledTransport; + + private Boolean logRequestBody; + + private Boolean resolveIndices; + + private Boolean resolveBulkRequests; + + private Boolean excludeSensitiveHeaders; + + private List ignoreUsers; + + private List ignoreRequests; + + private List disabledRestCategories; + + private List disabledTransportCategories; + + public AuditFilters() { + this.enabledRest = false; + this.enabledTransport = false; + + this.logRequestBody = true; + this.resolveIndices = true; + this.resolveBulkRequests = false; + this.excludeSensitiveHeaders = true; + + this.ignoreUsers = Collections.emptyList(); + this.ignoreRequests = Collections.emptyList(); + this.disabledRestCategories = Collections.emptyList(); + this.disabledTransportCategories = Collections.emptyList(); + } + + public AuditFilters enabledRest(boolean enabled) { + this.enabledRest = enabled; + return this; + } + + public AuditFilters enabledTransport(boolean enabled) { + this.enabledTransport = enabled; + return this; + } + + public AuditFilters logRequestBody(boolean logRequestBody) { + this.logRequestBody = logRequestBody; + return this; + } + + public AuditFilters resolveIndices(boolean resolveIndices) { + this.resolveIndices = resolveIndices; + return this; + } + + public AuditFilters resolveBulkRequests(boolean resolveBulkRequests) { + this.resolveBulkRequests = resolveBulkRequests; + return this; + } + + public AuditFilters excludeSensitiveHeaders(boolean excludeSensitiveHeaders) { + this.excludeSensitiveHeaders = excludeSensitiveHeaders; + return this; + } + + public AuditFilters ignoreUsers(List ignoreUsers) { + this.ignoreUsers = ignoreUsers; + return this; + } + + public AuditFilters ignoreRequests(List ignoreRequests) { + this.ignoreRequests = ignoreRequests; + return this; + } + + public AuditFilters disabledRestCategories(List disabledRestCategories) { + this.disabledRestCategories = disabledRestCategories; + return this; + } + + public AuditFilters disabledTransportCategories(List disabledTransportCategories) { + this.disabledTransportCategories = disabledTransportCategories; + return this; + } + + @Override public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("enable_rest", enabledRest); + xContentBuilder.field("enable_transport", enabledTransport); + xContentBuilder.field("resolve_indices", resolveIndices); + xContentBuilder.field("log_request_body", logRequestBody); + xContentBuilder.field("resolve_bulk_requests", resolveBulkRequests); + xContentBuilder.field("exclude_sensitive_headers", excludeSensitiveHeaders); + xContentBuilder.field("ignore_users", ignoreUsers); + xContentBuilder.field("ignore_requests", ignoreRequests); + xContentBuilder.field("disabled_rest_categories", disabledRestCategories); + xContentBuilder.field("disabled_transport_categories", disabledTransportCategories); + xContentBuilder.endObject(); + return xContentBuilder; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index b0eea6c336..1fd93be169 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -81,6 +81,8 @@ public class TestSecurityConfig { private Map internalUsers = new LinkedHashMap<>(); private Map roles = new LinkedHashMap<>(); + private AuditConfiguration auditConfiguration; + private String indexName = ".opendistro_security"; public TestSecurityConfig() { @@ -119,6 +121,11 @@ public TestSecurityConfig roles(Role... roles) { return this; } + public TestSecurityConfig audit(AuditConfiguration auditConfiguration) { + this.auditConfiguration = auditConfiguration; + return this; + } + public static class Config implements ToXContentObject { private boolean anonymousAuth; private Map authcDomainMap = new LinkedHashMap<>(); @@ -157,9 +164,9 @@ public static class User implements UserCredentialsHolder, ToXContentObject { public final static TestSecurityConfig.User USER_ADMIN = new TestSecurityConfig.User("admin") .roles(new Role("allaccess").indexPermissions("*").on("*").clusterPermissions("*")); - private String name; + String name; private String password; - private List roles = new ArrayList<>(); + List roles = new ArrayList<>(); private Map attributes = new HashMap<>(); public User(String name) { @@ -174,8 +181,8 @@ public User password(String password) { public User roles(Role... roles) { // We scope the role names by user to keep tests free of potential side effects - String roleNamePrefix = "user_" + this.name + "__"; - this.roles.addAll(Arrays.asList(roles).stream().map((r) -> r.clone().name(roleNamePrefix + r.name)).collect(Collectors.toSet())); + String roleNamePrefix = "user_" + this.getName() + "__"; + this.roles.addAll(Arrays.asList(roles).stream().map((r) -> r.clone().name(roleNamePrefix + r.getName())).collect(Collectors.toSet())); return this; } @@ -494,12 +501,15 @@ public void initIndex(Client client) { client.admin().indices().create(new CreateIndexRequest(indexName).settings(settings)).actionGet(); writeSingleEntryConfigToIndex(client, CType.CONFIG, config); + if(auditConfiguration != null) { + writeSingleEntryConfigToIndex(client, CType.AUDIT, "config", auditConfiguration); + } writeConfigToIndex(client, CType.ROLES, roles); writeConfigToIndex(client, CType.INTERNALUSERS, internalUsers); writeEmptyConfigToIndex(client, CType.ROLESMAPPING); writeEmptyConfigToIndex(client, CType.ACTIONGROUPS); writeEmptyConfigToIndex(client, CType.TENANTS); - + ConfigUpdateResponse configUpdateResponse = client.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0]))).actionGet(); @@ -509,7 +519,7 @@ public void initIndex(Client client) { } - private static String hash(final char[] clearTextPassword) { + static String hash(final char[] clearTextPassword) { final byte[] salt = new byte[16]; new SecureRandom().nextBytes(salt); final String hash = OpenBSDBCrypt.generate((Objects.requireNonNull(clearTextPassword)), salt, 12); @@ -552,6 +562,10 @@ private void writeConfigToIndex(Client client, CType configType, Map currentTestAuditMessages; + + public void waitForAuditLogs() { + try { + TimeUnit.SECONDS.sleep(3); + afterWaitingForAuditLogs(); + } catch (InterruptedException e) { + throw new RuntimeException("Waiting for audit logs interrupted.", e); + } + } + + private void afterWaitingForAuditLogs() { + if(log.isDebugEnabled()) { + log.debug("Audit records captured during test:\n{}", auditMessagesToString(currentTestAuditMessages)); + } + } + + public void assertExactlyOne(Predicate predicate) { + assertExactly(1, predicate); + } + + public void assertAuditLogsCount(int from, int to) { + int actualCount = currentTestAuditMessages.size(); + String message = "Expected audit log count is between " + from + " and " + to + " but was " + actualCount; + assertThat(message, actualCount, allOf(greaterThanOrEqualTo(from), lessThanOrEqualTo(to))); + } + + public void assertExactly(long expectedNumberOfAuditMessages, Predicate predicate) { + assertExactly(exactNumberOfAuditsFulfillPredicate(expectedNumberOfAuditMessages, predicate)); + } + + private void assertExactly(Matcher> matcher) { + //pollDelay - initial delay before first evaluation + Awaitility.await("Await for audit logs") + .atMost(3, TimeUnit.SECONDS).pollDelay(0, TimeUnit.MICROSECONDS) + .until(() -> new ArrayList<>(currentTestAuditMessages), matcher); + } + + public void assertAtLeast(long minCount, Predicate predicate) { + assertExactly(atLeastCertainNumberOfAuditsFulfillPredicate(minCount, predicate)); + } + + private static String auditMessagesToString(List audits) { + return audits.stream().map(AuditMessage::toString).collect(Collectors.joining(",\n")); + } + + @Override + public Statement apply(Statement statement, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + String methodName = description.getMethodName(); + beforeTest(methodName); + try { + statement.evaluate(); + } catch (ConditionTimeoutException ex) { + whenTimeoutOccurs(methodName); + throw ex; + } + finally { + afterTest(); + } + } + }; + } + + private void whenTimeoutOccurs(String methodName) { + List copy = new ArrayList<>(currentTestAuditMessages); + String auditMessages = auditMessagesToString(copy); + log.error("Timeout occured due to insufficient number ('{}') of captured audit messages during test '{}'\n{}", + copy.size(), methodName, auditMessages); + } + + private void afterTest() { + TestRuleAuditLogSink.unregisterListener(); + this.currentTestAuditMessages = null; + } + + private void beforeTest(String methodName) { + log.info("Start collecting audit logs before test {}", methodName); + this.currentTestAuditMessages = synchronizedList(new ArrayList<>()); + TestRuleAuditLogSink.registerListener(this); + } + + public void onAuditMessage(AuditMessage auditMessage) { + currentTestAuditMessages.add(auditMessage); + log.debug("New audit message received '{}', total number of audit messages '{}'.", auditMessage, currentTestAuditMessages.size()); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/audit/AuditMessagePredicate.java b/src/integrationTest/java/org/opensearch/test/framework/audit/AuditMessagePredicate.java new file mode 100644 index 0000000000..940294b938 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/audit/AuditMessagePredicate.java @@ -0,0 +1,154 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.audit; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.opensearch.rest.RestRequest.Method; +import org.opensearch.security.auditlog.AuditLog.Origin; +import org.opensearch.security.auditlog.impl.AuditCategory; +import org.opensearch.security.auditlog.impl.AuditMessage; +import org.opensearch.test.framework.TestSecurityConfig.User; + +import static org.opensearch.security.auditlog.impl.AuditCategory.AUTHENTICATED; +import static org.opensearch.security.auditlog.impl.AuditCategory.GRANTED_PRIVILEGES; +import static org.opensearch.security.auditlog.impl.AuditCategory.MISSING_PRIVILEGES; +import static org.opensearch.security.auditlog.impl.AuditMessage.REQUEST_LAYER; +import static org.opensearch.security.auditlog.impl.AuditMessage.RESOLVED_INDICES; +import static org.opensearch.security.auditlog.impl.AuditMessage.REST_REQUEST_PATH; + +public class AuditMessagePredicate implements Predicate { + + private final AuditCategory category; + private final Origin requestLayer; + private final String restRequestPath; + private final String initiatingUser; + private final Method requestMethod; + private final String transportRequestType; + private final String effectiveUser; + private final String index; + + private AuditMessagePredicate(AuditCategory category, Origin requestLayer, String restRequestPath, + String initiatingUser, Method requestMethod, String transportRequestType, String effectiveUser, String index) { + this.category = category; + this.requestLayer = requestLayer; + this.restRequestPath = restRequestPath; + this.initiatingUser = initiatingUser; + this.requestMethod = requestMethod; + this.transportRequestType = transportRequestType; + this.effectiveUser = effectiveUser; + this.index = index; + } + + private AuditMessagePredicate(AuditCategory category) { + this(category, null, null, null, null, null, null, null); + } + + public static AuditMessagePredicate auditPredicate(AuditCategory category) { + return new AuditMessagePredicate(category); + } + + public static AuditMessagePredicate userAuthenticated(User user) { + return auditPredicate(AUTHENTICATED).withInitiatingUser(user); + } + + public static AuditMessagePredicate grantedPrivilege(User user, String requestType) { + return auditPredicate(GRANTED_PRIVILEGES).withLayer(Origin.TRANSPORT).withEffectiveUser(user) + .withTransportRequestType(requestType); + } + + public static AuditMessagePredicate missingPrivilege(User user, String requestType) { + return auditPredicate(MISSING_PRIVILEGES).withLayer(Origin.TRANSPORT).withEffectiveUser(user) + .withTransportRequestType(requestType); + } + + public AuditMessagePredicate withLayer(Origin layer) { + return new AuditMessagePredicate(category, layer, restRequestPath, initiatingUser, requestMethod, + transportRequestType, effectiveUser, index); + } + + public AuditMessagePredicate withRequestPath(String path) { + return new AuditMessagePredicate(category, requestLayer, path, initiatingUser, requestMethod, transportRequestType, effectiveUser, + index); + } + + public AuditMessagePredicate withInitiatingUser(String user) { + return new AuditMessagePredicate(category, requestLayer, restRequestPath, user, requestMethod, transportRequestType, effectiveUser, + index); + } + + public AuditMessagePredicate withInitiatingUser(User user) { + return withInitiatingUser(user.getName()); + } + + public AuditMessagePredicate withRestMethod(Method method) { + return new AuditMessagePredicate(category, requestLayer, restRequestPath, initiatingUser, method, + transportRequestType, effectiveUser, index); + } + + public AuditMessagePredicate withTransportRequestType(String type) { + return new AuditMessagePredicate(category, requestLayer, restRequestPath, initiatingUser, requestMethod, type, effectiveUser, index); + } + + public AuditMessagePredicate withEffectiveUser(String user) { + return new AuditMessagePredicate(category, requestLayer, restRequestPath, initiatingUser, requestMethod, transportRequestType, user, + index); + } + + public AuditMessagePredicate withEffectiveUser(User user) { + return withEffectiveUser(user.getName()); + } + + public AuditMessagePredicate withRestRequest(Method method, String path) { + return this.withLayer(Origin.REST).withRestMethod(method).withRequestPath(path); + } + + public AuditMessagePredicate withIndex(String indexName) { + return new AuditMessagePredicate(category, requestLayer, restRequestPath, initiatingUser, requestMethod, transportRequestType, + effectiveUser, indexName); + } + + @Override + public boolean test(AuditMessage auditMessage) { + List> predicates = new ArrayList<>(); + predicates.add(audit -> Objects.isNull(category) || category.equals(audit.getCategory())); + predicates.add(audit -> Objects.isNull(requestLayer) || requestLayer.equals(audit.getAsMap().get(REQUEST_LAYER))); + predicates.add(audit -> Objects.isNull(restRequestPath) || restRequestPath.equals(audit.getAsMap().get(REST_REQUEST_PATH))); + predicates.add(audit -> Objects.isNull(initiatingUser) || initiatingUser.equals(audit.getInitiatingUser())); + predicates.add(audit -> Objects.isNull(requestMethod) || requestMethod.equals(audit.getRequestMethod())); + predicates.add(audit -> Objects.isNull(transportRequestType) || transportRequestType.equals(audit.getRequestType())); + predicates.add(audit -> Objects.isNull(effectiveUser) || effectiveUser.equals(audit.getEffectiveUser())); + predicates.add(audit -> Objects.isNull(index) || containIndex(audit, index)); + return predicates.stream().reduce(Predicate::and).orElseThrow().test(auditMessage); + } + + private boolean containIndex(AuditMessage auditMessage, String indexName) { + Map audit = auditMessage.getAsMap(); + return Optional.ofNullable(audit.get(RESOLVED_INDICES)) + .filter(String[].class::isInstance) + .map(String[].class::cast) + .stream() + .flatMap(Arrays::stream) + .collect(Collectors.toSet()) + .contains(indexName); + } + + @Override + public String toString() { + return "AuditMessagePredicate{" + "category=" + category + ", requestLayer=" + requestLayer + ", restRequestPath='" + restRequestPath + '\'' + ", requestInitiatingUser='" + initiatingUser + '\'' + ", requestMethod=" + requestMethod + ", transportRequestType='" + transportRequestType + '\'' + '}'; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/audit/TestRuleAuditLogSink.java b/src/integrationTest/java/org/opensearch/test/framework/audit/TestRuleAuditLogSink.java new file mode 100644 index 0000000000..0a9abaeb0d --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/audit/TestRuleAuditLogSink.java @@ -0,0 +1,51 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.audit; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.common.settings.Settings; +import org.opensearch.security.auditlog.impl.AuditMessage; +import org.opensearch.security.auditlog.sink.AuditLogSink; + +public class TestRuleAuditLogSink extends AuditLogSink { + private static final Logger log = LogManager.getLogger(TestRuleAuditLogSink.class); + + private static volatile AuditLogsRule listener; + + public TestRuleAuditLogSink(String name, Settings settings, String settingsPrefix, AuditLogSink fallbackSink) { + super(name, settings, settingsPrefix, fallbackSink); + log.info("Test rule audit log sink created"); + } + + @Override + protected boolean doStore(AuditMessage auditMessage) { + log.debug("New audit message received '{}'.", auditMessage); + AuditLogsRule currentListener = listener; + if(currentListener != null) { + currentListener.onAuditMessage(auditMessage); + } + return true; + } + + public static void registerListener(AuditLogsRule auditLogsRule) { + listener = auditLogsRule; + } + + public static void unregisterListener() { + listener = null; + } + + @Override + public boolean isHandlingBackpressure() { + return true; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index f6f4610121..489d94302f 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -48,9 +48,11 @@ import org.opensearch.node.PluginAwareNode; import org.opensearch.plugins.Plugin; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.test.framework.AuditConfiguration; import org.opensearch.test.framework.TestIndex; import org.opensearch.test.framework.TestSecurityConfig; import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.audit.TestRuleAuditLogSink; import org.opensearch.test.framework.certificate.TestCertificates; /** @@ -338,6 +340,18 @@ public Builder users(TestSecurityConfig.User... users) { return this; } + public Builder audit(AuditConfiguration auditConfiguration) { + if(auditConfiguration != null) { + testSecurityConfig.audit(auditConfiguration); + } + if(auditConfiguration.isEnabled()) { + nodeOverrideSettingsBuilder.put("plugins.security.audit.type", TestRuleAuditLogSink.class.getName()); + } else { + nodeOverrideSettingsBuilder.put("plugins.security.audit.type","noop"); + } + return this; + } + public Builder roles(Role... roles) { testSecurityConfig.roles(roles); return this; diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/AtLeastCertainNumberOfAuditsFulfillPredicateMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/AtLeastCertainNumberOfAuditsFulfillPredicateMatcher.java new file mode 100644 index 0000000000..922917bc21 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/AtLeastCertainNumberOfAuditsFulfillPredicateMatcher.java @@ -0,0 +1,44 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.List; +import java.util.function.Predicate; + +import org.hamcrest.Description; + +import org.opensearch.security.auditlog.impl.AuditMessage; + +class AtLeastCertainNumberOfAuditsFulfillPredicateMatcher extends AuditsFulfillPredicateMatcher { + + private final long minimumNumberOfAudits; + + public AtLeastCertainNumberOfAuditsFulfillPredicateMatcher(Predicate predicate, long minimumNumberOfAudits) { + super(predicate); + this.minimumNumberOfAudits = minimumNumberOfAudits; + } + + @Override + protected boolean matchesSafely(List audits, Description mismatchDescription) { + long count = countAuditsWhichMatchPredicate(audits); + if(count < minimumNumberOfAudits) { + mismatchDescription.appendText(" only ").appendValue(count).appendText(" match predicate. Examined audit logs ") + .appendText(auditMessagesToString(audits)); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Al least ").appendValue(minimumNumberOfAudits).appendText(" audits records should match predicate ") + .appendValue(predicate); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditMessageMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditMessageMatchers.java new file mode 100644 index 0000000000..f15750259f --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditMessageMatchers.java @@ -0,0 +1,32 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.List; +import java.util.function.Predicate; + +import org.hamcrest.Matcher; + +import org.opensearch.security.auditlog.impl.AuditMessage; + +public class AuditMessageMatchers { + + private AuditMessageMatchers() { + + } + + public static Matcher> exactNumberOfAuditsFulfillPredicate(long exactNumberOfAuditMessages, Predicate predicate) { + return new ExactNumberOfAuditsFulfillPredicateMatcher(exactNumberOfAuditMessages, predicate); + } + + public static Matcher> atLeastCertainNumberOfAuditsFulfillPredicate(long minimumNumberOfAudits, Predicate predicate) { + return new AtLeastCertainNumberOfAuditsFulfillPredicateMatcher(predicate, minimumNumberOfAudits); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditsFulfillPredicateMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditsFulfillPredicateMatcher.java new file mode 100644 index 0000000000..e73267b452 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditsFulfillPredicateMatcher.java @@ -0,0 +1,36 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.security.auditlog.impl.AuditMessage; + +abstract class AuditsFulfillPredicateMatcher extends TypeSafeDiagnosingMatcher> { + + protected final Predicate predicate; + + public AuditsFulfillPredicateMatcher(Predicate predicate) { + this.predicate = predicate; + } + + protected String auditMessagesToString(List audits) { + return audits.stream().map(AuditMessage::toString).collect(Collectors.joining(",\n")); + } + + protected long countAuditsWhichMatchPredicate(List audits) { + return audits.stream().filter(predicate).count(); + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainDocumentCountIndexMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainDocumentCountIndexMatcher.java new file mode 100644 index 0000000000..a8e1b5d78d --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainDocumentCountIndexMatcher.java @@ -0,0 +1,43 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.indices.get.GetIndexResponse; +import org.opensearch.client.Client; +import org.opensearch.test.framework.cluster.LocalCluster; + +import static java.util.Objects.requireNonNull; + +class ClusterContainDocumentCountIndexMatcher extends TypeSafeDiagnosingMatcher { + + private final String indexName; + private final int expectedDocumentCount; + + public ClusterContainDocumentCountIndexMatcher(String indexName, int expectedDocumentCount) { + this.indexName = requireNonNull(indexName, "Index name is required."); + this.expectedDocumentCount = expectedDocumentCount; + } + + @Override + protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { + try(Client client = cluster.getInternalNodeClient()) { + GetIndexResponse response = client.admin().indices().getIndex(null).actionGet(); + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("contains ").appendValue(expectedDocumentCount).appendText(" in index ").appendText(indexName); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExactNumberOfAuditsFulfillPredicateMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExactNumberOfAuditsFulfillPredicateMatcher.java new file mode 100644 index 0000000000..c7d918db2c --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExactNumberOfAuditsFulfillPredicateMatcher.java @@ -0,0 +1,43 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.List; +import java.util.function.Predicate; + +import org.hamcrest.Description; + +import org.opensearch.security.auditlog.impl.AuditMessage; + +class ExactNumberOfAuditsFulfillPredicateMatcher extends AuditsFulfillPredicateMatcher { + + private final long exactNumberOfAuditMessages; + + public ExactNumberOfAuditsFulfillPredicateMatcher(long exactNumberOfAuditMessages, Predicate predicate) { + super(predicate); + this.exactNumberOfAuditMessages = exactNumberOfAuditMessages; + } + + @Override + protected boolean matchesSafely(List audits, Description mismatchDescription) { + long count = countAuditsWhichMatchPredicate(audits); + if(exactNumberOfAuditMessages != count) { + mismatchDescription.appendText(" only ").appendValue(count).appendText(" match predicate. Examined audit logs ") + .appendText(auditMessagesToString(audits)); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendValue(exactNumberOfAuditMessages).appendText(" audit records should match predicate ").appendValue(predicate); + } +} diff --git a/src/integrationTest/resources/log4j2-test.properties b/src/integrationTest/resources/log4j2-test.properties index d9e87408a4..1925c087a6 100644 --- a/src/integrationTest/resources/log4j2-test.properties +++ b/src/integrationTest/resources/log4j2-test.properties @@ -19,3 +19,6 @@ logger.testsecconfig.name=org.opensearch.test.framework.TestSecurityConfig logger.testsecconfig.level = info logger.localopensearchcluster.name=org.opensearch.test.framework.cluster.LocalOpenSearchCluster logger.localopensearchcluster.level = info + +logger.auditlogs.name=org.opensearch.test.framework.audit +logger.auditlogs.level = info From f75be3edaf900df6f42aec2fd41175e66af06f3b Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Fri, 4 Nov 2022 12:58:00 -0400 Subject: [PATCH 071/369] 2.4 Release Notes for Security Plugin (#2231) * 2.4 Release Notes for Security Plugin (#2231) Signed-off-by: Stephen Crawford Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- ...ensearch-security.release-notes-2.4.0.0.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-2.4.0.0.md diff --git a/release-notes/opensearch-security.release-notes-2.4.0.0.md b/release-notes/opensearch-security.release-notes-2.4.0.0.md new file mode 100644 index 0000000000..f6af613459 --- /dev/null +++ b/release-notes/opensearch-security.release-notes-2.4.0.0.md @@ -0,0 +1,23 @@ +## 2022-11-10 Version 2.4.0.0 + +Compatible with OpenSearch 2.4.0 + +### Enhancements +* Add install_demo_configuration Batch script for Windows ([#2161](https://github.com/opensearch-project/security/pull/2161)[#2203](https://github.com/opensearch-project/security/commit/51a286230f5ba1829dd7e62af1b626540eee3600) +* Add CI for Windows and MacOS platforms ([#2190](https://github.com/opensearch-project/security/pull/2190)[#2205](https://github.com/opensearch-project/security/pull/2205)) +* Make ldap pool period and idle time configurable ([#2091](https://github.com/opensearch-project/security/commit/edd9f49e161739fe26f2d3652121e6c187636b79)[#2097](https://github.com/opensearch-project/security/pull/2097)) +* Allow custom LDAP return attributes ([#2093](https://github.com/opensearch-project/security/pull/2093)[#2110](https://github.com/opensearch-project/security/pull/2110)) +* Add bcpkix-jdk15on runtimeOnly dependency to read keys with bouncycastle ([#2191](https://github.com/opensearch-project/security/pull/2191)[#2200](https://github.com/opensearch-project/security/pull/2200)) + +### Bug Fixes +* Point in time API security changes ([#2094](https://github.com/opensearch-project/security/pull/2094)[#2223](https://github.com/opensearch-project/security/pull/2223)) +* Fix windows encoding issues ([#2206](https://github.com/opensearch-project/security/pull/2206)[#2218](https://github.com/opensearch-project/security/pull/2218)) + +### Maintenance +* Add groupId = org.opensearch.plugin ([#2158](https://github.com/opensearch-project/security/pull/2158)[#2185](https://github.com/opensearch-project/security/pull/2185)) +* Roles yml changes for security-analytics plugin ([#2192](https://github.com/opensearch-project/security/pull/2192)[#2225](https://github.com/opensearch-project/security/pull/2225)) +* Upgrade Kafka Client to 3.0.2 ([#2123](https://github.com/opensearch-project/security/pull/2123)[#2126](https://github.com/opensearch-project/security/pull/2126)) +* Log deprecation message on legacy ldap pool settings ([#2099](https://github.com/opensearch-project/security/pull/2099)[#2147](https://github.com/opensearch-project/security/pull/2147)) +* Address CVE-2022-42889 by updating commons-text ([#2177](https://github.com/opensearch-project/security/pull/2177)[#2186](https://github.com/opensearch-project/security/pull/2186)) +* Patch bump for scala dependency ([#2163](https://github.com/opensearch-project/security/pull/2163)[#2187](https://github.com/opensearch-project/security/commit/1f3de6a064696eb098749a340853c4f6af4c619f)) +* Woodstox Version Bump to 6.4.0 ([#2197](https://github.com/opensearch-project/security/pull/2197)[#2199](https://github.com/opensearch-project/security/pull/2199)) From f7cc569c9d3fa5d5432c76c854eed280d45ce6f4 Mon Sep 17 00:00:00 2001 From: Dave Lago Date: Mon, 7 Nov 2022 16:55:08 -0500 Subject: [PATCH 072/369] Merge pull request from GHSA-wmx7-x4jp-9jgg * Resolving backing indices of data streams when resolving for aliases * Fixing resolution of indices for non-wild card scenarios / exact matches * Adding tests for DLS/FLS/Field-Masking on Data Streams Co-authored-by: Sandesh Kumar --- .../security/securityconf/ConfigModelV7.java | 15 +- .../security/DataStreamIntegrationTests.java | 253 +++++++++++++++++- src/test/resources/internal_users.yml | 30 +++ src/test/resources/roles.yml | 134 +++++++++- src/test/resources/roles_mapping.yml | 50 ++++ 5 files changed, 472 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index eb42792c2a..cbd7cb8301 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -69,6 +69,7 @@ import org.opensearch.security.user.User; import static org.opensearch.cluster.metadata.IndexAbstraction.Type.ALIAS; +import static org.opensearch.cluster.metadata.IndexAbstraction.Type.DATA_STREAM; public class ConfigModelV7 extends ConfigModel { @@ -769,20 +770,22 @@ public Set getResolvedIndexPattern(final User user, final IndexNameExpre final ImmutableSet.Builder resolvedIndices = new ImmutableSet.Builder<>(); final WildcardMatcher matcher = WildcardMatcher.from(unresolved); + boolean includeDataStreams = true; if (!(matcher instanceof WildcardMatcher.Exact)) { - final String[] aliasesForPermittedPattern = cs.state().getMetadata().getIndicesLookup().entrySet().stream() - .filter(e -> e.getValue().getType() == ALIAS) + final String[] aliasesAndDataStreamsForPermittedPattern = cs.state().getMetadata().getIndicesLookup().entrySet().stream() + .filter(e -> (e.getValue().getType() == ALIAS) || (e.getValue().getType() == DATA_STREAM)) .filter(e -> matcher.test(e.getKey())) .map(e -> e.getKey()) .toArray(String[]::new); - if (aliasesForPermittedPattern.length > 0) { - final String[] resolvedAliases = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), aliasesForPermittedPattern); - resolvedIndices.addAll(Arrays.asList(resolvedAliases)); + if (aliasesAndDataStreamsForPermittedPattern.length > 0) { + final String[] resolvedAliasesAndDataStreamIndices = resolver.concreteIndexNames(cs.state(), + IndicesOptions.lenientExpandOpen(), includeDataStreams, aliasesAndDataStreamsForPermittedPattern); + resolvedIndices.addAll(Arrays.asList(resolvedAliasesAndDataStreamIndices)); } } if (Strings.isNotBlank(unresolved)) { - final String[] resolvedIndicesFromPattern = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), unresolved); + final String[] resolvedIndicesFromPattern = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), includeDataStreams, unresolved); resolvedIndices.addAll(Arrays.asList(resolvedIndicesFromPattern)); } diff --git a/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java b/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java index cc37a6d1d4..78c9ab7818 100644 --- a/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java +++ b/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java @@ -22,6 +22,17 @@ public class DataStreamIntegrationTests extends SingleClusterTest { + final String bulkDocsBody = + "{ \"create\" : {} }" + System.lineSeparator() + + "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"vlb44hny\", \"name\": \"Sam\"}, \"message\" : \"Login attempt failed\" }" + System.lineSeparator() + + "{ \"create\" : {} }" + System.lineSeparator() + + "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"8a4f500d\", \"name\": \"Dam\"}, \"message\" : \"Login successful\" }" + System.lineSeparator() + + "{ \"create\" : {} }" + System.lineSeparator() + + "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"l7gk7f82\", \"name\": \"Pam\"}, \"message\" : \"Login attempt failed\" }" + System.lineSeparator(); + + final String searchQuery1 = "{ \"seq_no_primary_term\" : true, \"query\": { \"match\": { \"user.id\": \"8a4f500d\"}}}"; + final String searchQuery2 = "{ \"seq_no_primary_term\" : true, \"query\": { \"match\": { \"user.id\": \"l7gk7f82\"}}}"; + public String getIndexTemplateBody() { return "{\"index_patterns\": [ \"my-data-stream*\" ], \"data_stream\": { }, \"priority\": 200, \"template\": {\"settings\": { } } }"; } @@ -208,7 +219,7 @@ public void testDataStreamStats() throws Exception { } @Test - public void testBackingIndicesOfDataStream() throws Exception { + public void testGetIndexOnBackingIndicesOfDataStream() throws Exception { setup(); RestHelper rh = nonSslRestHelper(); @@ -243,12 +254,248 @@ public void testBackingIndicesOfDataStream() throws Exception { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); response = rh.executeGetRequest(".ds-my-data-stream22-000001", encodeBasicHeader("ds2", "nagilum")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(".ds-my-data-stream21-000001,.ds-my-data-stream22-000001", encodeBasicHeader("ds2", "nagilum")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(".ds-my-data-stream2*", encodeBasicHeader("ds2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + } + + @Test + public void testDocumentLevelSecurityOnDataStream() throws Exception { + + setup(); + RestHelper rh = nonSslRestHelper(); + createSampleDataStreams(rh); + HttpResponse response; + + rh.executePutRequest("/my-data-stream11/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum")); + rh.executePutRequest("/my-data-stream21/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum")); + + response = rh.executePostRequest("/my-data-stream11/_search", searchQuery1, encodeBasicHeader("ds_dls1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("8a4f500d")); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + + response = rh.executePostRequest("/my-data-stream22/_search", searchQuery1, encodeBasicHeader("ds_dls1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery1, encodeBasicHeader("ds_dls1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("8a4f500d")); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + + response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery2, encodeBasicHeader("ds_dls1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertFalse(response.getBody().contains("l7gk7f82")); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":0,\"relation\":\"eq\"}")); + + response = rh.executePostRequest("/.ds-my-data-stream22-000001/_search", searchQuery2, encodeBasicHeader("ds_dls1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePostRequest("/my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_dls2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("8a4f500d")); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + + response = rh.executePostRequest("/my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_dls2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePostRequest("/.ds-my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_dls2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("8a4f500d")); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + + response = rh.executePostRequest("/.ds-my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_dls2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePostRequest("/my-*/_search", searchQuery1, encodeBasicHeader("ds_dls3", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("8a4f500d")); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":2,\"relation\":\"eq\"}")); + + response = rh.executePostRequest("/.ds-my-*/_search", searchQuery1, encodeBasicHeader("ds_dls3", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("8a4f500d")); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":2,\"relation\":\"eq\"}")); + + response = rh.executePostRequest("/my-*/_search", searchQuery2, encodeBasicHeader("ds_dls3", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertFalse(response.getBody().contains("l7gk7f82")); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":0,\"relation\":\"eq\"}")); + + response = rh.executePostRequest("/.ds-my-*/_search", searchQuery2, encodeBasicHeader("ds_dls3", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertFalse(response.getBody().contains("l7gk7f82")); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":0,\"relation\":\"eq\"}")); + } + + @Test + public void testFLSOnBackingIndicesOfDataStream() throws Exception { + + setup(); + RestHelper rh = nonSslRestHelper(); + createSampleDataStreams(rh); + HttpResponse response; + + rh.executePutRequest("/my-data-stream11/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum")); + rh.executePutRequest("/my-data-stream21/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum")); + + response = rh.executePostRequest("/my-data-stream11/_search", searchQuery1, encodeBasicHeader("ds_fls1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\"")); + Assert.assertFalse(response.getBody().contains("\"name\":\"")); + Assert.assertTrue(response.getBody().contains("\"message\":\"Login successful\"")); + + response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery1, encodeBasicHeader("ds_fls1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\"")); + Assert.assertFalse(response.getBody().contains("\"name\":\"")); + Assert.assertTrue(response.getBody().contains("\"message\":\"Login successful\"")); + + response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery2, encodeBasicHeader("ds_fls1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + Assert.assertTrue(response.getBody().contains("\"id\":\"l7gk7f82\"")); + Assert.assertFalse(response.getBody().contains("\"name\":\"")); + Assert.assertTrue(response.getBody().contains("\"message\":\"Login attempt failed\"")); + + response = rh.executePostRequest("/my-data-stream22/_search", searchQuery1, encodeBasicHeader("ds_fls1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePostRequest("/.ds-my-data-stream22-000001/_search", searchQuery2, encodeBasicHeader("ds_fls1", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePostRequest("/my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_fls2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"")); + + response = rh.executePostRequest("/.ds-my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_fls2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"")); + + response = rh.executePostRequest("/my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_fls2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePostRequest("/.ds-my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_fls2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePostRequest("/my-*/_search", searchQuery1, encodeBasicHeader("ds_fls3", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":2,\"relation\":\"eq\"}")); + Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"")); + + response = rh.executePostRequest("/.ds-my-*/_search", searchQuery1, encodeBasicHeader("ds_fls3", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"")); + + response = rh.executePostRequest("/my-*/_search", searchQuery2, encodeBasicHeader("ds_fls3", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"id\":\"l7gk7f82\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"Pam\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"")); + } + + @Test + public void testFieldMaskingOnDataStream() throws Exception { + + setup(); + RestHelper rh = nonSslRestHelper(); + createSampleDataStreams(rh); + HttpResponse response; + + rh.executePutRequest("/my-data-stream11/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum")); + rh.executePutRequest("/my-data-stream21/_bulk?refresh=true", bulkDocsBody, encodeBasicHeader("ds_admin", "nagilum")); + + response = rh.executePostRequest("/my-data-stream11/_search", searchQuery1, encodeBasicHeader("ds_fm1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\"")); + Assert.assertTrue(response.getBody().contains("\"message\":\"")); + + response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery1, encodeBasicHeader("ds_fm1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\"")); + Assert.assertTrue(response.getBody().contains("\"message\":\"")); + + response = rh.executePostRequest("/.ds-my-data-stream11-000001/_search", searchQuery2, encodeBasicHeader("ds_fm1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + Assert.assertTrue(response.getBody().contains("\"id\":\"l7gk7f82\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"Pam\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"Login attempt failed\"")); + Assert.assertTrue(response.getBody().contains("\"message\":\"")); + + response = rh.executePostRequest("/my-data-stream22/_search", searchQuery1, encodeBasicHeader("ds_fm1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePostRequest("/.ds-my-data-stream22-000001/_search", searchQuery2, encodeBasicHeader("ds_fm1", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePostRequest("/my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_fm2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\"")); + Assert.assertTrue(response.getBody().contains("\"message\":\"")); + + response = rh.executePostRequest("/.ds-my-data-stream2*/_search", searchQuery1, encodeBasicHeader("ds_fm2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}")); + Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"Dam\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\"")); + Assert.assertTrue(response.getBody().contains("\"message\":\"")); + + response = rh.executePostRequest("/my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_fm2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePostRequest("/.ds-my-data-stream1*/_search", searchQuery1, encodeBasicHeader("ds_fm2", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePostRequest("/my-*/_search", searchQuery1, encodeBasicHeader("ds_fm3", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"hits\":{\"total\":{\"value\":2,\"relation\":\"eq\"}")); + Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\"")); + Assert.assertFalse(response.getBody().contains("\"name\":\"Dam\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\"")); + Assert.assertTrue(response.getBody().contains("\"message\":\"")); + + response = rh.executePostRequest("/.ds-my-*/_search", searchQuery1, encodeBasicHeader("ds_fm3", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"id\":\"8a4f500d\"")); + Assert.assertFalse(response.getBody().contains("\"name\":\"Dam\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"Login successful\"")); + Assert.assertTrue(response.getBody().contains("\"message\":\"")); + + response = rh.executePostRequest("/my-*/_search", searchQuery2, encodeBasicHeader("ds_fm3", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("\"id\":\"l7gk7f82\"")); + Assert.assertFalse(response.getBody().contains("\"name\":\"Pam\"")); + Assert.assertTrue(response.getBody().contains("\"name\":\"")); + Assert.assertFalse(response.getBody().contains("\"message\":\"Login attempt failed\"")); + Assert.assertTrue(response.getBody().contains("\"message\":\"")); } } diff --git a/src/test/resources/internal_users.yml b/src/test/resources/internal_users.yml index 44464c9cf7..951b988bbe 100644 --- a/src/test/resources/internal_users.yml +++ b/src/test/resources/internal_users.yml @@ -355,6 +355,36 @@ pit-2: all-pit: hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m #password is: nagilum +ds_admin: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum +ds_dls1: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum +ds_dls2: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum +ds_dls3: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum +ds_fls1: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum +ds_fls2: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum +ds_fls3: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum +ds_fm1: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum +ds_fm2: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum +ds_fm3: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum hidden_test: hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m opendistro_security_roles: diff --git a/src/test/resources/roles.yml b/src/test/resources/roles.yml index 4da098e762..2bff2a97f0 100644 --- a/src/test/resources/roles.yml +++ b/src/test/resources/roles.yml @@ -1122,12 +1122,144 @@ data_stream_3: reserved: true hidden: false description: "Migrated from v6 (all types mapped)" - cluster_permissions: [] + cluster_permissions: + - "*" index_permissions: - index_patterns: - "*" allowed_actions: - "DATASTREAM_ALL" + - "indices:data/write/index" + - "indices:data/write/bulk*" + +data_stream_admin: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + cluster_permissions: + - "*" + index_permissions: + - index_patterns: + - "my-data-stream*" + allowed_actions: + - "*" + +data_stream_dls_1: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + cluster_permissions: [] + index_permissions: + - index_patterns: + - "my-data-stream11" + dls: "{\n \"bool\": {\n \"must\": {\n \"match\": {\n \"user.id\": \"8a4f500d\"\n }\n }\n }\n}" + allowed_actions: + - "read" + +data_stream_dls_2: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + cluster_permissions: [] + index_permissions: + - index_patterns: + - "my-data-stream2*" + dls: "{\n \"bool\": {\n \"must\": {\n \"match\": {\n \"user.id\": \"8a4f500d\"\n }\n }\n }\n}" + allowed_actions: + - "read" + +data_stream_dls_3: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + cluster_permissions: [] + index_permissions: + - index_patterns: + - "my-data-stream*" + dls: "{\n \"bool\": {\n \"must\": {\n \"match\": {\n \"user.id\": \"8a4f500d\"\n }\n }\n }\n}" + allowed_actions: + - "read" + +data_stream_fls_1: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + cluster_permissions: [] + index_permissions: + - index_patterns: + - "my-data-stream11" + fls: + - "user.id" + - "message" + allowed_actions: + - "read" + +data_stream_fls_2: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + cluster_permissions: [] + index_permissions: + - index_patterns: + - "my-data-stream2*" + fls: + - "user.id" + - "user.name" + allowed_actions: + - "read" + +data_stream_fls_3: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + cluster_permissions: [] + index_permissions: + - index_patterns: + - "my-data-stream*" + fls: + - "~message" + allowed_actions: + - "read" + +data_stream_fm_1: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + cluster_permissions: [] + index_permissions: + - index_patterns: + - "my-data-stream11" + masked_fields: + - "message" + allowed_actions: + - "read" + +data_stream_fm_2: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + cluster_permissions: [] + index_permissions: + - index_patterns: + - "my-data-stream2*" + masked_fields: + - "message" + allowed_actions: + - "read" + +data_stream_fm_3: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + cluster_permissions: [] + index_permissions: + - index_patterns: + - "my-data-stream*" + masked_fields: + - "user.name" + - "message" + allowed_actions: + - "read" point_in_time_1: reserved: true diff --git a/src/test/resources/roles_mapping.yml b/src/test/resources/roles_mapping.yml index bc32c5b403..8c65fab329 100644 --- a/src/test/resources/roles_mapping.yml +++ b/src/test/resources/roles_mapping.yml @@ -428,6 +428,56 @@ point_in_time_all: hidden: false users: - "all-pit" +data_stream_admin: + reserved: false + hidden: false + users: + - "ds_admin" +data_stream_dls_1: + reserved: false + hidden: false + users: + - "ds_dls1" +data_stream_dls_2: + reserved: false + hidden: false + users: + - "ds_dls2" +data_stream_dls_3: + reserved: false + hidden: false + users: + - "ds_dls3" +data_stream_fls_1: + reserved: false + hidden: false + users: + - "ds_fls1" +data_stream_fls_2: + reserved: false + hidden: false + users: + - "ds_fls2" +data_stream_fls_3: + reserved: false + hidden: false + users: + - "ds_fls3" +data_stream_fm_1: + reserved: false + hidden: false + users: + - "ds_fm1" +data_stream_fm_2: + reserved: false + hidden: false + users: + - "ds_fm2" +data_stream_fm_3: + reserved: false + hidden: false + users: + - "ds_fm3" sem-role: reserved: false hidden: false From 672f4d7c30ca1f33df7ac71dc75bb8bb5f2a1616 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 8 Nov 2022 13:38:43 -0500 Subject: [PATCH 073/369] Datastream test fixes (#2244) Signed-off-by: Craig Perkins --- ...exTemplateClusterPermissionsCheckTest.java | 4 +-- .../security/PitIntegrationTests.java | 4 +-- .../impl/v7/IndexPatternTests.java | 30 +++++++++---------- src/test/resources/internal_users.yml | 6 ++++ src/test/resources/roles.yml | 25 ++++++++++++++++ src/test/resources/roles_mapping.yml | 10 +++++++ 6 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/test/java/org/opensearch/security/IndexTemplateClusterPermissionsCheckTest.java b/src/test/java/org/opensearch/security/IndexTemplateClusterPermissionsCheckTest.java index 7bdbc57cf5..b2c483abd7 100644 --- a/src/test/java/org/opensearch/security/IndexTemplateClusterPermissionsCheckTest.java +++ b/src/test/java/org/opensearch/security/IndexTemplateClusterPermissionsCheckTest.java @@ -36,10 +36,10 @@ public void setupRestHelper() throws Exception{ } @Test public void testPutIndexTemplateByNonPrivilegedUser() throws Exception { - String expectedFailureResponse = getFailureResponseReason("ds3"); + String expectedFailureResponse = getFailureResponseReason("ds4"); // should fail, as user `ds3` doesn't have correct permissions - HttpResponse response = rh.executePutRequest("/_index_template/sem1234", indexTemplateBody, encodeBasicHeader("ds3", "nagilum")); + HttpResponse response = rh.executePutRequest("/_index_template/sem1234", indexTemplateBody, encodeBasicHeader("ds4", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); Assert.assertEquals(expectedFailureResponse, response.findValueInJson("error.root_cause[0].reason")); } diff --git a/src/test/java/org/opensearch/security/PitIntegrationTests.java b/src/test/java/org/opensearch/security/PitIntegrationTests.java index baab586beb..11c624eba6 100644 --- a/src/test/java/org/opensearch/security/PitIntegrationTests.java +++ b/src/test/java/org/opensearch/security/PitIntegrationTests.java @@ -229,10 +229,10 @@ public void testDataStreamWithPits() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); String pitId2 = resc.findValueInJson("pit_id"); - // since pit-2 doesn't have permission to backing data stream indices, throw security error + // since pit-3 doesn't have permission to backing data stream indices, throw security error resc = rh.executeGetRequest("/_cat/pit_segments", "{\"pit_id\":\"" + pitId2 +"\"}", - encodeBasicHeader("pit-2", "nagilum")); + encodeBasicHeader("pit-3", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); // Delete all PITs should work for user with all index access diff --git a/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java b/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java index 0c65e2bea9..be7c2da7b0 100644 --- a/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java +++ b/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java @@ -108,7 +108,7 @@ public void testAttemptResolveIndexNamesOverload() { public void testExactNameWithNoMatches() { doReturn("index-17").when(ip).getUnresolvedIndexPattern(user); when(clusterService.state()).thenReturn(mock(ClusterState.class)); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-17"))).thenReturn(new String[]{}); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-17"))).thenReturn(new String[]{}); final Set results = ip.concreteIndexNames(user, resolver, clusterService); @@ -116,7 +116,7 @@ public void testExactNameWithNoMatches() { verify(clusterService).state(); verify(ip).getUnresolvedIndexPattern(user); - verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-17")); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-17")); } /** Verify concreteIndexNames on exact name matches */ @@ -124,7 +124,7 @@ public void testExactNameWithNoMatches() { public void testExactName() { doReturn("index-17").when(ip).getUnresolvedIndexPattern(user); when(clusterService.state()).thenReturn(mock(ClusterState.class)); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-17"))).thenReturn(new String[]{"resolved-index-17"}); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-17"))).thenReturn(new String[]{"resolved-index-17"}); final Set results = ip.concreteIndexNames(user, resolver, clusterService); @@ -132,7 +132,7 @@ public void testExactName() { verify(clusterService).state(); verify(ip).getUnresolvedIndexPattern(user); - verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-17")); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-17")); } /** Verify concreteIndexNames on multiple matches */ @@ -140,7 +140,7 @@ public void testExactName() { public void testMultipleConcreteIndices() { doReturn("index-1*").when(ip).getUnresolvedIndexPattern(user); doReturn(createClusterState()).when(clusterService).state(); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-1*"))).thenReturn(new String[]{"resolved-index-17", "resolved-index-18"}); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*"))).thenReturn(new String[]{"resolved-index-17", "resolved-index-18"}); final Set results = ip.concreteIndexNames(user, resolver, clusterService); @@ -148,7 +148,7 @@ public void testMultipleConcreteIndices() { verify(clusterService, times(2)).state(); verify(ip).getUnresolvedIndexPattern(user); - verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-1*")); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*")); } /** Verify concreteIndexNames when there is an alias */ @@ -157,12 +157,11 @@ public void testMultipleConcreteIndicesWithOneAlias() { doReturn("index-1*").when(ip).getUnresolvedIndexPattern(user); doReturn(createClusterState( - new IndexShorthand("index-111", Type.DATA_STREAM), // Name matches/wrong type new IndexShorthand("index-100", Type.ALIAS), // Name and type match new IndexShorthand("19", Type.ALIAS) // Type matches/wrong name )).when(clusterService).state(); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-100"))).thenReturn(new String[]{"resolved-index-100"}); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-1*"))).thenReturn(new String[]{"resolved-index-17", "resolved-index-18"}); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-100"))).thenReturn(new String[]{"resolved-index-100"}); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*"))).thenReturn(new String[]{"resolved-index-17", "resolved-index-18"}); final Set results = ip.concreteIndexNames(user, resolver, clusterService); @@ -170,8 +169,8 @@ public void testMultipleConcreteIndicesWithOneAlias() { verify(clusterService, times(3)).state(); verify(ip).getUnresolvedIndexPattern(user); - verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-100")); - verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-1*")); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-100")); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*")); } /** Verify attemptResolveIndexNames with multiple aliases */ @@ -179,13 +178,12 @@ public void testMultipleConcreteIndicesWithOneAlias() { public void testMultipleConcreteAliasedAndUnresolved() { doReturn("index-1*").when(ip).getUnresolvedIndexPattern(user); doReturn(createClusterState( - new IndexShorthand("index-111", Type.DATA_STREAM), // Name matches/wrong type new IndexShorthand("index-100", Type.ALIAS), // Name and type match new IndexShorthand("index-101", Type.ALIAS), // Name and type match new IndexShorthand("19", Type.ALIAS) // Type matches/wrong name )).when(clusterService).state(); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-100"), eq("index-101"))).thenReturn(new String[]{"resolved-index-100", "resolved-index-101"}); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-1*"))).thenReturn(new String[]{"resolved-index-17", "resolved-index-18"}); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-100"), eq("index-101"))).thenReturn(new String[]{"resolved-index-100", "resolved-index-101"}); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*"))).thenReturn(new String[]{"resolved-index-17", "resolved-index-18"}); final Set results = ip.attemptResolveIndexNames(user, resolver, clusterService); @@ -193,8 +191,8 @@ public void testMultipleConcreteAliasedAndUnresolved() { verify(clusterService, times(3)).state(); verify(ip).getUnresolvedIndexPattern(user); - verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-100"), eq("index-101")); - verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq("index-1*")); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-100"), eq("index-101")); + verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*")); } private ClusterState createClusterState(final IndexShorthand... indices) { diff --git a/src/test/resources/internal_users.yml b/src/test/resources/internal_users.yml index 951b988bbe..62bf83ae58 100644 --- a/src/test/resources/internal_users.yml +++ b/src/test/resources/internal_users.yml @@ -346,12 +346,18 @@ ds2: ds3: hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m #password is: nagilum +ds4: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum pit-1: hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m #password is: nagilum pit-2: hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m #password is: nagilum +pit-3: + hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m + #password is: nagilum all-pit: hash: $2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m #password is: nagilum diff --git a/src/test/resources/roles.yml b/src/test/resources/roles.yml index 2bff2a97f0..898ea9215f 100644 --- a/src/test/resources/roles.yml +++ b/src/test/resources/roles.yml @@ -1132,6 +1132,17 @@ data_stream_3: - "indices:data/write/index" - "indices:data/write/bulk*" +data_stream_4: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + cluster_permissions: [] + index_permissions: + - index_patterns: + - "*" + allowed_actions: + - "DATASTREAM_ALL" + data_stream_admin: reserved: true hidden: false @@ -1289,6 +1300,20 @@ point_in_time_2: allowed_actions: - "manage_point_in_time" +point_in_time_3: + reserved: true + hidden: false + description: "Migrated from v6 (all types mapped)" + index_permissions: + - index_patterns: + - "my-data-stream31" + - "pit_3" + dls: null + fls: null + masked_fields: null + allowed_actions: + - "manage_point_in_time" + point_in_time_all: reserved: true hidden: false diff --git a/src/test/resources/roles_mapping.yml b/src/test/resources/roles_mapping.yml index 8c65fab329..2233827e51 100644 --- a/src/test/resources/roles_mapping.yml +++ b/src/test/resources/roles_mapping.yml @@ -413,6 +413,11 @@ data_stream_3: hidden: false users: - "ds3" +data_stream_4: + reserved: false + hidden: false + users: + - "ds4" point_in_time_1: reserved: false hidden: false @@ -423,6 +428,11 @@ point_in_time_2: hidden: false users: - "pit-2" +point_in_time_3: + reserved: false + hidden: false + users: + - "pit-3" point_in_time_all: reserved: false hidden: false From 40e2e9c628b20b932140288af890b7f67cbabbe7 Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Wed, 9 Nov 2022 19:50:34 +0100 Subject: [PATCH 074/369] Tests for cross cluster search (#2178) Signed-off-by: Lukasz Soszynski Signed-off-by: Lukasz Soszynski <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> --- .../security/CrossClusterSearchTests.java | 435 ++++++++++++++++++ .../java/org/opensearch/security/Song.java | 55 ++- .../test/framework/TestSecurityConfig.java | 4 + .../certificate/TestCertificates.java | 6 + .../framework/cluster/ClusterManager.java | 50 +- .../test/framework/cluster/LocalCluster.java | 13 +- .../cluster/LocalOpenSearchCluster.java | 9 +- .../test/framework/cluster/NodeRole.java | 14 + .../cluster/SearchRequestFactory.java | 8 + .../framework/cluster/TestRestClient.java | 8 + .../GetResponseDocumentFieldValueMatcher.java | 4 + .../matcher/GetResponseDocumentIdMatcher.java | 4 + ...earchHitContainsFieldWithValueMatcher.java | 2 +- .../SearchHitDoesNotContainFieldMatcher.java | 63 +++ ...HitsContainDocumentsInAnyOrderMatcher.java | 74 +++ .../matcher/SearchResponseMatchers.java | 22 + 16 files changed, 739 insertions(+), 32 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/CrossClusterSearchTests.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/NodeRole.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitDoesNotContainFieldMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentsInAnyOrderMatcher.java diff --git a/src/integrationTest/java/org/opensearch/security/CrossClusterSearchTests.java b/src/integrationTest/java/org/opensearch/security/CrossClusterSearchTests.java new file mode 100644 index 0000000000..5f812b921b --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/CrossClusterSearchTests.java @@ -0,0 +1,435 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.client.Client; +import org.opensearch.client.RestHighLevelClient; +import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.certificate.TestCertificates; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.SearchRequestFactory; +import org.opensearch.test.framework.cluster.TestRestClient; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.opensearch.client.RequestOptions.DEFAULT; +import static org.opensearch.rest.RestStatus.FORBIDDEN; +import static org.opensearch.security.Song.ARTIST_FIRST; +import static org.opensearch.security.Song.FIELD_ARTIST; +import static org.opensearch.security.Song.FIELD_GENRE; +import static org.opensearch.security.Song.FIELD_LYRICS; +import static org.opensearch.security.Song.FIELD_STARS; +import static org.opensearch.security.Song.FIELD_TITLE; +import static org.opensearch.security.Song.GENRE_JAZZ; +import static org.opensearch.security.Song.GENRE_ROCK; +import static org.opensearch.security.Song.QUERY_TITLE_MAGNUM_OPUS; +import static org.opensearch.security.Song.SONGS; +import static org.opensearch.security.Song.TITLE_MAGNUM_OPUS; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.queryStringQueryRequest; +import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy; +import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.statusException; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.isSuccessfulSearchResponse; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.numberOfTotalHitsIsEqualTo; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitContainsFieldWithValue; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitDoesNotContainField; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitsContainDocumentWithId; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitsContainDocumentsInAnyOrder; + +/** +* This is a parameterized test so that one test class is used to test security plugin behaviour when ccsMinimizeRoundtrips +* option is enabled or disabled. Method {@link #parameters()} is a source of parameters values. +*/ +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class CrossClusterSearchTests { + + private static final String SONG_INDEX_NAME = "song_lyrics"; + + private static final String PROHIBITED_SONG_INDEX_NAME = "prohibited_song_lyrics"; + + public static final String REMOTE_CLUSTER_NAME = "ccsRemote"; + public static final String REMOTE_SONG_INDEX = REMOTE_CLUSTER_NAME + ":" + SONG_INDEX_NAME; + + public static final String SONG_ID_1R = "remote-00001"; + public static final String SONG_ID_2L = "local-00002"; + public static final String SONG_ID_3R = "remote-00003"; + public static final String SONG_ID_4L = "local-00004"; + public static final String SONG_ID_5R = "remote-00005"; + public static final String SONG_ID_6R = "remote-00006"; + + private static final Role LIMITED_ROLE = new Role("limited_role") + .indexPermissions("indices:data/read/search", "indices:admin/shards/search_shards") + .on(SONG_INDEX_NAME, "user-${user.name}-${attr.internal.type}"); + + private static final Role DLS_ROLE_ROCK = new Role("dls_role_rock") + .indexPermissions("indices:data/read/search", "indices:data/read/get", "indices:admin/shards/search_shards") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_GENRE, GENRE_ROCK)) + .on(SONG_INDEX_NAME); + + private static final Role DLS_ROLE_JAZZ = new Role("dls_role_jazz") + .indexPermissions("indices:data/read/search", "indices:data/read/get", "indices:admin/shards/search_shards") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_GENRE, GENRE_JAZZ)) + .on(SONG_INDEX_NAME); + + private static final Role FLS_EXCLUDE_LYRICS_ROLE = new Role("fls_exclude_lyrics_role") + .indexPermissions("indices:data/read/search", "indices:data/read/get", "indices:admin/shards/search_shards") + .fls("~" + FIELD_LYRICS) + .on(SONG_INDEX_NAME); + + private static final Role FLS_INCLUDE_TITLE_ROLE = new Role("fls_include_title_role") + .indexPermissions("indices:data/read/search", "indices:data/read/get", "indices:admin/shards/search_shards") + .fls(FIELD_TITLE) + .on(SONG_INDEX_NAME); + + public static final String TYPE_ATTRIBUTE = "type"; + + private static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS).attr(TYPE_ATTRIBUTE, "administrative"); + private static final User LIMITED_USER = new User("limited_user").attr(TYPE_ATTRIBUTE, "personal"); + + private static final User FLS_INCLUDE_TITLE_USER = new User("fls_include_title_user"); + + private static final User FLS_EXCLUDE_LYRICS_USER = new User("fls_exclude_lyrics_user"); + + private static final User DLS_USER_ROCK = new User("dls-user-rock"); + + private static final User DLS_USER_JAZZ = new User("dls-user-jazz"); + + public static final String LIMITED_USER_INDEX_NAME = "user-" + LIMITED_USER.getName() + "-" + LIMITED_USER.getAttribute(TYPE_ATTRIBUTE); + public static final String ADMIN_USER_INDEX_NAME = "user-" + ADMIN_USER.getName() + "-" + ADMIN_USER.getAttribute(TYPE_ATTRIBUTE); + + private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); + + private final boolean ccsMinimizeRoundtrips; + + public static final String PLUGINS_SECURITY_RESTAPI_ROLES_ENABLED = "plugins.security.restapi.roles_enabled"; + @ClassRule + public static final LocalCluster remoteCluster = new LocalCluster.Builder().certificates(TEST_CERTIFICATES) + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false).clusterName(REMOTE_CLUSTER_NAME) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .roles(LIMITED_ROLE, DLS_ROLE_ROCK, DLS_ROLE_JAZZ, FLS_EXCLUDE_LYRICS_ROLE, FLS_INCLUDE_TITLE_ROLE).users(ADMIN_USER) + .build(); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().certificates(TEST_CERTIFICATES) + .clusterManager(ClusterManager.SINGLE_REMOTE_CLIENT).anonymousAuth(false).clusterName("ccsLocal") + .nodeSettings(Map.of(PLUGINS_SECURITY_RESTAPI_ROLES_ENABLED, List.of("user_" + ADMIN_USER.getName() +"__" + ALL_ACCESS.getName()))) + .remote(REMOTE_CLUSTER_NAME, remoteCluster) + .roles(LIMITED_ROLE, DLS_ROLE_ROCK, DLS_ROLE_JAZZ, FLS_EXCLUDE_LYRICS_ROLE, FLS_INCLUDE_TITLE_ROLE).authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER, LIMITED_USER, DLS_USER_ROCK, DLS_USER_JAZZ, FLS_INCLUDE_TITLE_USER, FLS_EXCLUDE_LYRICS_USER) + .build(); + + @ParametersFactory(shuffle = false) + public static Iterable parameters() { + return List.of(new Object[] { true }, new Object[] { false }); + } + + public CrossClusterSearchTests(Boolean ccsMinimizeRoundtrips) { + this.ccsMinimizeRoundtrips = ccsMinimizeRoundtrips; + } + + @BeforeClass + public static void createTestData() { + try(Client client = remoteCluster.getInternalNodeClient()){ + client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_1R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0]).get(); + client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_6R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[5]).get(); + client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(SONG_ID_3R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1]).get(); + client.prepareIndex(LIMITED_USER_INDEX_NAME).setId(SONG_ID_5R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[4]).get(); + } + try(Client client = cluster.getInternalNodeClient()){ + client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_2L).setRefreshPolicy(IMMEDIATE).setSource(SONGS[2]).get(); + client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(SONG_ID_4L).setRefreshPolicy(IMMEDIATE).setSource(SONGS[3]).get(); + } + try(TestRestClient client = cluster.getRestClient(ADMIN_USER)) { + client.assignRoleToUser(LIMITED_USER.getName(), LIMITED_ROLE.getName()).assertStatusCode(200); + client.assignRoleToUser(DLS_USER_ROCK.getName(), DLS_ROLE_ROCK.getName()).assertStatusCode(200); + client.assignRoleToUser(DLS_USER_JAZZ.getName(), DLS_ROLE_JAZZ.getName()).assertStatusCode(200); + client.assignRoleToUser(FLS_INCLUDE_TITLE_USER.getName(), FLS_INCLUDE_TITLE_ROLE.getName()).assertStatusCode(200); + client.assignRoleToUser(FLS_EXCLUDE_LYRICS_USER.getName(), FLS_EXCLUDE_LYRICS_ROLE.getName()).assertStatusCode(200); + } + } + + @Test + public void shouldFindDocumentOnRemoteCluster_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_SONG_INDEX); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(2)); + assertThat(response, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_1R)); + assertThat(response, searchHitsContainDocumentWithId(1, SONG_INDEX_NAME, SONG_ID_6R)); + } + } + + private SearchRequest searchAll(String indexName) { + SearchRequest searchRequest = SearchRequestFactory.searchAll(indexName); + searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); + return searchRequest; + } + + @Test + public void shouldFindDocumentOnRemoteCluster_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":" + PROHIBITED_SONG_INDEX_NAME); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentOnRemoteClustersWhenStarIsUsedAsClusterName_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll("*" + ":" + SONG_INDEX_NAME); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + //only remote documents are found + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(2)); + assertThat(response, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_1R)); + assertThat(response, searchHitsContainDocumentWithId(1, SONG_INDEX_NAME, SONG_ID_6R)); + } + } + + @Test + public void shouldSearchForDocumentOnRemoteClustersWhenStarIsUsedAsClusterName_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll("*" + ":" + PROHIBITED_SONG_INDEX_NAME); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentOnBothClustersWhenIndexOnBothClusterArePointedOut_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = SearchRequestFactory.searchAll(REMOTE_SONG_INDEX, SONG_INDEX_NAME); + searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(3)); + assertThat(response, searchHitsContainDocumentsInAnyOrder( + Pair.of(SONG_INDEX_NAME, SONG_ID_1R), + Pair.of(SONG_INDEX_NAME, SONG_ID_2L), + Pair.of(SONG_INDEX_NAME, SONG_ID_6R)) + ); + } + } + + @Test + public void shouldSearchForDocumentOnBothClustersWhenIndexOnBothClusterArePointedOut_negativeLackOfLocalAccess() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + var searchRequest = SearchRequestFactory.searchAll(REMOTE_SONG_INDEX, PROHIBITED_SONG_INDEX_NAME); + searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentOnBothClustersWhenIndexOnBothClusterArePointedOut_negativeLackOfRemoteAccess() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + String remoteIndex = REMOTE_CLUSTER_NAME + ":" + PROHIBITED_SONG_INDEX_NAME; + SearchRequest searchRequest = SearchRequestFactory.searchAll(remoteIndex, SONG_INDEX_NAME); + searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchViaAllAliasOnRemoteCluster_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":_all"); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(4)); + assertThat(response, searchHitsContainDocumentsInAnyOrder( + Pair.of(SONG_INDEX_NAME, SONG_ID_1R), + Pair.of(SONG_INDEX_NAME, SONG_ID_6R), + Pair.of(PROHIBITED_SONG_INDEX_NAME, SONG_ID_3R), + Pair.of(LIMITED_USER_INDEX_NAME, SONG_ID_5R)) + ); + } + } + + @Test + public void shouldSearchViaAllAliasOnRemoteCluster_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":_all"); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchAllIndexOnRemoteClusterWhenStarIsUsedAsIndexName_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":*"); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(4)); + assertThat(response, searchHitsContainDocumentsInAnyOrder( + Pair.of(SONG_INDEX_NAME, SONG_ID_1R), + Pair.of(SONG_INDEX_NAME, SONG_ID_6R), + Pair.of(PROHIBITED_SONG_INDEX_NAME, SONG_ID_3R), + Pair.of(LIMITED_USER_INDEX_NAME, SONG_ID_5R)) + ); + } + } + + @Test + public void shouldSearchAllIndexOnRemoteClusterWhenStarIsUsedAsIndexName_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":*"); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldResolveUserNameExpressionInRoleIndexPattern_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":" + LIMITED_USER_INDEX_NAME); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, numberOfTotalHitsIsEqualTo(1)); + } + } + + @Test + public void shouldResolveUserNameExpressionInRoleIndexPattern_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":" + ADMIN_USER_INDEX_NAME); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchInIndexWithPrefix_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":song*"); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(2)); + assertThat(response, searchHitsContainDocumentsInAnyOrder( + Pair.of(SONG_INDEX_NAME, SONG_ID_1R), + Pair.of(SONG_INDEX_NAME, SONG_ID_6R) + )); + } + } + + @Test + public void shouldSearchInIndexWithPrefix_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":prohibited*"); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldEvaluateDocumentLevelSecurityRulesOnRemoteClusterOnSearchRequest_caseRock() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DLS_USER_ROCK)) { + SearchRequest searchRequest = searchAll(REMOTE_SONG_INDEX); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + //searching for all documents, so is it important that result contain only one document with id SONG_ID_1 + // and document with SONG_ID_6 is excluded from result set by DLS + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(1)); + assertThat(response, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_1R)); + } + } + + @Test + public void shouldEvaluateDocumentLevelSecurityRulesOnRemoteClusterOnSearchRequest_caseJazz() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DLS_USER_JAZZ)) { + SearchRequest searchRequest = searchAll(REMOTE_SONG_INDEX); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + //searching for all documents, so is it important that result contain only one document with id SONG_ID_6 + // and document with SONG_ID_1 is excluded from result set by DLS + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(1)); + assertThat(response, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_6R)); + } + } + + @Test + public void shouldHaveAccessOnlyToSpecificField() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(FLS_INCLUDE_TITLE_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(REMOTE_SONG_INDEX, QUERY_TITLE_MAGNUM_OPUS); + searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(1)); + //document should contain only title field + assertThat(response, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat(response, searchHitDoesNotContainField(0, FIELD_ARTIST)); + assertThat(response, searchHitDoesNotContainField(0, FIELD_LYRICS)); + assertThat(response, searchHitDoesNotContainField(0, FIELD_STARS)); + assertThat(response, searchHitDoesNotContainField(0, FIELD_GENRE)); + } + } + + @Test + public void shouldLackAccessToSpecificField() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(FLS_EXCLUDE_LYRICS_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(REMOTE_SONG_INDEX, QUERY_TITLE_MAGNUM_OPUS); + searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(1)); + //document should not contain lyrics field + assertThat(response, searchHitDoesNotContainField(0, FIELD_LYRICS)); + + assertThat(response, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat(response, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + assertThat(response, searchHitContainsFieldWithValue(0, FIELD_STARS, 1)); + assertThat(response, searchHitContainsFieldWithValue(0, FIELD_GENRE, GENRE_ROCK)); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/Song.java b/src/integrationTest/java/org/opensearch/security/Song.java index e71a258169..b5ca1895b5 100644 --- a/src/integrationTest/java/org/opensearch/security/Song.java +++ b/src/integrationTest/java/org/opensearch/security/Song.java @@ -9,14 +9,17 @@ */ package org.opensearch.security; +import java.util.Map; +import java.util.Objects; -class Song { +public class Song { static final String FIELD_TITLE = "title"; static final String FIELD_ARTIST = "artist"; static final String FIELD_LYRICS = "lyrics"; - static final String FIELD_STARS = "stars"; + + static final String FIELD_GENRE = "genre"; static final String ARTIST_FIRST = "First artist"; static final String ARTIST_STRING = "String"; static final String ARTIST_TWINS = "Twins"; @@ -26,19 +29,57 @@ class Song { static final String ARTIST_NO = "No!"; static final String TITLE_POISON = "Poison"; + public static final String ARTIST_YES = "yes"; + + public static final String TITLE_AFFIRMATIVE = "Affirmative"; + + public static final String ARTIST_UNKNOWN = "unknown"; + public static final String TITLE_CONFIDENTIAL = "confidential"; + public static final String LYRICS_1 = "Very deep subject"; public static final String LYRICS_2 = "Once upon a time"; public static final String LYRICS_3 = "giant nonsense"; public static final String LYRICS_4 = "Much too much"; + public static final String LYRICS_5 = "Little to little"; + public static final String LYRICS_6 = "confidential secret classified"; + + static final String GENRE_ROCK = "rock"; + static final String GENRE_JAZZ = "jazz"; + static final String GENRE_BLUES = "blues"; static final String QUERY_TITLE_NEXT_SONG = FIELD_TITLE + ":" + "\"" + TITLE_NEXT_SONG + "\""; static final String QUERY_TITLE_POISON = FIELD_TITLE + ":" + TITLE_POISON; static final String QUERY_TITLE_MAGNUM_OPUS = FIELD_TITLE + ":" + TITLE_MAGNUM_OPUS; - static final Object[][] SONGS = { - {FIELD_ARTIST, ARTIST_FIRST, FIELD_TITLE, TITLE_MAGNUM_OPUS ,FIELD_LYRICS, LYRICS_1, FIELD_STARS, 1}, - {FIELD_ARTIST, ARTIST_STRING, FIELD_TITLE, TITLE_SONG_1_PLUS_1, FIELD_LYRICS, LYRICS_2, FIELD_STARS, 2}, - {FIELD_ARTIST, ARTIST_TWINS, FIELD_TITLE, TITLE_NEXT_SONG, FIELD_LYRICS, LYRICS_3, FIELD_STARS, 3}, - {FIELD_ARTIST, ARTIST_NO, FIELD_TITLE, TITLE_POISON, FIELD_LYRICS, LYRICS_4, FIELD_STARS, 4} + static final Map[] SONGS = { + new Song(ARTIST_FIRST, TITLE_MAGNUM_OPUS ,LYRICS_1, 1, GENRE_ROCK).asMap(), + new Song(ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES).asMap(), + new Song(ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ).asMap(), + new Song(ARTIST_NO, TITLE_POISON, LYRICS_4, 4, GENRE_ROCK).asMap(), + new Song(ARTIST_YES, TITLE_AFFIRMATIVE,LYRICS_5, 5, GENRE_BLUES).asMap(), + new Song(ARTIST_UNKNOWN, TITLE_CONFIDENTIAL, LYRICS_6, 6, GENRE_JAZZ).asMap() }; + + private final String artist; + private final String title; + private final String lyrics; + private final Integer stars; + + private final String genre; + + public Song(String artist, String title, String lyrics, Integer stars, String genre) { + this.artist = Objects.requireNonNull(artist, "Artist is required"); + this.title = Objects.requireNonNull(title, "Title is required"); + this.lyrics = Objects.requireNonNull(lyrics, "Lyrics is required"); + this.stars = Objects.requireNonNull(stars, "Stars field is required"); + this.genre = Objects.requireNonNull(genre, "Genre field is required"); + } + + public Map asMap() { + return Map.of(FIELD_ARTIST, artist, + FIELD_TITLE, title, + FIELD_LYRICS, lyrics, + FIELD_STARS, stars, + FIELD_GENRE, genre); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index 1fd93be169..300604d53e 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -203,6 +203,10 @@ public Set getRoleNames() { return roles.stream().map(Role::getName).collect(Collectors.toSet()); } + public Object getAttribute(String attributeName) { + return attributes.get(attributeName); + } + @Override public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { xContentBuilder.startObject(); diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java index df9bcfa41d..c770def8a8 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java @@ -36,6 +36,9 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import static org.opensearch.test.framework.certificate.PublicKeyUsage.CLIENT_AUTH; import static org.opensearch.test.framework.certificate.PublicKeyUsage.CRL_SIGN; import static org.opensearch.test.framework.certificate.PublicKeyUsage.DIGITAL_SIGNATURE; @@ -50,6 +53,8 @@ */ public class TestCertificates { + private static final Logger log = LogManager.getLogger(TestCertificates.class); + public static final Integer MAX_NUMBER_OF_NODE_CERTIFICATES = 3; private static final String CA_SUBJECT = "DC=com,DC=example,O=Example Com Inc.,OU=Example Com Inc. Root CA,CN=Example Com Inc. Root CA"; @@ -68,6 +73,7 @@ public TestCertificates() { .mapToObj(this::createNodeCertificate) .collect(Collectors.toList()); this.adminCertificate = createAdminCertificate(); + log.info("Test certificates successfully generated"); } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java index 760820a3b6..35c5229bfb 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java @@ -31,9 +31,11 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import org.opensearch.index.reindex.ReindexModulePlugin; @@ -50,17 +52,18 @@ import static org.opensearch.test.framework.cluster.NodeType.DATA; public enum ClusterManager { - //3 nodes (1m, 2d) - DEFAULT(new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true)), + DEFAULT(new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.DATA), new NodeSettings(NodeRole.DATA)), //1 node (1md) - SINGLENODE(new NodeSettings(true, true)), + SINGLENODE(new NodeSettings(NodeRole.CLUSTER_MANAGER, NodeRole.DATA)), + + SINGLE_REMOTE_CLIENT(new NodeSettings(NodeRole.CLUSTER_MANAGER, NodeRole.DATA, NodeRole.REMOTE_CLUSTER_CLIENT)), //4 node (1m, 2d, 1c) - CLIENTNODE(new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true), new NodeSettings(false, false)), + CLIENTNODE(new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.DATA), new NodeSettings(NodeRole.DATA), new NodeSettings()), - THREE_CLUSTER_MANAGERS(new NodeSettings(true, false), new NodeSettings(true, false), new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true)); + THREE_CLUSTER_MANAGERS(new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.DATA), new NodeSettings(NodeRole.DATA)); private List nodeSettings = new LinkedList<>(); @@ -73,11 +76,11 @@ public List getNodeSettings() { } public List getClusterManagerNodeSettings() { - return unmodifiableList(nodeSettings.stream().filter(a -> a.clusterManagerNode).collect(Collectors.toList())); + return unmodifiableList(nodeSettings.stream().filter(a -> a.containRole(NodeRole.CLUSTER_MANAGER)).collect(Collectors.toList())); } public List getNonClusterManagerNodeSettings() { - return unmodifiableList(nodeSettings.stream().filter(a -> !a.clusterManagerNode).collect(Collectors.toList())); + return unmodifiableList(nodeSettings.stream().filter(a -> !a.containRole(NodeRole.CLUSTER_MANAGER)).collect(Collectors.toList())); } public int getNodes() { @@ -85,39 +88,48 @@ public int getNodes() { } public int getClusterManagerNodes() { - return (int) nodeSettings.stream().filter(a -> a.clusterManagerNode).count(); + return (int) nodeSettings.stream().filter(a -> a.containRole(NodeRole.CLUSTER_MANAGER)).count(); } public int getDataNodes() { - return (int) nodeSettings.stream().filter(a -> a.dataNode).count(); + return (int) nodeSettings.stream().filter(a -> a.containRole(NodeRole.DATA)).count(); } public int getClientNodes() { - return (int) nodeSettings.stream().filter(a -> !a.clusterManagerNode && !a.dataNode).count(); + return (int) nodeSettings.stream().filter(a -> a.isClientNode()).count(); } + public static class NodeSettings { private final static List> DEFAULT_PLUGINS = List.of(Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, MatrixAggregationModulePlugin.class, ParentJoinModulePlugin.class, PercolatorModulePlugin.class, ReindexModulePlugin.class); - public final boolean clusterManagerNode; - public final boolean dataNode; + + private final Set roles; public final List> plugins; - public NodeSettings(boolean clusterManagerNode, boolean dataNode) { - this(clusterManagerNode, dataNode, Collections.emptyList()); + public NodeSettings(NodeRole...roles) { + this(roles.length == 0 ? Collections.emptySet() : EnumSet.copyOf(Arrays.asList(roles)), Collections.emptyList()); } - public NodeSettings(boolean clusterManagerNode, boolean dataNode, List> additionalPlugins) { + public NodeSettings(Set roles, List> additionalPlugins) { super(); - this.clusterManagerNode = clusterManagerNode; - this.dataNode = dataNode; + this.roles = Objects.requireNonNull(roles, "Node roles set must not be null"); this.plugins = mergePlugins(additionalPlugins, DEFAULT_PLUGINS); } + + public boolean containRole(NodeRole nodeRole) { + return roles.contains(nodeRole); + } + + public boolean isClientNode() { + return (roles.contains(NodeRole.DATA) == false) && (roles.contains(NodeRole.CLUSTER_MANAGER)); + } + NodeType recognizeNodeType() { - if (clusterManagerNode) { + if (roles.contains(NodeRole.CLUSTER_MANAGER)) { return CLUSTER_MANAGER; - } else if (dataNode) { + } else if (roles.contains(NodeRole.DATA)) { return DATA; } else { return CLIENT; diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index 489d94302f..4d483b3776 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -123,8 +123,11 @@ public void before() throws Throwable { for (Map.Entry entry : remotes.entrySet()) { @SuppressWarnings("resource") InetSocketAddress transportAddress = entry.getValue().localOpenSearchCluster.clusterManagerNode().getTransportAddress(); + String key = "cluster.remote." + entry.getKey() + ".seeds"; + String value = transportAddress.getHostString() + ":" + transportAddress.getPort(); + log.info("Remote cluster '{}' added to configuration with the following seed '{}'", key, value); nodeOverride = Settings.builder().put(nodeOverride) - .putList("cluster.remote." + entry.getKey() + ".seeds", transportAddress.getHostString() + ":" + transportAddress.getPort()) + .putList(key, value) .build(); } @@ -252,7 +255,6 @@ public static class Builder { private boolean loadConfigurationIntoIndex = true; public Builder() { - this.testCertificates = new TestCertificates(); } public Builder dependsOn(Object object) { @@ -381,9 +383,16 @@ public Builder loadConfigurationIntoIndex(boolean loadConfigurationIntoIndex) { this.loadConfigurationIntoIndex = loadConfigurationIntoIndex; return this; } + public Builder certificates(TestCertificates certificates) { + this.testCertificates = certificates; + return this; + } public LocalCluster build() { try { + if(testCertificates == null){ + testCertificates = new TestCertificates(); + } clusterName += "_" + num.incrementAndGet(); Settings settings = nodeOverrideSettingsBuilder.build(); diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java index 66f4f095cb..9a8602fb2e 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java @@ -370,7 +370,7 @@ boolean hasAssignedType(NodeType type) { CompletableFuture start() { CompletableFuture completableFuture = new CompletableFuture<>(); Class[] mergedPlugins = nodeSettings.pluginsWithAddition(additionalPlugins); - this.node = new PluginAwareNode(nodeSettings.clusterManagerNode, getOpenSearchSettings(), mergedPlugins); + this.node = new PluginAwareNode(nodeSettings.containRole(NodeRole.CLUSTER_MANAGER), getOpenSearchSettings(), mergedPlugins); new Thread(new Runnable() { @@ -495,12 +495,15 @@ private Settings getMinimalOpenSearchSettings() { private List createNodeRolesSettings() { final ImmutableList.Builder nodeRolesBuilder = ImmutableList.builder(); - if (nodeSettings.dataNode) { + if (nodeSettings.containRole(NodeRole.DATA)) { nodeRolesBuilder.add("data"); } - if (nodeSettings.clusterManagerNode) { + if (nodeSettings.containRole(NodeRole.CLUSTER_MANAGER)) { nodeRolesBuilder.add("cluster_manager"); } + if(nodeSettings.containRole(NodeRole.REMOTE_CLUSTER_CLIENT)) { + nodeRolesBuilder.add("remote_cluster_client"); + } return nodeRolesBuilder.build(); } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeRole.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeRole.java new file mode 100644 index 0000000000..7dc77ef37e --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeRole.java @@ -0,0 +1,14 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.cluster; + +enum NodeRole { + DATA, CLUSTER_MANAGER, REMOTE_CLUSTER_CLIENT +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java index 5c1d25911a..40f0d63c19 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java @@ -53,6 +53,14 @@ public static SearchRequest searchRequestWithScroll(String indexName, int pageSi return searchRequest; } + public static SearchRequest searchAll(String...indexNames) { + SearchRequest searchRequest = new SearchRequest(indexNames); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.matchAllQuery()); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } + public static SearchScrollRequest getSearchScrollRequest(SearchResponse searchResponse) { SearchScrollRequest scrollRequest = new SearchScrollRequest(searchResponse.getScrollId()); scrollRequest.scroll(new TimeValue(1, MINUTES)); diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java index b4b2ca0583..aac11008d6 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -37,6 +37,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; import javax.net.ssl.SSLContext; @@ -167,6 +168,13 @@ public HttpResponse patch(String path, String body) { return executeRequest(uriRequest, CONTENT_TYPE_JSON); } + public HttpResponse assignRoleToUser(String username, String roleName) { + Objects.requireNonNull(roleName, "Role name is required"); + Objects.requireNonNull(username, "User name is required"); + String body = String.format("[{\"op\":\"add\",\"path\":\"/opendistro_security_roles\",\"value\":[\"%s\"]}]", roleName); + return patch("_plugins/_security/api/internalusers/" + username, body); + } + public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... requestSpecificHeaders) { try(CloseableHttpClient httpClient = getHTTPClient()) { diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentFieldValueMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentFieldValueMatcher.java index 54b29949c1..72418fe6e0 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentFieldValueMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentFieldValueMatcher.java @@ -31,6 +31,10 @@ public GetResponseDocumentFieldValueMatcher(String fieldName, Object fieldValue) @Override protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { Map source = response.getSource(); + if(source == null) { + mismatchDescription.appendText("Source is not available in search results"); + return false; + } if(source.containsKey(fieldName) == false) { mismatchDescription.appendText("Document does not contain field ").appendValue(fieldName); return false; diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentIdMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentIdMatcher.java index 64a64f93dd..85519a7261 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentIdMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentIdMatcher.java @@ -36,6 +36,10 @@ protected boolean matchesSafely(GetResponse response, Description mismatchDescri mismatchDescription.appendText("Document contain incorrect id which is ").appendValue(response.getId()); return false; } + if(response.isExists() == false) { + mismatchDescription.appendText("Document does not exist or is inaccessible"); + return false; + } return true; } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitContainsFieldWithValueMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitContainsFieldWithValueMatcher.java index 2a8e866901..aa9cfe6864 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitContainsFieldWithValueMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitContainsFieldWithValueMatcher.java @@ -49,7 +49,7 @@ class SearchHitContainsFieldWithValueMatcher extends TypeSafeDiagnosingMatche mismatchDescription.appendText("Source document is null, is fetch source option set to true?"); return false; } - if(!source.containsKey(fieldName)) { + if(source.containsKey(fieldName) == false) { mismatchDescription.appendText("Document does not contain field ").appendValue(fieldName); return false; } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitDoesNotContainFieldMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitDoesNotContainFieldMatcher.java new file mode 100644 index 0000000000..106f2ee943 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitDoesNotContainFieldMatcher.java @@ -0,0 +1,63 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Map; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.search.SearchHit; + +import static java.util.Objects.requireNonNull; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.readTotalHits; + +class SearchHitDoesNotContainFieldMatcher extends TypeSafeDiagnosingMatcher { + + private final int hitIndex; + + private final String fieldName; + + public SearchHitDoesNotContainFieldMatcher(int hitIndex, String fieldName) { + this.hitIndex = hitIndex; + this.fieldName = requireNonNull(fieldName, "Field name is required."); + } + + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + Long numberOfHits = readTotalHits(searchResponse); + if(numberOfHits == null) { + mismatchDescription.appendText("Total number of hits is unknown."); + return false; + } + if(hitIndex >= numberOfHits) { + mismatchDescription.appendText("Search result contain only ").appendValue(numberOfHits).appendText(" hits"); + return false; + } + SearchHit searchHit = searchResponse.getHits().getAt(hitIndex); + Map source = searchHit.getSourceAsMap(); + if(source == null){ + mismatchDescription.appendText("Source document is null, is fetch source option set to true?"); + return false; + } + if(source.containsKey(fieldName)) { + mismatchDescription.appendText(" document contains field ").appendValue(fieldName); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("search hit with index ").appendValue(hitIndex).appendText(" does not contain field ") + .appendValue(fieldName); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentsInAnyOrderMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentsInAnyOrderMatcher.java new file mode 100644 index 0000000000..78fd20557e --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentsInAnyOrderMatcher.java @@ -0,0 +1,74 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.tuple.Pair; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.search.SearchHit; +import org.opensearch.search.SearchHits; + +import static java.util.Objects.requireNonNull; + +class SearchHitsContainDocumentsInAnyOrderMatcher extends TypeSafeDiagnosingMatcher { + + /** + * Pair contain index name and document id + */ + private final List> documentIds; + + /** + * + * @param documentIds Pair contain index name and document id + */ + public SearchHitsContainDocumentsInAnyOrderMatcher(List> documentIds) { + this.documentIds = requireNonNull(documentIds, "Document ids are required."); + } + + @Override + protected boolean matchesSafely(SearchResponse response, Description mismatchDescription) { + SearchHits hits = response.getHits(); + if(hits == null) { + mismatchDescription.appendText("Search response does not contains hits (null)."); + return false; + } + SearchHit[] hitsArray = hits.getHits(); + if(hitsArray == null) { + mismatchDescription.appendText("Search hits array is null"); + return false; + } + Set> actualDocumentIds = Arrays.stream(hitsArray) + .map(result -> Pair.of(result.getIndex(), result.getId())) + .collect(Collectors.toSet()); + for(Pair desiredDocumentId : documentIds) { + if(actualDocumentIds.contains(desiredDocumentId) == false) { + mismatchDescription.appendText("search result does not contain document with id ") + .appendValue(desiredDocumentId.getKey()).appendText("/").appendValue(desiredDocumentId.getValue()); + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + String documentIdsString = documentIds.stream() + .map(pair -> pair.getKey() + "/" + pair.getValue()) + .collect(Collectors.joining(", ")); + description.appendText("Search response should contains following documents ").appendValue(documentIdsString); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java index efd7da8cf9..acb669a85e 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java @@ -9,8 +9,11 @@ */ package org.opensearch.test.framework.matcher; +import java.util.Arrays; +import java.util.List; import java.util.Optional; +import org.apache.commons.lang3.tuple.Pair; import org.hamcrest.Matcher; import org.opensearch.action.search.SearchResponse; @@ -37,6 +40,11 @@ public static Matcher searchHitContainsFieldWithValue(int hi return new SearchHitContainsFieldWithValueMatcher<>(hitIndex, fieldName, expectedValue); } + public static Matcher searchHitDoesNotContainField(int hitIndex, String fieldName) { + return new SearchHitDoesNotContainFieldMatcher(hitIndex, fieldName); + } + + public static Matcher searchHitsContainDocumentWithId(int hitIndex, String indexName, String documentId) { return new SearchHitsContainDocumentWithIdMatcher(hitIndex, indexName, documentId); } @@ -53,6 +61,20 @@ public static Matcher containAggregationWithNameAndType(String e return new ContainsAggregationWithNameAndTypeMatcher(expectedAggregationName, expectedAggregationType); } + /** + * Matcher checks if search result contains all expected documents + * + * @param documentIds Pair contain index name and document id + * @return matcher + */ + public static Matcher searchHitsContainDocumentsInAnyOrder(List> documentIds) { + return new SearchHitsContainDocumentsInAnyOrderMatcher(documentIds); + } + + public static Matcher searchHitsContainDocumentsInAnyOrder(Pair...documentIds) { + return new SearchHitsContainDocumentsInAnyOrderMatcher(Arrays.asList(documentIds)); + } + static Long readTotalHits(SearchResponse searchResponse) { return Optional.ofNullable(searchResponse) .map(SearchResponse::getHits) From 977677d61120e8cd79c6c0f256eae413a47936fd Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Thu, 10 Nov 2022 18:28:17 +0100 Subject: [PATCH 075/369] Test cases related to JWT authentication. (#2179) * Test cases related to JWT authentication. * JWT authentication tests extended to verify unauthorized response reason. * Methods in class JwtAuthorizationHeaderFactory renamed. * Javadocs for classes LogCapturingAppender, LogsRule. Signed-off-by: Lukasz Soszynski Signed-off-by: Lukasz Soszynski <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> --- .../security/SearchOperationTest.java | 10 +- .../java/org/opensearch/security/Song.java | 40 +-- .../security/http/JwtAuthenticationTests.java | 263 ++++++++++++++++++ .../http/JwtAuthorizationHeaderFactory.java | 141 ++++++++++ .../test/framework/JwtConfigBuilder.java | 62 +++++ .../test/framework/TestSecurityConfig.java | 17 +- .../cluster/OpenSearchClientProvider.java | 22 +- .../framework/cluster/TestRestClient.java | 19 +- .../framework/log/LogCapturingAppender.java | 121 ++++++++ .../test/framework/log/LogsRule.java | 67 +++++ .../resources/log4j2-test.properties | 14 +- 11 files changed, 736 insertions(+), 40 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java create mode 100644 src/integrationTest/java/org/opensearch/security/http/JwtAuthorizationHeaderFactory.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/JwtConfigBuilder.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/log/LogCapturingAppender.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java diff --git a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java index a6702344b8..8efaeff648 100644 --- a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java +++ b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java @@ -221,15 +221,15 @@ public class SearchOperationTest { public static final String UNUSED_SNAPSHOT_REPOSITORY_NAME = "unused-snapshot-repository"; public static final String RESTORED_SONG_INDEX_NAME = "restored_" + WRITE_SONG_INDEX_NAME; - + public static final String UPDATE_DELETE_OPERATION_INDEX_NAME = "update_delete_index"; public static final String DOCUMENT_TO_UPDATE_ID = "doc_to_update"; - public static final String ID_P4 = "4"; - public static final String ID_S3 = "3"; - public static final String ID_S2 = "2"; - public static final String ID_S1 = "1"; + private static final String ID_P4 = "4"; + private static final String ID_S3 = "3"; + private static final String ID_S2 = "2"; + private static final String ID_S1 = "1"; static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS); diff --git a/src/integrationTest/java/org/opensearch/security/Song.java b/src/integrationTest/java/org/opensearch/security/Song.java index b5ca1895b5..cf585e5dc7 100644 --- a/src/integrationTest/java/org/opensearch/security/Song.java +++ b/src/integrationTest/java/org/opensearch/security/Song.java @@ -14,20 +14,20 @@ public class Song { - static final String FIELD_TITLE = "title"; - static final String FIELD_ARTIST = "artist"; - static final String FIELD_LYRICS = "lyrics"; - static final String FIELD_STARS = "stars"; + public static final String FIELD_TITLE = "title"; + public static final String FIELD_ARTIST = "artist"; + public static final String FIELD_LYRICS = "lyrics"; + public static final String FIELD_STARS = "stars"; - static final String FIELD_GENRE = "genre"; - static final String ARTIST_FIRST = "First artist"; - static final String ARTIST_STRING = "String"; - static final String ARTIST_TWINS = "Twins"; - static final String TITLE_MAGNUM_OPUS = "Magnum Opus"; - static final String TITLE_SONG_1_PLUS_1 = "Song 1+1"; - static final String TITLE_NEXT_SONG = "Next song"; - static final String ARTIST_NO = "No!"; - static final String TITLE_POISON = "Poison"; + public static final String FIELD_GENRE = "genre"; + public static final String ARTIST_FIRST = "First artist"; + public static final String ARTIST_STRING = "String"; + public static final String ARTIST_TWINS = "Twins"; + public static final String TITLE_MAGNUM_OPUS = "Magnum Opus"; + public static final String TITLE_SONG_1_PLUS_1 = "Song 1+1"; + public static final String TITLE_NEXT_SONG = "Next song"; + public static final String ARTIST_NO = "No!"; + public static final String TITLE_POISON = "Poison"; public static final String ARTIST_YES = "yes"; @@ -43,15 +43,15 @@ public class Song { public static final String LYRICS_5 = "Little to little"; public static final String LYRICS_6 = "confidential secret classified"; - static final String GENRE_ROCK = "rock"; - static final String GENRE_JAZZ = "jazz"; - static final String GENRE_BLUES = "blues"; + public static final String GENRE_ROCK = "rock"; + public static final String GENRE_JAZZ = "jazz"; + public static final String GENRE_BLUES = "blues"; - static final String QUERY_TITLE_NEXT_SONG = FIELD_TITLE + ":" + "\"" + TITLE_NEXT_SONG + "\""; - static final String QUERY_TITLE_POISON = FIELD_TITLE + ":" + TITLE_POISON; - static final String QUERY_TITLE_MAGNUM_OPUS = FIELD_TITLE + ":" + TITLE_MAGNUM_OPUS; + public static final String QUERY_TITLE_NEXT_SONG = FIELD_TITLE + ":" + "\"" + TITLE_NEXT_SONG + "\""; + public static final String QUERY_TITLE_POISON = FIELD_TITLE + ":" + TITLE_POISON; + public static final String QUERY_TITLE_MAGNUM_OPUS = FIELD_TITLE + ":" + TITLE_MAGNUM_OPUS; - static final Map[] SONGS = { + public static final Map[] SONGS = { new Song(ARTIST_FIRST, TITLE_MAGNUM_OPUS ,LYRICS_1, 1, GENRE_ROCK).asMap(), new Song(ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES).asMap(), new Song(ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ).asMap(), diff --git a/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java b/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java new file mode 100644 index 0000000000..bf8af72854 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java @@ -0,0 +1,263 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.io.IOException; +import java.security.KeyPair; +import java.util.Base64; +import java.util.List; +import java.util.Map; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.message.BasicHeader ; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.client.Client; +import org.opensearch.client.RestHighLevelClient; +import org.opensearch.test.framework.JwtConfigBuilder; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; +import org.opensearch.test.framework.log.LogsRule; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.apache.http.HttpHeaders.AUTHORIZATION; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.opensearch.client.RequestOptions.DEFAULT; +import static org.opensearch.rest.RestStatus.FORBIDDEN; +import static org.opensearch.security.Song.FIELD_TITLE; +import static org.opensearch.security.Song.QUERY_TITLE_MAGNUM_OPUS; +import static org.opensearch.security.Song.SONGS; +import static org.opensearch.security.Song.TITLE_MAGNUM_OPUS; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.BASIC_AUTH_DOMAIN_ORDER; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.queryStringQueryRequest; +import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy; +import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.statusException; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.isSuccessfulSearchResponse; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.numberOfTotalHitsIsEqualTo; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitContainsFieldWithValue; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitsContainDocumentWithId; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class JwtAuthenticationTests { + + public static final String CLAIM_USERNAME = "preferred-username"; + public static final String CLAIM_ROLES = "backend-user-roles"; + + public static final String USER_SUPERHERO = "superhero"; + public static final String USERNAME_ROOT = "root"; + public static final String ROLE_ADMIN = "role_admin"; + public static final String ROLE_DEVELOPER = "role_developer"; + public static final String ROLE_QA = "role_qa"; + public static final String ROLE_CTO = "role_cto"; + public static final String ROLE_CEO = "role_ceo"; + public static final String ROLE_VP = "role_vp"; + public static final String POINTER_BACKEND_ROLES = "/backend_roles"; + public static final String POINTER_USERNAME = "/user_name"; + + public static final String QA_DEPARTMENT = "qa-department"; + + public static final String CLAIM_DEPARTMENT = "department"; + + public static final String DEPARTMENT_SONG_INDEX_PATTERN = String.format("song_lyrics_${attr.jwt.%s}", CLAIM_DEPARTMENT); + + public static final String QA_SONG_INDEX_NAME = String.format("song_lyrics_%s", QA_DEPARTMENT); + + private static final KeyPair KEY_PAIR = Keys.keyPairFor(SignatureAlgorithm.RS256); + private static final String PUBLIC_KEY = new String(Base64.getEncoder().encode(KEY_PAIR.getPublic().getEncoded()), US_ASCII); + + static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + private static final String JWT_AUTH_HEADER = "jwt-auth"; + + private static final JwtAuthorizationHeaderFactory tokenFactory = new JwtAuthorizationHeaderFactory( + KEY_PAIR.getPrivate(), + CLAIM_USERNAME, + CLAIM_ROLES, + JWT_AUTH_HEADER); + + public static final TestSecurityConfig.AuthcDomain JWT_AUTH_DOMAIN = new TestSecurityConfig + .AuthcDomain("jwt", BASIC_AUTH_DOMAIN_ORDER - 1) + .jwtHttpAuthenticator(new JwtConfigBuilder().jwtHeader(JWT_AUTH_HEADER).signingKey(PUBLIC_KEY).subjectKey(CLAIM_USERNAME) + .rolesKey(CLAIM_ROLES)) + .backend("noop"); + public static final String SONG_ID_1 = "song-id-01"; + + public static final Role DEPARTMENT_SONG_LISTENER_ROLE = new Role("department-song-listener-role") + .indexPermissions("indices:data/read/search").on(DEPARTMENT_SONG_INDEX_PATTERN); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) + .nodeSettings(Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() +"__" + ALL_ACCESS.getName()))) + .authc(AUTHC_HTTPBASIC_INTERNAL).users(ADMIN_USER).roles(DEPARTMENT_SONG_LISTENER_ROLE) + .authc(JWT_AUTH_DOMAIN) + .build(); + + @Rule + public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator"); + + @BeforeClass + public static void createTestData() { + try (Client client = cluster.getInternalNodeClient()) { + client.prepareIndex(QA_SONG_INDEX_NAME).setId(SONG_ID_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0]).get(); + } + try(TestRestClient client = cluster.getRestClient(ADMIN_USER)){ + client.createRoleMapping(ROLE_VP, DEPARTMENT_SONG_LISTENER_ROLE.getName()); + } + } + + @Test + public void shouldAuthenticateWithJwtToken_positive() { + try(TestRestClient client = cluster.getRestClient(tokenFactory.generateValidToken(USER_SUPERHERO))){ + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(username)); + } + } + + @Test + public void shouldAuthenticateWithJwtToken_positiveWithAnotherUsername() { + try(TestRestClient client = cluster.getRestClient(tokenFactory.generateValidToken(USERNAME_ROOT))){ + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USERNAME_ROOT)); + } + } + + @Test + public void shouldAuthenticateWithJwtToken_failureLackingUserName() { + try(TestRestClient client = cluster.getRestClient(tokenFactory.generateTokenWithoutPreferredUsername(USER_SUPERHERO))){ + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + logsRule.assertThatContain("No subject found in JWT token"); + } + } + + @Test + public void shouldAuthenticateWithJwtToken_failureExpiredToken() { + try(TestRestClient client = cluster.getRestClient(tokenFactory.generateExpiredToken(USER_SUPERHERO))){ + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + logsRule.assertThatContain("Invalid or expired JWT token."); + } + } + + @Test + public void shouldAuthenticateWithJwtToken_failureIncorrectFormatOfToken() { + Header header = new BasicHeader(AUTHORIZATION, "not.a.token"); + try(TestRestClient client = cluster.getRestClient(header)){ + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + logsRule.assertThatContain(String.format("No JWT token found in '%s' header header", JWT_AUTH_HEADER)); + } + } + + @Test + public void shouldAuthenticateWithJwtToken_failureIncorrectSignature() { + KeyPair incorrectKeyPair = Keys.keyPairFor(SignatureAlgorithm.RS256); + Header header = tokenFactory.generateTokenSignedWithKey(incorrectKeyPair.getPrivate(), USER_SUPERHERO); + try(TestRestClient client = cluster.getRestClient(header)){ + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + logsRule.assertThatContain("Invalid or expired JWT token."); + } + } + + @Test + public void shouldReadRolesFromToken_positiveFirstRoleSet() { + Header header = tokenFactory.generateValidToken(USER_SUPERHERO, ROLE_ADMIN, ROLE_DEVELOPER, ROLE_QA); + try(TestRestClient client = cluster.getRestClient(header)){ + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List roles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(roles, hasSize(3)); + assertThat(roles, containsInAnyOrder(ROLE_ADMIN, ROLE_DEVELOPER, ROLE_QA)); + } + } + + @Test + public void shouldReadRolesFromToken_positiveSecondRoleSet() { + Header header = tokenFactory.generateValidToken(USER_SUPERHERO, ROLE_CTO, ROLE_CEO, ROLE_VP); + try(TestRestClient client = cluster.getRestClient(header)){ + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List roles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(roles, hasSize(3)); + assertThat(roles, containsInAnyOrder(ROLE_CTO, ROLE_CEO, ROLE_VP)); + } + } + + @Test + public void shouldExposeTokenClaimsAsUserAttributes_positive() throws IOException { + String[] roles = { ROLE_VP }; + Map additionalClaims = Map.of(CLAIM_DEPARTMENT, QA_DEPARTMENT); + Header header = tokenFactory.generateValidTokenWithCustomClaims(USER_SUPERHERO, roles, additionalClaims); + try(RestHighLevelClient client = cluster.getRestHighLevelClient(List.of(header))){ + SearchRequest searchRequest = queryStringQueryRequest(QA_SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse response = client.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(1)); + assertThat(response, searchHitsContainDocumentWithId(0, QA_SONG_INDEX_NAME, SONG_ID_1)); + assertThat(response, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + } + + @Test + public void shouldExposeTokenClaimsAsUserAttributes_negative() throws IOException { + String[] roles = { ROLE_VP }; + Map additionalClaims = Map.of(CLAIM_DEPARTMENT, "department-without-access-to-qa-song-index"); + Header header = tokenFactory.generateValidTokenWithCustomClaims(USER_SUPERHERO, roles, additionalClaims); + try(RestHighLevelClient client = cluster.getRestHighLevelClient(List.of(header))){ + SearchRequest searchRequest = queryStringQueryRequest(QA_SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS); + + assertThatThrownBy(() -> client.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/http/JwtAuthorizationHeaderFactory.java b/src/integrationTest/java/org/opensearch/security/http/JwtAuthorizationHeaderFactory.java new file mode 100644 index 0000000000..61d87b173f --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/JwtAuthorizationHeaderFactory.java @@ -0,0 +1,141 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.security.PrivateKey; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableMap; +import io.jsonwebtoken.Jwts; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.message.BasicHeader; + +import static io.jsonwebtoken.SignatureAlgorithm.RS256; +import static java.util.Objects.requireNonNull; + +class JwtAuthorizationHeaderFactory { + public static final String AUDIENCE = "OpenSearch"; + public static final String ISSUER = "test-code"; + private final PrivateKey privateKey; + + private final String usernameClaimName; + + private final String rolesClaimName; + + private final String headerName; + + public JwtAuthorizationHeaderFactory(PrivateKey privateKey, String usernameClaimName, String rolesClaimName, String headerName) { + this.privateKey = requireNonNull(privateKey, "Private key is required"); + this.usernameClaimName = requireNonNull(usernameClaimName, "Username claim name is required"); + this.rolesClaimName = requireNonNull(rolesClaimName, "Roles claim name is required."); + this.headerName = requireNonNull(headerName, "Header name is required"); + } + + Header generateValidToken(String username, String...roles) { + requireNonNull(username, "Username is required"); + Date now = new Date(); + String token = Jwts.builder() + .setClaims(customClaimsMap(username, roles)) + .setIssuer(ISSUER) + .setSubject(subject(username)) + .setAudience(AUDIENCE) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + 3600 * 1000)) + .signWith(privateKey, RS256) + .compact(); + return toHeader(token); + } + + private Map customClaimsMap(String username, String[] roles) { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + if(StringUtils.isNoneEmpty(username)) { + builder.put(usernameClaimName, username); + } + if((roles != null) && (roles.length > 0)) { + builder.put(rolesClaimName, Arrays.stream(roles).collect(Collectors.joining(","))); + } + return builder.build(); + } + + Header generateValidTokenWithCustomClaims(String username, String[] roles, Map additionalClaims) { + requireNonNull(username, "Username is required"); + requireNonNull(additionalClaims, "Custom claims are required"); + Map claims = new HashMap<>(customClaimsMap(username, roles)); + claims.putAll(additionalClaims); + Date now = new Date(); + String token = Jwts.builder() + .setClaims(claims) + .setIssuer(ISSUER) + .setSubject(subject(username)) + .setAudience(AUDIENCE) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + 3600 * 1000)) + .signWith(privateKey, RS256) + .compact(); + return toHeader(token); + } + + private BasicHeader toHeader(String token) { + return new BasicHeader(headerName, token); + } + + Header generateTokenWithoutPreferredUsername(String username) { + requireNonNull(username, "Username is required"); + Date now = new Date(); + String token = Jwts.builder() + .setIssuer(ISSUER) + .setSubject(username) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + 3600 * 1000)) + .signWith(privateKey, RS256) + .compact(); + return toHeader(token); + } + + public Header generateExpiredToken(String username) { + requireNonNull(username, "Username is required"); + Date now = new Date(1000); + String token = Jwts.builder() + .setClaims(Map.of(usernameClaimName, username)) + .setIssuer(ISSUER) + .setSubject(subject(username)) + .setAudience(AUDIENCE) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + 3600 * 1000)) + .signWith(privateKey, RS256) + .compact(); + return toHeader(token); + } + + public Header generateTokenSignedWithKey(PrivateKey key, String username) { + requireNonNull(key, "Private key is required"); + requireNonNull(username, "Username is required"); + Date now = new Date(); + String token = Jwts.builder() + .setClaims(Map.of(usernameClaimName, username)) + .setIssuer(ISSUER) + .setSubject(subject(username)) + .setAudience(AUDIENCE) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + 3600 * 1000)) + .signWith(key, RS256) + .compact(); + return toHeader(token); + } + + private static String subject(String username) { + return "subject-" + username; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/JwtConfigBuilder.java b/src/integrationTest/java/org/opensearch/test/framework/JwtConfigBuilder.java new file mode 100644 index 0000000000..5b1ea2c678 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/JwtConfigBuilder.java @@ -0,0 +1,62 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework; + +import java.util.Map; +import java.util.Objects; + +import com.google.common.collect.ImmutableMap.Builder; + +import static org.apache.commons.lang3.StringUtils.isNoneBlank; + +public class JwtConfigBuilder { + private String jwtHeader; + private String signingKey; + private String subjectKey; + private String rolesKey; + + public JwtConfigBuilder jwtHeader(String jwtHeader) { + this.jwtHeader = jwtHeader; + return this; + } + + public JwtConfigBuilder signingKey(String signingKey) { + this.signingKey = signingKey; + return this; + } + + public JwtConfigBuilder subjectKey(String subjectKey) { + this.subjectKey = subjectKey; + return this; + } + + public JwtConfigBuilder rolesKey(String rolesKey) { + this.rolesKey = rolesKey; + return this; + } + + public Map build() { + Builder builder = new Builder<>(); + if(Objects.isNull(signingKey)) { + throw new IllegalStateException("Signing key is required."); + } + builder.put("signing_key", signingKey); + if(isNoneBlank(jwtHeader)) { + builder.put("jwt_header", jwtHeader); + } + if(isNoneBlank(subjectKey)) { + builder.put("subject_key", subjectKey); + } + if(isNoneBlank(rolesKey)) { + builder.put("roles_key", rolesKey); + } + return builder.build(); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index 300604d53e..d86bfe7d48 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -42,7 +42,6 @@ import java.util.Set; import java.util.stream.Collectors; -import com.google.common.collect.ImmutableMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.crypto.generators.OpenBSDBCrypt; @@ -62,6 +61,8 @@ import org.opensearch.security.securityconf.impl.CType; import org.opensearch.test.framework.cluster.OpenSearchClientProvider.UserCredentialsHolder; +import static org.apache.http.HttpHeaders.AUTHORIZATION; + /** * This class allows the declarative specification of the security configuration; in particular: * @@ -350,18 +351,20 @@ public static class AuthcDomain implements ToXContentObject { private static String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqZbjLUAWc+DZTkinQAdvy1GFjPHPnxheU89hSiWoDD3NOW76H3u3T7cCDdOah2msdxSlBmCBH6wik8qLYkcV8owWukQg3PQmbEhrdPaKo0QCgomWs4nLgtmEYqcZ+QQldd82MdTlQ1QmoQmI9Uxqs1SuaKZASp3Gy19y8su5CV+FZ6BruUw9HELK055sAwl3X7j5ouabXGbcib2goBF3P52LkvbJLuWr5HDZEOeSkwIeqSeMojASM96K5SdotD+HwEyjaTjzRPL2Aa1BEQFWOQ6CFJLyLH7ZStDuPM1mJU1VxIVfMbZrhsUBjAnIhRynmWxML7YlNqkP9j6jyOIYQIDAQAB"; - public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL = new TestSecurityConfig.AuthcDomain("basic", 0) + public static final int BASIC_AUTH_DOMAIN_ORDER = 0; + public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL = new TestSecurityConfig.AuthcDomain("basic", BASIC_AUTH_DOMAIN_ORDER) .httpAuthenticatorWithChallenge("basic").backend("internal"); - public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE = new TestSecurityConfig.AuthcDomain("basic", 0) + public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE = new TestSecurityConfig.AuthcDomain("basic", + BASIC_AUTH_DOMAIN_ORDER) .httpAuthenticator("basic").backend("internal"); public final static AuthcDomain DISABLED_AUTHC_HTTPBASIC_INTERNAL = new TestSecurityConfig - .AuthcDomain("basic", 0, false).httpAuthenticator("basic").backend("internal"); + .AuthcDomain("basic", BASIC_AUTH_DOMAIN_ORDER, false).httpAuthenticator("basic").backend("internal"); public final static AuthcDomain JWT_AUTH_DOMAIN = new TestSecurityConfig .AuthcDomain("jwt", 1) - .jwtHttpAuthenticator("Authorization", PUBLIC_KEY).backend("noop"); + .jwtHttpAuthenticator(new JwtConfigBuilder().jwtHeader(AUTHORIZATION).signingKey(PUBLIC_KEY)).backend("noop"); private final String id; private boolean enabled = true; @@ -385,9 +388,9 @@ public AuthcDomain httpAuthenticator(String type) { return this; } - public AuthcDomain jwtHttpAuthenticator(String headerName, String signingKey) { + public AuthcDomain jwtHttpAuthenticator(JwtConfigBuilder builder) { this.httpAuthenticator = new HttpAuthenticator("jwt") - .challenge(false).config(ImmutableMap.of("jwt_header", headerName, "signing_key", signingKey)); + .challenge(false).config(builder.build()); return this; } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java index 3c6c375c38..5679788d39 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -35,6 +35,8 @@ import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Base64; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -99,9 +101,20 @@ default TestRestClient getRestClient(UserCredentialsHolder user, Header... heade } default RestHighLevelClient getRestHighLevelClient(UserCredentialsHolder user) { - InetSocketAddress httpAddress = getHttpAddress(); + BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(new AuthScope(null, -1), new UsernamePasswordCredentials(user.getName(), user.getPassword().toCharArray())); + + return getRestHighLevelClient(credentialsProvider, Collections.emptySet()); + } + + default RestHighLevelClient getRestHighLevelClient(Collection defaultHeaders) { + + + return getRestHighLevelClient(null, defaultHeaders); + } + + private RestHighLevelClient getRestHighLevelClient(BasicCredentialsProvider credentialsProvider, Collection defaultHeaders) { RestClientBuilder.HttpClientConfigCallback configCallback = httpClientBuilder -> { TlsStrategy tlsStrategy = ClientTlsStrategyBuilder .create() @@ -120,15 +133,18 @@ public TlsDetails create(final SSLEngine sslEngine) { .setTlsStrategy(tlsStrategy) .build(); - httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + if(credentialsProvider != null) { + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + } + httpClientBuilder.setDefaultHeaders(defaultHeaders); httpClientBuilder.setConnectionManager(cm); return httpClientBuilder; }; + InetSocketAddress httpAddress = getHttpAddress(); RestClientBuilder builder = RestClient.builder(new HttpHost("https", httpAddress.getHostString(), httpAddress.getPort())) .setHttpClientConfigCallback(configCallback); - return new RestHighLevelClient(builder); } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java index aac11008d6..092723f9dd 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -39,6 +39,8 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import javax.net.ssl.SSLContext; @@ -199,7 +201,16 @@ public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... requestS } } - public final String getHttpServerUri() { + public void createRoleMapping(String backendRoleName, String roleName) { + requireNonNull(backendRoleName, "Backend role name is required"); + requireNonNull(roleName, "Role name is required"); + String path = "_plugins/_security/api/rolesmapping/" + roleName; + String body = String.format("{\"backend_roles\": [\"%s\"]}", backendRoleName); + HttpResponse response = putJson(path, body); + response.assertStatusCode(201); + } + + protected final String getHttpServerUri() { return "http" + (enableHTTPClientSSL ? "s" : "") + "://" + nodeHttpAddress.getHostString() + ":" + nodeHttpAddress.getPort(); } @@ -299,6 +310,12 @@ public List

getHeaders() { public String getTextFromJsonBody(String jsonPointer) { return getJsonNodeAt(jsonPointer).asText(); } + + public List getTextArrayFromJsonBody(String jsonPointer) { + return StreamSupport.stream(getJsonNodeAt(jsonPointer).spliterator(), false) + .map(JsonNode::textValue) + .collect(Collectors.toList()); + } public int getIntFromJsonBody(String jsonPointer) { return getJsonNodeAt(jsonPointer).asInt(); diff --git a/src/integrationTest/java/org/opensearch/test/framework/log/LogCapturingAppender.java b/src/integrationTest/java/org/opensearch/test/framework/log/LogCapturingAppender.java new file mode 100644 index 0000000000..11a59f470d --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/log/LogCapturingAppender.java @@ -0,0 +1,121 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.log; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.collections.Buffer; +import org.apache.commons.collections.BufferUtils; +import org.apache.commons.collections.buffer.CircularFifoBuffer; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +import static org.opensearch.test.framework.log.LogCapturingAppender.PLUGIN_NAME; + +/** +*

The class acts as Log4j2 appender with a special purpose. The appender is used to capture logs which are generated during tests and +* then test can examine logs. To use the appender it is necessary to:

+*
    +*
  1. Add package with appender to log4j2 package scan in Log4j2 configuration file
  2. +*
  3. Create appender in log4j2 configuration
  4. +*
  5. Assign required loggers to appender
  6. +*
  7. Enable appender for certain classes with method {@link #enable(String...)}. Each test can enable appender for distinct classes
  8. +*
+*/ +@Plugin(name = PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +public class LogCapturingAppender extends AbstractAppender { + + public final static String PLUGIN_NAME = "LogCapturingAppender"; + /** + * Appender stores only last MAX_SIZE messages to avoid excessive RAM memory usage. + */ + public static final int MAX_SIZE = 100; + + /** + * Buffer for captured log messages + */ + private static final Buffer messages = BufferUtils.synchronizedBuffer(new CircularFifoBuffer(MAX_SIZE)); + + /** + * Log messages are stored in buffer {@link #messages} only for classes which are added to the {@link #activeLoggers} set. + */ + private static final Set activeLoggers = Collections.synchronizedSet(new HashSet<>()); + + protected LogCapturingAppender(String name, Filter filter, Layout layout, boolean ignoreExceptions, Property[] properties) { + super(name, filter, layout, ignoreExceptions, properties); + } + + /** + * Method used by Log4j2 to create appender + * @param name appender name from Log4j2 configuration + * @return newly created appender + */ + @PluginFactory + public static LogCapturingAppender createAppender(@PluginAttribute(value = "name", defaultString = "logCapturingAppender") String name) { + return new LogCapturingAppender(name, null, null, true, Property.EMPTY_ARRAY); + } + + /** + * Method invoked by Log4j2 to append log events + * @param event The LogEvent, represents log message. + */ + @Override + public void append(LogEvent event) { + String loggerName = event.getLoggerName(); + boolean loggable = activeLoggers.contains(loggerName); + if(loggable) { + messages.add(event.getMessage().getFormattedMessage()); + } + } + + /** + * To collect log messages form given logger the logger name must be passed to {@link #enable(String...)} method. + * @param loggerNames logger names + */ + public static void enable(String...loggerNames) { + disable(); + activeLoggers.addAll(Arrays.asList(loggerNames)); + } + + /** + * Invocation cause that appender stops collecting log messages. Additionally, memory used by collected messages so far is released. + */ + public static void disable() { + activeLoggers.clear(); + messages.clear(); + } + + /** + * Is used to obtain gathered log messages + * @return Log messages + */ + public static List getLogMessages() { + return new ArrayList<>(messages); + } + + @Override + public String toString() { + return "LogCapturingAppender{}"; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java b/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java new file mode 100644 index 0000000000..34fe6f4455 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java @@ -0,0 +1,67 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.log; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.junit.rules.ExternalResource; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; + +/** +* The class is a JUnit 4 rule and enables developers to write assertion related to log messages generated in the course of test. To use +* {@link LogsRule} appender {@link LogCapturingAppender} must be properly configured. The rule also manages {@link LogCapturingAppender} +* so that memory occupied by gathered log messages is released after each test. +*/ +public class LogsRule extends ExternalResource { + + private final String[] loggerNames; + + /** + * Constructor used to start gathering log messages from certain loggers + * @param loggerNames Loggers names. Log messages are collected only if the log message is associated with the logger with a name which + * is present in loggerNames parameter. + */ + public LogsRule(String...loggerNames) { + this.loggerNames = Objects.requireNonNull(loggerNames, "Logger names are required"); + } + + @Override + protected void before() { + LogCapturingAppender.enable(loggerNames); + } + + @Override + protected void after() { + LogCapturingAppender.disable(); + } + + /** + * Check if during the tests certain log message was logged + * @param expectedLogMessage expected log message + */ + public void assertThatContain(String expectedLogMessage) { + List messages = LogCapturingAppender.getLogMessages(); + String reason = reasonMessage(expectedLogMessage, messages); + assertThat(reason, messages, hasItem(expectedLogMessage)); + } + + private static String reasonMessage(String expectedLogMessage, List messages) { + String concatenatedLogMessages = messages.stream() + .map(message -> String.format("'%s'", message)) + .collect(Collectors.joining(", ")); + return String.format("Expected message '%s' has not been found in logs. All captured log messages: %s", + expectedLogMessage, + concatenatedLogMessages); + } +} diff --git a/src/integrationTest/resources/log4j2-test.properties b/src/integrationTest/resources/log4j2-test.properties index 1925c087a6..d9a4d672e0 100644 --- a/src/integrationTest/resources/log4j2-test.properties +++ b/src/integrationTest/resources/log4j2-test.properties @@ -1,7 +1,6 @@ status = info name = Integration test logging configuration - - +packages = org.opensearch.test.framework.log appender.console.type = Console appender.console.name = consoleAppender @@ -12,13 +11,20 @@ appender.console.filter.prerelease.regex=.+\\Qis a pre-release version of OpenSe appender.console.filter.prerelease.onMatch=DENY appender.console.filter.prerelease.onMismatch=NEUTRAL -rootLogger.level = warn +appender.capturing.type = LogCapturingAppender +appender.capturing.name = logCapturingAppender + +rootLogger.level = info rootLogger.appenderRef.stdout.ref = consoleAppender -logger.testsecconfig.name=org.opensearch.test.framework.TestSecurityConfig +logger.testsecconfig.name = org.opensearch.test.framework.TestSecurityConfig logger.testsecconfig.level = info logger.localopensearchcluster.name=org.opensearch.test.framework.cluster.LocalOpenSearchCluster logger.localopensearchcluster.level = info logger.auditlogs.name=org.opensearch.test.framework.audit logger.auditlogs.level = info +# Logger required by test org.opensearch.security.http.JwtAuthenticationTests +logger.httpjwtauthenticator.name = com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator +logger.httpjwtauthenticator.level = debug +logger.httpjwtauthenticator.appenderRef.capturing.ref = logCapturingAppender From 93fe6337f78a01e8316213b0998562732701511e Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Thu, 10 Nov 2022 18:51:32 +0100 Subject: [PATCH 076/369] Test cases with flag do not fail on forbidden enabled. (#2154) * Test cases with flag do not fail on forbidden enabled. * Remarks after CR applied to DNFOF tests * Missing mget tests related to flag do not fail on forbidden added. * Code style corrections for DNFON tests. * Mather GetResponseContainOnlyDocumentIdMatcher added so that DNFOF test still are green after rebase. Signed-off-by: Lukasz Soszynski Signed-off-by: Lukasz Soszynski <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> --- .../security/DoNotFailOnForbiddenTests.java | 380 ++++++++++++++++++ .../security/SearchOperationTest.java | 2 +- .../test/framework/TestSecurityConfig.java | 16 + .../test/framework/cluster/LocalCluster.java | 5 + .../cluster/SearchRequestFactory.java | 8 + ...tResponseContainOnlyDocumentIdMatcher.java | 51 +++ .../matcher/GetResponseMatchers.java | 4 + 7 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainOnlyDocumentIdMatcher.java diff --git a/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java b/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java new file mode 100644 index 0000000000..b4e582507e --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java @@ -0,0 +1,380 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.io.IOException; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.hamcrest.Matchers; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.opensearch.action.fieldcaps.FieldCapabilitiesRequest; +import org.opensearch.action.fieldcaps.FieldCapabilitiesResponse; +import org.opensearch.action.get.MultiGetItemResponse; +import org.opensearch.action.get.MultiGetRequest; +import org.opensearch.action.get.MultiGetResponse; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.search.MultiSearchRequest; +import org.opensearch.action.search.MultiSearchResponse; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.action.search.SearchScrollRequest; +import org.opensearch.client.Client; +import org.opensearch.client.RestHighLevelClient; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.arrayContainingInAnyOrder; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.nullValue; +import static org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions.Type.ADD; +import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.opensearch.client.RequestOptions.DEFAULT; +import static org.opensearch.rest.RestStatus.FORBIDDEN; +import static org.opensearch.security.Song.FIELD_STARS; +import static org.opensearch.security.Song.FIELD_TITLE; +import static org.opensearch.security.Song.QUERY_TITLE_MAGNUM_OPUS; +import static org.opensearch.security.Song.QUERY_TITLE_NEXT_SONG; +import static org.opensearch.security.Song.QUERY_TITLE_POISON; +import static org.opensearch.security.Song.SONGS; +import static org.opensearch.security.Song.TITLE_MAGNUM_OPUS; +import static org.opensearch.security.Song.TITLE_NEXT_SONG; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.averageAggregationRequest; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.getSearchScrollRequest; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.queryStringQueryRequest; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.searchRequestWithScroll; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.statsAggregationRequest; +import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy; +import static org.opensearch.test.framework.matcher.GetResponseMatchers.containDocument; +import static org.opensearch.test.framework.matcher.GetResponseMatchers.containOnlyDocumentId; +import static org.opensearch.test.framework.matcher.GetResponseMatchers.documentContainField; +import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.statusException; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.containAggregationWithNameAndType; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.containNotEmptyScrollingId; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.isSuccessfulSearchResponse; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.numberOfHitsInPageIsEqualTo; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.numberOfTotalHitsIsEqualTo; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitContainsFieldWithValue; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitsContainDocumentWithId; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class DoNotFailOnForbiddenTests { + + /** + * Songs accessible for {@link #LIMITED_USER} + */ + private static final String MARVELOUS_SONGS = "marvelous_songs"; + + /** + * Songs inaccessible for {@link #LIMITED_USER} + */ + private static final String HORRIBLE_SONGS = "horrible_songs"; + + private static final String BOTH_INDEX_PATTERN = "*songs"; + + private static final String ID_1 = "1"; + private static final String ID_2 = "2"; + private static final String ID_3 = "3"; + private static final String ID_4 = "4"; + + private static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS); + private static final User LIMITED_USER = new User("limited_user") + .roles(new TestSecurityConfig.Role("limited-role") + .clusterPermissions("indices:data/read/mget", "indices:data/read/msearch", "indices:data/read/scroll") + .indexPermissions("indices:data/read/search", "indices:data/read/mget*", "indices:data/read/field_caps", "indices:data/read/field_caps*", "indices:data/read/msearch", "indices:data/read/scroll") + .on(MARVELOUS_SONGS)); + + private static final String BOTH_INDEX_ALIAS = "both-indices"; + private static final String FORBIDDEN_INDEX_ALIAS = "forbidden-index"; + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER, LIMITED_USER).anonymousAuth(false).doNotFailOnForbidden(true).build(); + + @BeforeClass + public static void createTestData() { + try(Client client = cluster.getInternalNodeClient()) { + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_1).source(SONGS[0])).actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_2).source(SONGS[1])).actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_3).source(SONGS[2])).actionGet(); + + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(HORRIBLE_SONGS).id(ID_4).source(SONGS[3])).actionGet(); + + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD).indices( + MARVELOUS_SONGS, HORRIBLE_SONGS).alias(BOTH_INDEX_ALIAS))).actionGet(); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD).indices( + HORRIBLE_SONGS).alias(FORBIDDEN_INDEX_ALIAS))).actionGet(); + + } + } + + @Test + public void shouldPerformSimpleSearch_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(new String[]{MARVELOUS_SONGS, HORRIBLE_SONGS}, QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThatContainOneSong(searchResponse, ID_1, TITLE_MAGNUM_OPUS); + } + } + + private static void assertThatContainOneSong(SearchResponse searchResponse, String documentId, String title) { + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, MARVELOUS_SONGS, documentId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, title)); + } + + @Test + public void shouldPerformSimpleSearch_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(HORRIBLE_SONGS, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentsViaIndexPattern_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(BOTH_INDEX_PATTERN, QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThatContainOneSong(searchResponse, ID_1, TITLE_MAGNUM_OPUS); + } + } + + @Test + public void shouldSearchForDocumentsViaIndexPattern_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(HORRIBLE_SONGS, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentsViaAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(BOTH_INDEX_ALIAS, QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThatContainOneSong(searchResponse, ID_1, TITLE_MAGNUM_OPUS); + } + } + + @Test + public void shouldSearchForDocumentsViaAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(FORBIDDEN_INDEX_ALIAS, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentsViaAll_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("_all", QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThatContainOneSong(searchResponse, ID_1, TITLE_MAGNUM_OPUS); + } + } + + @Test + public void shouldSearchForDocumentsViaAll_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("_all", QUERY_TITLE_POISON); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(0)); + } + } + + @Test + public void shouldMGetDocument_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + MultiGetRequest request = new MultiGetRequest() + .add(BOTH_INDEX_PATTERN, ID_1) + .add(BOTH_INDEX_PATTERN, ID_4); + + MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); + + MultiGetItemResponse[] responses = response.getResponses(); + assertThat(responses, arrayWithSize(2)); + MultiGetItemResponse firstResult = responses[0]; + MultiGetItemResponse secondResult = responses[1]; + assertThat(firstResult.getFailure(), nullValue()); + assertThat(secondResult.getFailure(), nullValue()); + assertThat(firstResult.getResponse(), allOf( + containDocument(MARVELOUS_SONGS, ID_1), + documentContainField(FIELD_TITLE, TITLE_MAGNUM_OPUS)) + ); + assertThat(secondResult.getResponse(), containOnlyDocumentId(MARVELOUS_SONGS, ID_4)); + } + } + + @Test + public void shouldMGetDocument_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + MultiGetRequest request = new MultiGetRequest().add(HORRIBLE_SONGS, ID_4); + + assertThatThrownBy(() -> restHighLevelClient.mget(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldMSearchDocument_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + MultiSearchRequest request = new MultiSearchRequest(); + request.add(queryStringQueryRequest(BOTH_INDEX_PATTERN, QUERY_TITLE_MAGNUM_OPUS)); + request.add(queryStringQueryRequest(BOTH_INDEX_PATTERN, QUERY_TITLE_NEXT_SONG)); + + MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); + + MultiSearchResponse.Item[] responses = response.getResponses(); + assertThat(responses, Matchers.arrayWithSize(2)); + assertThat(responses[0].getFailure(), nullValue()); + assertThat(responses[1].getFailure(), nullValue()); + + assertThat(responses[0].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat(responses[0].getResponse(), searchHitsContainDocumentWithId(0, MARVELOUS_SONGS, ID_1)); + assertThat(responses[1].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); + assertThat(responses[1].getResponse(), searchHitsContainDocumentWithId(0, MARVELOUS_SONGS, ID_3)); + } + } + + @Test + public void shouldMSearchDocument_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + MultiSearchRequest request = new MultiSearchRequest(); + request.add(queryStringQueryRequest(FORBIDDEN_INDEX_ALIAS, QUERY_TITLE_POISON)); + + assertThatThrownBy(() -> restHighLevelClient.msearch(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldGetFieldCapabilities_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(MARVELOUS_SONGS, HORRIBLE_SONGS).fields(FIELD_TITLE); + + FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); + + assertThat(response.get(), aMapWithSize(1)); + assertThat(response.getIndices(), arrayWithSize(1)); + assertThat(response.getField(FIELD_TITLE), hasKey("text")); + assertThat(response.getIndices(), arrayContainingInAnyOrder(MARVELOUS_SONGS)); + } + } + + @Test + public void shouldGetFieldCapabilities_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(HORRIBLE_SONGS).fields(FIELD_TITLE); + + assertThatThrownBy(() -> restHighLevelClient.fieldCaps(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldScrollOverSearchResults_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchRequestWithScroll(BOTH_INDEX_PATTERN, 2); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containNotEmptyScrollingId()); + + SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); + + SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); + assertThat(scrollResponse, isSuccessfulSearchResponse()); + assertThat(scrollResponse, containNotEmptyScrollingId()); + assertThat(scrollResponse, numberOfTotalHitsIsEqualTo(3)); + assertThat(scrollResponse, numberOfHitsInPageIsEqualTo(1)); + } + } + + @Test + public void shouldScrollOverSearchResults_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchRequestWithScroll(HORRIBLE_SONGS, 2); + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldPerformAggregation_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + final String aggregationName = "averageStars"; + SearchRequest searchRequest = averageAggregationRequest(BOTH_INDEX_PATTERN, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + } + } + + @Test + public void shouldPerformAggregation_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + final String aggregationName = "averageStars"; + SearchRequest searchRequest = averageAggregationRequest(HORRIBLE_SONGS, aggregationName, FIELD_STARS); + + assertThatThrownBy( () -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldPerformStatAggregation_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + final String aggregationName = "statsStars"; + SearchRequest searchRequest = statsAggregationRequest(BOTH_INDEX_ALIAS, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "stats")); + } + } + + @Test + public void shouldPerformStatAggregation_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + final String aggregationName = "statsStars"; + SearchRequest searchRequest = statsAggregationRequest(HORRIBLE_SONGS, aggregationName, FIELD_STARS); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + +} diff --git a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java index 8efaeff648..5a48784970 100644 --- a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java +++ b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java @@ -319,7 +319,7 @@ public class SearchOperationTest { @BeforeClass public static void createTestData() { - try(Client client = cluster.getInternalNodeClient()){ + try(Client client = cluster.getInternalNodeClient()) { client.prepareIndex(SONG_INDEX_NAME).setId(ID_S1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0]).get(); client.prepareIndex(UPDATE_DELETE_OPERATION_INDEX_NAME).setId(DOCUMENT_TO_UPDATE_ID).setRefreshPolicy(IMMEDIATE).setSource("field", "value").get(); client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new AliasActions(ADD).indices(SONG_INDEX_NAME).alias(SONG_LYRICS_ALIAS))).actionGet(); diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index d86bfe7d48..ca64c1e00b 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -99,6 +99,11 @@ public TestSecurityConfig anonymousAuth(boolean anonymousAuthEnabled) { config.anonymousAuth(anonymousAuthEnabled); return this; } + + public TestSecurityConfig doNotFailOnForbidden(boolean doNotFailOnForbidden) { + config.doNotFailOnForbidden(doNotFailOnForbidden); + return this; + } public TestSecurityConfig authc(AuthcDomain authcDomain) { config.authc(authcDomain); @@ -129,6 +134,9 @@ public TestSecurityConfig audit(AuditConfiguration auditConfiguration) { public static class Config implements ToXContentObject { private boolean anonymousAuth; + + private Boolean doNotFailOnForbidden; + private Map authcDomainMap = new LinkedHashMap<>(); public Config anonymousAuth(boolean anonymousAuth) { @@ -136,6 +144,11 @@ public Config anonymousAuth(boolean anonymousAuth) { return this; } + public Config doNotFailOnForbidden(Boolean doNotFailOnForbidden) { + this.doNotFailOnForbidden = doNotFailOnForbidden; + return this; + } + public Config authc(AuthcDomain authcDomain) { authcDomainMap.put(authcDomain.id, authcDomain); return this; @@ -151,6 +164,9 @@ public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params xContentBuilder.field("anonymous_auth_enabled", true); xContentBuilder.endObject(); } + if(doNotFailOnForbidden != null) { + xContentBuilder.field("do_not_fail_on_forbidden", doNotFailOnForbidden); + } xContentBuilder.field("authc", authcDomainMap); diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index 4d483b3776..6c212ee10c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -388,6 +388,11 @@ public Builder certificates(TestCertificates certificates) { return this; } + public Builder doNotFailOnForbidden(boolean doNotFailOnForbidden) { + testSecurityConfig.doNotFailOnForbidden(doNotFailOnForbidden); + return this; + } + public LocalCluster build() { try { if(testCertificates == null){ diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java index 40f0d63c19..9e28b3a2fe 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java @@ -34,6 +34,14 @@ public static SearchRequest queryStringQueryRequest(String indexName, String que return searchRequest; } + public static SearchRequest queryStringQueryRequest(String[] indicesNames, String queryString) { + SearchRequest searchRequest = new SearchRequest(indicesNames); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.queryStringQuery(queryString)); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } + public static SearchRequest queryStringQueryRequest(String queryString) { SearchRequest searchRequest = new SearchRequest(); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainOnlyDocumentIdMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainOnlyDocumentIdMatcher.java new file mode 100644 index 0000000000..b677d6e6e1 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainOnlyDocumentIdMatcher.java @@ -0,0 +1,51 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.get.GetResponse; + +import static java.util.Objects.requireNonNull; + +class GetResponseContainOnlyDocumentIdMatcher extends TypeSafeDiagnosingMatcher { + + private final String indexName; + private final String documentId; + + public GetResponseContainOnlyDocumentIdMatcher(String indexName, String documentId) { + this.indexName = requireNonNull(indexName, "Index name is required"); + this.documentId = requireNonNull(documentId, "Document id is required"); + } + + @Override + protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { + if(indexName.equals(response.getIndex()) == false ) { + mismatchDescription.appendText(" index name ").appendValue(response.getIndex()).appendText(" is incorrect "); + return false; + } + if(documentId.equals(response.getId()) == false) { + mismatchDescription.appendText(" id ").appendValue(response.getId()).appendText(" is incorrect "); + return false; + } + if(response.isExists()) { + mismatchDescription.appendText(" document exist what is not desired "); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response should contain document id from index ").appendValue(indexName).appendText(" with id ") + .appendValue(documentId).appendText(" but document should not be present "); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java index 04cdcb1508..87f346d704 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java @@ -21,6 +21,10 @@ public static Matcher containDocument(String indexName, String docu return new GetResponseDocumentIdMatcher(indexName, documentId); } + public static Matcher containOnlyDocumentId(String indexName, String documentId) { + return new GetResponseContainOnlyDocumentIdMatcher(indexName, documentId); + } + public static Matcher documentContainField(String fieldName, Object fieldValue) { return new GetResponseDocumentFieldValueMatcher(fieldName, fieldValue); } From 943a7da397e06f1176098112415e308a3daec6f7 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Wed, 16 Nov 2022 11:54:24 -0500 Subject: [PATCH 077/369] Adding Open Triage meeting to CONTRIBUTING.md (#2164) Adding Open Triage meeting to CONTRIBUTING.md Signed-off-by: Stephen Crawford Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- CONTRIBUTING.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac2b6e9453..1d1f1a92e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,59 @@ + + ## Contributing to this Project OpenSearch is a community project that is built and maintained by people just like **you**. [This document](https://github.com/opensearch-project/.github/blob/main/CONTRIBUTING.md) explains how you can contribute to this and related projects. +The brief summary below outlines contribution guidelines specific to the OpenSearch-Security Repo. + +## Triaging + +The maintainers of the OpenSearch-Security Repo seek to promote an inclusive and engaged community of contributors. In order to facilitate this, weekly triage meetings are open-to-all and attendance is encouraged for anyone who hopes to contribute, discuss an issue, or learn more about the project. + +### Do I need to attend for my issue to be addressed/triaged? + +Attendance is not required for your issue to be triaged or addressed. All new issues are triaged weekly. + +### What happens if my issue does not get covered this time? + +Each meeting we seek to address all new issues. However, should we run out of time before your issue is discussed, you are always welcome to attend the next meeting or to follow up on the issue post itself. + +### How do I join the Backlog & Triage meeting? + +Meetings are hosted regularly at 3 PM Eastern Time (Noon Pacific Time) and can be joined via the links posted on the [Upcoming Events](https://opensearch.org/events) webpage. + +After joining the Zoom meeting, you can enable your video / voice to join the discussion. If you do not have a webcam or microphone available, you can still join in via the text chat. + +If you have an issue you'd like to bring forth please consider getting a link to the issue so it can be presented to everyone in the meeting. + +### Is there an agenda for each week? + +Meetings are lightly structured such that [new issues](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Auntriaged) are reviewed first, then the [sprint backlog](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A%22sprint+backlog%22), and finally the [backlog](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Atriaged+-label%3A%22sprint+backlog%22). There is no specific ordering within each category. + +If you have an issue you would like to discuss but do not have the ability to attend the entire meeting please attend when is best for you and signal that you have an issue to discuss when you arrive. + +### Do I need to have already contributed to the project to attend a triage meeting? + +No, all are welcome and encouraged to attend. Attending the Backlog & Triage meetings is a great way for a new contributor to learn about the project as well as explore different avenues of contribution. + +### What if I have an issue that is almost a duplicate, should I open a new one to be triaged? + +You can always open an [issue](ttps://github.com/opensearch-project/security/issues/new/choose) including one that you think may be a duplicate. However, in cases where you believe there is an important distinction to be made between an existing issue and your newly created one, you are encouraged to attend the triaging meeting to explain. + +### What if I have follow-up questions on an issue? + +If you have an existing issue you would like to discuss, you can always comment on the issue itself. Alternatively, you are welcome to come to the triage meeting to discuss. + +### Is this meeting a good place to get help setting up security features on my OpenSearch instance? + +While we are always happy to help the community, the best resource for implementation questions is [the OpenSearch forum](https://forum.opensearch.org/c/security/3). + +There you can find answers to many common questions as well as speak with implementation experts. + +### Is this where I should bring up potential security vulnerabilities? + +Due to the sensitive nature of security vulnerabilities, please report all potential vulnerabilities directly by following the steps outlined on the [SECURITY.md](https://github.com/opensearch-project/security/blob/main/SECURITY.md) document. + +### Who should I contact if I have further questions? + +You can always file an [issue](ttps://github.com/opensearch-project/security/issues/new/choose) for any question you have about the project. Alternatively, you can reach out to specific contacts helping to organize the project: Stephen Crawford (steecraw@amazon.com), Dave Lago (davelago@amazon.com), and Peter Nied (petern@amazon.com). From e36d3965f2069225e6fb590dab39e776bc91dd33 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Thu, 17 Nov 2022 13:50:11 -0500 Subject: [PATCH 078/369] GitHub Action for plugin install (#2239) Converts the security plugin install into a git hub action action.yml process instead of a workflow. Signed-off-by: Stephen Crawford Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- .../action.yml | 126 ++++++++++++++++++ .github/workflows/plugin_install.yml | 126 +++--------------- 2 files changed, 146 insertions(+), 106 deletions(-) create mode 100644 .github/actions/start-opensearch-with-one-plugin/action.yml diff --git a/.github/actions/start-opensearch-with-one-plugin/action.yml b/.github/actions/start-opensearch-with-one-plugin/action.yml new file mode 100644 index 0000000000..d75dc6fd13 --- /dev/null +++ b/.github/actions/start-opensearch-with-one-plugin/action.yml @@ -0,0 +1,126 @@ +name: 'Start OpenSearch with One Plugin' +description: 'Downloads latest build of OpenSearch, installs a plugin, executes a script and then starts OpenSearch on localhost:9200' + +inputs: + opensearch-version: + description: 'The version of OpenSearch that should be used, e.g "3.0.0"' + required: true + + plugin-name: + description: 'The name of the plugin to use, such as opensearch-security' + required: true + + plugin-start-script: + description: 'The file name for the configuration script for the plugin such as install_demo_configurations -- may not be needed for every plugin' + required: false + + docker-host-plugin-zip: + description: 'The name of the zip file for the plugin hosted on docker-host i.e. security-plugin.zip ' + required: true + +runs: + using: "composite" + steps: + + # Configure longpath names if on Windows + - name: Enable Longpaths if on Windows + if: ${{ runner.os == 'Windows' }} + run: git config --system core.longpaths true + shell: pwsh + + # Download OpenSearch + - uses: peternied/download-file@v1 + if: ${{ runner.os == 'Windows' }} + with: + url: https://artifacts.opensearch.org/snapshots/core/opensearch/${{ inputs.opensearch-version }}-SNAPSHOT/opensearch-min-${{ inputs.opensearch-version }}-SNAPSHOT-windows-x64-latest.zip + + - uses: peternied/download-file@v1 + if: ${{ runner.os == 'Linux' }} + with: + url: https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/${{ inputs.opensearch-version }}/latest/linux/x64/tar/builds/opensearch/dist/opensearch-min-${{ inputs.opensearch-version }}-linux-x64.tar.gz + + # Extract downloaded zip + - name: Extract downloaded zip for Linux + if: ${{ runner.os == 'Linux' }} + run: | + tar -xzf opensearch-*.tar.gz + rm -f opensearch-*.tar.gz + shell: bash + + - name: Extract downloaded zip for Windows + if: ${{ runner.os == 'Windows' }} + run: | + tar -xzf opensearch-min-${{ inputs.opensearch-version }}-SNAPSHOT-windows-x64-latest.zip + del opensearch-min-${{ inputs.opensearch-version }}-SNAPSHOT-windows-x64-latest.zip + shell: pwsh + + # Move and rename the plugin for installation + - name: Move and rename the plugin for installation + run: mv ./build/distributions/${{ inputs.plugin-name }}-*.zip ${{ inputs.plugin-name }}.zip + shell: bash + + # Install the plugin, runs its start-script, and start the OpenSearch server + - name: Install Plugin into OpenSearch for Linux + if: ${{ runner.os == 'Linux'}} + run: | + cat > os-ep.sh < os-ep.sh < Date: Fri, 18 Nov 2022 16:01:07 -0600 Subject: [PATCH 079/369] Generalize Backwards Compatibility tests so we can test from any version to any version (#2253) * Generalize Backwards Compatibility tests so we can test from any version to any version With an issue reported indicating that there are serialization issue between 1.3 and 2.X, making sure that we can reproduce the errors. This new workflow(s) will make sure that we aren't breaking BWC with changes we are adding to 2.X and will give us the flexibility to test certain migration workflows. Fixing an issue where the BWC tests were not actually building or executing causing the clusters to spin up and then immediately spin down. We will need to invest more energy into running multiple kinds of security plugin specific scenarios through the test system. Signed-off-by: Peter Nied --- .github/actions/create-bwc-build/action.yaml | 43 ++++++++++ .github/actions/run-bwc-suite/action.yaml | 46 ++++++++++ .github/workflows/bwc-tests.yml | 42 +++++++++ .github/workflows/ci.yml | 34 ++++---- bwc-test/build.gradle | 85 ++++++------------- .../SecurityBackwardsCompatibilityIT.java | 50 ++++++----- 6 files changed, 204 insertions(+), 96 deletions(-) create mode 100644 .github/actions/create-bwc-build/action.yaml create mode 100644 .github/actions/run-bwc-suite/action.yaml create mode 100644 .github/workflows/bwc-tests.yml diff --git a/.github/actions/create-bwc-build/action.yaml b/.github/actions/create-bwc-build/action.yaml new file mode 100644 index 0000000000..4d2d17a53a --- /dev/null +++ b/.github/actions/create-bwc-build/action.yaml @@ -0,0 +1,43 @@ +name: 'Create a backwards compatible ready build' +description: 'Checkouts the official version of a the Security plugin and builds it so it can be used for BWC tests' + +inputs: + plugin-branch: + description: 'The branch of the plugin that should be built, e.g "2.2", "1.x"' + required: true + +outputs: + built-version: + description: 'The version of OpenSearch that was associated with this branch' + value: ${{ steps.get-opensearch-version.outputs.version }} + +runs: + using: "composite" + steps: + - name: Enable Longpaths if on Windows + if: ${{ runner.os == 'Windows' }} + run: git config --system core.longpaths true + shell: pwsh + + - uses: actions/checkout@v3 + with: + repository: opensearch-project/security + ref: ${{ inputs.plugin-branch }} + path: ${{ inputs.plugin-branch }} + + - name: Build + uses: gradle/gradle-build-action@v2 + with: + arguments: assemble -Dbuild.snapshot=false + build-root-directory: ${{ inputs.plugin-branch }} + + - id: get-opensearch-version + uses: peternied/get-opensearch-version@v1 + with: + working-directory: ${{ inputs.plugin-branch }} + + - name: Copy current distro into the expected folder + run: | + mkdir -p ./bwc-test/src/test/resources/${{ steps.get-opensearch-version.outputs.version }} + cp ${{ inputs.plugin-branch }}/build/distributions/opensearch-security-${{ steps.get-opensearch-version.outputs.version }}.zip ./bwc-test/src/test/resources/${{ steps.get-opensearch-version.outputs.version }} + shell: bash diff --git a/.github/actions/run-bwc-suite/action.yaml b/.github/actions/run-bwc-suite/action.yaml new file mode 100644 index 0000000000..4614872858 --- /dev/null +++ b/.github/actions/run-bwc-suite/action.yaml @@ -0,0 +1,46 @@ +name: 'Runs the backward bompatiblity test suite' +description: 'Tests backwards compability between a previous and next version of this plugin' + +inputs: + plugin-previous-branch: + description: 'The branch of the plugin that should be built for the previous version, e.g "2.2", "1.x"' + required: true + + plugin-next-branch: + description: 'The branch of the plugin that should be built for the next version, e.g "2.3", "main"' + required: true + + report-artifact-name: + description: 'The name of the artifacts for this run, e.g. "BWC-2.1-to-2.4-results"' + required: true + +runs: + using: "composite" + steps: + + - id: build-previous + uses: ./.github/actions/create-bwc-build + with: + plugin-branch: ${{ inputs.plugin-previous-branch }} + + - id: build-next + uses: ./.github/actions/create-bwc-build + with: + plugin-branch: ${{ inputs.plugin-next-branch }} + + - name: Run BWC tests + uses: gradle/gradle-build-action@v2 + with: + arguments: | + bwcTestSuite + -Dtests.security.manager=false + -Dbwc.version.previous=${{ steps.build-previous.outputs.built-version }} + -Dbwc.version.next=${{ steps.build-next.outputs.built-version }} -i + build-root-directory: bwc-test + + - uses: actions/upload-artifact@v3 + if: always() + with: + name: ${{ inputs.report-artifact-name }} + path: | + ./bwc-test/build/reports/ diff --git a/.github/workflows/bwc-tests.yml b/.github/workflows/bwc-tests.yml new file mode 100644 index 0000000000..ed65970a30 --- /dev/null +++ b/.github/workflows/bwc-tests.yml @@ -0,0 +1,42 @@ +name: Backwards Compability Suite + +on: [workflow_dispatch] + +jobs: + last-supported-major-to-current: + name: Make sure that the last supported major version can move to the most current version + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Checkout Security Repo + uses: actions/checkout@v2 + + - id: build-previous + uses: ./.github/actions/run-bwc-suite + with: + plugin-previous-branch: "1.3" + plugin-next-branch: "2.x" + report-artifact-name: BWC-Last-Supported-Major + + current-to-next-unreleased-major: + name: Make sure that the current version is compatible with the next major version + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Checkout Security Repo + uses: actions/checkout@v2 + + - id: build-previous + uses: ./.github/actions/run-bwc-suite + with: + plugin-previous-branch: "2.x" + plugin-next-branch: "main" + report-artifact-name: BWC-Next-Major diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e7fc78987..7b4319f1c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,24 +84,28 @@ jobs: -x spotbugsMain backward-compatibility: - runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + jdk: [11, 17] + platform: ["ubuntu-latest", "windows-latest"] + runs-on: ${{ matrix.platform }} + steps: - - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: 11 - - run: ./gradlew clean build -Dbuild.snapshot=false -x test -x integrationTest - - run: | - echo "Running backwards compatibility tests ..." - security_plugin_version_no_snapshot=$(./gradlew properties -q | grep -E '^version:' | awk '{print $2}' | sed 's/-SNAPSHOT//g') - cp -r build/ ./bwc-test/ - mkdir ./bwc-test/src/test/resources/security_plugin_version_no_snapshot - cp build/distributions/opensearch-security-${security_plugin_version_no_snapshot}.zip ./bwc-test/src/test/resources/${security_plugin_version_no_snapshot} - mkdir bwc-test/src/test/resources/2.4.0.0 - wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/2.4.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.4.0.0.zip - mv opensearch-security-2.4.0.0.zip bwc-test/src/test/resources/2.4.0.0/ - cd bwc-test/ - ./gradlew bwcTestSuite -Dtests.security.manager=false + java-version: ${{ matrix.jdk }} + + - name: Checkout Security Repo + uses: actions/checkout@v2 + + - id: build-previous + uses: ./.github/actions/run-bwc-suite + with: + plugin-previous-branch: "2.x" + plugin-next-branch: "main" + report-artifact-name: bwc-${{ matrix.platform }}-jdk${{ matrix.jdk }} code-ql: runs-on: ubuntu-latest diff --git a/bwc-test/build.gradle b/bwc-test/build.gradle index 9badfd1c85..4175581f09 100644 --- a/bwc-test/build.gradle +++ b/bwc-test/build.gradle @@ -37,10 +37,7 @@ apply plugin: 'java' apply plugin: 'opensearch.testclusters' -compileTestJava.enabled = false - ext { - projectSubstitutions = [:] licenseFile = rootProject.file('LICENSE.TXT') noticeFile = rootProject.file('NOTICE') } @@ -70,19 +67,41 @@ repositories { } dependencies { + testImplementation 'com.google.guava:guava:30.0-jre' testImplementation "org.opensearch.test:framework:${opensearch_version}" + testImplementation 'org.apache.logging.log4j:log4j-core:2.17.1' } -String bwcVersion = "2.4.0.0"; +loggerUsageCheck.enabled = false +testingConventions.enabled = false +validateNebulaPom.enabled = false + +String previousVersion = System.getProperty("bwc.version.previous", "2.4.0.0") +String nextVersion = System.getProperty("bwc.version.next", "3.0.0.0") + +String bwcVersion = previousVersion String baseName = "securityBwcCluster" String bwcFilePath = "src/test/resources/" -String projectVersion = "3.0.0.0" +String projectVersion = nextVersion + +String previousOpenSearch = extractVersion(previousVersion); +String nextOpenSearch = extractVersion(nextVersion); + +println previousOpenSearch + nextOpenSearch; + + +// Extracts the OpenSearch version from a plugin version string, 2.4.0.0 -> 2.4.0. +def String extractVersion(versionStr) { + def versionMatcher = versionStr =~ /(.+?)(\.\d+)$/ + versionMatcher.find() + return versionMatcher.group(1) +} 2.times {i -> testClusters { "${baseName}$i" { testDistribution = "ARCHIVE" - versions = ["2.4.0","3.0.0"] + versions = [previousOpenSearch, nextOpenSearch] numberOfNodes = 3 plugin(provider(new Callable() { @Override @@ -148,24 +167,7 @@ List> plugins = [ // Creates a test cluster with 3 nodes of the old version. 2.times {i -> task "${baseName}#oldVersionClusterTask$i"(type: StandaloneRestIntegTestTask) { - exclude '**/*Test*' - exclude '**/*Sanity*' useCluster testClusters."${baseName}$i" - if (System.getProperty("mixedCluster") != null) { - filter { - includeTest("org.opensearch.security.bwc.SecurityBackwardsCompatibilityIT", "testPluginUpgradeInAMixedCluster") - } - } - if (System.getProperty("rollingUpgradeCluster") != null) { - filter { - includeTest("org.opensearch.security.bwc.SecurityBackwardsCompatibilityIT", "testPluginUpgradeInARollingUpgradedCluster") - } - } - if (System.getProperty("fullRestartCluster") != null) { - filter { - includeTest("org.opensearch.security.bwc.SecurityBackwardsCompatibilityIT", "testPluginUpgradeInAnUpgradedCluster") - } - } systemProperty 'tests.rest.bwcsuite', 'old_cluster' systemProperty 'tests.rest.bwcsuite_round', 'old' systemProperty 'tests.plugin_bwc_version', bwcVersion @@ -178,23 +180,11 @@ List> plugins = [ // This results in a mixed cluster with 2 nodes on the old version and 1 upgraded node. // This is also used as a one third upgraded cluster for a rolling upgrade. task "${baseName}#mixedClusterTask"(type: StandaloneRestIntegTestTask) { - exclude '**/*Test*' - exclude '**/*Sanity*' dependsOn "${baseName}#oldVersionClusterTask0" useCluster testClusters."${baseName}0" doFirst { testClusters."${baseName}0".upgradeNodeAndPluginToNextVersion(plugins) } - if (System.getProperty("mixedCluster") != null) { - filter { - includeTest("org.opensearch.security.bwc.SecurityBackwardsCompatibilityIT", "testPluginUpgradeInAMixedCluster") - } - } - if (System.getProperty("rollingUpgradeCluster") != null) { - filter { - includeTest("org.opensearch.security.bwc.SecurityBackwardsCompatibilityIT", "testPluginUpgradeInARollingUpgradedCluster") - } - } systemProperty 'tests.rest.bwcsuite', 'mixed_cluster' systemProperty 'tests.rest.bwcsuite_round', 'first' systemProperty 'tests.plugin_bwc_version', bwcVersion @@ -206,18 +196,11 @@ task "${baseName}#mixedClusterTask"(type: StandaloneRestIntegTestTask) { // This results in a mixed cluster with 1 node on the old version and 2 upgraded nodes. // This is used for rolling upgrade. task "${baseName}#twoThirdsUpgradedClusterTask"(type: StandaloneRestIntegTestTask) { - exclude '**/*Test*' - exclude '**/*Sanity*' dependsOn "${baseName}#mixedClusterTask" useCluster testClusters."${baseName}0" doFirst { testClusters."${baseName}0".upgradeNodeAndPluginToNextVersion(plugins) } - if (System.getProperty("rollingUpgradeCluster") != null) { - filter { - includeTest("org.opensearch.security.bwc.SecurityBackwardsCompatibilityIT", "testPluginUpgradeInARollingUpgradedCluster") - } - } systemProperty 'tests.rest.bwcsuite', 'mixed_cluster' systemProperty 'tests.rest.bwcsuite_round', 'second' systemProperty 'tests.plugin_bwc_version', bwcVersion @@ -229,18 +212,11 @@ task "${baseName}#twoThirdsUpgradedClusterTask"(type: StandaloneRestIntegTestTas // This results in a fully upgraded cluster. // This is used for rolling upgrade. task "${baseName}#rollingUpgradeClusterTask"(type: StandaloneRestIntegTestTask) { - exclude '**/*Test*' - exclude '**/*Sanity*' dependsOn "${baseName}#twoThirdsUpgradedClusterTask" useCluster testClusters."${baseName}0" doFirst { testClusters."${baseName}0".upgradeNodeAndPluginToNextVersion(plugins) } - if (System.getProperty("rollingUpgradeCluster") != null) { - filter { - includeTest("org.opensearch.security.bwc.SecurityBackwardsCompatibilityIT", "testPluginUpgradeInARollingUpgradedCluster") - } - } systemProperty 'tests.rest.bwcsuite', 'mixed_cluster' systemProperty 'tests.rest.bwcsuite_round', 'third' systemProperty 'tests.plugin_bwc_version', bwcVersion @@ -251,28 +227,21 @@ task "${baseName}#rollingUpgradeClusterTask"(type: StandaloneRestIntegTestTask) // Upgrades all the nodes of the old cluster to new OpenSearch version with upgraded plugin version // at the same time resulting in a fully upgraded cluster. tasks.register("${baseName}#fullRestartClusterTask", StandaloneRestIntegTestTask) { - exclude '**/*Test*' - exclude '**/*Sanity*' dependsOn "${baseName}#oldVersionClusterTask1" useCluster testClusters."${baseName}1" doFirst { testClusters."${baseName}1".upgradeAllNodesAndPluginsToNextVersion(plugins) } - if (System.getProperty("fullRestartCluster") != null) { - filter { - includeTest("org.opensearch.security.bwc.SecurityBackwardsCompatibilityIT", "testPluginUpgradeInAnUpgradedCluster") - } - } systemProperty 'tests.rest.bwcsuite', 'upgraded_cluster' systemProperty 'tests.plugin_bwc_version', bwcVersion + systemProperty 'tests.rest.bwcsuite_round', 'first' nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}1".allHttpSocketURI.join(",")}") nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}1".getName()}") } // A bwc test suite which runs all the bwc tasks combined. task bwcTestSuite(type: StandaloneRestIntegTestTask) { - exclude '**/*Test*' - exclude '**/*Sanity*' + exclude '**/**' // Do not run any tests as part of this aggregate task dependsOn tasks.named("${baseName}#mixedClusterTask") dependsOn tasks.named("${baseName}#rollingUpgradeClusterTask") dependsOn tasks.named("${baseName}#fullRestartClusterTask") diff --git a/bwc-test/src/test/java/SecurityBackwardsCompatibilityIT.java b/bwc-test/src/test/java/SecurityBackwardsCompatibilityIT.java index 273de3f6b2..1afc1b88d5 100644 --- a/bwc-test/src/test/java/SecurityBackwardsCompatibilityIT.java +++ b/bwc-test/src/test/java/SecurityBackwardsCompatibilityIT.java @@ -12,18 +12,32 @@ import java.util.Set; import java.util.stream.Collectors; +import org.junit.Assume; import org.junit.Assert; +import org.junit.Before; import org.opensearch.client.Response; import org.opensearch.common.settings.Settings; import org.opensearch.rest.RestStatus; import org.opensearch.test.rest.OpenSearchRestTestCase; +import org.opensearch.Version; import com.google.common.collect.ImmutableMap; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; + public class SecurityBackwardsCompatibilityIT extends OpenSearchRestTestCase { - private static final ClusterType CLUSTER_TYPE = ClusterType.parse(System.getProperty("tests.rest.bwcsuite")); - private static final String CLUSTER_NAME = System.getProperty("tests.clustername"); + private ClusterType CLUSTER_TYPE; + private String CLUSTER_NAME; + + @Before + private void testSetup() { + final String bwcsuiteString = System.getProperty("tests.rest.bwcsuite"); + Assume.assumeTrue("Test cannot be run outside the BWC gradle task 'bwcTestSuite' or its dependent tasks", bwcsuiteString != null); + CLUSTER_TYPE = ClusterType.parse(bwcsuiteString); + CLUSTER_NAME = System.getProperty("tests.clustername"); + } @Override protected final boolean preserveIndicesUponCompletion() { @@ -52,16 +66,9 @@ protected final Settings restClientSettings() { .build(); } - public void testPluginUpgradeInAMixedCluster() throws Exception { - assertPluginUpgrade("_nodes/" + CLUSTER_NAME + "-0/plugins"); - } - - public void testPluginUpgradeInAnUpgradedCluster() throws Exception { - assertPluginUpgrade("_nodes/plugins"); - } - - public void testPluginUpgradeInARollingUpgradedCluster() throws Exception { + public void testBasicBackwardsCompatibility() throws Exception { String round = System.getProperty("tests.rest.bwcsuite_round"); + if (round.equals("first") || round.equals("old")) { assertPluginUpgrade("_nodes/" + CLUSTER_NAME + "-0/plugins"); } else if (round.equals("second")) { @@ -95,19 +102,16 @@ private void assertPluginUpgrade(String uri) throws Exception { Map> responseMap = (Map>) getAsMap(uri).get("nodes"); for (Map response : responseMap.values()) { List> plugins = (List>) response.get("plugins"); - Set pluginNames = plugins.stream().map(map -> map.get("name")).collect(Collectors.toSet()); - switch (CLUSTER_TYPE) { - case OLD: - Assert.assertTrue(pluginNames.contains("opendistro_security")); - break; - case MIXED: - Assert.assertTrue(pluginNames.contains("opensearch-security")); - break; - case UPGRADED: - Assert.assertTrue(pluginNames.contains("opendistro_security")); - break; + Set pluginNames = plugins.stream().map(map -> (String) map.get("name")).collect(Collectors.toSet()); + + final Version minNodeVersion = this.minimumNodeVersion(); + + if (minNodeVersion.major <= 1) { + assertThat(pluginNames, hasItem("opensearch_security")); + } else { + assertThat(pluginNames, hasItem("opensearch-security")); } - break; + } } } From 25ea092ff51cf03e32780acaf60cbf28dfbb30f9 Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Mon, 21 Nov 2022 18:57:51 +0100 Subject: [PATCH 080/369] Tests related to brute force attack prevention. (#2245) Tests related to brute force attack prevention. Signed-off-by: Lukasz Soszynski --- .../IpBruteForceAttacksPreventionTests.java | 158 ++++++++++++++++ .../org/opensearch/security/TlsTests.java | 26 ++- .../UserBruteForceAttacksPreventionTests.java | 125 +++++++++++++ .../security/http/JwtAuthenticationTests.java | 8 +- .../test/framework/AuthFailureListeners.java | 39 ++++ .../test/framework/RateLimiting.java | 85 +++++++++ .../test/framework/TestSecurityConfig.java | 16 ++ .../cluster/CloseableHttpClientFactory.java | 12 +- .../cluster/LocalAddressRoutePlanner.java | 48 +++++ .../test/framework/cluster/LocalCluster.java | 6 + .../cluster/OpenSearchClientProvider.java | 32 +--- .../framework/cluster/TestRestClient.java | 11 +- .../cluster/TestRestClientConfiguration.java | 169 ++++++++++++++++++ .../test/framework/log/LogsRule.java | 13 +- .../resources/log4j2-test.properties | 7 + 15 files changed, 716 insertions(+), 39 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java create mode 100644 src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/RateLimiting.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/LocalAddressRoutePlanner.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClientConfiguration.java diff --git a/src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java b/src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java new file mode 100644 index 0000000000..819f4225e8 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java @@ -0,0 +1,158 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.util.concurrent.TimeUnit; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.AuthFailureListeners; +import org.opensearch.test.framework.RateLimiting; +import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; +import org.opensearch.test.framework.cluster.TestRestClientConfiguration; +import org.opensearch.test.framework.log.LogsRule; + +import static org.apache.hc.core5.http.HttpStatus.SC_OK; +import static org.apache.hc.core5.http.HttpStatus.SC_UNAUTHORIZED; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; +import static org.opensearch.test.framework.cluster.TestRestClientConfiguration.userWithSourceIp; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class IpBruteForceAttacksPreventionTests { + private static final User USER_1 = new User("simple-user-1").roles(ALL_ACCESS); + private static final User USER_2 = new User("simple-user-2").roles(ALL_ACCESS); + + public static final int ALLOWED_TRIES = 3; + public static final int TIME_WINDOW_SECONDS = 3; + + public static final String CLIENT_IP_2 = "127.0.0.2"; + public static final String CLIENT_IP_3 = "127.0.0.3"; + public static final String CLIENT_IP_4 = "127.0.0.4"; + public static final String CLIENT_IP_5 = "127.0.0.5"; + public static final String CLIENT_IP_6 = "127.0.0.6"; + public static final String CLIENT_IP_7 = "127.0.0.7"; + public static final String CLIENT_IP_8 = "127.0.0.8"; + public static final String CLIENT_IP_9 = "127.0.0.9"; + + private static final AuthFailureListeners listener = new AuthFailureListeners() + .addRateLimit(new RateLimiting("internal_authentication_backend_limiting").type("ip") + .allowedTries(ALLOWED_TRIES).timeWindowSeconds(TIME_WINDOW_SECONDS).blockExpirySeconds(2).maxBlockedClients(500) + .maxTrackedClients(500)); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false).authFailureListeners(listener) + .authc(AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE).users(USER_1, USER_2).build(); + + @Rule + public LogsRule logsRule = new LogsRule("org.opensearch.security.auth.BackendRegistry"); + + @Test + public void shouldAuthenticateUserWhenBlockadeIsNotActive() { + try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_2))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + } + + @Test + public void shouldBlockIpAddress() { + authenticateUserWithIncorrectPassword(CLIENT_IP_3, USER_2, ALLOWED_TRIES); + try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_2, CLIENT_IP_3))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + logsRule.assertThatContain("Rejecting REST request because of blocked address: /" + CLIENT_IP_3); + } + } + + @Test + public void shouldBlockUsersWhoUseTheSameIpAddress() { + authenticateUserWithIncorrectPassword(CLIENT_IP_4, USER_1, ALLOWED_TRIES); + try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_2, CLIENT_IP_4))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + logsRule.assertThatContain("Rejecting REST request because of blocked address: /" + CLIENT_IP_4); + } + } + + @Test + public void testUserShouldBeAbleToAuthenticateFromAnotherNotBlockedIpAddress() { + authenticateUserWithIncorrectPassword(CLIENT_IP_5, USER_1, ALLOWED_TRIES); + try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_6))) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + } + + @Test + public void shouldNotBlockIpWhenFailureAuthenticationCountIsLessThanAllowedTries() { + authenticateUserWithIncorrectPassword(CLIENT_IP_7, USER_1, ALLOWED_TRIES - 1); + try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_7))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + } + + @Test + public void shouldBlockIpWhenFailureAuthenticationCountIsGraterThanAllowedTries() { + authenticateUserWithIncorrectPassword(CLIENT_IP_8, USER_1, ALLOWED_TRIES * 2); + try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_8))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + logsRule.assertThatContain("Rejecting REST request because of blocked address: /" + CLIENT_IP_8); + } + } + + @Test + public void shouldReleaseIpAddressLock() throws InterruptedException { + authenticateUserWithIncorrectPassword(CLIENT_IP_9, USER_1, ALLOWED_TRIES * 2); + TimeUnit.SECONDS.sleep(TIME_WINDOW_SECONDS); + try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_9))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + logsRule.assertThatContain("Rejecting REST request because of blocked address: /" + CLIENT_IP_9); + } + } + + private static void authenticateUserWithIncorrectPassword(String sourceIpAddress, User user, int numberOfRequests) { + var clientConfiguration = new TestRestClientConfiguration().username(user.getName()) + .password("incorrect password").sourceInetAddress(sourceIpAddress); + try(TestRestClient client = cluster.createGenericClientRestClient(clientConfiguration)) { + for(int i = 0; i < numberOfRequests; ++i) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + } + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/TlsTests.java b/src/integrationTest/java/org/opensearch/security/TlsTests.java index 1bc0e47252..7a57cb57b8 100644 --- a/src/integrationTest/java/org/opensearch/security/TlsTests.java +++ b/src/integrationTest/java/org/opensearch/security/TlsTests.java @@ -21,21 +21,29 @@ import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.NoHttpResponseException; -import org.apache.hc.core5.http.message.BasicHeader; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.opensearch.security.auditlog.impl.AuditCategory; +import org.opensearch.test.framework.AuditCompliance; +import org.opensearch.test.framework.AuditConfiguration; +import org.opensearch.test.framework.AuditFilters; import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.audit.AuditLogsRule; import org.opensearch.test.framework.cluster.ClusterManager; import org.opensearch.test.framework.cluster.LocalCluster; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; +import static org.opensearch.security.auditlog.AuditLog.Origin.REST; import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS; import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; +import static org.opensearch.test.framework.audit.AuditMessagePredicate.auditPredicate; +import static org.opensearch.test.framework.cluster.TestRestClientConfiguration.getBasicAuthHeader; import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy; @RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) @@ -49,10 +57,17 @@ public class TlsTests { public static final String AUTH_INFO_ENDPOINT = "/_opendistro/_security/authinfo?pretty"; @ClassRule - public static LocalCluster cluster = new LocalCluster.Builder() + public static final LocalCluster cluster = new LocalCluster.Builder() .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) .nodeSettings(Map.of(SECURITY_SSL_HTTP_ENABLED_CIPHERS, List.of(SUPPORTED_CIPHER_SUIT))) - .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_ADMIN).build(); + .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_ADMIN) + .audit(new AuditConfiguration(true) + .compliance(new AuditCompliance().enabled(true)) + .filters(new AuditFilters().enabledRest(true).enabledTransport(true)) + ).build(); + + @Rule + public AuditLogsRule auditLogsRule = new AuditLogsRule(); @Test public void shouldCreateAuditOnIncomingNonTlsConnection() throws IOException { @@ -61,15 +76,14 @@ public void shouldCreateAuditOnIncomingNonTlsConnection() throws IOException { assertThatThrownBy(() -> httpClient.execute(request), instanceOf(NoHttpResponseException.class)); } - //TODO check if audit is created, audit_category = SSL_EXCEPTION + auditLogsRule.assertAtLeast(1, auditPredicate(AuditCategory.SSL_EXCEPTION).withLayer(REST)); } @Test public void shouldSupportClientCipherSuite_positive() throws IOException { try(CloseableHttpClient client = cluster.getClosableHttpClient(new String[] { SUPPORTED_CIPHER_SUIT })) { HttpGet httpGet = new HttpGet("https://localhost:" + cluster.getHttpPort() + AUTH_INFO_ENDPOINT); - BasicHeader header = cluster.getBasicAuthHeader(USER_ADMIN.getName(), USER_ADMIN.getPassword()); - httpGet.addHeader(header); + httpGet.addHeader(getBasicAuthHeader(USER_ADMIN.getName(), USER_ADMIN.getPassword())); try(CloseableHttpResponse response = client.execute(httpGet)) { diff --git a/src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java b/src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java new file mode 100644 index 0000000000..1c06bd9cff --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java @@ -0,0 +1,125 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.util.concurrent.TimeUnit; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.AuthFailureListeners; +import org.opensearch.test.framework.RateLimiting; +import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; +import org.opensearch.test.framework.log.LogsRule; + +import static org.apache.hc.core5.http.HttpStatus.SC_OK; +import static org.apache.hc.core5.http.HttpStatus.SC_UNAUTHORIZED; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class UserBruteForceAttacksPreventionTests { + + private static final User USER_1 = new User("simple-user-1").roles(ALL_ACCESS); + private static final User USER_2 = new User("simple-user-2").roles(ALL_ACCESS); + private static final User USER_3 = new User("simple-user-3").roles(ALL_ACCESS); + private static final User USER_4 = new User("simple-user-4").roles(ALL_ACCESS); + private static final User USER_5 = new User("simple-user-5").roles(ALL_ACCESS); + + public static final int ALLOWED_TRIES = 3; + public static final int TIME_WINDOW_SECONDS = 3; + private static final AuthFailureListeners listener = new AuthFailureListeners() + .addRateLimit(new RateLimiting("internal_authentication_backend_limiting").type("username").authenticationBackend("intern") + .allowedTries(ALLOWED_TRIES).timeWindowSeconds(TIME_WINDOW_SECONDS).blockExpirySeconds(2).maxBlockedClients(500) + .maxTrackedClients(500)); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false).authFailureListeners(listener) + .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_1, USER_2, USER_3, USER_4, USER_5).build(); + + @Rule + public LogsRule logsRule = new LogsRule("org.opensearch.security.auth.BackendRegistry"); + + @Test + public void shouldAuthenticateUserWhenBlockadeIsNotActive() { + try(TestRestClient client = cluster.getRestClient(USER_1)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + } + + @Test + public void shouldBlockUserWhenNumberOfFailureLoginAttemptIsEqualToLimit() { + authenticateUserWithIncorrectPassword(USER_2, ALLOWED_TRIES); + try(TestRestClient client = cluster.getRestClient(USER_2)) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + } + //Rejecting REST request because of blocked user: + logsRule.assertThatContain("Rejecting REST request because of blocked user: " + USER_2.getName()); + } + + @Test + public void shouldBlockUserWhenNumberOfFailureLoginAttemptIsGraterThanLimit() { + authenticateUserWithIncorrectPassword(USER_3, ALLOWED_TRIES * 2); + try(TestRestClient client = cluster.getRestClient(USER_3)) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + } + logsRule.assertThatContain("Rejecting REST request because of blocked user: " + USER_3.getName()); + } + + @Test + public void shouldNotBlockUserWhenNumberOfLoginAttemptIsBelowLimit() { + authenticateUserWithIncorrectPassword(USER_4, ALLOWED_TRIES - 1); + try(TestRestClient client = cluster.getRestClient(USER_4)) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + } + + @Test + public void shouldReleaseLock() throws InterruptedException { + authenticateUserWithIncorrectPassword(USER_5, ALLOWED_TRIES); + try(TestRestClient client = cluster.getRestClient(USER_5)) { + HttpResponse response = client.getAuthInfo(); + response.assertStatusCode(SC_UNAUTHORIZED); + TimeUnit.SECONDS.sleep(TIME_WINDOW_SECONDS); + + response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + logsRule.assertThatContain("Rejecting REST request because of blocked user: " + USER_5.getName()); + } + + private static void authenticateUserWithIncorrectPassword(User user, int numberOfAttempts) { + try(TestRestClient client = cluster.getRestClient(user.getName(), "incorrect password")) { + for(int i = 0; i < numberOfAttempts; ++i) { + HttpResponse response = client.getAuthInfo(); + response.assertStatusCode(SC_UNAUTHORIZED); + } + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java b/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java index bf8af72854..341a956b4a 100644 --- a/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java @@ -164,7 +164,7 @@ public void shouldAuthenticateWithJwtToken_failureLackingUserName() { HttpResponse response = client.getAuthInfo(); response.assertStatusCode(401); - logsRule.assertThatContain("No subject found in JWT token"); + logsRule.assertThatContainExactly("No subject found in JWT token"); } } @@ -175,7 +175,7 @@ public void shouldAuthenticateWithJwtToken_failureExpiredToken() { HttpResponse response = client.getAuthInfo(); response.assertStatusCode(401); - logsRule.assertThatContain("Invalid or expired JWT token."); + logsRule.assertThatContainExactly("Invalid or expired JWT token."); } } @@ -187,7 +187,7 @@ public void shouldAuthenticateWithJwtToken_failureIncorrectFormatOfToken() { HttpResponse response = client.getAuthInfo(); response.assertStatusCode(401); - logsRule.assertThatContain(String.format("No JWT token found in '%s' header header", JWT_AUTH_HEADER)); + logsRule.assertThatContainExactly(String.format("No JWT token found in '%s' header header", JWT_AUTH_HEADER)); } } @@ -200,7 +200,7 @@ public void shouldAuthenticateWithJwtToken_failureIncorrectSignature() { HttpResponse response = client.getAuthInfo(); response.assertStatusCode(401); - logsRule.assertThatContain("Invalid or expired JWT token."); + logsRule.assertThatContainExactly("Invalid or expired JWT token."); } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java b/src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java new file mode 100644 index 0000000000..1f506cba71 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java @@ -0,0 +1,39 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; + +public class AuthFailureListeners implements ToXContentObject { + + private Map limits = new LinkedHashMap<>(); + + public AuthFailureListeners addRateLimit(RateLimiting rateLimiting) { + Objects.requireNonNull(rateLimiting, "Rate limiting is required"); + limits.put(rateLimiting.getName(), rateLimiting); + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + for(Map.Entry entry : limits.entrySet()) { + xContentBuilder.field(entry.getKey(), entry.getValue()); + } + xContentBuilder.endObject(); + return xContentBuilder; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/RateLimiting.java b/src/integrationTest/java/org/opensearch/test/framework/RateLimiting.java new file mode 100644 index 0000000000..4b43194572 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/RateLimiting.java @@ -0,0 +1,85 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework; + +import java.io.IOException; +import java.util.Objects; + +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; + +public class RateLimiting implements ToXContentObject { + + private final String name; + private String type; + private String authenticationBackend; + private Integer allowedTries; + private Integer timeWindowSeconds; + private Integer blockExpirySeconds; + private Integer maxBlockedClients; + private Integer maxTrackedClients; + + public String getName() { + return name; + } + + public RateLimiting(String name) { + this.name = Objects.requireNonNull(name, "Rate limit name is required."); + } + + public RateLimiting type(String type) { + this.type = type; + return this; + } + + public RateLimiting authenticationBackend(String authenticationBackend) { + this.authenticationBackend = authenticationBackend; + return this; + } + + public RateLimiting allowedTries(Integer allowedTries) { + this.allowedTries = allowedTries; + return this; + } + + public RateLimiting timeWindowSeconds(Integer timeWindowSeconds) { + this.timeWindowSeconds = timeWindowSeconds; + return this; + } + + public RateLimiting blockExpirySeconds(Integer blockExpirySeconds) { + this.blockExpirySeconds = blockExpirySeconds; + return this; + } + + public RateLimiting maxBlockedClients(Integer maxBlockedClients) { + this.maxBlockedClients = maxBlockedClients; + return this; + } + + public RateLimiting maxTrackedClients(Integer maxTrackedClients) { + this.maxTrackedClients = maxTrackedClients; + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("type", type); + xContentBuilder.field("authentication_backend", authenticationBackend); + xContentBuilder.field("allowed_tries", allowedTries); + xContentBuilder.field("time_window_seconds", timeWindowSeconds); + xContentBuilder.field("block_expiry_seconds", blockExpirySeconds); + xContentBuilder.field("max_blocked_clients", maxBlockedClients); + xContentBuilder.field("max_tracked_clients", maxTrackedClients); + xContentBuilder.endObject(); + return xContentBuilder; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index ca64c1e00b..f8dc90a947 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -95,6 +95,11 @@ public TestSecurityConfig configIndexName(String configIndexName) { return this; } + public TestSecurityConfig authFailureListeners(AuthFailureListeners listener) { + config.authFailureListeners(listener); + return this; + } + public TestSecurityConfig anonymousAuth(boolean anonymousAuthEnabled) { config.anonymousAuth(anonymousAuthEnabled); return this; @@ -139,6 +144,8 @@ public static class Config implements ToXContentObject { private Map authcDomainMap = new LinkedHashMap<>(); + private AuthFailureListeners authFailureListeners; + public Config anonymousAuth(boolean anonymousAuth) { this.anonymousAuth = anonymousAuth; return this; @@ -154,6 +161,11 @@ public Config authc(AuthcDomain authcDomain) { return this; } + public Config authFailureListeners(AuthFailureListeners authFailureListeners) { + this.authFailureListeners = authFailureListeners; + return this; + } + @Override public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { xContentBuilder.startObject(); @@ -170,6 +182,10 @@ public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params xContentBuilder.field("authc", authcDomainMap); + if(authFailureListeners != null) { + xContentBuilder.field("auth_failure_listeners", authFailureListeners); + } + xContentBuilder.endObject(); xContentBuilder.endObject(); return xContentBuilder; diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/CloseableHttpClientFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/CloseableHttpClientFactory.java index b6980ededf..e0e57d2ef1 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/CloseableHttpClientFactory.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/CloseableHttpClientFactory.java @@ -20,6 +20,7 @@ import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.core5.http.io.SocketConfig; @@ -30,11 +31,17 @@ class CloseableHttpClientFactory { private final RequestConfig requestConfig; + private final HttpRoutePlanner routePlanner; + private final String[] supportedCipherSuit; - public CloseableHttpClientFactory(SSLContext sslContext, RequestConfig requestConfig, String[] supportedCipherSuit) { + public CloseableHttpClientFactory(SSLContext sslContext, + RequestConfig requestConfig, + HttpRoutePlanner routePlanner, + String[] supportedCipherSuit) { this.sslContext = Objects.requireNonNull(sslContext, "SSL context is required."); this.requestConfig = requestConfig; + this.routePlanner = routePlanner; this.supportedCipherSuit = supportedCipherSuit; } @@ -50,6 +57,9 @@ public CloseableHttpClient getHTTPClient() { .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60, TimeUnit.SECONDS).build()) .build(); hcb.setConnectionManager(cm); + if(routePlanner != null) { + hcb.setRoutePlanner(routePlanner); + } if (requestConfig != null) { hcb.setDefaultRequestConfig(requestConfig); diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalAddressRoutePlanner.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalAddressRoutePlanner.java new file mode 100644 index 0000000000..ab29d3206e --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalAddressRoutePlanner.java @@ -0,0 +1,48 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.cluster; + +import java.net.InetAddress; +import java.util.Objects; + +import org.apache.hc.client5.http.impl.DefaultSchemePortResolver; +import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.protocol.HttpContext; + +/** +* Class which can be used to bind Apache HTTP client to a particular network interface or its IP address so that the IP address of +* network interface is used as a source IP address of HTTP request. +*/ +class LocalAddressRoutePlanner extends DefaultRoutePlanner { + + /** + * IP address of one of the local network interfaces. + */ + private final InetAddress localAddress; + + /** + * Creates {@link LocalAddressRoutePlanner} + * @param localAddress IP address of one of the local network interfaces. Client socket used by Apache HTTP client will be bind to + * address from this parameter. The parameter must not be null. + */ + public LocalAddressRoutePlanner(InetAddress localAddress) { + super(DefaultSchemePortResolver.INSTANCE); + this.localAddress = Objects.requireNonNull(localAddress); + } + + /** + * Determines IP address used by the client socket of Apache HTTP client + */ + @Override + protected InetAddress determineLocalAddress(HttpHost firstHop, HttpContext context) { + return localAddress; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index 6c212ee10c..d4ed05db45 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -49,6 +49,7 @@ import org.opensearch.plugins.Plugin; import org.opensearch.security.support.ConfigConstants; import org.opensearch.test.framework.AuditConfiguration; +import org.opensearch.test.framework.AuthFailureListeners; import org.opensearch.test.framework.TestIndex; import org.opensearch.test.framework.TestSecurityConfig; import org.opensearch.test.framework.TestSecurityConfig.Role; @@ -315,6 +316,11 @@ public Builder plugin(Class plugin) { return this; } + public Builder authFailureListeners(AuthFailureListeners listener) { + testSecurityConfig.authFailureListeners(listener); + return this; + } + /** * Specifies a remote cluster and its name. The remote cluster can be then used in Cross Cluster Search * operations with the specified name. diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java index 5679788d39..7e23f35fd5 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -30,17 +30,12 @@ import java.net.InetSocketAddress; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.cert.X509Certificate; import java.util.Arrays; -import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -57,7 +52,6 @@ import org.apache.hc.core5.function.Factory; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpHost; -import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.reactor.ssl.TlsDetails; @@ -129,9 +123,9 @@ public TlsDetails create(final SSLEngine sslEngine) { }) .build(); - final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() - .setTlsStrategy(tlsStrategy) - .build(); + final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(tlsStrategy) + .build(); if(credentialsProvider != null) { httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); @@ -149,7 +143,7 @@ public TlsDetails create(final SSLEngine sslEngine) { } default CloseableHttpClient getClosableHttpClient(String[] supportedCipherSuit) { - CloseableHttpClientFactory factory = new CloseableHttpClientFactory(getSSLContext(), null, supportedCipherSuit); + CloseableHttpClientFactory factory = new CloseableHttpClientFactory(getSSLContext(), null, null, supportedCipherSuit); return factory.getHTTPClient(); } @@ -161,12 +155,7 @@ default CloseableHttpClient getClosableHttpClient(String[] supportedCipherSuit) * control over username and password - for example, when you want to send a wrong password. */ default TestRestClient getRestClient(String user, String password, Header... headers) { - BasicHeader basicAuthHeader = getBasicAuthHeader(user, password); - if (headers != null && headers.length > 0) { - List
concatenatedHeaders = Stream.concat(Stream.of(basicAuthHeader), Stream.of(headers)).collect(Collectors.toList()); - return getRestClient(concatenatedHeaders); - } - return getRestClient(basicAuthHeader); + return createGenericClientRestClient(new TestRestClientConfiguration().username(user).password(password).headers(headers)); } /** @@ -178,16 +167,11 @@ default TestRestClient getRestClient(Header... headers) { } default TestRestClient getRestClient(List
headers) { - return createGenericClientRestClient(headers); - } - - default TestRestClient createGenericClientRestClient(List
headers) { - return new TestRestClient(getHttpAddress(), headers, getSSLContext()); + return createGenericClientRestClient(new TestRestClientConfiguration().headers(headers)); } - default BasicHeader getBasicAuthHeader(String user, String password) { - return new BasicHeader("Authorization", - "Basic " + Base64.getEncoder().encodeToString((user + ":" + Objects.requireNonNull(password)).getBytes(StandardCharsets.UTF_8))); + default TestRestClient createGenericClientRestClient(TestRestClientConfiguration configuration) { + return new TestRestClient(getHttpAddress(), configuration.getHeaders(), getSSLContext(), configuration.getSourceInetAddress()); } private SSLContext getSSLContext() { diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java index 092723f9dd..e8d704eb4a 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -29,6 +29,7 @@ package org.opensearch.test.framework.cluster; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; @@ -58,6 +59,7 @@ import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.NameValuePair; @@ -95,10 +97,13 @@ public class TestRestClient implements AutoCloseable { private Header CONTENT_TYPE_JSON = new BasicHeader("Content-Type", "application/json"); private SSLContext sslContext; - public TestRestClient(InetSocketAddress nodeHttpAddress, List
headers, SSLContext sslContext) { + private final InetAddress sourceInetAddress; + + public TestRestClient(InetSocketAddress nodeHttpAddress, List
headers, SSLContext sslContext, InetAddress sourceInetAddress) { this.nodeHttpAddress = nodeHttpAddress; this.headers.addAll(headers); this.sslContext = sslContext; + this.sourceInetAddress = sourceInetAddress; } public HttpResponse get(String path, List queryParameters, Header... headers) { @@ -178,7 +183,6 @@ public HttpResponse assignRoleToUser(String username, String roleName) { } public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... requestSpecificHeaders) { - try(CloseableHttpClient httpClient = getHTTPClient()) { @@ -215,7 +219,8 @@ protected final String getHttpServerUri() { } protected final CloseableHttpClient getHTTPClient() { - var factory = new CloseableHttpClientFactory(sslContext, requestConfig, null); + HttpRoutePlanner routePlanner = Optional.ofNullable(sourceInetAddress).map(LocalAddressRoutePlanner::new).orElse(null); + var factory = new CloseableHttpClientFactory(sslContext, requestConfig, routePlanner, null); return factory.getHTTPClient(); } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClientConfiguration.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClientConfiguration.java new file mode 100644 index 0000000000..c1f7a7a737 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClientConfiguration.java @@ -0,0 +1,169 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.cluster; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.message.BasicHeader; + +import org.opensearch.test.framework.cluster.OpenSearchClientProvider.UserCredentialsHolder; + +import static java.util.Objects.requireNonNull; + +/** +* Object which groups some parameters needed for {@link TestRestClient} creation. The class was created to reduce number of parameters +* of methods which are used to create {@link TestRestClient} . The class provides convenient builder-like methods. All fields of a class +* are nullable. +*/ +public class TestRestClientConfiguration { + + /** + * Username + */ + private String username; + /** + * Password + */ + private String password; + /** + * HTTP headers which should be attached to each HTTP request which is sent by {@link TestRestClient} + */ + private final List
headers = new ArrayList<>(); + /** + * IP address of client socket of {@link TestRestClient} + */ + private InetAddress sourceInetAddress; + + /** + * Set username + * @param username username + * @return builder + */ + public TestRestClientConfiguration username(String username) { + this.username = username; + return this; + } + + /** + * Set user's password + * @param password password + * @return builder + */ + public TestRestClientConfiguration password(String password) { + this.password = password; + return this; + } + + /** + * The method sets username and password read form userCredentialsHolder + * @param userCredentialsHolder source of credentials + * @return builder + */ + public TestRestClientConfiguration credentials(UserCredentialsHolder userCredentialsHolder) { + Objects.requireNonNull(userCredentialsHolder, "User credential holder is required."); + this.username = userCredentialsHolder.getName(); + this.password = userCredentialsHolder.getPassword(); + return this; + } + + /** + * Add HTTP headers which are attached to each HTTP request + * @param headers headers + * @return builder + */ + public TestRestClientConfiguration headers(Header...headers) { + this.headers.addAll(Arrays.asList(Objects.requireNonNull(headers, "Headers are required"))); + return this; + } + /** + * Add HTTP headers which are attached to each HTTP request + * @param headers list of headers + * @return builder + */ + public TestRestClientConfiguration headers(List
headers) { + this.headers.addAll(Objects.requireNonNull(headers, "Cannot add null headers")); + return this; + } + + /** + * Add HTTP header to each request + * @param name header name + * @param value header value + * @return builder + */ + public TestRestClientConfiguration header(String name, Object value) { + return headers(new BasicHeader(name, value)); + } + + /** + * Set IP address of client socket used by {@link TestRestClient} + * @param sourceInetAddress IP address + * @return builder + */ + public TestRestClientConfiguration sourceInetAddress(InetAddress sourceInetAddress) { + this.sourceInetAddress = sourceInetAddress; + return this; + } + + public TestRestClientConfiguration sourceInetAddress(String sourceInetAddress) { + try { + this.sourceInetAddress = InetAddress.getByName(sourceInetAddress); + return this; + } catch (UnknownHostException e) { + throw new RuntimeException("Cannot get IP address for string " + sourceInetAddress, e); + } + } + + public static TestRestClientConfiguration userWithSourceIp(UserCredentialsHolder credentials, String sourceIpAddress) { + return new TestRestClientConfiguration().credentials(credentials).sourceInetAddress(sourceIpAddress); + } + + /** + * Return complete header list. Basic authentication header is created using fields {@link #username} and {@link #password} + * @return header list + */ + List
getHeaders() { + return Stream.concat(createBasicAuthHeader().stream(), headers.stream()).collect(Collectors.toList()); + } + + private Optional
createBasicAuthHeader() { + if(containsCredentials()) { + return Optional.of(getBasicAuthHeader(username, password)); + } + return Optional.empty(); + } + + private boolean containsCredentials() { + return StringUtils.isNoneBlank(username) && StringUtils.isNoneBlank(password); + } + + InetAddress getSourceInetAddress() { + return sourceInetAddress; + } + + public static Header getBasicAuthHeader(String user, String password) { + String value ="Basic " + Base64.getEncoder() + .encodeToString((user + ":" + requireNonNull(password)) + .getBytes(StandardCharsets.UTF_8)); + return new BasicHeader("Authorization", value); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java b/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java index 34fe6f4455..203cbb80b1 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java +++ b/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java @@ -16,6 +16,7 @@ import org.junit.rules.ExternalResource; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItem; /** @@ -50,12 +51,22 @@ protected void after() { * Check if during the tests certain log message was logged * @param expectedLogMessage expected log message */ - public void assertThatContain(String expectedLogMessage) { + public void assertThatContainExactly(String expectedLogMessage) { List messages = LogCapturingAppender.getLogMessages(); String reason = reasonMessage(expectedLogMessage, messages); assertThat(reason, messages, hasItem(expectedLogMessage)); } + /** + * Check if during the tests certain log message was logged + * @param messageFragment expected log message fragment + */ + public void assertThatContain(String messageFragment) { + List messages = LogCapturingAppender.getLogMessages(); + String reason = reasonMessage(messageFragment, messages); + assertThat(reason, messages, hasItem(containsString(messageFragment))); + } + private static String reasonMessage(String expectedLogMessage, List messages) { String concatenatedLogMessages = messages.stream() .map(message -> String.format("'%s'", message)) diff --git a/src/integrationTest/resources/log4j2-test.properties b/src/integrationTest/resources/log4j2-test.properties index d9a4d672e0..6982473cef 100644 --- a/src/integrationTest/resources/log4j2-test.properties +++ b/src/integrationTest/resources/log4j2-test.properties @@ -28,3 +28,10 @@ logger.auditlogs.level = info logger.httpjwtauthenticator.name = com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator logger.httpjwtauthenticator.level = debug logger.httpjwtauthenticator.appenderRef.capturing.ref = logCapturingAppender + +#Required by tests: +# org.opensearch.security.IpBruteForceAttacksPreventionTests +# org.opensearch.security.UserBruteForceAttacksPreventionTests +logger.backendreg.name = org.opensearch.security.auth.BackendRegistry +logger.backendreg.level = debug +logger.backendreg.appenderRef.capturing.ref = logCapturingAppender From 7cad5e40916bca1e423cffe530215b83ae6807c1 Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Tue, 22 Nov 2022 21:11:57 +0100 Subject: [PATCH 081/369] Test related to authentication with x.509 certificates. (#2227) Signed-off-by: Lukasz Soszynski --- .../http/CertificateAuthenticationTest.java | 145 ++++++++++++++++++ .../test/framework/RolesMapping.java | 69 +++++++++ .../test/framework/TestSecurityConfig.java | 18 ++- .../certificate/CertificateData.java | 18 ++- .../certificate/TestCertificates.java | 16 ++ .../test/framework/cluster/LocalCluster.java | 6 + .../cluster/OpenSearchClientProvider.java | 58 ++++++- .../framework/cluster/TestRestClient.java | 12 +- 8 files changed, 333 insertions(+), 9 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/http/CertificateAuthenticationTest.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java diff --git a/src/integrationTest/java/org/opensearch/security/http/CertificateAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/CertificateAuthenticationTest.java new file mode 100644 index 0000000000..18a79abcef --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/CertificateAuthenticationTest.java @@ -0,0 +1,145 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.util.List; +import java.util.Map; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.RolesMapping; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.HttpAuthenticator; +import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.certificate.CertificateData; +import org.opensearch.test.framework.certificate.TestCertificates; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; + +import static org.apache.hc.core5.http.HttpStatus.SC_OK; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasSize; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class CertificateAuthenticationTest { + + private static final User USER_ADMIN = new User("admin").roles(ALL_ACCESS); + + public static final String POINTER_BACKEND_ROLES = "/backend_roles"; + public static final String POINTER_ROLES = "/roles"; + + private static final String USER_SPOCK = "spock"; + private static final String USER_KIRK = "kirk"; + + private static final String BACKEND_ROLE_BRIDGE = "bridge"; + private static final String BACKEND_ROLE_CAPTAIN = "captain"; + + private static final Role ROLE_ALL_INDEX_SEARCH = new Role("all-index-search").indexPermissions("indices:data/read/search") + .on("*"); + + private static final Map CERT_AUTH_CONFIG = Map.of( + "username_attribute", "cn", + "roles_attribute", "ou" + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .nodeSettings(Map.of("plugins.security.ssl.http.clientauth_mode", "OPTIONAL")) + .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) + .authc(new AuthcDomain("clientcert_auth_domain", -1, true) + .httpAuthenticator(new HttpAuthenticator("clientcert").challenge(false) + .config(CERT_AUTH_CONFIG)).backend("noop")) + .authc(AUTHC_HTTPBASIC_INTERNAL).roles(ROLE_ALL_INDEX_SEARCH).users(USER_ADMIN) + .rolesMapping(new RolesMapping(ROLE_ALL_INDEX_SEARCH).backendRoles(BACKEND_ROLE_BRIDGE)).build(); + + private static final TestCertificates TEST_CERTIFICATES = cluster.getTestCertificates(); + + @Test + public void shouldAuthenticateUserWithBasicAuthWhenCertificateAuthenticationIsConfigured() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + } + + @Test + public void shouldAuthenticateUserWithCertificate_positiveUserSpoke() { + CertificateData userSpockCertificate = TEST_CERTIFICATES.issueUserCertificate(BACKEND_ROLE_BRIDGE, USER_SPOCK); + try (TestRestClient client = cluster.getRestClient(userSpockCertificate)) { + + client.assertCorrectCredentials(USER_SPOCK); + } + } + + @Test + public void shouldAuthenticateUserWithCertificate_positiveUserKirk() { + CertificateData userSpockCertificate = TEST_CERTIFICATES.issueUserCertificate(BACKEND_ROLE_BRIDGE, USER_KIRK); + try (TestRestClient client = cluster.getRestClient(userSpockCertificate)) { + + client.assertCorrectCredentials(USER_KIRK); + } + } + + @Test + public void shouldAuthenticateUserWithCertificate_negative() { + CertificateData untrustedUserCertificate = TEST_CERTIFICATES.createSelfSignedCertificate("CN=untrusted"); + try (TestRestClient client = cluster.getRestClient(untrustedUserCertificate)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + } + } + + @Test + public void shouldRetrieveBackendRoleFromCertificate_positiveRoleBridge() { + CertificateData userSpockCertificate = TEST_CERTIFICATES.issueUserCertificate(BACKEND_ROLE_BRIDGE, USER_KIRK); + try (TestRestClient client = cluster.getRestClient(userSpockCertificate)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, containsInAnyOrder(BACKEND_ROLE_BRIDGE)); + List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); + assertThat(roles, hasSize(1)); + assertThat(roles, containsInAnyOrder(ROLE_ALL_INDEX_SEARCH.getName())); + } + } + + @Test + public void shouldRetrieveBackendRoleFromCertificate_positiveRoleCaptain() { + CertificateData userSpockCertificate = TEST_CERTIFICATES.issueUserCertificate(BACKEND_ROLE_CAPTAIN, USER_KIRK); + try (TestRestClient client = cluster.getRestClient(userSpockCertificate)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, containsInAnyOrder(BACKEND_ROLE_CAPTAIN)); + List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); + assertThat(roles, hasSize(0)); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java b/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java new file mode 100644 index 0000000000..d66a55eb36 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java @@ -0,0 +1,69 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.test.framework.TestSecurityConfig.Role; + +import static java.util.Objects.requireNonNull; + +public class RolesMapping implements ToXContentObject { + private String roleName; + private List backendRoles; + private List hosts; + private List users; + + private boolean reserved = false; + + public RolesMapping(Role role) { + requireNonNull(role); + this.roleName = requireNonNull(role.getName()); + this.backendRoles = new ArrayList<>(); + } + + public RolesMapping backendRoles(String...backendRoles) { + this.backendRoles.addAll(Arrays.asList(backendRoles)); + return this; + } + + public RolesMapping hosts(List hosts) { + this.hosts = hosts; + return this; + } + + public RolesMapping users(List users) { + this.users = users; + return this; + } + + public RolesMapping reserved(boolean reserved) { + this.reserved = reserved; + return this; + } + + public String getRoleName() { + return roleName; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("reserved", reserved); + xContentBuilder.field("backend_roles", backendRoles); + xContentBuilder.endObject(); + return xContentBuilder; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index f8dc90a947..fb10cdb2f4 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -81,8 +81,8 @@ public class TestSecurityConfig { private Config config = new Config(); private Map internalUsers = new LinkedHashMap<>(); private Map roles = new LinkedHashMap<>(); - private AuditConfiguration auditConfiguration; + private Map rolesMapping = new LinkedHashMap<>(); private String indexName = ".opendistro_security"; @@ -126,6 +126,9 @@ public TestSecurityConfig user(User user) { public TestSecurityConfig roles(Role... roles) { for (Role role : roles) { + if(this.roles.containsKey(role.name)) { + throw new IllegalStateException("Role with name " + role.name + " is already defined"); + } this.roles.put(role.name, role); } @@ -137,6 +140,17 @@ public TestSecurityConfig audit(AuditConfiguration auditConfiguration) { return this; } + public TestSecurityConfig rolesMapping(RolesMapping...mappings) { + for (RolesMapping mapping : mappings) { + String roleName = mapping.getRoleName(); + if(rolesMapping.containsKey(roleName)) { + throw new IllegalArgumentException("Role mapping " + roleName + " already exists"); + } + this.rolesMapping.put(roleName, mapping); + } + return this; + } + public static class Config implements ToXContentObject { private boolean anonymousAuth; @@ -545,7 +559,7 @@ public void initIndex(Client client) { } writeConfigToIndex(client, CType.ROLES, roles); writeConfigToIndex(client, CType.INTERNALUSERS, internalUsers); - writeEmptyConfigToIndex(client, CType.ROLESMAPPING); + writeConfigToIndex(client, CType.ROLESMAPPING, rolesMapping); writeEmptyConfigToIndex(client, CType.ACTIONGROUPS); writeEmptyConfigToIndex(client, CType.TENANTS); diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateData.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateData.java index 52aab926dc..3712bd1763 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateData.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateData.java @@ -26,15 +26,19 @@ package org.opensearch.test.framework.certificate; +import java.security.Key; import java.security.KeyPair; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; /** * The class contains all data related to Certificate including private key which is considered to be a secret. */ -class CertificateData { +public class CertificateData { private final X509CertificateHolder certificate; private final KeyPair keyPair; @@ -53,6 +57,14 @@ public String certificateInPemFormat() { return PemConverter.toPem(certificate); } + public X509Certificate certificate() { + try { + return new JcaX509CertificateConverter().getCertificate(certificate); + } catch (CertificateException e) { + throw new RuntimeException("Cannot retrieve certificate", e); + } + } + /** * It returns the private key associated with certificate encoded in PEM format. PEM format is defined by * RFC 1421. @@ -70,4 +82,8 @@ X500Name getCertificateSubject() { KeyPair getKeyPair() { return keyPair; } + + public Key getKey() { + return keyPair.getPrivate(); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java index c770def8a8..93cbce5c84 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java @@ -93,6 +93,13 @@ private CertificateData createAdminCertificate() { .issueSelfSignedCertificate(metadata); } + public CertificateData createSelfSignedCertificate(String distinguishedName) { + CertificateMetadata metadata = CertificateMetadata.basicMetadata(distinguishedName, CERTIFICATE_VALIDITY_DAYS); + return CertificatesIssuerFactory + .rsaBaseCertificateIssuer() + .issueSelfSignedCertificate(metadata); + } + /** * It returns the most trusted certificate. Certificates for nodes and users are derived from this certificate. * @return file which contains certificate in PEM format, defined by RFC 1421 @@ -131,6 +138,15 @@ private CertificateData createNodeCertificate(Integer node) { .issueSignedCertificate(metadata, caCertificate); } + public CertificateData issueUserCertificate(String organizationUnit, String username) { + String subject = String.format("DC=de,L=test,O=users,OU=%s,CN=%s", organizationUnit, username); + CertificateMetadata metadata = CertificateMetadata.basicMetadata(subject, CERTIFICATE_VALIDITY_DAYS) + .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH); + return CertificatesIssuerFactory + .rsaBaseCertificateIssuer() + .issueSignedCertificate(metadata, caCertificate); + } + /** * It returns private key associated with node certificate returned by method {@link #getNodeCertificate(int)} * diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index d4ed05db45..aa5768e523 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -50,6 +50,7 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.test.framework.AuditConfiguration; import org.opensearch.test.framework.AuthFailureListeners; +import org.opensearch.test.framework.RolesMapping; import org.opensearch.test.framework.TestIndex; import org.opensearch.test.framework.TestSecurityConfig; import org.opensearch.test.framework.TestSecurityConfig.Role; @@ -365,6 +366,11 @@ public Builder roles(Role... roles) { return this; } + public Builder rolesMapping(RolesMapping...mappings) { + testSecurityConfig.rolesMapping(mappings); + return this; + } + public Builder authc(TestSecurityConfig.AuthcDomain authc) { testSecurityConfig.authc(authc); return this; diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java index 7e23f35fd5..54a13630be 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -28,15 +28,21 @@ package org.opensearch.test.framework.cluster; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.security.KeyStore; +import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManagerFactory; @@ -59,8 +65,11 @@ import org.opensearch.client.RestClientBuilder; import org.opensearch.client.RestHighLevelClient; import org.opensearch.security.support.PemKeyReader; +import org.opensearch.test.framework.certificate.CertificateData; import org.opensearch.test.framework.certificate.TestCertificates; +import static org.opensearch.test.framework.cluster.TestRestClientConfiguration.getBasicAuthHeader; + /** * OpenSearchClientProvider provides methods to get a REST client for an underlying cluster or node. * @@ -90,8 +99,12 @@ default URI getHttpAddressAsURI() { * This method should be usually preferred. The other getRestClient() methods shall be only used for specific * situations. */ + default TestRestClient getRestClient(UserCredentialsHolder user, CertificateData useCertificateData, Header... headers) { + return getRestClient(user.getName(), user.getPassword(), useCertificateData, headers); + } + default TestRestClient getRestClient(UserCredentialsHolder user, Header... headers) { - return getRestClient(user.getName(), user.getPassword(), headers); + return getRestClient(user.getName(), user.getPassword(), null, headers); } default RestHighLevelClient getRestHighLevelClient(UserCredentialsHolder user) { @@ -157,17 +170,39 @@ default CloseableHttpClient getClosableHttpClient(String[] supportedCipherSuit) default TestRestClient getRestClient(String user, String password, Header... headers) { return createGenericClientRestClient(new TestRestClientConfiguration().username(user).password(password).headers(headers)); } - + default TestRestClient getRestClient(String user, String password, CertificateData useCertificateData, Header... headers) { + Header basicAuthHeader = getBasicAuthHeader(user, password); + if (headers != null && headers.length > 0) { + List
concatenatedHeaders = Stream.concat(Stream.of(basicAuthHeader), Stream.of(headers)).collect(Collectors.toList()); + return getRestClient(concatenatedHeaders, useCertificateData); + } + return getRestClient(useCertificateData, basicAuthHeader); + } /** * Returns a REST client. You can specify additional HTTP headers that will be sent with each request. Use this * method to test non-basic authentication, such as JWT bearer authentication. */ + default TestRestClient getRestClient(CertificateData useCertificateData, Header... headers) { + return getRestClient(Arrays.asList(headers), useCertificateData); + } + default TestRestClient getRestClient(Header... headers) { - return getRestClient(Arrays.asList(headers)); + return getRestClient((CertificateData) null, headers); } + default TestRestClient getRestClient(List
headers) { return createGenericClientRestClient(new TestRestClientConfiguration().headers(headers)); + + } + + default TestRestClient getRestClient(List
headers, CertificateData useCertificateData) { + return createGenericClientRestClient(headers, useCertificateData, null); + } + + default TestRestClient createGenericClientRestClient(List
headers, CertificateData useCertificateData, + InetAddress sourceInetAddress) { + return new TestRestClient(getHttpAddress(), headers, getSSLContext(useCertificateData), sourceInetAddress); } default TestRestClient createGenericClientRestClient(TestRestClientConfiguration configuration) { @@ -175,24 +210,37 @@ default TestRestClient createGenericClientRestClient(TestRestClientConfiguration } private SSLContext getSSLContext() { + return getSSLContext(null); + } + + private SSLContext getSSLContext(CertificateData useCertificateData) { X509Certificate[] trustCertificates; try { trustCertificates = PemKeyReader.loadCertificatesFromFile(getTestCertificates().getRootCertificate() ); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(null); for (int i = 0; i < trustCertificates.length; i++) { ks.setCertificateEntry("caCert-" + i, trustCertificates[i]); } + KeyManager[] keyManagers = null; + if(useCertificateData != null) { + Certificate[] chainOfTrust = {useCertificateData.certificate()}; + ks.setKeyEntry("admin-certificate", useCertificateData.getKey(), null, chainOfTrust); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(ks, null); + keyManagers = keyManagerFactory.getKeyManagers(); + } tmf.init(ks); SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, tmf.getTrustManagers(), null); + + sslContext.init(keyManagers, tmf.getTrustManagers(), null); return sslContext; } catch (Exception e) { diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java index e8d704eb4a..cf6565ea12 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -77,6 +77,7 @@ import static java.util.Objects.requireNonNull; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; /** * A OpenSearch REST client, which is tailored towards use in integration tests. Instances of this class can be @@ -123,6 +124,15 @@ public HttpResponse getAuthInfo( Header... headers) { return executeRequest(new HttpGet(getHttpServerUri() + "/_opendistro/_security/authinfo?pretty"), headers); } + public void assertCorrectCredentials(String expectedUserName) { + HttpResponse response = getAuthInfo(); + assertThat(response, notNullValue()); + response.assertStatusCode(200); + String username = response.getTextFromJsonBody("/user_name"); + String message = String.format("Expected user name is '%s', but was '%s'", expectedUserName, username); + assertThat(message, username, equalTo(expectedUserName)); + } + public HttpResponse head(String path, Header... headers) { return executeRequest(new HttpHead(getHttpServerUri() + "/" + path), headers); } @@ -321,7 +331,7 @@ public List getTextArrayFromJsonBody(String jsonPointer) { .map(JsonNode::textValue) .collect(Collectors.toList()); } - + public int getIntFromJsonBody(String jsonPointer) { return getJsonNodeAt(jsonPointer).asInt(); } From 0b3c993acc1a89e5535f8a9f8ea0b8008905f9f2 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Mon, 28 Nov 2022 15:14:47 -0500 Subject: [PATCH 082/369] Add sub-section on System Index protection under Onboarding new APIs to README (#2272) Signed-off-by: Craig Perkins --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 120523c2b0..eb5ccb4fa4 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,22 @@ It is common practice to create new transport actions to perform different tasks 2. Register the action in the [OpenSearch Security plugin](https://github.com/opensearch-project/security). Each new action is registered in the plugin as a new permission. Usually, plugins will define different roles for their plugin (e.g., read-only access, write access). Each role will contain a set of permissions. An example of adding a new permission to the `anomaly_read_access` role for the [Anomaly Detection plugin](https://github.com/opensearch-project/anomaly-detection) can be found in [this PR](https://github.com/opensearch-project/security/pull/997/files). 3. Register the action in the [OpenSearch Dashboards Security plugin](https://github.com/opensearch-project/security-dashboards-plugin). This plugin maintains the full list of possible permissions, so users can see all of them when creating new roles or searching permissions via Dashboards. An example of adding different permissions can be found in [this PR](https://github.com/opensearch-project/security-dashboards-plugin/pull/689/files). + +### System Index Protection + +The Security Plugin provides protection to system indices used by plugins. The system index names must be explicitly registered in `opensearch.yml` under the `plugins.security.system_indices.indices` setting. See below for an example setup of system index protection from the demo configuration: + +``` +plugins.security.system_indices.enabled: true +plugins.security.system_indices.indices: [".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".opendistro-asynchronous-search-response*", ".replication-metadata-store"] +``` + +The demo configuration can be modified in the following files to add a new system index to the demo configuration: + +- https://github.com/opensearch-project/security/blob/main/tools/install_demo_configuration.sh +- https://github.com/opensearch-project/security/blob/main/tools/install_demo_configuration.bat + + ## Contributing See [developer guide](DEVELOPER_GUIDE.md) and [how to contribute to this project](CONTRIBUTING.md). From a57880399d5b16c9d36d34ae16d955ef17f51582 Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Mon, 28 Nov 2022 21:29:51 +0100 Subject: [PATCH 083/369] LDAP authentication tests. (#2212) Adds LDAP authentication integration tests Signed-off-by: Lukasz Soszynski --- build.gradle | 1 + .../http/DirectoryInformationTrees.java | 124 ++++++ .../security/http/LdapAuthenticationTest.java | 112 +++++ .../http/LdapStartTlsAuthenticationTest.java | 109 +++++ .../http/LdapTlsAuthenticationTest.java | 399 ++++++++++++++++++ .../UntrustedLdapServerCertificateTest.java | 97 +++++ .../test/framework/AuthorizationBackend.java | 45 ++ .../test/framework/AuthzDomain.java | 70 +++ .../LdapAuthenticationConfigBuilder.java | 119 ++++++ .../LdapAuthorizationConfigBuilder.java | 75 ++++ .../test/framework/RolesMapping.java | 12 +- .../test/framework/TestSecurityConfig.java | 27 +- .../certificate/TestCertificates.java | 37 +- .../test/framework/cluster/LocalCluster.java | 14 +- .../cluster/OpenSearchClientProvider.java | 31 +- .../framework/ldap/EmbeddedLDAPServer.java | 56 +++ .../test/framework/ldap/LdapServer.java | 224 ++++++++++ .../test/framework/ldap/LdifBuilder.java | 68 +++ .../test/framework/ldap/LdifData.java | 49 +++ .../test/framework/ldap/Record.java | 68 +++ .../test/framework/ldap/RecordBuilder.java | 92 ++++ .../framework/log/LogCapturingAppender.java | 13 +- .../test/framework/log/LogMessage.java | 40 ++ .../test/framework/log/LogsRule.java | 18 +- .../resources/log4j2-test.properties | 7 + 25 files changed, 1875 insertions(+), 32 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/http/DirectoryInformationTrees.java create mode 100644 src/integrationTest/java/org/opensearch/security/http/LdapAuthenticationTest.java create mode 100644 src/integrationTest/java/org/opensearch/security/http/LdapStartTlsAuthenticationTest.java create mode 100644 src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java create mode 100644 src/integrationTest/java/org/opensearch/security/http/UntrustedLdapServerCertificateTest.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/AuthorizationBackend.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/AuthzDomain.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/LdapAuthenticationConfigBuilder.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/LdapAuthorizationConfigBuilder.java create mode 100755 src/integrationTest/java/org/opensearch/test/framework/ldap/EmbeddedLDAPServer.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/ldap/LdapServer.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/ldap/LdifBuilder.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/ldap/LdifData.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/ldap/Record.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/ldap/RecordBuilder.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/log/LogMessage.java diff --git a/build.gradle b/build.gradle index 927124723c..c4e35bbff5 100644 --- a/build.gradle +++ b/build.gradle @@ -450,6 +450,7 @@ dependencies { integrationTestImplementation('org.awaitility:awaitility:4.2.0') { exclude(group: 'org.hamcrest', module: 'hamcrest') } + integrationTestImplementation 'com.unboundid:unboundid-ldapsdk:4.0.9' } jar { diff --git a/src/integrationTest/java/org/opensearch/security/http/DirectoryInformationTrees.java b/src/integrationTest/java/org/opensearch/security/http/DirectoryInformationTrees.java new file mode 100644 index 0000000000..8403106522 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/DirectoryInformationTrees.java @@ -0,0 +1,124 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import org.opensearch.test.framework.ldap.LdifBuilder; +import org.opensearch.test.framework.ldap.LdifData; + +class DirectoryInformationTrees { + + public static final String DN_PEOPLE_TEST_ORG = "ou=people,o=test.org"; + public static final String DN_OPEN_SEARCH_PEOPLE_TEST_ORG = "cn=Open Search,ou=people,o=test.org"; + public static final String DN_CHRISTPHER_PEOPLE_TEST_ORG = "cn=Christpher,ou=people,o=test.org"; + public static final String DN_KIRK_PEOPLE_TEST_ORG = "cn=Kirk,ou=people,o=test.org"; + public static final String DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG = "cn=Captain Spock,ou=people,o=test.org"; + public static final String DN_LEONARD_PEOPLE_TEST_ORG = "cn=Leonard,ou=people,o=test.org"; + public static final String DN_JEAN_PEOPLE_TEST_ORG = "cn=Jean,ou=people,o=test.org"; + public static final String DN_GROUPS_TEST_ORG = "ou=groups,o=test.org"; + public static final String DN_BRIDGE_GROUPS_TEST_ORG = "cn=bridge,ou=groups,o=test.org"; + + public static final String USER_KIRK = "kirk"; + public static final String PASSWORD_KIRK = "kirk-secret"; + public static final String USER_SPOCK = "spock"; + public static final String PASSWORD_SPOCK = "spocksecret"; + public static final String USER_OPENS = "opens"; + public static final String PASSWORD_OPEN_SEARCH = "open_search-secret"; + public static final String USER_JEAN = "jean"; + public static final String PASSWORD_JEAN = "jeansecret"; + public static final String USER_LEONARD = "leonard"; + public static final String PASSWORD_LEONARD = "Leonard-secret"; + public static final String PASSWORD_CHRISTPHER = "christpher_secret"; + + public static final String CN_GROUP_ADMIN = "admin"; + public static final String CN_GROUP_CREW = "crew"; + public static final String CN_GROUP_BRIDGE = "bridge"; + + public static final String USER_SEARCH = "(uid={0})"; + public static final String USERNAME_ATTRIBUTE = "uid"; + + static final LdifData LDIF_DATA = new LdifBuilder() + .root("o=test.org") + .dc("TEST") + .classes("top", "domain") + .newRecord(DN_PEOPLE_TEST_ORG) + .ou("people") + .classes("organizationalUnit", "top") + .newRecord(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) + .classes("inetOrgPerson") + .cn("Open Search") + .sn("Search") + .uid(USER_OPENS) + .userPassword(PASSWORD_OPEN_SEARCH) + .mail("open.search@example.com") + .ou("Human Resources") + .newRecord(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) + .classes("inetOrgPerson") + .cn("Captain Spock") + .sn(USER_SPOCK) + .uid(USER_SPOCK) + .userPassword(PASSWORD_SPOCK) + .mail("spock@example.com") + .ou("Human Resources") + .newRecord(DN_KIRK_PEOPLE_TEST_ORG) + .classes("inetOrgPerson") + .cn("Kirk") + .sn("Kirk") + .uid(USER_KIRK) + .userPassword(PASSWORD_KIRK) + .mail("spock@example.com") + .ou("Human Resources") + .newRecord(DN_CHRISTPHER_PEOPLE_TEST_ORG) + .classes("inetOrgPerson") + .cn("Christpher") + .sn("Christpher") + .uid("christpher") + .userPassword(PASSWORD_CHRISTPHER) + .mail("christpher@example.com") + .ou("Human Resources") + .newRecord(DN_LEONARD_PEOPLE_TEST_ORG) + .classes("inetOrgPerson") + .cn("Leonard") + .sn("Leonard") + .uid(USER_LEONARD) + .userPassword(PASSWORD_LEONARD) + .mail("leonard@example.com") + .ou("Human Resources") + .newRecord(DN_JEAN_PEOPLE_TEST_ORG) + .classes("inetOrgPerson") + .cn("Jean") + .sn("Jean") + .uid(USER_JEAN) + .userPassword(PASSWORD_JEAN) + .mail("jean@example.com") + .ou("Human Resources") + .newRecord(DN_GROUPS_TEST_ORG) + .ou("groups") + .cn("groupsRoot") + .classes("groupofuniquenames", "top") + .newRecord("cn=admin,ou=groups,o=test.org") + .ou("groups") + .cn(CN_GROUP_ADMIN) + .uniqueMember(DN_KIRK_PEOPLE_TEST_ORG) + .classes("groupofuniquenames", "top") + .newRecord("cn=crew,ou=groups,o=test.org") + .ou("groups") + .cn(CN_GROUP_CREW) + .uniqueMember(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) + .uniqueMember(DN_CHRISTPHER_PEOPLE_TEST_ORG) + .uniqueMember(DN_BRIDGE_GROUPS_TEST_ORG) + .classes("groupofuniquenames", "top") + .newRecord(DN_BRIDGE_GROUPS_TEST_ORG) + .ou("groups") + .cn(CN_GROUP_BRIDGE) + .uniqueMember(DN_JEAN_PEOPLE_TEST_ORG) + .classes("groupofuniquenames", "top") + .buildRecord() + .buildLdif(); +} diff --git a/src/integrationTest/java/org/opensearch/security/http/LdapAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/LdapAuthenticationTest.java new file mode 100644 index 0000000000..f6c85165d2 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/LdapAuthenticationTest.java @@ -0,0 +1,112 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.util.List; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.LdapAuthenticationConfigBuilder; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AuthenticationBackend; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.HttpAuthenticator; +import org.opensearch.test.framework.certificate.TestCertificates; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.ldap.EmbeddedLDAPServer; +import org.opensearch.test.framework.log.LogsRule; + +import static org.opensearch.security.http.DirectoryInformationTrees.DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG; +import static org.opensearch.security.http.DirectoryInformationTrees.DN_OPEN_SEARCH_PEOPLE_TEST_ORG; +import static org.opensearch.security.http.DirectoryInformationTrees.DN_PEOPLE_TEST_ORG; +import static org.opensearch.security.http.DirectoryInformationTrees.LDIF_DATA; +import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_OPEN_SEARCH; +import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_SPOCK; +import static org.opensearch.security.http.DirectoryInformationTrees.USERNAME_ATTRIBUTE; +import static org.opensearch.security.http.DirectoryInformationTrees.USER_SEARCH; +import static org.opensearch.security.http.DirectoryInformationTrees.USER_SPOCK; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.BASIC_AUTH_DOMAIN_ORDER; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; + +/** +* Test uses plain (non TLS) connection between OpenSearch and LDAP server. +*/ +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class LdapAuthenticationTest { + + private static final Logger log = LogManager.getLogger(LdapAuthenticationTest.class); + + private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); + + public static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer(TEST_CERTIFICATES.getRootCertificateData(), + TEST_CERTIFICATES.getLdapCertificateData(), LDIF_DATA); + + public static LocalCluster cluster = new LocalCluster.Builder() + .testCertificates(TEST_CERTIFICATES) + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) + .authc(new AuthcDomain("ldap", BASIC_AUTH_DOMAIN_ORDER + 1, true) + .httpAuthenticator(new HttpAuthenticator("basic").challenge(false)) + .backend(new AuthenticationBackend("ldap") + .config(() -> LdapAuthenticationConfigBuilder.config() + // this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used to postpone + // execution of the code in this block. + .enableSsl(false) + .enableStartTls(false) + .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapNonTlsPort())) + .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) + .password(PASSWORD_OPEN_SEARCH) + .userBase(DN_PEOPLE_TEST_ORG) + .userSearch(USER_SEARCH) + .usernameAttribute(USERNAME_ATTRIBUTE) + .build()))) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER) + .build(); + + @ClassRule + public static RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster); + + @Rule + public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); + + @Test + public void shouldAuthenticateUserWithLdap_positive() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_negativeWhenIncorrectPassword() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, "incorrect password")) { + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + String expectedStackTraceFragment = "Unable to bind as user '".concat(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) + .concat("' because the provided password was incorrect."); + logsRule.assertThatStackTraceContain(expectedStackTraceFragment); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/http/LdapStartTlsAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/LdapStartTlsAuthenticationTest.java new file mode 100644 index 0000000000..6a117a8b5a --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/LdapStartTlsAuthenticationTest.java @@ -0,0 +1,109 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.util.List; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.LdapAuthenticationConfigBuilder; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AuthenticationBackend; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.HttpAuthenticator; +import org.opensearch.test.framework.certificate.TestCertificates; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.ldap.EmbeddedLDAPServer; +import org.opensearch.test.framework.log.LogsRule; + +import static org.opensearch.security.http.DirectoryInformationTrees.DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG; +import static org.opensearch.security.http.DirectoryInformationTrees.DN_OPEN_SEARCH_PEOPLE_TEST_ORG; +import static org.opensearch.security.http.DirectoryInformationTrees.DN_PEOPLE_TEST_ORG; +import static org.opensearch.security.http.DirectoryInformationTrees.LDIF_DATA; +import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_OPEN_SEARCH; +import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_SPOCK; +import static org.opensearch.security.http.DirectoryInformationTrees.USERNAME_ATTRIBUTE; +import static org.opensearch.security.http.DirectoryInformationTrees.USER_SEARCH; +import static org.opensearch.security.http.DirectoryInformationTrees.USER_SPOCK; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.BASIC_AUTH_DOMAIN_ORDER; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; + +/** +* Test initiates plain (non-TLS) connection between OpenSearch and LDAP server and then in the course of the test connection is upgraded +* to TLS. +*/ +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class LdapStartTlsAuthenticationTest { + + private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); + + public static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer(TEST_CERTIFICATES.getRootCertificateData(), + TEST_CERTIFICATES.getLdapCertificateData(), LDIF_DATA); + + public static LocalCluster cluster = new LocalCluster.Builder() + .testCertificates(TEST_CERTIFICATES) + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) + .authc(new AuthcDomain("ldap-config-id", BASIC_AUTH_DOMAIN_ORDER + 1, true) + .httpAuthenticator(new HttpAuthenticator("basic").challenge(false)) + .backend(new AuthenticationBackend("ldap") + .config(() -> LdapAuthenticationConfigBuilder.config() + // this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used + .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapNonTlsPort())) + .enableSsl(false) + .enableStartTls(true) + .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) + .password(PASSWORD_OPEN_SEARCH) + .userBase(DN_PEOPLE_TEST_ORG) + .userSearch(USER_SEARCH) + .usernameAttribute(USERNAME_ATTRIBUTE) + .penTrustedCasFilePath(TEST_CERTIFICATES.getRootCertificate().getAbsolutePath()) + .build()))) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER) + .build(); + + @ClassRule + public static RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster); + + @Rule + public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); + + @Test + public void shouldAuthenticateUserWithLdap_positive() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_negativeWhenIncorrectPassword() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, "incorrect password")) { + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + String expectedStackTraceFragment = "Unable to bind as user '".concat(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) + .concat("' because the provided password was incorrect."); + logsRule.assertThatStackTraceContain(expectedStackTraceFragment); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java new file mode 100644 index 0000000000..ce51e2f920 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java @@ -0,0 +1,399 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.hc.core5.http.message.BasicHeader; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; + +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.client.Client; +import org.opensearch.client.RestHighLevelClient; +import org.opensearch.test.framework.AuthorizationBackend; +import org.opensearch.test.framework.AuthzDomain; +import org.opensearch.test.framework.LdapAuthenticationConfigBuilder; +import org.opensearch.test.framework.LdapAuthorizationConfigBuilder; +import org.opensearch.test.framework.RolesMapping; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AuthenticationBackend; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.HttpAuthenticator; +import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.certificate.TestCertificates; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; +import org.opensearch.test.framework.ldap.EmbeddedLDAPServer; +import org.opensearch.test.framework.log.LogsRule; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; +import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.opensearch.client.RequestOptions.DEFAULT; +import static org.opensearch.rest.RestStatus.FORBIDDEN; +import static org.opensearch.security.Song.SONGS; +import static org.opensearch.security.http.DirectoryInformationTrees.CN_GROUP_ADMIN; +import static org.opensearch.security.http.DirectoryInformationTrees.CN_GROUP_BRIDGE; +import static org.opensearch.security.http.DirectoryInformationTrees.CN_GROUP_CREW; +import static org.opensearch.security.http.DirectoryInformationTrees.DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG; +import static org.opensearch.security.http.DirectoryInformationTrees.DN_GROUPS_TEST_ORG; +import static org.opensearch.security.http.DirectoryInformationTrees.DN_OPEN_SEARCH_PEOPLE_TEST_ORG; +import static org.opensearch.security.http.DirectoryInformationTrees.DN_PEOPLE_TEST_ORG; +import static org.opensearch.security.http.DirectoryInformationTrees.LDIF_DATA; +import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_JEAN; +import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_KIRK; +import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_LEONARD; +import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_OPEN_SEARCH; +import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_SPOCK; +import static org.opensearch.security.http.DirectoryInformationTrees.USERNAME_ATTRIBUTE; +import static org.opensearch.security.http.DirectoryInformationTrees.USER_JEAN; +import static org.opensearch.security.http.DirectoryInformationTrees.USER_KIRK; +import static org.opensearch.security.http.DirectoryInformationTrees.USER_LEONARD; +import static org.opensearch.security.http.DirectoryInformationTrees.USER_SEARCH; +import static org.opensearch.security.http.DirectoryInformationTrees.USER_SPOCK; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.BASIC_AUTH_DOMAIN_ORDER; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.queryStringQueryRequest; +import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy; +import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.statusException; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.isSuccessfulSearchResponse; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.numberOfTotalHitsIsEqualTo; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitsContainDocumentWithId; + +/** +* Test uses plain TLS connection between OpenSearch and LDAP server. +*/ +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class LdapTlsAuthenticationTest { + + private static final String SONG_INDEX_NAME = "song_lyrics"; + + private static final String HEADER_NAME_IMPERSONATE = "opendistro_security_impersonate_as"; + + private static final String PERSONAL_INDEX_NAME_SPOCK = "personal-" + USER_SPOCK; + private static final String PERSONAL_INDEX_NAME_KIRK = "personal-" + USER_KIRK; + + private static final String POINTER_BACKEND_ROLES = "/backend_roles"; + private static final String POINTER_ROLES = "/roles"; + private static final String POINTER_USERNAME = "/user_name"; + private static final String POINTER_ERROR_REASON = "/error/reason"; + + private static final String SONG_ID_1 = "l0001"; + private static final String SONG_ID_2 = "l0002"; + private static final String SONG_ID_3 = "l0003"; + + private static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS); + + private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); + + private static final Role ROLE_INDEX_ADMINISTRATOR = new Role("index_administrator").indexPermissions("*").on("*"); + private static final Role ROLE_PERSONAL_INDEX_ACCESS = new Role("personal_index_access").indexPermissions("*").on("personal-${attr.ldap.uid}"); + + private static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer(TEST_CERTIFICATES.getRootCertificateData(), + TEST_CERTIFICATES.getLdapCertificateData(), LDIF_DATA); + + private static final Map USER_IMPERSONATION_CONFIGURATION = Map.of( + "plugins.security.authcz.rest_impersonation_user." + USER_KIRK, List.of(USER_SPOCK) + ); + + private static final LocalCluster cluster = new LocalCluster.Builder() + .testCertificates(TEST_CERTIFICATES) + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) + .nodeSettings(USER_IMPERSONATION_CONFIGURATION) + .authc(new AuthcDomain("ldap", BASIC_AUTH_DOMAIN_ORDER + 1, true) + .httpAuthenticator(new HttpAuthenticator("basic").challenge(false)) + .backend(new AuthenticationBackend("ldap") + .config(() -> LdapAuthenticationConfigBuilder.config() + // this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used + .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapTlsPort())) + .enableSsl(true) + .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) + .password(PASSWORD_OPEN_SEARCH) + .userBase(DN_PEOPLE_TEST_ORG) + .userSearch(USER_SEARCH) + .usernameAttribute(USERNAME_ATTRIBUTE) + .penTrustedCasFilePath(TEST_CERTIFICATES.getRootCertificate().getAbsolutePath()) + .build()))) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER) + .roles(ROLE_INDEX_ADMINISTRATOR, ROLE_PERSONAL_INDEX_ACCESS) + .rolesMapping( + new RolesMapping(ROLE_INDEX_ADMINISTRATOR).backendRoles(CN_GROUP_ADMIN), + new RolesMapping(ROLE_PERSONAL_INDEX_ACCESS).backendRoles(CN_GROUP_CREW) + ) + .authz(new AuthzDomain("ldap_roles").httpEnabled(true).transportEnabled(true) + .authorizationBackend(new AuthorizationBackend("ldap") + .config(() -> new LdapAuthorizationConfigBuilder() + .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapTlsPort())) + .enableSsl(true) + .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) + .password(PASSWORD_OPEN_SEARCH) + .userBase(DN_PEOPLE_TEST_ORG) + .userSearch(USER_SEARCH) + .usernameAttribute(USERNAME_ATTRIBUTE) + .penTrustedCasFilePath(TEST_CERTIFICATES.getRootCertificate().getAbsolutePath()) + .roleBase(DN_GROUPS_TEST_ORG) + .roleSearch("(uniqueMember={0})") + .userRoleAttribute(null) + .userRoleName("disabled") + .roleName("cn") + .resolveNestedRoles(true) + .build()))) + .build(); + + @ClassRule + public static final RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster); + + @Rule + public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); + + @BeforeClass + public static void createTestData() { + try(Client client = cluster.getInternalNodeClient()){ + client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0]).get(); + client.prepareIndex(PERSONAL_INDEX_NAME_SPOCK).setId(SONG_ID_2).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1]).get(); + client.prepareIndex(PERSONAL_INDEX_NAME_KIRK).setId(SONG_ID_3).setRefreshPolicy(IMMEDIATE).setSource(SONGS[2]).get(); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_positiveSpockUser() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USER_SPOCK)); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_positiveKirkUser() { + try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USER_KIRK)); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_negativeWhenIncorrectPassword() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, "incorrect password")) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + String expectedStackTraceFragment = "Unable to bind as user '".concat(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) + .concat("' because the provided password was incorrect."); + logsRule.assertThatStackTraceContain(expectedStackTraceFragment); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_negativeWhenIncorrectUsername() { + final String username = "invalid-user-name"; + try (TestRestClient client = cluster.getRestClient(username, PASSWORD_SPOCK)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + logsRule.assertThatStackTraceContain(String.format("No user %s found", username)); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_negativeWhenUserDoesNotExist() { + final String username = "doesNotExist"; + try (TestRestClient client = cluster.getRestClient(username, "password")) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + logsRule.assertThatStackTraceContain(String.format("No user %s found", username)); + } + } + + @Test + public void shouldResolveUserRolesAgainstLdapBackend_positiveSpockUser() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, contains(CN_GROUP_CREW)); + assertThat(response.getTextArrayFromJsonBody(POINTER_ROLES), contains(ROLE_PERSONAL_INDEX_ACCESS.getName())); + } + } + + @Test + public void shouldResolveUserRolesAgainstLdapBackend_positiveKirkUser() { + try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + assertThat(response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES), contains(CN_GROUP_ADMIN)); + assertThat(response.getTextArrayFromJsonBody(POINTER_ROLES), contains(ROLE_INDEX_ADMINISTRATOR.getName())); + } + } + + @Test + public void shouldPerformAuthorizationAgainstLdapToAccessIndex_positive() throws IOException { + try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_KIRK, PASSWORD_KIRK)) { + SearchRequest request = queryStringQueryRequest(SONG_INDEX_NAME, "*"); + + SearchResponse searchResponse = client.search(request, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_1)); + } + } + + @Test + public void shouldPerformAuthorizationAgainstLdapToAccessIndex_negative() throws IOException { + try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_LEONARD, PASSWORD_LEONARD)) { + SearchRequest request = queryStringQueryRequest(SONG_INDEX_NAME, "*"); + + assertThatThrownBy(() -> client.search(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldResolveUserAttributesLoadedFromLdap_positive() throws IOException { + try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_SPOCK, PASSWORD_SPOCK)) { + SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_SPOCK, "*"); + + SearchResponse searchResponse = client.search(request, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, PERSONAL_INDEX_NAME_SPOCK, SONG_ID_2)); + } + } + + @Test + public void shouldResolveUserAttributesLoadedFromLdap_negative() throws IOException { + try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_SPOCK, PASSWORD_SPOCK)) { + SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_KIRK, "*"); + + assertThatThrownBy(() -> client.search(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldResolveNestedGroups_positive() { + try (TestRestClient client = cluster.getRestClient(USER_JEAN, PASSWORD_JEAN)) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(2)); + //CN_GROUP_CREW is retrieved recursively: cn=Jean,ou=people,o=test.org -> cn=bridge,ou=groups,o=test.org -> cn=crew,ou=groups,o=test.org + assertThat(backendRoles, containsInAnyOrder(CN_GROUP_CREW, CN_GROUP_BRIDGE)); + assertThat(response.getTextArrayFromJsonBody(POINTER_ROLES), contains(ROLE_PERSONAL_INDEX_ACCESS.getName())); + } + } + + @Test + public void shouldResolveNestedGroups_negative() { + try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, not(containsInAnyOrder(CN_GROUP_CREW))); + } + } + + @Test + public void shouldImpersonateUser_positive() { + try(TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)){ + + HttpResponse response = client.getAuthInfo(new BasicHeader(HEADER_NAME_IMPERSONATE, USER_SPOCK)); + + response.assertStatusCode(200); + assertThat(response.getTextFromJsonBody(POINTER_USERNAME), equalTo(USER_SPOCK)); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, contains(CN_GROUP_CREW)); + } + } + + @Test + public void shouldImpersonateUser_negativeJean() { + try(TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)){ + + HttpResponse response = client.getAuthInfo(new BasicHeader(HEADER_NAME_IMPERSONATE, USER_JEAN)); + + response.assertStatusCode(403); + String expectedMessage = String.format("'%s' is not allowed to impersonate as '%s'", USER_KIRK, USER_JEAN); + assertThat(response.getTextFromJsonBody(POINTER_ERROR_REASON), equalTo(expectedMessage)); + } + } + + @Test + public void shouldImpersonateUser_negativeKirk() { + try(TestRestClient client = cluster.getRestClient(USER_JEAN, PASSWORD_JEAN)){ + + HttpResponse response = client.getAuthInfo(new BasicHeader(HEADER_NAME_IMPERSONATE, USER_KIRK)); + + response.assertStatusCode(403); + String expectedMessage = String.format("'%s' is not allowed to impersonate as '%s'", USER_JEAN, USER_KIRK); + assertThat(response.getTextFromJsonBody(POINTER_ERROR_REASON), equalTo(expectedMessage)); + } + } + + @Test + public void shouldAccessImpersonatedUserPersonalIndex_positive() throws IOException { + BasicHeader impersonateHeader = new BasicHeader(HEADER_NAME_IMPERSONATE, USER_SPOCK); + try(RestHighLevelClient client = cluster.getRestHighLevelClient(USER_KIRK, PASSWORD_KIRK, impersonateHeader)){ + SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_SPOCK, "*"); + + SearchResponse searchResponse = client.search(request, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, PERSONAL_INDEX_NAME_SPOCK, SONG_ID_2)); + } + } + + @Test + public void shouldAccessImpersonatedUserPersonalIndex_negative() throws IOException { + BasicHeader impersonateHeader = new BasicHeader(HEADER_NAME_IMPERSONATE, USER_SPOCK); + try(RestHighLevelClient client = cluster.getRestHighLevelClient(USER_KIRK, PASSWORD_KIRK, impersonateHeader)){ + SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_KIRK, "*"); + + assertThatThrownBy(() -> client.search(request, DEFAULT), statusException(FORBIDDEN)); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/http/UntrustedLdapServerCertificateTest.java b/src/integrationTest/java/org/opensearch/security/http/UntrustedLdapServerCertificateTest.java new file mode 100644 index 0000000000..87a55f4757 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/UntrustedLdapServerCertificateTest.java @@ -0,0 +1,97 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.util.List; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.LdapAuthenticationConfigBuilder; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AuthenticationBackend; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.HttpAuthenticator; +import org.opensearch.test.framework.certificate.TestCertificates; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.ldap.EmbeddedLDAPServer; +import org.opensearch.test.framework.log.LogsRule; + +import static org.opensearch.security.http.DirectoryInformationTrees.DN_OPEN_SEARCH_PEOPLE_TEST_ORG; +import static org.opensearch.security.http.DirectoryInformationTrees.DN_PEOPLE_TEST_ORG; +import static org.opensearch.security.http.DirectoryInformationTrees.LDIF_DATA; +import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_OPEN_SEARCH; +import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_SPOCK; +import static org.opensearch.security.http.DirectoryInformationTrees.USERNAME_ATTRIBUTE; +import static org.opensearch.security.http.DirectoryInformationTrees.USER_SEARCH; +import static org.opensearch.security.http.DirectoryInformationTrees.USER_SPOCK; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.BASIC_AUTH_DOMAIN_ORDER; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; + +/** +* Negative test case related to LDAP server certificate. Connection between OpenSearch and LDAP server should not be established if +* OpenSearch "does not trust" LDAP server certificate. +*/ +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class UntrustedLdapServerCertificateTest { + + private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); + + public static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer(TEST_CERTIFICATES.getRootCertificateData(), + TEST_CERTIFICATES.createSelfSignedCertificate("CN=untrusted"), LDIF_DATA); + + public static LocalCluster cluster = new LocalCluster.Builder() + .testCertificates(TEST_CERTIFICATES) + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) + .authc(new AuthcDomain("ldap", BASIC_AUTH_DOMAIN_ORDER + 1, true) + .httpAuthenticator(new HttpAuthenticator("basic").challenge(false)) + .backend(new AuthenticationBackend("ldap") + .config(() -> LdapAuthenticationConfigBuilder.config() + // this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used + .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapTlsPort())) + .enableSsl(true) + .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) + .password(PASSWORD_OPEN_SEARCH) + .userBase(DN_PEOPLE_TEST_ORG) + .userSearch(USER_SEARCH) + .usernameAttribute(USERNAME_ATTRIBUTE) + .penTrustedCasFilePath(TEST_CERTIFICATES.getRootCertificate().getAbsolutePath()) + .build()))) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER) + .build(); + + @ClassRule + public static RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster); + + @Rule + public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); + + @Test + public void shouldNotAuthenticateUserWithLdap() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + } + logsRule.assertThatStackTraceContain("javax.net.ssl.SSLHandshakeException"); + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuthorizationBackend.java b/src/integrationTest/java/org/opensearch/test/framework/AuthorizationBackend.java new file mode 100644 index 0000000000..56f94e9673 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/AuthorizationBackend.java @@ -0,0 +1,45 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; + +public class AuthorizationBackend implements ToXContentObject { + private final String type; + private Supplier> config; + + public AuthorizationBackend(String type) { + this.type = type; + } + + public AuthorizationBackend config(Map ldapConfig) { + return config(() -> ldapConfig); + } + + public AuthorizationBackend config(Supplier> ldapConfigSupplier) { + this.config = Objects.requireNonNull(ldapConfigSupplier, "Configuration supplier is required"); + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("type", type); + xContentBuilder.field("config", config.get()); + xContentBuilder.endObject(); + return xContentBuilder; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuthzDomain.java b/src/integrationTest/java/org/opensearch/test/framework/AuthzDomain.java new file mode 100644 index 0000000000..d42caa9f0c --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/AuthzDomain.java @@ -0,0 +1,70 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework; + +import java.io.IOException; + +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; + +/** +* The class represents authorization domain +*/ +public class AuthzDomain implements ToXContentObject { + + private final String id; + + private String description; + + private boolean httpEnabled; + + private boolean transportEnabled; + + private AuthorizationBackend authorizationBackend; + + public AuthzDomain(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public AuthzDomain description(String description) { + this.description = description; + return this; + } + + public AuthzDomain httpEnabled(boolean httpEnabled) { + this.httpEnabled = httpEnabled; + return this; + } + + public AuthzDomain authorizationBackend(AuthorizationBackend authorizationBackend) { + this.authorizationBackend = authorizationBackend; + return this; + } + + public AuthzDomain transportEnabled(boolean transportEnabled) { + this.transportEnabled = transportEnabled; + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("description", description); + xContentBuilder.field("http_enabled", httpEnabled); + xContentBuilder.field("transport_enabled", transportEnabled); + xContentBuilder.field("authorization_backend", authorizationBackend); + xContentBuilder.endObject(); + return xContentBuilder; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/LdapAuthenticationConfigBuilder.java b/src/integrationTest/java/org/opensearch/test/framework/LdapAuthenticationConfigBuilder.java new file mode 100644 index 0000000000..b6519f7b87 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/LdapAuthenticationConfigBuilder.java @@ -0,0 +1,119 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** +* @param is related to subclasses thus method defined in the class LdapAuthenticationConfigBuilder return proper subclass +* type so that all method defined in subclass are available in one of builder superclass method is invoked. Please see +* {@link LdapAuthorizationConfigBuilder} +*/ +public class LdapAuthenticationConfigBuilder { + private boolean enableSsl = false; + private boolean enableStartTls = false; + private boolean enableSslClientAuth = false; + private boolean verifyHostnames = false; + private List hosts; + private String bindDn; + private String password; + private String userBase; + private String userSearch; + private String usernameAttribute; + + private String penTrustedCasFilePath; + + /** + * Subclass of this + */ + private final T builderSubclass; + + protected LdapAuthenticationConfigBuilder(Function thisCastFunction) { + this.builderSubclass = thisCastFunction.apply(this); + } + + public static LdapAuthenticationConfigBuilder config() { + return new LdapAuthenticationConfigBuilder<>(Function.identity()); + } + + public T enableSsl(boolean enableSsl) { + this.enableSsl = enableSsl; + return builderSubclass; + } + + public T enableStartTls(boolean enableStartTls) { + this.enableStartTls = enableStartTls; + return builderSubclass; + } + + public T enableSslClientAuth(boolean enableSslClientAuth) { + this.enableSslClientAuth = enableSslClientAuth; + return builderSubclass; + } + + public T verifyHostnames(boolean verifyHostnames) { + this.verifyHostnames = verifyHostnames; + return builderSubclass; + } + + public T hosts(List hosts) { + this.hosts = hosts; + return builderSubclass; + } + + public T bindDn(String bindDn) { + this.bindDn = bindDn; + return builderSubclass; + } + + public T password(String password) { + this.password = password; + return builderSubclass; + } + + public T userBase(String userBase) { + this.userBase = userBase; + return builderSubclass; + } + + public T userSearch(String userSearch) { + this.userSearch = userSearch; + return builderSubclass; + } + + public T usernameAttribute(String usernameAttribute) { + this.usernameAttribute = usernameAttribute; + return builderSubclass; + } + + public T penTrustedCasFilePath(String penTrustedCasFilePath) { + this.penTrustedCasFilePath = penTrustedCasFilePath; + return builderSubclass; + } + + public Map build() { + HashMap config = new HashMap<>(); + config.put("enable_ssl", enableSsl); + config.put("enable_start_tls", enableStartTls); + config.put("enable_ssl_client_auth", enableSslClientAuth); + config.put("verify_hostnames", verifyHostnames); + config.put("hosts", hosts); + config.put("bind_dn", bindDn); + config.put("password", password); + config.put("userbase", userBase); + config.put("usersearch", userSearch); + config.put("username_attribute", usernameAttribute); + config.put("pemtrustedcas_filepath", penTrustedCasFilePath); + return config; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/LdapAuthorizationConfigBuilder.java b/src/integrationTest/java/org/opensearch/test/framework/LdapAuthorizationConfigBuilder.java new file mode 100644 index 0000000000..43611d8d08 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/LdapAuthorizationConfigBuilder.java @@ -0,0 +1,75 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework; + +import java.util.List; +import java.util.Map; + +public class LdapAuthorizationConfigBuilder extends LdapAuthenticationConfigBuilder { + private List skipUsers; + private String roleBase; + private String roleSearch; + private String userRoleAttribute; + private String userRoleName; + private String roleName; + private boolean resolveNestedRoles; + + public LdapAuthorizationConfigBuilder() { + super(LdapAuthorizationConfigBuilder.class::cast); + } + + public LdapAuthorizationConfigBuilder skipUsers(List skipUsers) { + this.skipUsers = skipUsers; + return this; + } + + public LdapAuthorizationConfigBuilder roleBase(String roleBase) { + this.roleBase = roleBase; + return this; + } + + public LdapAuthorizationConfigBuilder roleSearch(String roleSearch) { + this.roleSearch = roleSearch; + return this; + } + + public LdapAuthorizationConfigBuilder userRoleAttribute(String userRoleAttribute) { + this.userRoleAttribute = userRoleAttribute; + return this; + } + + public LdapAuthorizationConfigBuilder userRoleName(String userRoleName) { + this.userRoleName = userRoleName; + return this; + } + + public LdapAuthorizationConfigBuilder roleName(String roleName) { + this.roleName = roleName; + return this; + } + + public LdapAuthorizationConfigBuilder resolveNestedRoles(boolean resolveNestedRoles) { + this.resolveNestedRoles = resolveNestedRoles; + return this; + } + + @Override + public Map build() { + Map map = super.build(); + map.put("skip_users", skipUsers); + map.put("rolebase", roleBase); + map.put("rolesearch", roleSearch); + map.put("userroleattribute", userRoleAttribute); + map.put("userrolename", userRoleName); + map.put("rolename", roleName); + map.put("resolve_nested_roles", resolveNestedRoles); + return map; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java b/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java index d66a55eb36..574e1540c7 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java +++ b/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java @@ -23,7 +23,7 @@ public class RolesMapping implements ToXContentObject { private String roleName; private List backendRoles; - private List hosts; +// private List hosts; private List users; private boolean reserved = false; @@ -39,16 +39,6 @@ public RolesMapping backendRoles(String...backendRoles) { return this; } - public RolesMapping hosts(List hosts) { - this.hosts = hosts; - return this; - } - - public RolesMapping users(List users) { - this.users = users; - return this; - } - public RolesMapping reserved(boolean reserved) { this.reserved = reserved; return this; diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index fb10cdb2f4..d3f1ee1e16 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -40,6 +40,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; @@ -114,6 +115,11 @@ public TestSecurityConfig authc(AuthcDomain authcDomain) { config.authc(authcDomain); return this; } + + public TestSecurityConfig authz(AuthzDomain authzDomain) { + config.authz(authzDomain); + return this; + } public TestSecurityConfig user(User user) { this.internalUsers.put(user.name, user); @@ -159,6 +165,7 @@ public static class Config implements ToXContentObject { private Map authcDomainMap = new LinkedHashMap<>(); private AuthFailureListeners authFailureListeners; + private Map authzDomainMap = new LinkedHashMap<>(); public Config anonymousAuth(boolean anonymousAuth) { this.anonymousAuth = anonymousAuth; @@ -180,6 +187,11 @@ public Config authFailureListeners(AuthFailureListeners authFailureListeners) { return this; } + public Config authz(AuthzDomain authzDomain) { + authzDomainMap.put(authzDomain.getId(), authzDomain); + return this; + } + @Override public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { xContentBuilder.startObject(); @@ -195,6 +207,9 @@ public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params } xContentBuilder.field("authc", authcDomainMap); + if(authzDomainMap.isEmpty() == false) { + xContentBuilder.field("authz", authzDomainMap); + } if(authFailureListeners != null) { xContentBuilder.field("auth_failure_listeners", authFailureListeners); @@ -522,14 +537,20 @@ public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params public static class AuthenticationBackend implements ToXContentObject { private final String type; - private Map config = new HashMap(); + private Supplier> config = () -> new HashMap(); public AuthenticationBackend(String type) { this.type = type; } public AuthenticationBackend config(Map config) { - this.config.putAll(config); + Map configCopy = new HashMap<>(config); + this.config = () -> configCopy; + return this; + } + + public AuthenticationBackend config(Supplier> configSupplier) { + this.config = configSupplier; return this; } @@ -538,7 +559,7 @@ public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params xContentBuilder.startObject(); xContentBuilder.field("type", type); - xContentBuilder.field("config", config); + xContentBuilder.field("config", config.get()); xContentBuilder.endObject(); return xContentBuilder; diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java index 93cbce5c84..57d8ce012e 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java @@ -63,20 +63,21 @@ public class TestCertificates { private static final String CERTIFICATE_FILE_EXTENSION = ".cert"; private static final String KEY_FILE_EXTENSION = ".key"; private final CertificateData caCertificate; - private final CertificateData adminCertificate; private final List nodeCertificates; + private final CertificateData ldapCertificate; + public TestCertificates() { this.caCertificate = createCaCertificate(); this.nodeCertificates = IntStream.range(0, MAX_NUMBER_OF_NODE_CERTIFICATES) .mapToObj(this::createNodeCertificate) .collect(Collectors.toList()); this.adminCertificate = createAdminCertificate(); + this.ldapCertificate = createLdapCertificate(); log.info("Test certificates successfully generated"); } - private CertificateData createCaCertificate() { CertificateMetadata metadata = CertificateMetadata.basicMetadata(CA_SUBJECT, CERTIFICATE_VALIDITY_DAYS) .withKeyUsage(true, DIGITAL_SIGNATURE, KEY_CERT_SIGN, CRL_SIGN); @@ -90,7 +91,7 @@ private CertificateData createAdminCertificate() { .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH); return CertificatesIssuerFactory .rsaBaseCertificateIssuer() - .issueSelfSignedCertificate(metadata); + .issueSignedCertificate(metadata, caCertificate); } public CertificateData createSelfSignedCertificate(String distinguishedName) { @@ -108,17 +109,25 @@ public File getRootCertificate() { return createTempFile("root", CERTIFICATE_FILE_EXTENSION, caCertificate.certificateInPemFormat()); } + public CertificateData getRootCertificateData() { + return caCertificate; + } + /** * Certificate for Open Search node. The certificate is derived from root certificate, returned by method {@link #getRootCertificate()} * @param node is a node index. It has to be less than {@link #MAX_NUMBER_OF_NODE_CERTIFICATES} * @return file which contains certificate in PEM format, defined by RFC 1421 */ public File getNodeCertificate(int node) { - isCorrectNodeNumber(node); - CertificateData certificateData = nodeCertificates.get(node); + CertificateData certificateData = getNodeCertificateData(node); return createTempFile("node-" + node, CERTIFICATE_FILE_EXTENSION, certificateData.certificateInPemFormat()); } + public CertificateData getNodeCertificateData(int node) { + isCorrectNodeNumber(node); + return nodeCertificates.get(node); + } + private void isCorrectNodeNumber(int node) { if (node >= MAX_NUMBER_OF_NODE_CERTIFICATES) { String message = String.format("Cannot get certificate for node %d, number of created certificates for nodes is %d", node, @@ -140,13 +149,25 @@ private CertificateData createNodeCertificate(Integer node) { public CertificateData issueUserCertificate(String organizationUnit, String username) { String subject = String.format("DC=de,L=test,O=users,OU=%s,CN=%s", organizationUnit, username); + CertificateMetadata metadata = CertificateMetadata.basicMetadata(subject, CERTIFICATE_VALIDITY_DAYS).withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH); + return CertificatesIssuerFactory.rsaBaseCertificateIssuer().issueSignedCertificate(metadata, caCertificate); + } + + private CertificateData createLdapCertificate() { + String subject = "DC=de,L=test,O=node,OU=node,CN=ldap.example.com"; CertificateMetadata metadata = CertificateMetadata.basicMetadata(subject, CERTIFICATE_VALIDITY_DAYS) - .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH); + .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH) + .withSubjectAlternativeName(null, List.of("localhost"), "127.0.0.1"); return CertificatesIssuerFactory .rsaBaseCertificateIssuer() .issueSignedCertificate(metadata, caCertificate); } + + public CertificateData getLdapCertificateData() { + return ldapCertificate; + } + /** * It returns private key associated with node certificate returned by method {@link #getNodeCertificate(int)} * @@ -171,6 +192,10 @@ public File getAdminCertificate() { return createTempFile("admin", CERTIFICATE_FILE_EXTENSION, adminCertificate.certificateInPemFormat()); } + public CertificateData getAdminCertificateData() { + return adminCertificate; + } + /** * It returns private key associated with admin certificate returned by {@link #getAdminCertificate()}. * diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index aa5768e523..b9bb11fcf5 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -50,6 +50,7 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.test.framework.AuditConfiguration; import org.opensearch.test.framework.AuthFailureListeners; +import org.opensearch.test.framework.AuthzDomain; import org.opensearch.test.framework.RolesMapping; import org.opensearch.test.framework.TestIndex; import org.opensearch.test.framework.TestSecurityConfig; @@ -376,6 +377,11 @@ public Builder authc(TestSecurityConfig.AuthcDomain authc) { return this; } + public Builder authz(AuthzDomain authzDomain) { + testSecurityConfig.authz(authzDomain); + return this; + } + public Builder clusterName(String clusterName) { this.clusterName = clusterName; return this; @@ -386,6 +392,11 @@ public Builder configIndexName(String configIndexName) { return this; } + public Builder testCertificates(TestCertificates certificates) { + this.testCertificates = certificates; + return this; + } + public Builder anonymousAuth(boolean anonAuthEnabled) { testSecurityConfig.anonymousAuth(anonAuthEnabled); return this; @@ -407,10 +418,9 @@ public Builder doNotFailOnForbidden(boolean doNotFailOnForbidden) { public LocalCluster build() { try { - if(testCertificates == null){ + if(testCertificates == null) { testCertificates = new TestCertificates(); } - clusterName += "_" + num.incrementAndGet(); Settings settings = nodeOverrideSettingsBuilder.build(); return new LocalCluster(clusterName, testSecurityConfig, sslOnly, settings, clusterManager, plugins, diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java index 54a13630be..e395bfceda 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -107,21 +107,38 @@ default TestRestClient getRestClient(UserCredentialsHolder user, Header... heade return getRestClient(user.getName(), user.getPassword(), null, headers); } + default RestHighLevelClient getRestHighLevelClient(String username, String password, Header... headers) { + return getRestHighLevelClient(new UserCredentialsHolder() { + @Override + public String getName() { + return username; + } + + @Override + public String getPassword() { + return password; + } + }, Arrays.asList(headers)); + } + + default RestHighLevelClient getRestHighLevelClient(UserCredentialsHolder user) { - + return getRestHighLevelClient(user, Collections.emptySet()); + } + + default RestHighLevelClient getRestHighLevelClient(UserCredentialsHolder user, Collection defaultHeaders) { + BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(new AuthScope(null, -1), new UsernamePasswordCredentials(user.getName(), user.getPassword().toCharArray())); - return getRestHighLevelClient(credentialsProvider, Collections.emptySet()); + return getRestHighLevelClient(credentialsProvider, defaultHeaders); } default RestHighLevelClient getRestHighLevelClient(Collection defaultHeaders) { - - - return getRestHighLevelClient(null, defaultHeaders); + return getRestHighLevelClient((BasicCredentialsProvider)null, defaultHeaders); } - private RestHighLevelClient getRestHighLevelClient(BasicCredentialsProvider credentialsProvider, Collection defaultHeaders) { + default RestHighLevelClient getRestHighLevelClient(BasicCredentialsProvider credentialsProvider, Collection defaultHeaders) { RestClientBuilder.HttpClientConfigCallback configCallback = httpClientBuilder -> { TlsStrategy tlsStrategy = ClientTlsStrategyBuilder .create() @@ -145,6 +162,7 @@ public TlsDetails create(final SSLEngine sslEngine) { } httpClientBuilder.setDefaultHeaders(defaultHeaders); httpClientBuilder.setConnectionManager(cm); + httpClientBuilder.setDefaultHeaders(defaultHeaders); return httpClientBuilder; }; @@ -152,6 +170,7 @@ public TlsDetails create(final SSLEngine sslEngine) { RestClientBuilder builder = RestClient.builder(new HttpHost("https", httpAddress.getHostString(), httpAddress.getPort())) .setHttpClientConfigCallback(configCallback); + return new RestHighLevelClient(builder); } diff --git a/src/integrationTest/java/org/opensearch/test/framework/ldap/EmbeddedLDAPServer.java b/src/integrationTest/java/org/opensearch/test/framework/ldap/EmbeddedLDAPServer.java new file mode 100755 index 0000000000..fd40a85292 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/ldap/EmbeddedLDAPServer.java @@ -0,0 +1,56 @@ +/* +* 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.test.framework.ldap; + +import java.util.Objects; + +import org.junit.rules.ExternalResource; + +import org.opensearch.test.framework.certificate.CertificateData; + +public class EmbeddedLDAPServer extends ExternalResource { + + private final LdapServer server; + + private final LdifData ldifData; + + public EmbeddedLDAPServer(CertificateData trustAnchor, CertificateData ldapCertificate, LdifData ldifData) { + this.ldifData = Objects.requireNonNull(ldifData, "Ldif data is required"); + this.server = new LdapServer(trustAnchor, ldapCertificate); + } + + @Override + protected void before() { + try { + server.start(ldifData); + } catch (Exception e) { + throw new RuntimeException("Cannot start ldap server", e); + } + } + + @Override + protected void after() { + try { + server.stop(); + } catch (InterruptedException e) { + throw new RuntimeException("Cannot stop LDAP server.", e); + } + } + + public int getLdapNonTlsPort() { + return server.getLdapNonTlsPort(); + } + + public int getLdapTlsPort() { + return server.getLdapsTlsPort(); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/ldap/LdapServer.java b/src/integrationTest/java/org/opensearch/test/framework/ldap/LdapServer.java new file mode 100644 index 0000000000..13add742e4 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/ldap/LdapServer.java @@ -0,0 +1,224 @@ +/* +* 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.test.framework.ldap; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.net.BindException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +import com.unboundid.ldap.listener.InMemoryDirectoryServer; +import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; +import com.unboundid.ldap.listener.InMemoryListenerConfig; +import com.unboundid.ldap.sdk.DN; +import com.unboundid.ldap.sdk.Entry; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.schema.Schema; +import com.unboundid.ldif.LDIFReader; +import com.unboundid.util.ssl.SSLUtil; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.test.framework.certificate.CertificateData; +import org.opensearch.test.framework.cluster.SocketUtils; + +/** +* Based on class com.amazon.dlic.auth.ldap.srv.LdapServer from older tests +*/ +final class LdapServer { + private static final Logger log = LogManager.getLogger(LdapServer.class); + + private static final int LOCK_TIMEOUT = 60; + private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS; + + private static final String LOCK_TIMEOUT_MSG = "Unable to obtain lock due to timeout after " + LOCK_TIMEOUT + " " + TIME_UNIT.toString(); + private static final String SERVER_NOT_STARTED = "The LDAP server is not started."; + private static final String SERVER_ALREADY_STARTED = "The LDAP server is already started."; + + private final CertificateData trustAnchor; + + private final CertificateData ldapCertificate; + + private InMemoryDirectoryServer server; + private final AtomicBoolean isStarted = new AtomicBoolean(Boolean.FALSE); + private final ReentrantLock serverStateLock = new ReentrantLock(); + + private int ldapNonTlsPort = -1; + private int ldapTlsPort = -1; + + + public LdapServer(CertificateData trustAnchor, CertificateData ldapCertificate) { + this.trustAnchor = trustAnchor; + this.ldapCertificate = ldapCertificate; + } + + public boolean isStarted() { + return this.isStarted.get(); + } + + public int getLdapNonTlsPort() { + return ldapNonTlsPort; + } + + public int getLdapsTlsPort() { + return ldapTlsPort; + } + + public void start(LdifData ldifData) throws Exception { + Objects.requireNonNull(ldifData, "Ldif data is required"); + boolean hasLock = false; + try { + hasLock = serverStateLock.tryLock(LdapServer.LOCK_TIMEOUT, LdapServer.TIME_UNIT); + if (hasLock) { + doStart(ldifData); + this.isStarted.set(Boolean.TRUE); + } else { + throw new IllegalStateException(LdapServer.LOCK_TIMEOUT_MSG); + } + } catch (InterruptedException ioe) { + //lock interrupted + log.error("LDAP server start lock interrupted", ioe); + throw ioe; + } finally { + if (hasLock) { + serverStateLock.unlock(); + } + } + } + + private void doStart(LdifData ldifData) throws Exception { + if (isStarted.get()) { + throw new IllegalStateException(LdapServer.SERVER_ALREADY_STARTED); + } + configureAndStartServer(ldifData); + } + + private Collection getInMemoryListenerConfigs() throws Exception { + KeyStore keyStore = createEmptyKeyStore(); + addLdapCertificatesToKeystore(keyStore); + final SSLUtil sslUtil = new SSLUtil(createKeyManager(keyStore), createTrustManagers(keyStore)); + + ldapNonTlsPort = SocketUtils.findAvailableTcpPort(); + ldapTlsPort = SocketUtils.findAvailableTcpPort(); + + Collection listenerConfigs = new ArrayList<>(); + listenerConfigs.add(InMemoryListenerConfig.createLDAPConfig("ldap", null, ldapNonTlsPort, sslUtil.createSSLSocketFactory())); + listenerConfigs.add(InMemoryListenerConfig.createLDAPSConfig("ldaps", ldapTlsPort, sslUtil.createSSLServerSocketFactory())); + return listenerConfigs; + } + + private static KeyManager[] createKeyManager(KeyStore keyStore) + throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, null); + return keyManagerFactory.getKeyManagers(); + } + + private static TrustManager[] createTrustManagers(KeyStore keyStore) throws NoSuchAlgorithmException, KeyStoreException { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + return trustManagerFactory.getTrustManagers(); + } + + private void addLdapCertificatesToKeystore(KeyStore keyStore) throws KeyStoreException { + keyStore.setCertificateEntry("trustAnchor", trustAnchor.certificate()); + keyStore.setKeyEntry("ldap-key", ldapCertificate.getKey(), null, new Certificate[]{ ldapCertificate.certificate()}); + } + + private static KeyStore createEmptyKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null); + return keyStore; + } + + private synchronized void configureAndStartServer(LdifData ldifData) throws Exception { + Collection listenerConfigs = getInMemoryListenerConfigs(); + + Schema schema = Schema.getDefaultStandardSchema(); + + final String rootObjectDN = ldifData.getRootDistinguishedName(); + InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(new DN(rootObjectDN)); + + config.setSchema(schema); //schema can be set on the rootDN too, per javadoc. + config.setListenerConfigs(listenerConfigs); + config.setEnforceAttributeSyntaxCompliance(false); + config.setEnforceSingleStructuralObjectClass(false); + + server = new InMemoryDirectoryServer(config); + + try { + /* Clear entries from server. */ + server.clear(); + server.startListening(); + loadLdifData(ldifData); + } catch (LDAPException ldape) { + if (ldape.getMessage().contains("java.net.BindException")) { + throw new BindException(ldape.getMessage()); + } + throw ldape; + } + + } + + public void stop() throws InterruptedException { + boolean hasLock = false; + try { + hasLock = serverStateLock.tryLock(LdapServer.LOCK_TIMEOUT, LdapServer.TIME_UNIT); + if (hasLock) { + if (!isStarted.get()) { + throw new IllegalStateException(LdapServer.SERVER_NOT_STARTED); + } + log.info("Shutting down in-Memory Ldap Server."); + server.shutDown(true); + } else { + throw new IllegalStateException(LdapServer.LOCK_TIMEOUT_MSG); + } + } catch (InterruptedException ioe) { + //lock interrupted + log.error("Canot stop LDAP server due to interruption", ioe); + throw ioe; + } finally { + if (hasLock) { + serverStateLock.unlock(); + } + } + } + + private void loadLdifData(LdifData ldifData) throws Exception { + try (LDIFReader r = new LDIFReader(new BufferedReader(new StringReader(ldifData.getContent())))){ + Entry entry; + while ((entry = r.readEntry()) != null) { + server.add(entry); + } + } catch(Exception e) { + log.error("Cannot load data into LDAP server", e); + throw e; + } + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifBuilder.java b/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifBuilder.java new file mode 100644 index 0000000000..cea53a92f3 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifBuilder.java @@ -0,0 +1,68 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.ldap; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class LdifBuilder { + + private static final Logger log = LogManager.getLogger(LdifBuilder.class); + + private final List records; + + private Record root; + + public LdifBuilder() { + this.records = new ArrayList<>(); + } + + public RecordBuilder root(String distinguishedName) { + if(root != null) { + throw new IllegalStateException("Root object is already defined"); + } + return new RecordBuilder(this, distinguishedName); + } + + RecordBuilder newRecord(String distinguishedName) { + if(root == null) { + throw new IllegalStateException("Define root object first"); + } + return new RecordBuilder(this, distinguishedName); + } + + void addRecord(Record record) { + Objects.requireNonNull(record, "Cannot add null record"); + if(records.isEmpty()) { + this.root = record; + } + records.add(Objects.requireNonNull(record, "Cannot add null record")); + } + + public LdifData buildLdif() { + String ldif = records.stream() + .map(record -> record.toLdifRepresentation()) + .collect(Collectors.joining("\n##########\n")); + log.debug("Built ldif file: \n{}", ldif); + return new LdifData(getRootDistinguishedName(), ldif); + } + + private String getRootDistinguishedName() { + if(root == null) { + throw new IllegalStateException("Root object is not present."); + } + return root.getDistinguishedName(); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifData.java b/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifData.java new file mode 100644 index 0000000000..a0f2026b73 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifData.java @@ -0,0 +1,49 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.ldap; + +import org.apache.commons.lang3.StringUtils; + + +/** +* Value object which represents LDIF file data and some metadata. Ensure type safety. +*/ +public class LdifData { + + private final String rootDistinguishedName; + + private final String content; + + LdifData(String rootDistinguishedName, String content) { + this.rootDistinguishedName = requireNotBlank(rootDistinguishedName, "Root distinguished name is required"); + this.content = requireNotBlank(content, "Ldif file content is required"); + + } + + private static String requireNotBlank(String string, String message) { + if(StringUtils.isBlank(string)) { + throw new IllegalArgumentException(message); + } + return string; + } + + String getContent() { + return content; + } + + String getRootDistinguishedName() { + return rootDistinguishedName; + } + + @Override + public String toString() { + return "LdifData{" + "content='" + content + '\'' + '}'; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/ldap/Record.java b/src/integrationTest/java/org/opensearch/test/framework/ldap/Record.java new file mode 100644 index 0000000000..76eea1771d --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/ldap/Record.java @@ -0,0 +1,68 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.ldap; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.tuple.Pair; + +class Record { + + private final String distinguishedName; + + private final List classes; + private final List> attributes; + + public Record(String distinguishedName) { + this.distinguishedName = Objects.requireNonNull(distinguishedName, "Distinguished name is required"); + this.classes = new ArrayList<>(); + this.attributes = new ArrayList<>(); + } + + public String getDistinguishedName() { + return distinguishedName; + } + + public void addClass(String clazz) { + classes.add(Objects.requireNonNull(clazz, "Object class is required.")); + } + + public void addAttribute(String name, String value) { + Objects.requireNonNull(name, "Attribute name is required"); + Objects.requireNonNull(value, "Attribute value is required"); + attributes.add(Pair.of(name, value)); + } + + boolean isValid() { + return classes.size() > 0; + } + + String toLdifRepresentation() { + return new StringBuilder("dn: ").append(distinguishedName).append("\n") + .append(formattedClasses()).append("\n") + .append(formattedAttributes()).append("\n") + .toString(); + } + + private String formattedAttributes() { + return attributes.stream() + .map(pair -> pair.getKey() + ": " + pair.getValue()) + .collect(Collectors.joining("\n")); + } + + private String formattedClasses() { + return classes.stream() + .map(clazz -> "objectClass: " + clazz) + .collect(Collectors.joining("\n")); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/ldap/RecordBuilder.java b/src/integrationTest/java/org/opensearch/test/framework/ldap/RecordBuilder.java new file mode 100644 index 0000000000..a54caef2af --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/ldap/RecordBuilder.java @@ -0,0 +1,92 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.ldap; + +import java.util.Objects; + +public class RecordBuilder { + + private final LdifBuilder builder; + private final Record record; + + RecordBuilder(LdifBuilder builder, String distinguishedName) { + this.builder = Objects.requireNonNull(builder, "LdifBuilder is required"); + this.record = new Record(distinguishedName); + } + + public RecordBuilder classes(String...classes) { + for(String clazz : classes) { + this.record.addClass(clazz); + } + return this; + } + + public RecordBuilder dn(String distinguishedName) { + record.addAttribute("dn", distinguishedName); + return this; + } + + public RecordBuilder dc(String domainComponent) { + record.addAttribute("dc", domainComponent); + return this; + } + + public RecordBuilder ou(String organizationUnit) { + record.addAttribute("ou", organizationUnit); + return this; + } + + public RecordBuilder cn(String commonName) { + record.addAttribute("cn", commonName); + return this; + } + + public RecordBuilder sn(String surname) { + record.addAttribute("sn", surname); + return this; + } + + public RecordBuilder uid(String userId) { + record.addAttribute("uid", userId); + return this; + } + + public RecordBuilder userPassword(String password) { + record.addAttribute("userpassword", password); + return this; + } + + public RecordBuilder mail(String emailAddress) { + record.addAttribute("mail", emailAddress); + return this; + } + + public RecordBuilder uniqueMember(String userDistinguishedName) { + record.addAttribute("uniquemember", userDistinguishedName); + return this; + } + + public RecordBuilder attribute(String name, String value) { + record.addAttribute(name, value); + return this; + } + + public LdifBuilder buildRecord() { + if(record.isValid() == false) { + throw new IllegalStateException("Record is invalid"); + } + builder.addRecord(record); + return builder; + } + + public RecordBuilder newRecord(String distinguishedName) { + return buildRecord().newRecord(distinguishedName); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/log/LogCapturingAppender.java b/src/integrationTest/java/org/opensearch/test/framework/log/LogCapturingAppender.java index 11a59f470d..e0baec9cb2 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/log/LogCapturingAppender.java +++ b/src/integrationTest/java/org/opensearch/test/framework/log/LogCapturingAppender.java @@ -16,6 +16,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.collections.Buffer; import org.apache.commons.collections.BufferUtils; @@ -85,7 +86,8 @@ public void append(LogEvent event) { String loggerName = event.getLoggerName(); boolean loggable = activeLoggers.contains(loggerName); if(loggable) { - messages.add(event.getMessage().getFormattedMessage()); + event.getThrown(); + messages.add(new LogMessage(event.getMessage().getFormattedMessage(), event.getThrown())); } } @@ -110,10 +112,17 @@ public static void disable() { * Is used to obtain gathered log messages * @return Log messages */ - public static List getLogMessages() { + public static List getLogMessages() { return new ArrayList<>(messages); } + public static List getLogMessagesAsString() { + return getLogMessages() + .stream() + .map(LogMessage::getMessage) + .collect(Collectors.toList()); + } + @Override public String toString() { return "LogCapturingAppender{}"; diff --git a/src/integrationTest/java/org/opensearch/test/framework/log/LogMessage.java b/src/integrationTest/java/org/opensearch/test/framework/log/LogMessage.java new file mode 100644 index 0000000000..327e91c759 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/log/LogMessage.java @@ -0,0 +1,40 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.log; + +import java.util.Objects; +import java.util.Optional; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +class LogMessage { + + private final String message; + private final String stackTrace; + + public LogMessage(String message, Throwable throwable) { + this.message = message; + this.stackTrace = Optional.ofNullable(throwable).map(ExceptionUtils::getStackTrace).orElse(""); + } + + public boolean containMessage(String expectedMessage) { + Objects.requireNonNull(expectedMessage, "Expected message must not be null."); + return expectedMessage.equals(message); + } + + public boolean stackTraceContains(String stackTraceFragment) { + Objects.requireNonNull(stackTraceFragment, "Stack trace fragment is required."); + return stackTrace.contains(stackTraceFragment); + } + + public String getMessage() { + return message; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java b/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java index 203cbb80b1..c2dfabf537 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java +++ b/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java @@ -17,6 +17,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasItem; /** @@ -52,7 +53,7 @@ protected void after() { * @param expectedLogMessage expected log message */ public void assertThatContainExactly(String expectedLogMessage) { - List messages = LogCapturingAppender.getLogMessages(); + List messages = LogCapturingAppender.getLogMessagesAsString(); String reason = reasonMessage(expectedLogMessage, messages); assertThat(reason, messages, hasItem(expectedLogMessage)); } @@ -62,11 +63,24 @@ public void assertThatContainExactly(String expectedLogMessage) { * @param messageFragment expected log message fragment */ public void assertThatContain(String messageFragment) { - List messages = LogCapturingAppender.getLogMessages(); + List messages = LogCapturingAppender.getLogMessagesAsString();; String reason = reasonMessage(messageFragment, messages); assertThat(reason, messages, hasItem(containsString(messageFragment))); } + /** + * Check if during the tests a stack trace was logged which contain given fragment + * @param stackTraceFragment stack trace fragment + */ + public void assertThatStackTraceContain(String stackTraceFragment) { + long count = LogCapturingAppender.getLogMessages() + .stream() + .filter(logMessage -> logMessage.stackTraceContains(stackTraceFragment)) + .count(); + String reason = "Stack trace does not contain element " + stackTraceFragment; + assertThat(reason, count, greaterThan(0L)); + } + private static String reasonMessage(String expectedLogMessage, List messages) { String concatenatedLogMessages = messages.stream() .map(message -> String.format("'%s'", message)) diff --git a/src/integrationTest/resources/log4j2-test.properties b/src/integrationTest/resources/log4j2-test.properties index 6982473cef..8d9cf87666 100644 --- a/src/integrationTest/resources/log4j2-test.properties +++ b/src/integrationTest/resources/log4j2-test.properties @@ -24,6 +24,7 @@ logger.localopensearchcluster.level = info logger.auditlogs.name=org.opensearch.test.framework.audit logger.auditlogs.level = info + # Logger required by test org.opensearch.security.http.JwtAuthenticationTests logger.httpjwtauthenticator.name = com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator logger.httpjwtauthenticator.level = debug @@ -35,3 +36,9 @@ logger.httpjwtauthenticator.appenderRef.capturing.ref = logCapturingAppender logger.backendreg.name = org.opensearch.security.auth.BackendRegistry logger.backendreg.level = debug logger.backendreg.appenderRef.capturing.ref = logCapturingAppender + +#com.amazon.dlic.auth.ldap +#logger.ldap.name=com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend +logger.ldap.name=com.amazon.dlic.auth.ldap.backend +logger.ldap.level=TRACE +logger.ldap.appenderRef.capturing.ref = logCapturingAppender From 933a9615621bc0442904ba6cbef93942c14d5f3b Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Mon, 28 Nov 2022 17:55:10 -0500 Subject: [PATCH 084/369] Universal Command-line only Plugin Installation Action & Workflow (#2271) Universal Command-line only Plugin Installation Action & Workflow Signed-off-by: Stephen Crawford Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- .../action.yml | 73 +++++++++---------- .github/workflows/integration-tests.yml | 2 +- .github/workflows/plugin_install.yml | 33 +++++++-- 3 files changed, 64 insertions(+), 44 deletions(-) diff --git a/.github/actions/start-opensearch-with-one-plugin/action.yml b/.github/actions/start-opensearch-with-one-plugin/action.yml index d75dc6fd13..fd838a6cbf 100644 --- a/.github/actions/start-opensearch-with-one-plugin/action.yml +++ b/.github/actions/start-opensearch-with-one-plugin/action.yml @@ -1,4 +1,4 @@ -name: 'Start OpenSearch with One Plugin' +name: 'Launch OpenSearch with a single plugin installed' description: 'Downloads latest build of OpenSearch, installs a plugin, executes a script and then starts OpenSearch on localhost:9200' inputs: @@ -10,14 +10,10 @@ inputs: description: 'The name of the plugin to use, such as opensearch-security' required: true - plugin-start-script: - description: 'The file name for the configuration script for the plugin such as install_demo_configurations -- may not be needed for every plugin' + setup-script-name: + description: 'The name of the setup script you want to run i.e. "setup" (do not include file extension). Leave empty to indicate one should not be run.' required: false - docker-host-plugin-zip: - description: 'The name of the zip file for the plugin hosted on docker-host i.e. security-plugin.zip ' - required: true - runs: using: "composite" steps: @@ -29,25 +25,28 @@ runs: shell: pwsh # Download OpenSearch - - uses: peternied/download-file@v1 + - name: Download OpenSearch for Windows + uses: peternied/download-file@v1 if: ${{ runner.os == 'Windows' }} with: url: https://artifacts.opensearch.org/snapshots/core/opensearch/${{ inputs.opensearch-version }}-SNAPSHOT/opensearch-min-${{ inputs.opensearch-version }}-SNAPSHOT-windows-x64-latest.zip - - uses: peternied/download-file@v1 + + - name: Download OpenSearch for Linux + uses: peternied/download-file@v1 if: ${{ runner.os == 'Linux' }} with: - url: https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/${{ inputs.opensearch-version }}/latest/linux/x64/tar/builds/opensearch/dist/opensearch-min-${{ inputs.opensearch-version }}-linux-x64.tar.gz + url: https://artifacts.opensearch.org/snapshots/core/opensearch/${{ inputs.opensearch-version }}-SNAPSHOT/opensearch-min-${{ inputs.opensearch-version }}-SNAPSHOT-linux-x64-latest.tar.gz # Extract downloaded zip - - name: Extract downloaded zip for Linux + - name: Extract downloaded tar if: ${{ runner.os == 'Linux' }} run: | tar -xzf opensearch-*.tar.gz rm -f opensearch-*.tar.gz shell: bash - - name: Extract downloaded zip for Windows + - name: Extract downloaded zip if: ${{ runner.os == 'Windows' }} run: | tar -xzf opensearch-min-${{ inputs.opensearch-version }}-SNAPSHOT-windows-x64-latest.zip @@ -59,41 +58,38 @@ runs: run: mv ./build/distributions/${{ inputs.plugin-name }}-*.zip ${{ inputs.plugin-name }}.zip shell: bash - # Install the plugin, runs its start-script, and start the OpenSearch server + # Install the plugin - name: Install Plugin into OpenSearch for Linux if: ${{ runner.os == 'Linux'}} run: | - cat > os-ep.sh < setup.sh <<'EOF' + chmod +x ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/plugins/${{ env.PLUGIN_NAME }}/tools/install_demo_configuration.sh + /bin/bash -c "yes | ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/plugins/${{ env.PLUGIN_NAME }}/tools/install_demo_configuration.sh" + EOF + + - name: Create Setup Script + if: ${{ runner.os == 'Windows' }} + run: | + New-Item .\setup.bat -type file + Set-Content .\setup.bat -Value "powershell.exe -noexit -command `"echo 'y' | .\opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT\plugins\${{ env.PLUGIN_NAME }}\tools\install_demo_configuration.bat`"" + Get-Content .\setup.bat - name: Run Opensearch with A Single Plugin uses: ./.github/actions/start-opensearch-with-one-plugin with: - opensearch-version: 3.0.0 - plugin-name: opensearch-security - plugin-start-script: install_demo_configuration - docker-host-plugin-zip: security-plugin.zip + opensearch-version: ${{ env.OPENSEARCH_VERSION }} + plugin-name: ${{ env.PLUGIN_NAME }} + setup-script-name: setup - name: Run sanity tests uses: gradle/gradle-build-action@v2 From 6692941089c253902bd2ecb3ccc79cd7934630cb Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Tue, 29 Nov 2022 17:07:34 +0100 Subject: [PATCH 085/369] Proxy authentication tests (#2211) Signed-off-by: Lukasz Soszynski Signed-off-by: Lukasz Soszynski <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> --- .../java/org/opensearch/security/Song.java | 1 - .../http/CommonProxyAuthenticationTests.java | 258 ++++++++++++++++++ .../http/ExtendedProxyAuthenticationTest.java | 249 +++++++++++++++++ .../http/ProxyAuthenticationTest.java | 121 ++++++++ .../test/framework/RolesMapping.java | 42 ++- .../test/framework/TestSecurityConfig.java | 19 +- .../opensearch/test/framework/XffConfig.java | 82 ++++++ .../test/framework/cluster/LocalCluster.java | 6 + 8 files changed, 772 insertions(+), 6 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/http/CommonProxyAuthenticationTests.java create mode 100644 src/integrationTest/java/org/opensearch/security/http/ExtendedProxyAuthenticationTest.java create mode 100644 src/integrationTest/java/org/opensearch/security/http/ProxyAuthenticationTest.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/XffConfig.java diff --git a/src/integrationTest/java/org/opensearch/security/Song.java b/src/integrationTest/java/org/opensearch/security/Song.java index cf585e5dc7..14b1ce9131 100644 --- a/src/integrationTest/java/org/opensearch/security/Song.java +++ b/src/integrationTest/java/org/opensearch/security/Song.java @@ -18,7 +18,6 @@ public class Song { public static final String FIELD_ARTIST = "artist"; public static final String FIELD_LYRICS = "lyrics"; public static final String FIELD_STARS = "stars"; - public static final String FIELD_GENRE = "genre"; public static final String ARTIST_FIRST = "First artist"; public static final String ARTIST_STRING = "String"; diff --git a/src/integrationTest/java/org/opensearch/security/http/CommonProxyAuthenticationTests.java b/src/integrationTest/java/org/opensearch/security/http/CommonProxyAuthenticationTests.java new file mode 100644 index 0000000000..244bd6b80e --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/CommonProxyAuthenticationTests.java @@ -0,0 +1,258 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.List; + +import org.opensearch.test.framework.RolesMapping; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClientConfiguration; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; + +/** +* Class defines common tests for proxy and extended-proxy authentication. Subclasses are used to run tests. +*/ +abstract class CommonProxyAuthenticationTests { + + protected static final String RESOURCE_AUTH_INFO = "/_opendistro/_security/authinfo"; + protected static final TestSecurityConfig.User USER_ADMIN = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + protected static final String ATTRIBUTE_DEPARTMENT = "department"; + protected static final String ATTRIBUTE_SKILLS = "skills"; + + protected static final String USER_ATTRIBUTE_DEPARTMENT_NAME = "attr.proxy." + ATTRIBUTE_DEPARTMENT; + protected static final String USER_ATTRIBUTE_SKILLS_NAME = "attr.proxy." + ATTRIBUTE_SKILLS; + protected static final String USER_ATTRIBUTE_USERNAME_NAME = "attr.proxy.username"; + + protected static final String HEADER_PREFIX_CUSTOM_ATTRIBUTES = "x-custom-attr"; + protected static final String HEADER_PROXY_USER = "x-proxy-user"; + protected static final String HEADER_PROXY_ROLES = "x-proxy-roles"; + protected static final String HEADER_FORWARDED_FOR = "X-Forwarded-For"; + protected static final String HEADER_DEPARTMENT = HEADER_PREFIX_CUSTOM_ATTRIBUTES + ATTRIBUTE_DEPARTMENT; + protected static final String HEADER_SKILLS = HEADER_PREFIX_CUSTOM_ATTRIBUTES + ATTRIBUTE_SKILLS; + + protected static final String IP_PROXY = "127.0.0.10"; + protected static final String IP_NON_PROXY = "127.0.0.5"; + protected static final String IP_CLIENT = "127.0.0.1"; + + protected static final String USER_KIRK = "kirk"; + protected static final String USER_SPOCK = "spock"; + + protected static final String BACKEND_ROLE_FIRST_MATE = "firstMate"; + protected static final String BACKEND_ROLE_CAPTAIN = "captain"; + protected static final String DEPARTMENT_BRIDGE = "bridge"; + + protected static final String PERSONAL_INDEX_NAME_PATTERN = "personal-${" + USER_ATTRIBUTE_DEPARTMENT_NAME + "}-${" + USER_ATTRIBUTE_USERNAME_NAME + "}"; + protected static final String PERSONAL_INDEX_NAME_SPOCK = "personal-" + DEPARTMENT_BRIDGE + "-" + USER_SPOCK; + protected static final String PERSONAL_INDEX_NAME_KIRK = "personal-" + DEPARTMENT_BRIDGE + "-" + USER_KIRK; + + protected static final String POINTER_USERNAME = "/user_name"; + protected static final String POINTER_BACKEND_ROLES = "/backend_roles"; + protected static final String POINTER_ROLES = "/roles"; + protected static final String POINTER_CUSTOM_ATTRIBUTES = "/custom_attribute_names"; + protected static final String POINTER_TOTAL_HITS = "/hits/total/value"; + protected static final String POINTER_FIRST_DOCUMENT_ID = "/hits/hits/0/_id"; + protected static final String POINTER_FIRST_DOCUMENT_INDEX = "/hits/hits/0/_index"; + protected static final String POINTER_FIRST_DOCUMENT_SOURCE_TITLE = "/hits/hits/0/_source/title"; + + protected static final TestSecurityConfig.Role ROLE_ALL_INDEX_SEARCH = new TestSecurityConfig.Role("all-index-search").indexPermissions("indices:data/read/search") + .on("*"); + + protected static final TestSecurityConfig.Role ROLE_PERSONAL_INDEX_SEARCH = new TestSecurityConfig.Role("personal-index-search").indexPermissions("indices:data/read/search") + .on(PERSONAL_INDEX_NAME_PATTERN); + + protected static final RolesMapping ROLES_MAPPING_CAPTAIN = new RolesMapping(ROLE_PERSONAL_INDEX_SEARCH).backendRoles(BACKEND_ROLE_CAPTAIN); + + protected static final RolesMapping ROLES_MAPPING_FIRST_MATE = new RolesMapping(ROLE_ALL_INDEX_SEARCH) + .backendRoles(BACKEND_ROLE_FIRST_MATE); + + protected abstract LocalCluster getCluster(); + + protected void shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured() { + try(TestRestClient client = getCluster().getRestClient(USER_ADMIN)) { + TestRestClient.HttpResponse response = client.get(RESOURCE_AUTH_INFO); + + response.assertStatusCode(200); + } + } + + protected void shouldAuthenticateWithProxy_positiveUserKirk() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_KIRK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN); + try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USER_KIRK)); + } + } + + protected void shouldAuthenticateWithProxy_positiveUserSpock() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_FIRST_MATE); + try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USER_SPOCK)); + } + } + + protected void shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_PROXY_USER, USER_KIRK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN); + try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + } + } + + protected void shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN); + try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + } + } + + protected void shouldAuthenticateWithProxyWhenRolesHeaderIsMissing() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_KIRK); + try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USER_KIRK)); + } + } + + protected void shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_NON_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_KIRK); + try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + } + } + + protected void shouldRetrieveEmptyListOfRoles() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK); + try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(0)); + List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); + assertThat(roles, hasSize(0)); + } + } + + protected void shouldRetrieveSingleRoleFirstMate() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_FIRST_MATE); + try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, contains(BACKEND_ROLE_FIRST_MATE)); + List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); + assertThat(roles, hasSize(1)); + assertThat(roles, contains(ROLE_ALL_INDEX_SEARCH.getName())); + } + } + + protected void shouldRetrieveSingleRoleCaptain() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN); + try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, contains(BACKEND_ROLE_CAPTAIN)); + List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); + assertThat(roles, hasSize(1)); + assertThat(roles, contains(ROLE_PERSONAL_INDEX_SEARCH.getName())); + } + } + + protected void shouldRetrieveMultipleRoles() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN + "," + BACKEND_ROLE_FIRST_MATE); + try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(2)); + assertThat(backendRoles, containsInAnyOrder(BACKEND_ROLE_CAPTAIN, BACKEND_ROLE_FIRST_MATE)); + List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); + assertThat(roles, hasSize(2)); + assertThat(roles, containsInAnyOrder(ROLE_PERSONAL_INDEX_SEARCH.getName(), ROLE_ALL_INDEX_SEARCH.getName())); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/http/ExtendedProxyAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/ExtendedProxyAuthenticationTest.java new file mode 100644 index 0000000000..7ba2a6a72e --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/ExtendedProxyAuthenticationTest.java @@ -0,0 +1,249 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Map; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.client.Client; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AuthenticationBackend; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.HttpAuthenticator; +import org.opensearch.test.framework.XffConfig; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; +import org.opensearch.test.framework.cluster.TestRestClientConfiguration; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.opensearch.security.Song.SONGS; +import static org.opensearch.security.Song.TITLE_MAGNUM_OPUS; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; + +/** +* Class used to run tests defined in supper class and adds tests specific for extended-proxy authentication. +*/ +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class ExtendedProxyAuthenticationTest extends CommonProxyAuthenticationTests { + + + public static final String ID_ONE_1 = "one#1"; + public static final String ID_TWO_2 = "two#2"; + public static final Map PROXY_AUTHENTICATOR_CONFIG = Map.of( + "user_header", HEADER_PROXY_USER, + "roles_header", HEADER_PROXY_ROLES, + "attr_header_prefix", HEADER_PREFIX_CUSTOM_ATTRIBUTES + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) + .xff(new XffConfig(true).internalProxiesRegexp("127\\.0\\.0\\.10")) + .authc(new AuthcDomain("proxy_auth_domain", -5, true) + .httpAuthenticator(new HttpAuthenticator("extended-proxy").challenge(false).config(PROXY_AUTHENTICATOR_CONFIG)) + .backend(new AuthenticationBackend("noop"))) + .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_ADMIN).roles(ROLE_ALL_INDEX_SEARCH, ROLE_PERSONAL_INDEX_SEARCH) + .rolesMapping(ROLES_MAPPING_CAPTAIN, ROLES_MAPPING_FIRST_MATE).build(); + + @Override + protected LocalCluster getCluster() { + return cluster; + } + + @BeforeClass + public static void createTestData() { + try(Client client = cluster.getInternalNodeClient()){ + client.prepareIndex(PERSONAL_INDEX_NAME_SPOCK).setId(ID_ONE_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0]).get(); + client.prepareIndex(PERSONAL_INDEX_NAME_KIRK).setId(ID_TWO_2).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1]).get(); + } + } + + @Test + @Override + public void shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured() { + super.shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_positiveUserKirk() throws IOException { + super.shouldAuthenticateWithProxy_positiveUserKirk(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_positiveUserSpock() throws IOException { + super.shouldAuthenticateWithProxy_positiveUserSpock(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing() throws IOException { + super.shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing() throws IOException { + super.shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing(); + } + + @Test + @Override + public void shouldAuthenticateWithProxyWhenRolesHeaderIsMissing() throws IOException { + super.shouldAuthenticateWithProxyWhenRolesHeaderIsMissing(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy() throws IOException { + super.shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy(); + } + + @Test + @Override + public void shouldRetrieveEmptyListOfRoles() throws IOException { + super.shouldRetrieveEmptyListOfRoles(); + } + + @Test + @Override + public void shouldRetrieveSingleRoleFirstMate() throws IOException { + super.shouldRetrieveSingleRoleFirstMate(); + } + + @Test + @Override + public void shouldRetrieveSingleRoleCaptain() throws IOException { + super.shouldRetrieveSingleRoleCaptain(); + } + + @Test + @Override + public void shouldRetrieveMultipleRoles() throws IOException { + super.shouldRetrieveMultipleRoles(); + } + + // tests specific for extended proxy authentication + + @Test + public void shouldRetrieveCustomAttributeNameDepartment() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) + .header(HEADER_DEPARTMENT, DEPARTMENT_BRIDGE); + try(TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List customAttributes = response.getTextArrayFromJsonBody(POINTER_CUSTOM_ATTRIBUTES); + assertThat(customAttributes, hasSize(2)); + assertThat(customAttributes, containsInAnyOrder(USER_ATTRIBUTE_USERNAME_NAME, USER_ATTRIBUTE_DEPARTMENT_NAME)); + } + } + + @Test + public void shouldRetrieveCustomAttributeNameSkills() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) + .header(HEADER_SKILLS, "bilocation"); + try(TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List customAttributes = response.getTextArrayFromJsonBody(POINTER_CUSTOM_ATTRIBUTES); + assertThat(customAttributes, hasSize(2)); + assertThat(customAttributes, containsInAnyOrder(USER_ATTRIBUTE_USERNAME_NAME, USER_ATTRIBUTE_SKILLS_NAME)); + } + } + + @Test + public void shouldRetrieveMultipleCustomAttributes() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) + .header(HEADER_DEPARTMENT, DEPARTMENT_BRIDGE) + .header(HEADER_SKILLS, "bilocation"); + try(TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List customAttributes = response.getTextArrayFromJsonBody(POINTER_CUSTOM_ATTRIBUTES); + assertThat(customAttributes, hasSize(3)); + assertThat(customAttributes, containsInAnyOrder( + USER_ATTRIBUTE_DEPARTMENT_NAME, + USER_ATTRIBUTE_USERNAME_NAME, + USER_ATTRIBUTE_SKILLS_NAME) + ); + } + } + + @Test + public void shouldRetrieveUserRolesAndAttributesSoThatAccessToPersonalIndexIsPossible_positive() throws UnknownHostException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) + .header(HEADER_DEPARTMENT, DEPARTMENT_BRIDGE); + try(TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { + + HttpResponse response = client.get("/" + PERSONAL_INDEX_NAME_SPOCK + "/_search"); + + response.assertStatusCode(200); + assertThat(response.getLongFromJsonBody(POINTER_TOTAL_HITS), equalTo(1L)); + assertThat(response.getTextFromJsonBody(POINTER_FIRST_DOCUMENT_ID), equalTo(ID_ONE_1)); + assertThat(response.getTextFromJsonBody(POINTER_FIRST_DOCUMENT_INDEX), equalTo(PERSONAL_INDEX_NAME_SPOCK)); + assertThat(response.getTextFromJsonBody(POINTER_FIRST_DOCUMENT_SOURCE_TITLE), equalTo(TITLE_MAGNUM_OPUS)); + } + } + + @Test + public void shouldRetrieveUserRolesAndAttributesSoThatAccessToPersonalIndexIsPossible_negative() throws UnknownHostException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() + .sourceInetAddress(InetAddress.getByName(IP_PROXY)) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) + .header(HEADER_DEPARTMENT, DEPARTMENT_BRIDGE); + try(TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { + + HttpResponse response = client.get("/" + PERSONAL_INDEX_NAME_KIRK + "/_search"); + + response.assertStatusCode(403); + } + } + +} diff --git a/src/integrationTest/java/org/opensearch/security/http/ProxyAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/ProxyAuthenticationTest.java new file mode 100644 index 0000000000..be8a68fb9d --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/ProxyAuthenticationTest.java @@ -0,0 +1,121 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.io.IOException; +import java.util.Map; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AuthenticationBackend; +import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.HttpAuthenticator; +import org.opensearch.test.framework.XffConfig; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; + +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; + +/** +* Class used to run tests defined in the supper class against OpenSearch cluster with configured proxy authentication. +*/ +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class ProxyAuthenticationTest extends CommonProxyAuthenticationTests { + + private static final Map PROXY_AUTHENTICATOR_CONFIG = Map.of( + "user_header", HEADER_PROXY_USER, + "roles_header", HEADER_PROXY_ROLES + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) + .xff(new XffConfig(true).internalProxiesRegexp("127\\.0\\.0\\.10")) + .authc(new AuthcDomain("proxy_auth_domain", -5, true) + .httpAuthenticator(new HttpAuthenticator("proxy").challenge(false).config(PROXY_AUTHENTICATOR_CONFIG)) + .backend(new AuthenticationBackend("noop"))) + .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_ADMIN).roles(ROLE_ALL_INDEX_SEARCH, ROLE_PERSONAL_INDEX_SEARCH) + .rolesMapping(ROLES_MAPPING_CAPTAIN, ROLES_MAPPING_FIRST_MATE).build(); + + @Override + protected LocalCluster getCluster() { + return cluster; + } + + @Test + @Override + public void shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured() { + super.shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_positiveUserKirk() throws IOException { + super.shouldAuthenticateWithProxy_positiveUserKirk(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_positiveUserSpock() throws IOException { + super.shouldAuthenticateWithProxy_positiveUserSpock(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing() throws IOException { + super.shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing() throws IOException { + super.shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing(); + } + + @Test + @Override + public void shouldAuthenticateWithProxyWhenRolesHeaderIsMissing() throws IOException { + super.shouldAuthenticateWithProxyWhenRolesHeaderIsMissing(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy() throws IOException { + super.shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy(); + } + + @Test + @Override + public void shouldRetrieveEmptyListOfRoles() throws IOException { + super.shouldRetrieveEmptyListOfRoles(); + } + + @Test + @Override + public void shouldRetrieveSingleRoleFirstMate() throws IOException { + super.shouldRetrieveSingleRoleFirstMate(); + } + + @Test + @Override + public void shouldRetrieveSingleRoleCaptain() throws IOException { + super.shouldRetrieveSingleRoleCaptain(); + } + + @Test + @Override + public void shouldRetrieveMultipleRoles() throws IOException { + super.shouldRetrieveMultipleRoles(); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java b/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java index 574e1540c7..d5f6dbee07 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java +++ b/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java @@ -20,34 +20,72 @@ import static java.util.Objects.requireNonNull; +/** +* The class represents mapping between backend roles {@link #backendRoles} to OpenSearch role defined by field {@link #roleName}. The +* class provides convenient builder-like methods and can be serialized to JSON. Serialization to JSON is required to store the class +* in an OpenSearch index which contains Security plugin configuration. +*/ public class RolesMapping implements ToXContentObject { + + /** + * OpenSearch role name + */ private String roleName; + + /** + * Backend role names + */ private List backendRoles; -// private List hosts; - private List users; private boolean reserved = false; + /** + * Creates roles mapping to OpenSearch role defined by parameter role + * @param role OpenSearch role, must not be null. + */ public RolesMapping(Role role) { requireNonNull(role); this.roleName = requireNonNull(role.getName()); this.backendRoles = new ArrayList<>(); } + + /** + * Defines backend role names + * @param backendRoles backend roles names + * @return current {@link RolesMapping} instance + */ public RolesMapping backendRoles(String...backendRoles) { this.backendRoles.addAll(Arrays.asList(backendRoles)); return this; } + /** + * Determines if role is reserved + * @param reserved true for reserved roles + * @return current {@link RolesMapping} instance + */ public RolesMapping reserved(boolean reserved) { this.reserved = reserved; return this; } + + /** + * Returns OpenSearch role name + * @return role name + */ public String getRoleName() { return roleName; } + /** + * Controls serialization to JSON + * @param xContentBuilder must not be null + * @param params not used parameter, but required by the interface {@link ToXContentObject} + * @return builder form parameter xContentBuilder + * @throws IOException denotes error during serialization to JSON + */ @Override public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { xContentBuilder.startObject(); diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index d3f1ee1e16..ed58a9f60a 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -110,6 +110,11 @@ public TestSecurityConfig doNotFailOnForbidden(boolean doNotFailOnForbidden) { config.doNotFailOnForbidden(doNotFailOnForbidden); return this; } + + public TestSecurityConfig xff(XffConfig xffConfig) { + config.xffConfig(xffConfig); + return this; + } public TestSecurityConfig authc(AuthcDomain authcDomain) { config.authc(authcDomain); @@ -161,7 +166,7 @@ public static class Config implements ToXContentObject { private boolean anonymousAuth; private Boolean doNotFailOnForbidden; - + private XffConfig xffConfig; private Map authcDomainMap = new LinkedHashMap<>(); private AuthFailureListeners authFailureListeners; @@ -177,6 +182,11 @@ public Config doNotFailOnForbidden(Boolean doNotFailOnForbidden) { return this; } + public Config xffConfig(XffConfig xffConfig) { + this.xffConfig = xffConfig; + return this; + } + public Config authc(AuthcDomain authcDomain) { authcDomainMap.put(authcDomain.id, authcDomain); return this; @@ -197,9 +207,12 @@ public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params xContentBuilder.startObject(); xContentBuilder.startObject("dynamic"); - if (anonymousAuth) { + if (anonymousAuth || (xffConfig != null)) { xContentBuilder.startObject("http"); - xContentBuilder.field("anonymous_auth_enabled", true); + xContentBuilder.field("anonymous_auth_enabled", anonymousAuth); + if(xffConfig != null) { + xContentBuilder.field("xff", xffConfig); + } xContentBuilder.endObject(); } if(doNotFailOnForbidden != null) { diff --git a/src/integrationTest/java/org/opensearch/test/framework/XffConfig.java b/src/integrationTest/java/org/opensearch/test/framework/XffConfig.java new file mode 100644 index 0000000000..7214104597 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/XffConfig.java @@ -0,0 +1,82 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework; + +import java.io.IOException; + +import org.apache.commons.lang3.StringUtils; + +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; + +/** +*

+* XFF is an abbreviation of X-Forwarded-For. X-Forwarded-For is an HTTP header which contains client source IP address +* and additionally IP addresses of proxies which forward the request. +* The X-Forwarded-For header is used by HTTP authentication of type +*

+*
    +*
  1. proxy defined by class {@link org.opensearch.security.http.HTTPProxyAuthenticator}
  2. +*
  3. extended-proxy defined by the class {@link org.opensearch.security.http.proxy.HTTPExtendedProxyAuthenticator}
  4. +*
+* +*

+* The above authenticators use the X-Forwarded-For to determine if an HTTP request comes from trusted proxies. The trusted proxies +* are defined by a regular expression {@link #internalProxiesRegexp}. The proxy authentication can be applied only to HTTP requests +* which were forwarded by trusted HTTP proxies. +*

+* +*

+* The class can be serialized to JSON and then stored in an OpenSearch index which contains security plugin configuration. +*

+*/ +public class XffConfig implements ToXContentObject { + + private final boolean enabled; + + /** + * Regular expression used to determine if HTTP proxy is trusted or not. IP address of trusted proxies must match the regular + * expression defined by the below field. + */ + private String internalProxiesRegexp; + + private String remoteIpHeader; + + public XffConfig(boolean enabled) { + this.enabled = enabled; + } + + /** + * Builder-like method used to set value of the field {@link #internalProxiesRegexp} + * @param internalProxiesRegexp regular expression which matches IP address of a HTTP proxies if the proxies are trusted. + * @return builder + */ + public XffConfig internalProxiesRegexp(String internalProxiesRegexp) { + this.internalProxiesRegexp = internalProxiesRegexp; + return this; + } + + public XffConfig remoteIpHeader(String remoteIpHeader) { + this.remoteIpHeader = remoteIpHeader; + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("enabled", enabled); + xContentBuilder.field("internalProxies", internalProxiesRegexp); + if(StringUtils.isNoneBlank(remoteIpHeader)) { + xContentBuilder.field("remoteIpHeader", remoteIpHeader); + } + xContentBuilder.endObject(); + return xContentBuilder; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index b9bb11fcf5..ea27d7a2b1 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -55,6 +55,7 @@ import org.opensearch.test.framework.TestIndex; import org.opensearch.test.framework.TestSecurityConfig; import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.XffConfig; import org.opensearch.test.framework.audit.TestRuleAuditLogSink; import org.opensearch.test.framework.certificate.TestCertificates; @@ -402,6 +403,11 @@ public Builder anonymousAuth(boolean anonAuthEnabled) { return this; } + public Builder xff(XffConfig xffConfig){ + testSecurityConfig.xff(xffConfig); + return this; + } + public Builder loadConfigurationIntoIndex(boolean loadConfigurationIntoIndex) { this.loadConfigurationIntoIndex = loadConfigurationIntoIndex; return this; From 79f2079fa38f806380ab2a48c769edaea8916dc7 Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Tue, 29 Nov 2022 20:29:55 +0100 Subject: [PATCH 086/369] Test related to removing index by alias request and alias creation together with index (#2255) Signed-off-by: Lukasz Soszynski Signed-off-by: Lukasz Soszynski <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> --- .../security/SearchOperationTest.java | 77 ++++++++++++++++++- .../framework/matcher/AliasExistsMatcher.java | 66 ++++++++++++++++ .../framework/matcher/ClusterMatchers.java | 4 + 3 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java diff --git a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java index 5a48784970..f664ab47cd 100644 --- a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java +++ b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java @@ -121,6 +121,7 @@ import static org.hamcrest.Matchers.nullValue; import static org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions.Type.ADD; import static org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions.Type.REMOVE; +import static org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions.Type.REMOVE_INDEX; import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; import static org.opensearch.client.RequestOptions.DEFAULT; import static org.opensearch.rest.RestRequest.Method.DELETE; @@ -156,6 +157,7 @@ import static org.opensearch.test.framework.matcher.BulkResponseMatchers.bulkResponseContainExceptions; import static org.opensearch.test.framework.matcher.BulkResponseMatchers.failureBulkResponse; import static org.opensearch.test.framework.matcher.BulkResponseMatchers.successBulkResponse; +import static org.opensearch.test.framework.matcher.ClusterMatchers.aliasExists; import static org.opensearch.test.framework.matcher.ClusterMatchers.clusterContainSuccessSnapshot; import static org.opensearch.test.framework.matcher.ClusterMatchers.clusterContainTemplate; import static org.opensearch.test.framework.matcher.ClusterMatchers.clusterContainTemplateWithAlias; @@ -211,6 +213,8 @@ public class SearchOperationTest { public static final String INDEX_NAME_SONG_TRANSCRIPTION_JAZZ = "song-transcription-jazz"; public static final String MUSICAL_INDEX_TEMPLATE = "musical-index-template"; + public static final String ALIAS_CREATE_INDEX_WITH_ALIAS_POSITIVE = "alias_create_index_with_alias_positive"; + public static final String ALIAS_CREATE_INDEX_WITH_ALIAS_NEGATIVE = "alias_create_index_with_alias_negative"; public static final String UNDELETABLE_TEMPLATE_NAME = "undeletable-template-name"; @@ -299,16 +303,19 @@ public class SearchOperationTest { "indices:admin/create", "indices:admin/get", "indices:admin/delete", "indices:admin/close", "indices:admin/close*", "indices:admin/open", "indices:admin/resize", "indices:monitor/stats", "indices:monitor/settings/get", "indices:admin/settings/update", "indices:admin/mapping/put", - "indices:admin/mappings/get", "indices:admin/cache/clear" + "indices:admin/mappings/get", "indices:admin/cache/clear", "indices:admin/aliases" ) .on(INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("*"))); + private static User USER_ALLOWED_TO_CREATE_INDEX = new User("user-allowed-to-create-index") + .roles(new Role("create-index-role").indexPermissions("indices:admin/create").on("*")); + @ClassRule public static final LocalCluster cluster = new LocalCluster.Builder() .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) .authc(AUTHC_HTTPBASIC_INTERNAL) .users(ADMIN_USER, LIMITED_READ_USER, LIMITED_WRITE_USER, DOUBLE_READER_USER, REINDEXING_USER, UPDATE_DELETE_USER, - USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES) + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES, USER_ALLOWED_TO_CREATE_INDEX) .audit(new AuditConfiguration(true) .compliance(new AuditCompliance().enabled(true)) .filters(new AuditFilters().enabledRest(true).enabledTransport(true)) @@ -362,7 +369,9 @@ public void cleanData() throws ExecutionException, InterruptedException { } } - for(String aliasToBeDeleted : List.of(TEMPORARY_ALIAS_NAME, ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002)) { + List aliasesToBeDeleted = List.of(TEMPORARY_ALIAS_NAME, ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, + ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002, ALIAS_CREATE_INDEX_WITH_ALIAS_POSITIVE, ALIAS_CREATE_INDEX_WITH_ALIAS_NEGATIVE); + for(String aliasToBeDeleted : aliasesToBeDeleted) { if(indices.exists(new IndicesExistsRequest(aliasToBeDeleted)).get().isExists()) { AliasActions aliasAction = new AliasActions(AliasActions.Type.REMOVE).indices(SONG_INDEX_NAME).alias(aliasToBeDeleted); internalClient.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(aliasAction)).get(); @@ -1838,6 +1847,34 @@ public void deleteIndex_negative() throws IOException { } } + @Test + //required permissions: indices:admin/aliases, indices:admin/delete + public void shouldDeleteIndexByAliasRequest_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("delete_index_by_alias_request_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + IndicesAliasesRequest request = new IndicesAliasesRequest().addAliasAction(new AliasActions(REMOVE_INDEX).indices(indexName)); + + var response = restHighLevelClient.indices().updateAliases(request, DEFAULT); + + assertThat(response.isAcknowledged(), is(true)); + assertThat(cluster, not(indexExists(indexName))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES).withRestRequest(POST, "/_aliases")); + auditLogsRule.assertExactly(2, grantedPrivilege(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES, "IndicesAliasesRequest")); + auditLogsRule.assertExactly(2, auditPredicate(INDEX_EVENT).withEffectiveUser(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)); + } + + @Test + public void shouldDeleteIndexByAliasRequest_negative() throws IOException { + String indexName = "delete_index_by_alias_request_negative"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + IndicesAliasesRequest request = new IndicesAliasesRequest().addAliasAction(new AliasActions(REMOVE_INDEX).indices(indexName)); + + assertThatThrownBy(() -> restHighLevelClient.indices().updateAliases(request, DEFAULT), statusException(FORBIDDEN)); + } + } + @Test //required permissions: "indices:admin/get" public void getIndex_positive() throws IOException { @@ -2274,4 +2311,38 @@ public void clearIndexCache_negative() throws IOException { ); } } + + @Test + //required permissions: "indices:admin/create", "indices:admin/aliases" + public void shouldCreateIndexWithAlias_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("create_index_with_alias_positive"); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { + CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName) + .alias(new Alias(ALIAS_CREATE_INDEX_WITH_ALIAS_POSITIVE)); + + CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, DEFAULT); + + assertThat(createIndexResponse, isSuccessfulCreateIndexResponse(indexName)); + assertThat(cluster, indexExists(indexName)); + assertThat(internalClient, aliasExists(ALIAS_CREATE_INDEX_WITH_ALIAS_POSITIVE)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES).withRestRequest(PUT, "/index_operations_create_index_with_alias_positive")); + auditLogsRule.assertExactly(2, grantedPrivilege(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES, "CreateIndexRequest")); + auditLogsRule.assertExactly(2, auditPredicate(INDEX_EVENT).withEffectiveUser(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)); + } + + @Test + public void shouldCreateIndexWithAlias_negative() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("create_index_with_alias_negative"); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_CREATE_INDEX)) { + CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName) + .alias(new Alias(ALIAS_CREATE_INDEX_WITH_ALIAS_NEGATIVE)); + + assertThatThrownBy(() -> restHighLevelClient.indices().create(createIndexRequest, DEFAULT), statusException(FORBIDDEN)); + + assertThat(internalClient, not(aliasExists(ALIAS_CREATE_INDEX_WITH_ALIAS_NEGATIVE))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(USER_ALLOWED_TO_CREATE_INDEX).withRestRequest(PUT, "/index_operations_create_index_with_alias_negative")); + auditLogsRule.assertExactlyOne(missingPrivilege(USER_ALLOWED_TO_CREATE_INDEX, "CreateIndexRequest")); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java new file mode 100644 index 0000000000..ef2ffb365b --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java @@ -0,0 +1,66 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.admin.indices.alias.get.GetAliasesRequest; +import org.opensearch.action.admin.indices.alias.get.GetAliasesResponse; +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.AliasMetadata; +import org.opensearch.common.collect.ImmutableOpenMap; + +import static java.util.Objects.requireNonNull; +import static java.util.Spliterator.IMMUTABLE; +import static java.util.Spliterators.spliteratorUnknownSize; + +class AliasExistsMatcher extends TypeSafeDiagnosingMatcher { + + private final String aliasName; + + public AliasExistsMatcher(String aliasName) { + this.aliasName = requireNonNull(aliasName, "Alias name is required"); + } + + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + try { + GetAliasesResponse response = client.admin().indices().getAliases(new GetAliasesRequest(aliasName)).get(); + ImmutableOpenMap> aliases = response.getAliases(); + Set actualAliasNames = StreamSupport.stream(spliteratorUnknownSize(aliases.valuesIt(), IMMUTABLE), false) + .flatMap(Collection::stream) + .map(AliasMetadata::getAlias) + .collect(Collectors.toSet()); + if(actualAliasNames.contains(aliasName) == false) { + String existingAliases = String.join(", ", actualAliasNames); + mismatchDescription.appendText(" alias does not exist, defined aliases ").appendValue(existingAliases); + return false; + } + return true; + } catch (InterruptedException | ExecutionException e) { + mismatchDescription.appendText("Error occured during checking if cluster contains alias ") + .appendValue(e); + return false; + } + } + + @Override + public void describeTo(Description description) { + description.appendText("Cluster should contain ").appendValue(aliasName).appendText(" alias"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java index 7023301f34..eff1f4c803 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java @@ -52,6 +52,10 @@ public static Matcher snapshotInClusterDoesNotExists(String repositoryNa return new SnapshotInClusterDoesNotExist(repositoryName, snapshotName); } + public static Matcher aliasExists(String aliasName) { + return new AliasExistsMatcher(aliasName); + } + public static Matcher indexExists(String expectedIndexName) { return new IndexExistsMatcher(expectedIndexName); } From cad7c7201e902aeb711b407648d41d3c55fe7a49 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Date: Tue, 29 Nov 2022 16:25:16 -0500 Subject: [PATCH 087/369] Fixes CVE-2022-42920 by forcing bcel version to resovle to 6.6 (#2275) Signed-off-by: Darshit Chanpura Signed-off-by: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index c4e35bbff5..994ca7c24e 100644 --- a/build.gradle +++ b/build.gradle @@ -262,6 +262,7 @@ configurations { force "io.netty:netty-handler:${versions.netty}" force "io.netty:netty-transport:${versions.netty}" force "io.netty:netty-transport-native-unix-common:${versions.netty}" + force "org.apache.bcel:bcel:6.6.0" // This line should be removed once Spotbugs is upgraded to 4.7.4 } } From 1a8654d3491f2a619482644a135762d10d9f670d Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Fri, 2 Dec 2022 13:14:24 -0500 Subject: [PATCH 088/369] Modify reusable action to support workflow call acrossing repositories (#2290) Signed-off-by: Ryan Liang Signed-off-by: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> --- .../start-opensearch-with-one-plugin/action.yml | 10 ---------- .github/workflows/plugin_install.yml | 5 +++++ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/actions/start-opensearch-with-one-plugin/action.yml b/.github/actions/start-opensearch-with-one-plugin/action.yml index fd838a6cbf..d2f4b4f334 100644 --- a/.github/actions/start-opensearch-with-one-plugin/action.yml +++ b/.github/actions/start-opensearch-with-one-plugin/action.yml @@ -53,11 +53,6 @@ runs: del opensearch-min-${{ inputs.opensearch-version }}-SNAPSHOT-windows-x64-latest.zip shell: pwsh - # Move and rename the plugin for installation - - name: Move and rename the plugin for installation - run: mv ./build/distributions/${{ inputs.plugin-name }}-*.zip ${{ inputs.plugin-name }}.zip - shell: bash - # Install the plugin - name: Install Plugin into OpenSearch for Linux if: ${{ runner.os == 'Linux'}} @@ -118,8 +113,3 @@ runs: $Headers = @{ Authorization = $baseCredentials } Invoke-WebRequest -SkipCertificateCheck -Uri 'https://localhost:9200/_cat/plugins' -Headers $Headers; shell: pwsh - - - name: Run sanity tests - uses: gradle/gradle-build-action@v2 - with: - arguments: integTestRemote -Dtests.rest.cluster=localhost:9200 -Dtests.cluster=localhost:9200 -Dtests.clustername="opensearch" -Dhttps=true -Duser=admin -Dpassword=admin diff --git a/.github/workflows/plugin_install.yml b/.github/workflows/plugin_install.yml index 6df8fa3728..f757e0aefb 100644 --- a/.github/workflows/plugin_install.yml +++ b/.github/workflows/plugin_install.yml @@ -29,6 +29,11 @@ jobs: with: arguments: assemble + # Move and rename the plugin for installation + - name: Move and rename the plugin for installation + run: mv ./build/distributions/${{ env.PLUGIN_NAME }}-*.zip ${{ env.PLUGIN_NAME }}.zip + shell: bash + - name: Create Setup Script if: ${{ runner.os == 'Linux' }} run: | From 063c1e94d9fac0ea896f0c6a88006df218359857 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 2 Dec 2022 20:59:25 -0600 Subject: [PATCH 089/369] Cleanup download file output and improved error logging (#2293) Signed-off-by: Peter Nied --- .github/actions/start-opensearch-with-one-plugin/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/start-opensearch-with-one-plugin/action.yml b/.github/actions/start-opensearch-with-one-plugin/action.yml index d2f4b4f334..c6d9b9e68b 100644 --- a/.github/actions/start-opensearch-with-one-plugin/action.yml +++ b/.github/actions/start-opensearch-with-one-plugin/action.yml @@ -26,14 +26,14 @@ runs: # Download OpenSearch - name: Download OpenSearch for Windows - uses: peternied/download-file@v1 + uses: peternied/download-file@v2 if: ${{ runner.os == 'Windows' }} with: url: https://artifacts.opensearch.org/snapshots/core/opensearch/${{ inputs.opensearch-version }}-SNAPSHOT/opensearch-min-${{ inputs.opensearch-version }}-SNAPSHOT-windows-x64-latest.zip - name: Download OpenSearch for Linux - uses: peternied/download-file@v1 + uses: peternied/download-file@v2 if: ${{ runner.os == 'Linux' }} with: url: https://artifacts.opensearch.org/snapshots/core/opensearch/${{ inputs.opensearch-version }}-SNAPSHOT/opensearch-min-${{ inputs.opensearch-version }}-SNAPSHOT-linux-x64-latest.tar.gz From e7a120ccab9962fdb63710585ec83c13477dea13 Mon Sep 17 00:00:00 2001 From: John Mazanec Date: Tue, 6 Dec 2022 09:59:57 -0800 Subject: [PATCH 090/369] Integrate k-NN functionality with security plugin (#2274) Adds k-NN read only and full access roles to the default roles file. Adds k-NN model index to demo for system index. Signed-off-by: John Mazanec --- config/roles.yml | 23 +++++++++++++++++++++++ tools/install_demo_configuration.bat | 2 +- tools/install_demo_configuration.sh | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/config/roles.yml b/config/roles.yml index 1d081a5fd0..29f6fcbe5d 100644 --- a/config/roles.yml +++ b/config/roles.yml @@ -68,6 +68,29 @@ anomaly_full_access: - 'indices:admin/aliases/get' - 'indices:admin/mappings/get' +# Allow users to execute read only k-NN actions +knn_read_access: + reserved: true + cluster_permissions: + - 'cluster:admin/knn_search_model_action' + - 'cluster:admin/knn_get_model_action' + - 'cluster:admin/knn_stats_action' + +# Allow users to use all k-NN functionality +knn_full_access: + reserved: true + cluster_permissions: + - 'cluster:admin/knn_training_model_action' + - 'cluster:admin/knn_training_job_router_action' + - 'cluster:admin/knn_training_job_route_decision_info_action' + - 'cluster:admin/knn_warmup_action' + - 'cluster:admin/knn_delete_model_action' + - 'cluster:admin/knn_remove_model_from_cache_action' + - 'cluster:admin/knn_update_model_graveyard_action' + - 'cluster:admin/knn_search_model_action' + - 'cluster:admin/knn_get_model_action' + - 'cluster:admin/knn_stats_action' + # Allows users to read Notebooks notebooks_read_access: reserved: true diff --git a/tools/install_demo_configuration.bat b/tools/install_demo_configuration.bat index 410df30b08..06fbf5ec51 100755 --- a/tools/install_demo_configuration.bat +++ b/tools/install_demo_configuration.bat @@ -315,7 +315,7 @@ echo plugins.security.enable_snapshot_restore_privilege: true >> "%OPENSEARCH_CO echo plugins.security.check_snapshot_restore_write_privileges: true >> "%OPENSEARCH_CONF_FILE%" echo plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"] >> "%OPENSEARCH_CONF_FILE%" echo plugins.security.system_indices.enabled: true >> "%OPENSEARCH_CONF_FILE%" -echo plugins.security.system_indices.indices: [".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".opendistro-asynchronous-search-response*", ".replication-metadata-store"] >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.system_indices.indices: [".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models"] >> "%OPENSEARCH_CONF_FILE%" :: network.host >nul findstr /b /c:"network.host" "%OPENSEARCH_CONF_FILE%" && ( diff --git a/tools/install_demo_configuration.sh b/tools/install_demo_configuration.sh index 287ded080c..323618c89a 100755 --- a/tools/install_demo_configuration.sh +++ b/tools/install_demo_configuration.sh @@ -378,7 +378,7 @@ echo "plugins.security.enable_snapshot_restore_privilege: true" | $SUDO_CMD tee echo "plugins.security.check_snapshot_restore_write_privileges: true" | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null echo 'plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null echo 'plugins.security.system_indices.enabled: true' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null -echo 'plugins.security.system_indices.indices: [".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".opendistro-asynchronous-search-response*", ".replication-metadata-store"]' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null +echo 'plugins.security.system_indices.indices: [".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models"]' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null #network.host if $SUDO_CMD grep --quiet -i "^network.host" "$OPENSEARCH_CONF_FILE"; then From c8095bcc24a30034966e316cd64cb059d8de26d4 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Tue, 6 Dec 2022 17:06:41 -0600 Subject: [PATCH 091/369] Automatically add `untriaged` label on issues (#2297) Sometimes when new issues are opened, they don't use the template or are transfered in from another repo, or even are reopened when we ran triage slightly differently. This automation adds the `untriaged` tag if any of these states are applied. Coming from https://github.com/opensearch-project/.github/pull/111 Signed-off-by: Peter Nied --- .github/workflows/add-untriaged.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/add-untriaged.yml diff --git a/.github/workflows/add-untriaged.yml b/.github/workflows/add-untriaged.yml new file mode 100644 index 0000000000..15b9a55651 --- /dev/null +++ b/.github/workflows/add-untriaged.yml @@ -0,0 +1,19 @@ +name: Apply 'untriaged' label during issue lifecycle + +on: + issues: + types: [opened, reopened, transferred] + +jobs: + apply-label: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v6 + with: + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['untriaged'] + }) From 9ae87ae4240d5af7fbec7bd45eef29e97ae525cb Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Wed, 7 Dec 2022 17:16:41 -0500 Subject: [PATCH 092/369] [Feature] Triage Agenda (#2292) Signed-off-by: Stephen Crawford Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- CONTRIBUTING.md | 53 ++---------------------------------------- TRIAGING.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 51 deletions(-) create mode 100644 TRIAGING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d1f1a92e4..202f8977b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,56 +4,7 @@ OpenSearch is a community project that is built and maintained by people just like **you**. [This document](https://github.com/opensearch-project/.github/blob/main/CONTRIBUTING.md) explains how you can contribute to this and related projects. -The brief summary below outlines contribution guidelines specific to the OpenSearch-Security Repo. -## Triaging +Visit the following link(s) for more information on specific practices: -The maintainers of the OpenSearch-Security Repo seek to promote an inclusive and engaged community of contributors. In order to facilitate this, weekly triage meetings are open-to-all and attendance is encouraged for anyone who hopes to contribute, discuss an issue, or learn more about the project. - -### Do I need to attend for my issue to be addressed/triaged? - -Attendance is not required for your issue to be triaged or addressed. All new issues are triaged weekly. - -### What happens if my issue does not get covered this time? - -Each meeting we seek to address all new issues. However, should we run out of time before your issue is discussed, you are always welcome to attend the next meeting or to follow up on the issue post itself. - -### How do I join the Backlog & Triage meeting? - -Meetings are hosted regularly at 3 PM Eastern Time (Noon Pacific Time) and can be joined via the links posted on the [Upcoming Events](https://opensearch.org/events) webpage. - -After joining the Zoom meeting, you can enable your video / voice to join the discussion. If you do not have a webcam or microphone available, you can still join in via the text chat. - -If you have an issue you'd like to bring forth please consider getting a link to the issue so it can be presented to everyone in the meeting. - -### Is there an agenda for each week? - -Meetings are lightly structured such that [new issues](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Auntriaged) are reviewed first, then the [sprint backlog](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A%22sprint+backlog%22), and finally the [backlog](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Atriaged+-label%3A%22sprint+backlog%22). There is no specific ordering within each category. - -If you have an issue you would like to discuss but do not have the ability to attend the entire meeting please attend when is best for you and signal that you have an issue to discuss when you arrive. - -### Do I need to have already contributed to the project to attend a triage meeting? - -No, all are welcome and encouraged to attend. Attending the Backlog & Triage meetings is a great way for a new contributor to learn about the project as well as explore different avenues of contribution. - -### What if I have an issue that is almost a duplicate, should I open a new one to be triaged? - -You can always open an [issue](ttps://github.com/opensearch-project/security/issues/new/choose) including one that you think may be a duplicate. However, in cases where you believe there is an important distinction to be made between an existing issue and your newly created one, you are encouraged to attend the triaging meeting to explain. - -### What if I have follow-up questions on an issue? - -If you have an existing issue you would like to discuss, you can always comment on the issue itself. Alternatively, you are welcome to come to the triage meeting to discuss. - -### Is this meeting a good place to get help setting up security features on my OpenSearch instance? - -While we are always happy to help the community, the best resource for implementation questions is [the OpenSearch forum](https://forum.opensearch.org/c/security/3). - -There you can find answers to many common questions as well as speak with implementation experts. - -### Is this where I should bring up potential security vulnerabilities? - -Due to the sensitive nature of security vulnerabilities, please report all potential vulnerabilities directly by following the steps outlined on the [SECURITY.md](https://github.com/opensearch-project/security/blob/main/SECURITY.md) document. - -### Who should I contact if I have further questions? - -You can always file an [issue](ttps://github.com/opensearch-project/security/issues/new/choose) for any question you have about the project. Alternatively, you can reach out to specific contacts helping to organize the project: Stephen Crawford (steecraw@amazon.com), Dave Lago (davelago@amazon.com), and Peter Nied (petern@amazon.com). +- [Triaging](./TRIAGING.md) diff --git a/TRIAGING.md b/TRIAGING.md new file mode 100644 index 0000000000..c9e129c982 --- /dev/null +++ b/TRIAGING.md @@ -0,0 +1,62 @@ + + +The maintainers of the OpenSearch-Security Repo seek to promote an inclusive and engaged community of contributors. In order to facilitate this, weekly triage meetings are open-to-all and attendance is encouraged for anyone who hopes to contribute, discuss an issue, or learn more about the project. To learn more about contributing to the OpenSearch-security Repo visit the [Contributing](./CONTRIBUTING.md) documentation. + +### Do I need to attend for my issue to be addressed/triaged? + +Attendance is not required for your issue to be triaged or addressed. All new issues are triaged weekly. + +### What happens if my issue does not get covered this time? + +Each meeting we seek to address all new issues. However, should we run out of time before your issue is discussed, you are always welcome to attend the next meeting or to follow up on the issue post itself. + +### How do I join the Backlog & Triage meeting? + +Meetings are hosted regularly at 3 PM Eastern Time (Noon Pacific Time) and can be joined via the links posted on the [Upcoming Events](https://opensearch.org/events) webpage. + +After joining the Zoom meeting, you can enable your video / voice to join the discussion. If you do not have a webcam or microphone available, you can still join in via the text chat. + +If you have an issue you'd like to bring forth please consider getting a link to the issue so it can be presented to everyone in the meeting. + +### Is there an agenda for each week? + +Meetings are lightly structured as follows: + +1. Announcements: If there are any announcements to be made they will happen at the start of the meeting. +2. Review of new issues: The meetings always start with reviewing all untriaged issues for the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Auntriaged) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aissue+is%3Aopen+-label%3Atriaged) repo. We will also provide an opportunity for any issues introduced since the last meeting but already triaged to be discussed. +3. Sprint backlog review: Next, we will review persisting issues in the sprint backlog for the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A%22sprint+backlog%22) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aopen+is%3Aissue+label%3A%22sprint+backlog%22) repo, checking to see that issues have not gone stale or been addressed. +4. Backlog review: Finally, any remaining time will be spent reviewing the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Atriaged+-label%3A%22sprint+backlog%22) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aopen+is%3Aissue+label%3Atriaged+-label%3A%22sprint+backlog%22+) repo. + +There is no specific ordering within each category. + +If you have an issue you would like to discuss but do not have the ability to attend the entire meeting please attend when is best for you and signal that you have an issue to discuss when you arrive. + +### Do I need to have already contributed to the project to attend a triage meeting? + +No, all are welcome and encouraged to attend. Attending the Backlog & Triage meetings is a great way for a new contributor to learn about the project as well as explore different avenues of contribution. + +### What if I have an issue that is almost a duplicate, should I open a new one to be triaged? + +You can always open an [issue](ttps://github.com/opensearch-project/security/issues/new/choose) including one that you think may be a duplicate. However, in cases where you believe there is an important distinction to be made between an existing issue and your newly created one, you are encouraged to attend the triaging meeting to explain. + +### What if I have follow-up questions on an issue? + +If you have an existing issue you would like to discuss, you can always comment on the issue itself. Alternatively, you are welcome to come to the triage meeting to discuss. + +### Is this meeting a good place to get help setting up security features on my OpenSearch instance? + +While we are always happy to help the community, the best resource for implementation questions is [the OpenSearch forum](https://forum.opensearch.org/c/security/3). + +There you can find answers to many common questions as well as speak with implementation experts. + +### What if my issue is critical to OpenSearch operations, do I have to wait for the weekly meeting for it to be addressed? + +All new issues for the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Auntriaged) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aissue+is%3Aopen+-label%3Atriaged) repo are reviewed daily to check for critical issues which require immediate triaging. If an issue relates to a severe concern for OpenSearch operation, it will be triaged by a maintainer mid-week. You can still come to discuss an issue at the following meeting even if it has already been triaged during the week. + +### Is this where I should bring up potential security vulnerabilities? + +Due to the sensitive nature of security vulnerabilities, please report all potential vulnerabilities directly by following the steps outlined on the [SECURITY.md](https://github.com/opensearch-project/security/blob/main/SECURITY.md) document. + +### Who should I contact if I have further questions? + +You can always file an [issue](ttps://github.com/opensearch-project/security/issues/new/choose) for any question you have about the project. Alternatively, you can reach out to specific contacts helping to organize the project: Stephen Crawford (steecraw@amazon.com), Dave Lago (davelago@amazon.com), and Peter Nied (petern@amazon.com). From aad93794227fca92703ceda24459038703c2a8cc Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Wed, 7 Dec 2022 18:00:02 -0500 Subject: [PATCH 093/369] Fix behavior of assume yes in install_demo_configuration tool (#2308) Signed-off-by: Stephen Crawford --- .github/actions/start-opensearch-with-one-plugin/action.yml | 4 ++++ .github/workflows/bwc-tests.yml | 2 +- .github/workflows/plugin_install.yml | 4 ++-- tools/install_demo_configuration.bat | 5 +++++ tools/install_demo_configuration.sh | 4 ++++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/actions/start-opensearch-with-one-plugin/action.yml b/.github/actions/start-opensearch-with-one-plugin/action.yml index c6d9b9e68b..b562851b0c 100644 --- a/.github/actions/start-opensearch-with-one-plugin/action.yml +++ b/.github/actions/start-opensearch-with-one-plugin/action.yml @@ -113,3 +113,7 @@ runs: $Headers = @{ Authorization = $baseCredentials } Invoke-WebRequest -SkipCertificateCheck -Uri 'https://localhost:9200/_cat/plugins' -Headers $Headers; shell: pwsh + + - if: always() + run: cat ./opensearch-${{ inputs.opensearch-version }}-SNAPSHOT/logs/opensearch.log + shell: bash diff --git a/.github/workflows/bwc-tests.yml b/.github/workflows/bwc-tests.yml index ed65970a30..4a378c3e7d 100644 --- a/.github/workflows/bwc-tests.yml +++ b/.github/workflows/bwc-tests.yml @@ -1,4 +1,4 @@ -name: Backwards Compability Suite +name: Backwards Compatibility Suite on: [workflow_dispatch] diff --git a/.github/workflows/plugin_install.yml b/.github/workflows/plugin_install.yml index f757e0aefb..d3900381f3 100644 --- a/.github/workflows/plugin_install.yml +++ b/.github/workflows/plugin_install.yml @@ -39,14 +39,14 @@ jobs: run: | cat > setup.sh <<'EOF' chmod +x ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/plugins/${{ env.PLUGIN_NAME }}/tools/install_demo_configuration.sh - /bin/bash -c "yes | ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/plugins/${{ env.PLUGIN_NAME }}/tools/install_demo_configuration.sh" + /bin/bash -c "./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/plugins/${{ env.PLUGIN_NAME }}/tools/install_demo_configuration.sh -y" EOF - name: Create Setup Script if: ${{ runner.os == 'Windows' }} run: | New-Item .\setup.bat -type file - Set-Content .\setup.bat -Value "powershell.exe -noexit -command `"echo 'y' | .\opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT\plugins\${{ env.PLUGIN_NAME }}\tools\install_demo_configuration.bat`"" + Set-Content .\setup.bat -Value "powershell.exe .\opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT\plugins\${{ env.PLUGIN_NAME }}\tools\install_demo_configuration.bat -y" Get-Content .\setup.bat - name: Run Opensearch with A Single Plugin diff --git a/tools/install_demo_configuration.bat b/tools/install_demo_configuration.bat index 06fbf5ec51..88e3c185c0 100755 --- a/tools/install_demo_configuration.bat +++ b/tools/install_demo_configuration.bat @@ -62,6 +62,11 @@ if %cluster_mode% == 0 ( ) ) +if %assumeyes% == 1 ( + set "initsecurity=1" + set "cluster_mode=1" +) + set BASE_DIR=%SCRIPT_DIR%\..\..\..\ if not exist %BASE_DIR% ( echo "basedir does not exist" diff --git a/tools/install_demo_configuration.sh b/tools/install_demo_configuration.sh index 323618c89a..fc0d7de095 100755 --- a/tools/install_demo_configuration.sh +++ b/tools/install_demo_configuration.sh @@ -97,6 +97,10 @@ if [ "$cluster_mode" == 0 ] && [ "$assumeyes" == 0 ]; then esac fi +if [ "$assumeyes" == 1 ]; then + cluster_mode=1 + initsecurity=1 +fi set -e BASE_DIR="$DIR/../../.." From 2c20be065775a02ef376abd273c1d5fc8976e8c7 Mon Sep 17 00:00:00 2001 From: Chris White Date: Thu, 8 Dec 2022 11:55:20 -0800 Subject: [PATCH 094/369] Added secure settings for ssl related passwords: (#2296) Added secure settings for ssl related passwords: * plugins.security.ssl.http.pemkey_password_secure * plugins.security.ssl.http.keystore_password_secure * plugins.security.ssl.http.keystore_keypassword_secure * plugins.security.ssl.http.truststore_password_secure * plugins.security.ssl.transport.pemkey_password_secure * plugins.security.ssl.transport.server.pemkey_password_secure * plugins.security.ssl.transport.client.pemkey_password_secure * plugins.security.ssl.transport.keystore_password_secure * plugins.security.ssl.transport.keystore_keypassword_secure * plugins.security.ssl.transport.server.keystore_keypassword_secure * plugins.security.ssl.transport.client.keystore_keypassword_secure * plugins.security.ssl.transport.truststore_password_secure Signed-off-by: Chris White --- .../backend/LDAPAuthorizationBackend.java | 13 +- .../util/SettingsBasedSSLConfigurator.java | 10 +- .../util/SettingsBasedSSLConfiguratorV4.java | 12 +- .../auditlog/sink/ExternalOpenSearchSink.java | 9 +- .../security/auditlog/sink/WebhookSink.java | 4 +- .../security/ssl/DefaultSecurityKeyStore.java | 60 +- .../ssl/OpenSearchSecuritySSLPlugin.java | 19 +- .../security/ssl/SecureSSLSettings.java | 127 ++++ .../security/ssl/util/SSLConfigConstants.java | 13 - .../security/ssl/util/SSLRequestHelper.java | 4 +- .../sanity/tests/SecurityRestTestCase.java | 29 +- .../org/opensearch/security/ssl/SSLTest.java | 699 ++++++++++-------- .../security/ssl/SecureSSLSettingsTest.java | 52 ++ .../SettingsBasedSSLConfiguratorV4Test.java | 64 +- 14 files changed, 694 insertions(+), 421 deletions(-) create mode 100644 src/main/java/org/opensearch/security/ssl/SecureSSLSettings.java create mode 100644 src/test/java/org/opensearch/security/ssl/SecureSSLSettingsTest.java diff --git a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java index e6adea241a..1e7adeb488 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java @@ -83,6 +83,9 @@ import org.opensearch.security.user.AuthCredentials; import org.opensearch.security.user.User; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD; + public class LDAPAuthorizationBackend implements AuthorizationBackend { private static final AtomicInteger CONNECTION_COUNTER = new AtomicInteger(); @@ -566,8 +569,7 @@ private static void configureSSL(final ConnectionConfig config, final Settings s final KeyStore trustStore = PemKeyReader.loadKeyStore( PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, configPath, !trustAll), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, - SSLConfigConstants.DEFAULT_STORE_PASSWORD), + SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); final List trustStoreAliases = settings.getAsList(ConfigConstants.LDAPS_JKS_TRUST_ALIAS, null); @@ -576,12 +578,11 @@ private static void configureSSL(final ConnectionConfig config, final Settings s final KeyStore keyStore = PemKeyReader.loadKeyStore( PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, settings, configPath, enableClientAuth), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, + SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD), settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE)); - final String keyStorePassword = settings.get( - SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, - SSLConfigConstants.DEFAULT_STORE_PASSWORD); + final String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD + .getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD); final String keyStoreAlias = settings.get(ConfigConstants.LDAPS_JKS_CERT_ALIAS, null); final String[] keyStoreAliases = keyStoreAlias == null ? null : new String[] { keyStoreAlias }; diff --git a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfigurator.java b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfigurator.java index 03800feace..ea99625e6b 100644 --- a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfigurator.java +++ b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfigurator.java @@ -48,6 +48,9 @@ import org.opensearch.security.ssl.util.SSLConfigConstants; import org.opensearch.security.support.PemKeyReader; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD; + public class SettingsBasedSSLConfigurator { private static final Logger log = LogManager.getLogger(SettingsBasedSSLConfigurator.class); @@ -305,8 +308,7 @@ private void initFromKeyStore() throws SSLConfigException { trustStore = PemKeyReader.loadKeyStore( PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, configPath, !isTrustAllEnabled()), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, - SSLConfigConstants.DEFAULT_STORE_PASSWORD), + SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); } catch (Exception e) { throw new SSLConfigException("Error loading trust store from " @@ -321,7 +323,7 @@ private void initFromKeyStore() throws SSLConfigException { keyStore = PemKeyReader.loadKeyStore( PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, settings, configPath, enableSslClientAuth), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, + SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD), settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE)); } catch (Exception e) { @@ -329,7 +331,7 @@ private void initFromKeyStore() throws SSLConfigException { + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH), e); } - String keyStorePassword = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, + String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD); effectiveKeyPassword = keyStorePassword == null || keyStorePassword.isEmpty() ? null : keyStorePassword.toCharArray(); diff --git a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java index 4f34b04499..013d8b70d7 100644 --- a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java +++ b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java @@ -49,6 +49,9 @@ import org.opensearch.security.ssl.util.SSLConfigConstants; import org.opensearch.security.support.PemKeyReader; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD; + public class SettingsBasedSSLConfiguratorV4 { private static final Logger log = LogManager.getLogger(SettingsBasedSSLConfigurator.class); @@ -306,8 +309,7 @@ private void initFromKeyStore() throws SSLConfigException { trustStore = PemKeyReader.loadKeyStore( PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, configPath, !isTrustAllEnabled()), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, - SSLConfigConstants.DEFAULT_STORE_PASSWORD), + SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); } catch (Exception e) { throw new SSLConfigException("Error loading trust store from " @@ -322,7 +324,7 @@ private void initFromKeyStore() throws SSLConfigException { keyStore = PemKeyReader.loadKeyStore( PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, settings, configPath, enableSslClientAuth), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, + SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD), settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE)); } catch (Exception e) { @@ -330,8 +332,8 @@ private void initFromKeyStore() throws SSLConfigException { + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH), e); } - String keyStorePassword = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, - SSLConfigConstants.DEFAULT_STORE_PASSWORD); + String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD + .getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD); effectiveKeyPassword = keyStorePassword == null || keyStorePassword.isEmpty() ? null : keyStorePassword.toCharArray(); effectiveKeyAlias = getSetting(CERT_ALIAS); diff --git a/src/main/java/org/opensearch/security/auditlog/sink/ExternalOpenSearchSink.java b/src/main/java/org/opensearch/security/auditlog/sink/ExternalOpenSearchSink.java index e03d3e32dd..2aac222181 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/ExternalOpenSearchSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/ExternalOpenSearchSink.java @@ -31,6 +31,9 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.PemKeyReader; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD; + public final class ExternalOpenSearchSink extends AuditLogSink { private static final List DEFAULT_TLS_PROTOCOLS = Arrays.asList(new String[] { "TLSv1.2", "TLSv1.1"}); @@ -117,14 +120,14 @@ public ExternalOpenSearchSink(final String name, final Settings settings, final } else { final KeyStore trustStore = PemKeyReader.loadKeyStore(PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, configPath, true) - , settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, SSLConfigConstants.DEFAULT_STORE_PASSWORD) + , SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings) , settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); //for client authentication final KeyStore keyStore = PemKeyReader.loadKeyStore(PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, settings, configPath, enableSslClientAuth) - , settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, SSLConfigConstants.DEFAULT_STORE_PASSWORD) + , SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD) , settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE)); - final String keyStorePassword = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, SSLConfigConstants.DEFAULT_STORE_PASSWORD); + final String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD); effectiveKeyPassword = keyStorePassword==null||keyStorePassword.isEmpty()?null:keyStorePassword.toCharArray(); effectiveKeyAlias = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_JKS_CERT_ALIAS, null); diff --git a/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java b/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java index af4525fcba..3205eb1fd6 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java @@ -50,6 +50,8 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.PemKeyReader; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD; + public class WebhookSink extends AuditLogSink { /* HttpClient is thread safe */ @@ -328,7 +330,7 @@ public KeyStore run() { } else { return PemKeyReader.loadKeyStore(PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, configPath, false) - , settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, SSLConfigConstants.DEFAULT_STORE_PASSWORD) + , SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings) , settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); } } catch(Exception ex) { diff --git a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java index d186b26869..8b704c84d3 100644 --- a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java +++ b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java @@ -88,6 +88,19 @@ import org.opensearch.security.ssl.util.SSLConfigConstants; import org.opensearch.transport.NettyAllocator; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_HTTP_KEYSTORE_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_HTTP_PEMKEY_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_HTTP_TRUSTSTORE_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_KEYPASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_SERVER_KEYSTORE_KEYPASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD; + public class DefaultSecurityKeyStore implements SecurityKeyStore { private static final String DEFAULT_STORE_TYPE = "JKS"; @@ -293,9 +306,7 @@ public void initTransportSSLConfig() { true); final String keystoreType = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, DEFAULT_STORE_TYPE); - final String keystorePassword = settings.get( - SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, - SSLConfigConstants.DEFAULT_STORE_PASSWORD); + final String keystorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD); final String truststoreFilePath = resolve( SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, true); @@ -307,9 +318,7 @@ public void initTransportSSLConfig() { final String truststoreType = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE, DEFAULT_STORE_TYPE); - final String truststorePassword = settings.get( - SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, - SSLConfigConstants.DEFAULT_STORE_PASSWORD); + final String truststorePassword = SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings); KeystoreProps keystoreProps = new KeystoreProps( keystoreFilePath, keystoreType, keystorePassword); @@ -328,10 +337,8 @@ public void initTransportSSLConfig() { null); final String keystoreClientAlias = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_ALIAS, null); - final String serverKeyPassword = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_KEYSTORE_KEYPASSWORD, - keystorePassword); - final String clientKeyPassword = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_KEYPASSWORD, - keystorePassword); + final String serverKeyPassword = SECURITY_SSL_TRANSPORT_SERVER_KEYSTORE_KEYPASSWORD.getSetting(settings, keystorePassword); + final String clientKeyPassword = SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_KEYPASSWORD.getSetting(settings, keystorePassword); // we require all aliases to be set explicitly // because they should be different for client and server @@ -355,8 +362,7 @@ public void initTransportSSLConfig() { null); final String keystoreAlias = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, null); - final String keyPassword = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD, - keystorePassword); + final String keyPassword = SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD.getSetting(settings, keystorePassword); certFromKeystore = new CertFromKeystore(keystoreProps, keystoreAlias, keyPassword); certFromTruststore = new CertFromTruststore(truststoreProps, truststoreAlias); @@ -386,14 +392,14 @@ public void initTransportSSLConfig() { resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMCERT_FILEPATH, true), resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_FILEPATH, true), resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMTRUSTEDCAS_FILEPATH, true), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_PASSWORD) + SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_PASSWORD.getSetting(settings) ); CertFileProps serverCertProps = new CertFileProps( resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMCERT_FILEPATH, true), resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_FILEPATH, true), resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMTRUSTEDCAS_FILEPATH, true), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_PASSWORD) + SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_PASSWORD.getSetting(settings) ); certFromFile = new CertFromFile(clientCertProps, serverCertProps); @@ -402,7 +408,7 @@ public void initTransportSSLConfig() { resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, true), resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, true), resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, true), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD) + SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD.getSetting(settings) ); certFromFile = new CertFromFile(certProps); } @@ -447,12 +453,9 @@ public void initHttpSSLConfig() { true); final String keystoreType = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_TYPE, DEFAULT_STORE_TYPE); - final String keystorePassword = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_PASSWORD, - SSLConfigConstants.DEFAULT_STORE_PASSWORD); + final String keystorePassword = SECURITY_SSL_HTTP_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD); - final String keyPassword = settings.get( - SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD, - keystorePassword); + final String keyPassword = SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD.getSetting(settings, keystorePassword); final String keystoreAlias = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, null); @@ -486,13 +489,12 @@ public void initHttpSSLConfig() { final String truststoreFilePath = resolve( SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, true); - final String truststoreType = settings - .get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_TYPE, DEFAULT_STORE_TYPE); - final String truststorePassword = settings.get( - SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_PASSWORD, - SSLConfigConstants.DEFAULT_STORE_PASSWORD); - final String truststoreAlias = settings - .get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_ALIAS, null); + final String truststoreType = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_TYPE, + DEFAULT_STORE_TYPE); + final String truststorePassword = SECURITY_SSL_HTTP_TRUSTSTORE_PASSWORD.getSetting(settings); + + final String truststoreAlias = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_ALIAS, + null); KeystoreProps truststoreProps = new KeystoreProps( truststoreFilePath, truststoreType, truststorePassword); @@ -525,7 +527,7 @@ public void initHttpSSLConfig() { resolve(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, true), resolve(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, true), trustedCas, - settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_PASSWORD) + SECURITY_SSL_HTTP_PEMKEY_PASSWORD.getSetting(settings) ); CertFromFile certFromFile = new CertFromFile(certFileProps); @@ -533,7 +535,7 @@ public void initHttpSSLConfig() { httpSslContext = buildSSLServerContext( certFromFile.getServerPemKey(), certFromFile.getServerPemCert(), certFromFile.getServerTrustedCas(), - settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_PASSWORD), + SECURITY_SSL_HTTP_PEMKEY_PASSWORD.getSetting(settings), getEnabledSSLCiphers(this.sslHTTPProvider, true), sslHTTPProvider, httpClientAuthMode); setHttpSSLCerts(certFromFile.getCerts()); diff --git a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java index 52983e7814..fa1539596d 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java @@ -318,15 +318,17 @@ public Collection createComponents(Client localClient, ClusterService cl @Override public List> getSettings() { List> settings = new ArrayList>(); + + // add secure settings (with fallbacks for legacy insecure settings) + settings.addAll(SecureSSLSettings.getSecureSettings()); + + // add non secure settings settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_PASSWORD, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_TYPE, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_ALIAS, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_PASSWORD, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_TYPE, Property.NodeScope, Property.Filtered)); settings.add(Setting.boolSetting(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, OPENSSL_SUPPORTED, Property.NodeScope, Property.Filtered)); settings.add(Setting.boolSetting(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_DEFAULT, Property.NodeScope, Property.Filtered)); @@ -335,10 +337,8 @@ public List> getSettings() { settings.add(Setting.boolSetting(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, true, Property.NodeScope, Property.Filtered)); settings.add(Setting.boolSetting(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, true, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE, Property.NodeScope, Property.Filtered)); settings.add(Setting.listSetting(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, Collections.emptyList(), Function.identity(), Property.NodeScope));//not filtered here settings.add(Setting.listSetting(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, Collections.emptyList(), Function.identity(), Property.NodeScope));//not filtered here @@ -352,34 +352,27 @@ public List> getSettings() { if(extendedKeyUsageEnabled) { settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_KEYSTORE_ALIAS, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_TRUSTSTORE_ALIAS, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_KEYSTORE_KEYPASSWORD, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_ALIAS, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_TRUSTSTORE_ALIAS, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_KEYPASSWORD, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMCERT_FILEPATH, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_PASSWORD, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMTRUSTEDCAS_FILEPATH, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMCERT_FILEPATH, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_PASSWORD, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMTRUSTEDCAS_FILEPATH, Property.NodeScope, Property.Filtered)); } else { settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_ALIAS, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, Property.NodeScope, Property.Filtered)); } settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_PASSWORD, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(SSLConfigConstants.SSECURITY_SSL_HTTP_CRL_FILE, Property.NodeScope, Property.Filtered)); @@ -389,10 +382,10 @@ public List> getSettings() { settings.add(Setting.boolSetting(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_DISABLE_CRLDP, false, Property.NodeScope, Property.Filtered)); settings.add(Setting.boolSetting(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_DISABLE_OCSP, false, Property.NodeScope, Property.Filtered)); settings.add(Setting.longSetting(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATION_DATE, -1, -1, Property.NodeScope, Property.Filtered)); + return settings; } - @Override public Settings additionalSettings() { final Settings.Builder builder = Settings.builder(); diff --git a/src/main/java/org/opensearch/security/ssl/SecureSSLSettings.java b/src/main/java/org/opensearch/security/ssl/SecureSSLSettings.java new file mode 100644 index 0000000000..272a2ce887 --- /dev/null +++ b/src/main/java/org/opensearch/security/ssl/SecureSSLSettings.java @@ -0,0 +1,127 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.ssl; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.common.settings.SecureSetting; +import org.opensearch.common.settings.SecureString; +import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.Settings; + +import static org.opensearch.security.ssl.util.SSLConfigConstants.DEFAULT_STORE_PASSWORD; + +/** + * Container for secured settings (passwords for certs, keystores) and the now deprecated original settings + */ +public final class SecureSSLSettings { + private static final Logger LOG = LogManager.getLogger(SecureSSLSettings.class); + + private static final String SECURE_SUFFIX = "_secure"; + private static final String PREFIX = "plugins.security.ssl"; + private static final String HTTP_PREFIX = PREFIX + ".http"; + private static final String TRANSPORT_PREFIX = PREFIX + ".transport"; + + public enum SSLSetting { + // http settings + SECURITY_SSL_HTTP_PEMKEY_PASSWORD(HTTP_PREFIX + ".pemkey_password"), + SECURITY_SSL_HTTP_KEYSTORE_PASSWORD(HTTP_PREFIX + ".keystore_password"), + SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD(HTTP_PREFIX + ".keystore_keypassword"), + SECURITY_SSL_HTTP_TRUSTSTORE_PASSWORD(HTTP_PREFIX + ".truststore_password", DEFAULT_STORE_PASSWORD), + + // transport settings + SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD(TRANSPORT_PREFIX + ".pemkey_password"), + SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_PASSWORD(TRANSPORT_PREFIX + ".server.pemkey_password"), + SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_PASSWORD(TRANSPORT_PREFIX + ".client.pemkey_password"), + SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD(TRANSPORT_PREFIX + ".keystore_password"), + SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD(TRANSPORT_PREFIX + ".keystore_keypassword"), + SECURITY_SSL_TRANSPORT_SERVER_KEYSTORE_KEYPASSWORD(TRANSPORT_PREFIX + ".server.keystore_keypassword"), + SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_KEYPASSWORD(TRANSPORT_PREFIX + ".client.keystore_keypassword"), + SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD(TRANSPORT_PREFIX + ".truststore_password", DEFAULT_STORE_PASSWORD); + + SSLSetting(String insecurePropertyName) { + this(insecurePropertyName, null); + } + + SSLSetting(String insecurePropertyName, String defaultValue) { + this.insecurePropertyName = insecurePropertyName; + this.propertyName = String.format("%s%s", this.insecurePropertyName, SECURE_SUFFIX); + this.defaultValue = defaultValue; + } + + public final String insecurePropertyName; + + public final String propertyName; + + public final String defaultValue; + + public Setting asSetting() { + return SecureSetting.secureString(this.propertyName, + new InsecureFallbackStringSetting(this.insecurePropertyName)); + } + + public Setting asInsecureSetting() { + return new InsecureFallbackStringSetting(this.insecurePropertyName); + } + + public String getSetting(Settings settings) { + return this.getSetting(settings, this.defaultValue); + } + + public String getSetting(Settings settings, String defaultValue) { + return Optional.of(this.asSetting().get(settings)) + .filter(ss -> ss.length() > 0) + .map(SecureString::toString) + .orElse(defaultValue); + } + } + + private SecureSSLSettings() {} + + public static List> getSecureSettings() { + return Arrays.stream(SSLSetting.values()) + .flatMap(setting -> Stream.of(setting.asSetting(), setting.asInsecureSetting())) + .collect(Collectors.toList()); + } + + /** + * Alternative to InsecureStringSetting, which doesn't raise an exception if allow_insecure_settings is false, but + * instead log.WARNs the violation. This is to appease a potential cyclic dependency between commons-utils + */ + private static class InsecureFallbackStringSetting extends Setting { + private final String name; + + private InsecureFallbackStringSetting(String name) { + super(name, "", s -> new SecureString(s.toCharArray()), Property.Deprecated, Property.Filtered, Property.NodeScope); + this.name = name; + } + + public SecureString get(Settings settings) { + if (this.exists(settings)) { + LOG.warn("Setting [{}] has a secure counterpart [{}{}] which should be used instead - allowing for legacy SSL setups", + this.name, this.name, SECURE_SUFFIX); + } + + return super.get(settings); + } + } +} diff --git a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java index 287152d9dc..65eaec238a 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -32,15 +32,11 @@ public final class SSLConfigConstants { public static final String SECURITY_SSL_HTTP_KEYSTORE_ALIAS = "plugins.security.ssl.http.keystore_alias"; public static final String SECURITY_SSL_HTTP_KEYSTORE_FILEPATH = "plugins.security.ssl.http.keystore_filepath"; public static final String SECURITY_SSL_HTTP_PEMKEY_FILEPATH = "plugins.security.ssl.http.pemkey_filepath"; - public static final String SECURITY_SSL_HTTP_PEMKEY_PASSWORD = "plugins.security.ssl.http.pemkey_password"; public static final String SECURITY_SSL_HTTP_PEMCERT_FILEPATH = "plugins.security.ssl.http.pemcert_filepath"; public static final String SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH = "plugins.security.ssl.http.pemtrustedcas_filepath"; - public static final String SECURITY_SSL_HTTP_KEYSTORE_PASSWORD = "plugins.security.ssl.http.keystore_password"; - public static final String SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD = "plugins.security.ssl.http.keystore_keypassword"; public static final String SECURITY_SSL_HTTP_KEYSTORE_TYPE = "plugins.security.ssl.http.keystore_type"; public static final String SECURITY_SSL_HTTP_TRUSTSTORE_ALIAS = "plugins.security.ssl.http.truststore_alias"; public static final String SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH = "plugins.security.ssl.http.truststore_filepath"; - public static final String SECURITY_SSL_HTTP_TRUSTSTORE_PASSWORD = "plugins.security.ssl.http.truststore_password"; public static final String SECURITY_SSL_HTTP_TRUSTSTORE_TYPE = "plugins.security.ssl.http.truststore_type"; public static final String SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE = "plugins.security.ssl.transport.enable_openssl_if_available"; public static final String SECURITY_SSL_TRANSPORT_ENABLED = "plugins.security.ssl.transport.enabled"; @@ -54,26 +50,18 @@ public final class SSLConfigConstants { public static final String SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH = "plugins.security.ssl.transport.keystore_filepath"; public static final String SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH = "plugins.security.ssl.transport.pemkey_filepath"; - public static final String SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD = "plugins.security.ssl.transport.pemkey_password"; public static final String SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH = "plugins.security.ssl.transport.pemcert_filepath"; public static final String SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH = "plugins.security.ssl.transport.pemtrustedcas_filepath"; public static final String SECURITY_SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED = "plugins.security.ssl.transport.extended_key_usage_enabled"; public static final boolean SECURITY_SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED_DEFAULT = false; public static final String SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_FILEPATH = "plugins.security.ssl.transport.server.pemkey_filepath"; - public static final String SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_PASSWORD = "plugins.security.ssl.transport.server.pemkey_password"; public static final String SECURITY_SSL_TRANSPORT_SERVER_PEMCERT_FILEPATH = "plugins.security.ssl.transport.server.pemcert_filepath"; public static final String SECURITY_SSL_TRANSPORT_SERVER_PEMTRUSTEDCAS_FILEPATH = "plugins.security.ssl.transport.server.pemtrustedcas_filepath"; public static final String SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_FILEPATH = "plugins.security.ssl.transport.client.pemkey_filepath"; - public static final String SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_PASSWORD = "plugins.security.ssl.transport.client.pemkey_password"; public static final String SECURITY_SSL_TRANSPORT_CLIENT_PEMCERT_FILEPATH = "plugins.security.ssl.transport.client.pemcert_filepath"; public static final String SECURITY_SSL_TRANSPORT_CLIENT_PEMTRUSTEDCAS_FILEPATH = "plugins.security.ssl.transport.client.pemtrustedcas_filepath"; - public static final String SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD = "plugins.security.ssl.transport.keystore_password"; - public static final String SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD = "plugins.security.ssl.transport.keystore_keypassword"; - public static final String SECURITY_SSL_TRANSPORT_SERVER_KEYSTORE_KEYPASSWORD = "plugins.security.ssl.transport.server.keystore_keypassword"; - public static final String SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_KEYPASSWORD = "plugins.security.ssl.transport.client.keystore_keypassword"; - public static final String SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE = "plugins.security.ssl.transport.keystore_type"; public static final String SECURITY_SSL_TRANSPORT_TRUSTSTORE_ALIAS = "plugins.security.ssl.transport.truststore_alias"; @@ -81,7 +69,6 @@ public final class SSLConfigConstants { public static final String SECURITY_SSL_TRANSPORT_CLIENT_TRUSTSTORE_ALIAS = "plugins.security.ssl.transport.client.truststore_alias"; public static final String SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH = "plugins.security.ssl.transport.truststore_filepath"; - public static final String SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD = "plugins.security.ssl.transport.truststore_password"; public static final String SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE = "plugins.security.ssl.transport.truststore_type"; public static final String SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS = "plugins.security.ssl.transport.enabled_ciphers"; public static final String SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS = "plugins.security.ssl.transport.enabled_protocols"; diff --git a/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java b/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java index 3ec6649013..87452f8a9c 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java @@ -50,6 +50,8 @@ import org.opensearch.security.ssl.transport.PrincipalExtractor; import org.opensearch.security.ssl.transport.PrincipalExtractor.Type; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_HTTP_TRUSTSTORE_PASSWORD; + public class SSLRequestHelper { private static final Logger log = LogManager.getLogger(SSLRequestHelper.class); @@ -222,7 +224,7 @@ private static boolean validate(X509Certificate[] x509Certs, final Settings sett if(truststore != null) { final String truststoreType = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_TYPE, "JKS"); - final String truststorePassword = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_PASSWORD, "changeit"); + final String truststorePassword = SECURITY_SSL_HTTP_TRUSTSTORE_PASSWORD.getSetting(settings); //final String truststoreAlias = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_ALIAS, null); final KeyStore ts = KeyStore.getInstance(truststoreType); diff --git a/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java b/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java index c6d61bf617..258c18b384 100644 --- a/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java +++ b/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java @@ -27,11 +27,13 @@ import org.opensearch.commons.rest.SecureRestClientBuilder; import org.opensearch.test.rest.OpenSearchRestTestCase; -import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_ENABLED; -import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_FILEPATH; -import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD; -import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_PASSWORD; -import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_PEMCERT_FILEPATH; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_HTTP_KEYSTORE_PASSWORD; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH; /** * Overrides OpenSearchRestTestCase to fit the use-case for testing @@ -52,17 +54,16 @@ private boolean securityEnabled() { @Override protected Settings restAdminSettings(){ - return Settings .builder() .put("http.port", 9200) - .put(OPENSEARCH_SECURITY_SSL_HTTP_ENABLED, isHttps()) - .put(OPENSEARCH_SECURITY_SSL_HTTP_PEMCERT_FILEPATH, CERT_FILE_DIRECTORY + "opensearch-node.pem") - .put("plugins.security.ssl.http.pemkey_filepath", CERT_FILE_DIRECTORY + "opensearch-node-key.pem") - .put("plugins.security.ssl.transport.pemtrustedcas_filepath", CERT_FILE_DIRECTORY + "root-ca.pem") - .put(OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, CERT_FILE_DIRECTORY + "test-kirk.jks") - .put(OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_PASSWORD, "changeit") - .put(OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD, "changeit") + .put(SECURITY_SSL_HTTP_ENABLED, isHttps()) + .put(SECURITY_SSL_HTTP_PEMCERT_FILEPATH, CERT_FILE_DIRECTORY + "opensearch-node.pem") + .put(SECURITY_SSL_HTTP_PEMKEY_FILEPATH, CERT_FILE_DIRECTORY + "opensearch-node-key.pem") + .put(SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, CERT_FILE_DIRECTORY + "root-ca.pem") + .put(SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, CERT_FILE_DIRECTORY + "test-kirk.jks") + .put(SECURITY_SSL_HTTP_KEYSTORE_PASSWORD.insecurePropertyName, "changeit") + .put(SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD.insecurePropertyName, "changeit") .build(); } @@ -70,7 +71,7 @@ protected Settings restAdminSettings(){ protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOException { if(securityEnabled()){ - String keystore = settings.get(OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_FILEPATH); + String keystore = settings.get(SECURITY_SSL_HTTP_KEYSTORE_FILEPATH); if(keystore != null){ // create adminDN (super-admin) client diff --git a/src/test/java/org/opensearch/security/ssl/SSLTest.java b/src/test/java/org/opensearch/security/ssl/SSLTest.java index 08bbbb15d3..d150353aeb 100644 --- a/src/test/java/org/opensearch/security/ssl/SSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/SSLTest.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl; @@ -47,6 +47,7 @@ import org.opensearch.action.search.SearchRequest; import org.opensearch.action.support.WriteRequest.RefreshPolicy; import org.opensearch.client.Client; +import org.opensearch.common.settings.MockSecureSettings; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; import org.opensearch.common.xcontent.XContentType; @@ -62,6 +63,13 @@ import org.opensearch.security.test.helper.rest.RestHelper; import org.opensearch.transport.Netty4ModulePlugin; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_HTTP_PEMKEY_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD; + @SuppressWarnings({"resource", "unchecked"}) public class SSLTest extends SingleClusterTest { @@ -69,22 +77,23 @@ public class SSLTest extends SingleClusterTest { public final ExpectedException thrown = ExpectedException.none(); protected boolean allowOpenSSL = false; - + @Test public void testHttps() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", false) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put("plugins.security.ssl.http.enabled", true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "TLSv1.1","TLSv1.2") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "TLSv1.1", "TLSv1.2") .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256") - .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.1","TLSv1.2") + .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.1", "TLSv1.2") .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) .build(); setupSslOnlyMode(settings); @@ -94,7 +103,7 @@ public void testHttps() throws Exception { rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; rh.keystore = "node-untspec5-keystore.p12"; - + System.out.println(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty&show_dn=true")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty&show_dn=true").contains("EMAILADDRESS=unt@tst.com")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty&show_dn=true").contains("local_certificates_list")); @@ -106,113 +115,116 @@ public void testHttps() throws Exception { //Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); } - + @Test public void testCipherAndProtocols() throws Exception { - - Security.setProperty("jdk.tls.disabledAlgorithms",""); - System.out.println("Disabled algos: "+Security.getProperty("jdk.tls.disabledAlgorithms")); - System.out.println("allowOpenSSL: "+allowOpenSSL); - Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", false) + Security.setProperty("jdk.tls.disabledAlgorithms", ""); + System.out.println("Disabled algos: " + Security.getProperty("jdk.tls.disabledAlgorithms")); + System.out.println("allowOpenSSL: " + allowOpenSSL); + + Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put("plugins.security.ssl.http.enabled", true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - //WEAK and insecure cipher, do NOT use this, its here for unittesting only!!! - .put("plugins.security.ssl.http.enabled_ciphers","SSL_RSA_EXPORT_WITH_RC4_40_MD5") - //WEAK and insecure protocol, do NOT use this, its here for unittesting only!!! - .put("plugins.security.ssl.http.enabled_protocols","SSLv3") - .put("client.type","node") - .put("path.home",".") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + //WEAK and insecure cipher, do NOT use this, its here for unittesting only!!! + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "SSL_RSA_EXPORT_WITH_RC4_40_MD5") + //WEAK and insecure protocol, do NOT use this, its here for unittesting only!!! + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "SSLv3") + .put("client.type", "node") + .put("path.home", ".") .build(); - + try { String[] enabledCiphers = new DefaultSecurityKeyStore(settings, Paths.get(".")).createHTTPSSLEngine().getEnabledCipherSuites(); String[] enabledProtocols = new DefaultSecurityKeyStore(settings, Paths.get(".")).createHTTPSSLEngine().getEnabledProtocols(); - if(allowOpenSSL) { + if (allowOpenSSL) { Assert.assertEquals(2, enabledProtocols.length); //SSLv2Hello is always enabled when using openssl Assert.assertTrue("Check SSLv3", "SSLv3".equals(enabledProtocols[0]) || "SSLv3".equals(enabledProtocols[1])); Assert.assertEquals(1, enabledCiphers.length); - Assert.assertEquals("TLS_RSA_EXPORT_WITH_RC4_40_MD5",enabledCiphers[0]); + Assert.assertEquals("TLS_RSA_EXPORT_WITH_RC4_40_MD5", enabledCiphers[0]); } else { Assert.assertEquals(1, enabledProtocols.length); Assert.assertEquals("SSLv3", enabledProtocols[0]); Assert.assertEquals(1, enabledCiphers.length); - Assert.assertEquals("SSL_RSA_EXPORT_WITH_RC4_40_MD5",enabledCiphers[0]); + Assert.assertEquals("SSL_RSA_EXPORT_WITH_RC4_40_MD5", enabledCiphers[0]); } - - settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + + settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - //WEAK and insecure cipher, do NOT use this, its here for unittesting only!!! - .put("plugins.security.ssl.transport.enabled_ciphers","SSL_RSA_EXPORT_WITH_RC4_40_MD5") - //WEAK and insecure protocol, do NOT use this, its here for unittesting only!!! - .put("plugins.security.ssl.transport.enabled_protocols","SSLv3") - .put("client.type","node") - .put("path.home",".") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + //WEAK and insecure cipher, do NOT use this, its here for unittesting only!!! + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "SSL_RSA_EXPORT_WITH_RC4_40_MD5") + //WEAK and insecure protocol, do NOT use this, its here for unittesting only!!! + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "SSLv3") + .put("client.type", "node") + .put("path.home", ".") .build(); - + enabledCiphers = new DefaultSecurityKeyStore(settings, Paths.get(".")).createServerTransportSSLEngine().getEnabledCipherSuites(); enabledProtocols = new DefaultSecurityKeyStore(settings, Paths.get(".")).createServerTransportSSLEngine().getEnabledProtocols(); - if(allowOpenSSL) { + if (allowOpenSSL) { Assert.assertEquals(2, enabledProtocols.length); //SSLv2Hello is always enabled when using openssl Assert.assertTrue("Check SSLv3", "SSLv3".equals(enabledProtocols[0]) || "SSLv3".equals(enabledProtocols[1])); Assert.assertEquals(1, enabledCiphers.length); - Assert.assertEquals("TLS_RSA_EXPORT_WITH_RC4_40_MD5",enabledCiphers[0]); + Assert.assertEquals("TLS_RSA_EXPORT_WITH_RC4_40_MD5", enabledCiphers[0]); } else { Assert.assertEquals(1, enabledProtocols.length); Assert.assertEquals("SSLv3", enabledProtocols[0]); Assert.assertEquals(1, enabledCiphers.length); - Assert.assertEquals("SSL_RSA_EXPORT_WITH_RC4_40_MD5",enabledCiphers[0]); + Assert.assertEquals("SSL_RSA_EXPORT_WITH_RC4_40_MD5", enabledCiphers[0]); } enabledCiphers = new DefaultSecurityKeyStore(settings, Paths.get(".")).createClientTransportSSLEngine(null, -1).getEnabledCipherSuites(); enabledProtocols = new DefaultSecurityKeyStore(settings, Paths.get(".")).createClientTransportSSLEngine(null, -1).getEnabledProtocols(); - if(allowOpenSSL) { + if (allowOpenSSL) { Assert.assertEquals(2, enabledProtocols.length); //SSLv2Hello is always enabled when using openssl - Assert.assertTrue("Check SSLv3","SSLv3".equals(enabledProtocols[0]) || "SSLv3".equals(enabledProtocols[1])); + Assert.assertTrue("Check SSLv3", "SSLv3".equals(enabledProtocols[0]) || "SSLv3".equals(enabledProtocols[1])); Assert.assertEquals(1, enabledCiphers.length); - Assert.assertEquals("TLS_RSA_EXPORT_WITH_RC4_40_MD5",enabledCiphers[0]); + Assert.assertEquals("TLS_RSA_EXPORT_WITH_RC4_40_MD5", enabledCiphers[0]); } else { Assert.assertEquals(1, enabledProtocols.length); Assert.assertEquals("SSLv3", enabledProtocols[0]); Assert.assertEquals(1, enabledCiphers.length); - Assert.assertEquals("SSL_RSA_EXPORT_WITH_RC4_40_MD5",enabledCiphers[0]); + Assert.assertEquals("SSL_RSA_EXPORT_WITH_RC4_40_MD5", enabledCiphers[0]); } } catch (OpenSearchSecurityException e) { - System.out.println("EXPECTED "+e.getClass().getSimpleName()+" for "+System.getProperty("java.specification.version")+": "+e.toString()); + System.out.println("EXPECTED " + e.getClass().getSimpleName() + " for " + System.getProperty("java.specification.version") + ": " + e.toString()); e.printStackTrace(); - Assert.assertTrue("Check if error contains 'no valid cipher suites' -> "+e.toString(),e.toString().contains("no valid cipher suites") + Assert.assertTrue("Check if error contains 'no valid cipher suites' -> " + e.toString(), e.toString().contains("no valid cipher suites") || e.toString().contains("failed to set cipher suite") || e.toString().contains("Unable to configure permitted SSL ciphers") || e.toString().contains("OPENSSL_internal:NO_CIPHER_MATCH") - ); - Assert.assertTrue("Check if >= Java 8 and no openssl",allowOpenSSL?true:Constants.JRE_IS_MINIMUM_JAVA8 ); + ); + Assert.assertTrue("Check if >= Java 8 and no openssl", allowOpenSSL ? true : Constants.JRE_IS_MINIMUM_JAVA8); } } - + @Test public void testHttpsOptionalAuth() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", false) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put("plugins.security.ssl.http.enabled", true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); setupSslOnlyMode(settings); - + RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; @@ -224,34 +236,35 @@ public void testHttpsOptionalAuth() throws Exception { Assert.assertFalse(rh.executeSimpleRequest("_nodes/settings?pretty").contains("\"opendistro_security\"")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); } - + @Test public void testHttpsAndNodeSSL() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) - - .put("plugins.security.ssl.http.enabled", true).put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true).put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .build(); setupSslOnlyMode(settings); - + RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; - + System.out.println(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); @@ -261,29 +274,28 @@ public void testHttpsAndNodeSSL() throws Exception { Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_count\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_size_in_bytes\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"tx_count\" : 0")); - + } - + @Test public void testHttpsAndNodeSSLPKCS8Pem() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) - //.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD, "changeit") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) - - .put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) - //.put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_PASSWORD, "changeit") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) .build(); setupSslOnlyMode(settings); @@ -292,7 +304,7 @@ public void testHttpsAndNodeSSLPKCS8Pem() throws Exception { rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; - + System.out.println(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); @@ -304,21 +316,22 @@ public void testHttpsAndNodeSSLPKCS8Pem() throws Exception { @Test public void testHttpsAndNodeSSLPKCS1Pem() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-pkcs1.key.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) - - .put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-pkcs1.key.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-pkcs1.key.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-pkcs1.key.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) .build(); setupSslOnlyMode(settings); @@ -336,33 +349,36 @@ public void testHttpsAndNodeSSLPKCS1Pem() throws Exception { @Test public void testHttpsAndNodeSSLPemEnc() throws Exception { + final MockSecureSettings mockSecureSettings = new MockSecureSettings(); + mockSecureSettings.setString(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.propertyName, "changeit"); + mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD.propertyName, "changeit"); - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD, "changeit") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) - - .put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem")) .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_PASSWORD, "changeit") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .setSecureSettings(mockSecureSettings) .build(); setupSslOnlyMode(settings); - + RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; - + System.out.println(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); @@ -371,27 +387,62 @@ public void testHttpsAndNodeSSLPemEnc() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); } - + @Test + public void testSSLPemEncWithInsecureSettings() throws Exception { + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key")) + // legacy insecure passwords + .put(SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD.insecurePropertyName, "changeit") + .put(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.insecurePropertyName, "changeit") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .build(); + + setupSslOnlyMode(settings); + + RestHelper rh = restHelper(); + rh.enableHTTPClientSSL = true; + rh.trustHTTPServerCertificate = true; + rh.sendAdminCertificate = true; + + Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + } + + @Test public void testHttpsAndNodeSSLFailedCipher() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) - - .put("plugins.security.ssl.http.enabled", true).put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - - .put("plugins.security.ssl.transport.enabled_ciphers","INVALID_CIPHER") - + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "INVALID_CIPHER") + .build(); try { @@ -399,7 +450,7 @@ public void testHttpsAndNodeSSLFailedCipher() throws Exception { Assert.fail(); } catch (Exception e1) { e1.printStackTrace(); - System.out.println("##1 "+e1.toString()); + System.out.println("##1 " + e1.toString()); Throwable e = ExceptionUtils.getRootCause(e1); Assert.assertTrue(e.toString(), e.toString().contains("no valid cipher")); } @@ -409,23 +460,24 @@ public void testHttpsAndNodeSSLFailedCipher() throws Exception { public void testHttpPlainFail() throws Exception { thrown.expect(NoHttpResponseException.class); - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", false) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.clientauth_mode", "OPTIONAL") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "OPTIONAL") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); setupSslOnlyMode(settings); - - + + RestHelper rh = restHelper(); rh.enableHTTPClientSSL = false; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = false; - + Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); @@ -434,38 +486,40 @@ public void testHttpPlainFail() throws Exception { @Test public void testHttpsNoEnforce() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", false) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.clientauth_mode", "NONE") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "NONE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); setupSslOnlyMode(settings); - + RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = false; - + Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); Assert.assertFalse(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); } - + @Test public void testHttpsEnforceFail() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", false) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); setupSslOnlyMode(settings); @@ -473,16 +527,16 @@ public void testHttpsEnforceFail() throws Exception { rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = false; - + try { rh.executeSimpleRequest(""); Assert.fail(); } catch (SocketException | SSLException e) { //expected - System.out.println("Expected SSLHandshakeException "+e.toString()); + System.out.println("Expected SSLHandshakeException " + e.toString()); } catch (Exception e) { e.printStackTrace(); - Assert.fail("Unexpected exception "+e.toString()); + Assert.fail("Unexpected exception " + e.toString()); } } @@ -490,23 +544,24 @@ public void testHttpsEnforceFail() throws Exception { public void testHttpsV3Fail() throws Exception { thrown.expect(SSLHandshakeException.class); - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", false) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.clientauth_mode", "NONE") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "NONE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); setupSslOnlyMode(settings); - + RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = false; rh.enableHTTPClientSSLv3Only = true; - + Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); } @@ -514,29 +569,30 @@ public void testHttpsV3Fail() throws Exception { @Test public void testNodeClientSSL() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) .build(); setupSslOnlyMode(settings); RestHelper rh = nonSslRestHelper(); - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put("cluster.name", clusterInfo.clustername).put("path.home", ".") .put("node.name", "client_node_" + new Random().nextInt()) - .put("path.data", "./target/data/"+clusterInfo.clustername+"/ssl/data") - .put("path.logs", "./target/data/"+clusterInfo.clustername+"/ssl/logs") + .put("path.data", "./target/data/" + clusterInfo.clustername + "/ssl/data") + .put("path.logs", "./target/data/" + clusterInfo.clustername + "/ssl/logs") .put("path.home", "./target") - .put("discovery.initial_state_timeout","8s") - .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost+":"+clusterInfo.nodePort) + .put("discovery.initial_state_timeout", "8s") + .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) .put(settings)// ----- .build(); @@ -569,12 +625,12 @@ public void testAvailCiphers() throws Exception { System.out.println("JDK enabled ciphers: " + jdkEnabledCiphers); Assert.assertTrue(jdkEnabledCiphers.size() > 0); } - + @Test public void testUnmodifieableCipherProtocolConfig() throws Exception { SSLConfigConstants.getSecureSSLProtocols(Settings.EMPTY, false)[0] = "bogus"; Assert.assertEquals("TLSv1.3", SSLConfigConstants.getSecureSSLProtocols(Settings.EMPTY, false)[0]); - + try { SSLConfigConstants.getSecureSSLCiphers(Settings.EMPTY, false).set(0, "bogus"); Assert.fail(); @@ -582,55 +638,56 @@ public void testUnmodifieableCipherProtocolConfig() throws Exception { //expected } } - + @Test public void testCustomPrincipalExtractor() throws Exception { - - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) - .put("plugins.security.ssl.transport.principal_extractor_class", "org.opensearch.security.ssl.TestPrincipalExtractor") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PRINCIPAL_EXTRACTOR_CLASS, "org.opensearch.security.ssl.TestPrincipalExtractor") .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); setupSslOnlyMode(settings); - + RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; - + log.debug("OpenSearch started"); final Settings tcSettings = Settings.builder().put("cluster.name", clusterInfo.clustername).put("path.home", ".").put(settings).build(); try (Client tc = getClient()) { - + log.debug("Client built, connect now to {}:{}", clusterInfo.nodeHost, clusterInfo.httpPort); - + Assert.assertEquals(3, tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); log.debug("Client connected"); TestPrincipalExtractor.reset(); Assert.assertEquals("test", tc.index(new IndexRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"a\":5}", XContentType.JSON)).actionGet().getIndex()); - log.debug("Index created"); + log.debug("Index created"); Assert.assertEquals(1L, tc.search(new SearchRequest("test")).actionGet().getHits().getTotalHits().value); log.debug("Search done"); Assert.assertEquals(3, tc.admin().cluster().health(new ClusterHealthRequest("test")).actionGet().getNumberOfNodes()); - log.debug("ClusterHealth done"); - Assert.assertEquals(3, tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); + log.debug("ClusterHealth done"); + Assert.assertEquals(3, tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); log.debug("NodesInfoRequest asserted"); } - + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty"); - + //we need to test this in SG itself because in the SSL only plugin the info is not longer propagated //Assert.assertTrue(TestPrincipalExtractor.getTransportCount() > 0); Assert.assertTrue(TestPrincipalExtractor.getHttpCount() > 0); @@ -639,85 +696,87 @@ public void testCustomPrincipalExtractor() throws Exception { @Test public void testCRLPem() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) - //.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD, "changeit") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) - - .put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) - //.put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_PASSWORD, "changeit") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/chain-ca.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/chain-ca.pem")) .put(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATE, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATION_DATE, CertificateValidatorTest.CRL_DATE.getTime()) .build(); setupSslOnlyMode(settings); - + RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; - + Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); } - + @Test public void testCRL() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", false) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put("plugins.security.ssl.http.enabled", true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) .put(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATE, true) - .put(SSLConfigConstants.SSECURITY_SSL_HTTP_CRL_FILE, FileHelper. getAbsoluteFilePathFromClassPath("ssl/crl/revoked.crl")) + .put(SSLConfigConstants.SSECURITY_SSL_HTTP_CRL_FILE, FileHelper.getAbsoluteFilePathFromClassPath("ssl/crl/revoked.crl")) .put(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATION_DATE, CertificateValidatorTest.CRL_DATE.getTime()) .build(); setupSslOnlyMode(settings); - + RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); - + } - + @Test public void testNodeClientSSLwithJavaTLSv13() throws Exception { - + //Java TLS 1.3 is available since Java 11 Assume.assumeTrue(!allowOpenSSL && PlatformDependent.javaVersion() >= 11); - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.3") .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_AES_128_GCM_SHA256") .build(); setupSslOnlyMode(settings); - + RestHelper rh = nonSslRestHelper(); final Settings tcSettings = Settings.builder() @@ -726,8 +785,8 @@ public void testNodeClientSSLwithJavaTLSv13() throws Exception { .put("path.logs", "./target/data/" + clusterInfo.clustername + "/ssl/logs") .put("path.home", "./target") .put("node.name", "client_node_" + new Random().nextInt()) - .put("discovery.initial_state_timeout","8s") - .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost+":"+clusterInfo.nodePort) + .put("discovery.initial_state_timeout", "8s") + .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) .put(settings)// ----- .build(); @@ -743,27 +802,28 @@ public void testNodeClientSSLwithJavaTLSv13() throws Exception { Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_size_in_bytes\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"tx_count\" : 0")); } - + @Test public void testTLSv12() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "TLSv1.2") .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) .build(); setupSslOnlyMode(settings); - + RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; @@ -771,37 +831,40 @@ public void testTLSv12() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"tx_size_in_bytes\"")); } - + @Test public void testHttpsAndNodeSSLKeyPass() throws Exception { + final MockSecureSettings mockSecureSettings = new MockSecureSettings(); + mockSecureSettings.setString(SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD.propertyName, "changeit"); + mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD.propertyName, "changeit"); - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD, "changeit") - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) - - .put("plugins.security.ssl.http.enabled", true).put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD, "changeit") - - + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .setSecureSettings(mockSecureSettings) + .build(); setupSslOnlyMode(settings); - + RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; - + System.out.println(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); @@ -811,38 +874,41 @@ public void testHttpsAndNodeSSLKeyPass() throws Exception { Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_count\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_size_in_bytes\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"tx_count\" : 0")); - + } @Test public void testHttpsAndNodeSSLKeyStoreExtendedUsageEnabled() throws Exception { + final MockSecureSettings mockSecureSettings = new MockSecureSettings(); + mockSecureSettings.setString(SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD.propertyName, "changeit"); + mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.propertyName, "changeit"); + mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.propertyName, "changeit"); - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_ALIAS, "node-0-client") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_KEYSTORE_ALIAS, "node-0-server") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_TRUSTSTORE_ALIAS, "root-ca") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_TRUSTSTORE_ALIAS, "root-ca") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_ALIAS, "node-0-client") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_KEYSTORE_ALIAS, "node-0-server") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_TRUSTSTORE_ALIAS, "root-ca") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_TRUSTSTORE_ALIAS, "root-ca") - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, "changeit") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, "changeit") - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.http.enabled", true).put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD, "changeit") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true).put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .setSecureSettings(mockSecureSettings) - .build(); + .build(); setupSslOnlyMode(settings); @@ -862,64 +928,69 @@ public void testHttpsAndNodeSSLKeyStoreExtendedUsageEnabled() throws Exception { Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"tx_count\" : 0")); } - - @Test(expected=IllegalStateException.class) + + @Test(expected = IllegalStateException.class) public void testHttpsAndNodeSSLKeyPassFail() throws Exception { + final MockSecureSettings mockSecureSettings = new MockSecureSettings(); + mockSecureSettings.setString(SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD.propertyName, "wrongpass"); + mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD.propertyName, "wrongpass"); - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(ConfigConstants.SECURITY_SSL_ONLY, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD, "wrongpass") - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) - - .put("plugins.security.ssl.http.enabled", true).put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper. getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD, "wrongpass") - - + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) + .setSecureSettings(mockSecureSettings) + + .build(); setupSslOnlyMode(settings); - + RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; - + Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); - + } @Test public void testHttpsAndNodeSSLPemExtendedUsageEnabled() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-client.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-key-client.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/root-ca.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-server.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-key-server.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/root-ca.pem")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) - - .put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .build(); + final Settings settings = Settings.builder() + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-client.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-key-client.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/root-ca.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-server.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-key-server.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/root-ca.pem")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) + .build(); setupSslOnlyMode(settings); diff --git a/src/test/java/org/opensearch/security/ssl/SecureSSLSettingsTest.java b/src/test/java/org/opensearch/security/ssl/SecureSSLSettingsTest.java new file mode 100644 index 0000000000..d60c2cfcc5 --- /dev/null +++ b/src/test/java/org/opensearch/security/ssl/SecureSSLSettingsTest.java @@ -0,0 +1,52 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.security.ssl; + +import org.junit.Assert; +import org.junit.Test; + +import org.opensearch.common.settings.MockSecureSettings; +import org.opensearch.common.settings.Settings; + +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_HTTP_PEMKEY_PASSWORD; + +public class SecureSSLSettingsTest { + @Test + public void testGetSettings() { + final var settings = SecureSSLSettings.getSecureSettings(); + Assert.assertNotNull(settings); + Assert.assertTrue(settings.size() > 0); + } + + @Test + public void testGetSecureSetting() { + final var mockSecureSettings = new MockSecureSettings(); + mockSecureSettings.setString(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.propertyName, "test-password"); + final var settings = Settings.builder().setSecureSettings(mockSecureSettings).build(); + final var password = SECURITY_SSL_HTTP_PEMKEY_PASSWORD.getSetting(settings); + Assert.assertEquals("test-password", password); + } + + @Test + public void testGetInsecureSetting() { + final var settings = Settings.builder() + .put(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.insecurePropertyName, "test-password") + .build(); + final var password = SECURITY_SSL_HTTP_PEMKEY_PASSWORD.getSetting(settings); + Assert.assertEquals("test-password", password); + } + + @Test + public void testShouldFavorSecureOverInsecureSetting() { + final var mockSecureSettings = new MockSecureSettings(); + mockSecureSettings.setString(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.propertyName, "secure-password"); + final var settings = Settings.builder() + .setSecureSettings(mockSecureSettings) + .put(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.insecurePropertyName, "insecure-password") + .build(); + final var password = SECURITY_SSL_HTTP_PEMKEY_PASSWORD.getSetting(settings); + Assert.assertEquals("secure-password", password); + } +} diff --git a/src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorV4Test.java b/src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorV4Test.java index afcd9549a3..cc75ec6eb0 100644 --- a/src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorV4Test.java +++ b/src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorV4Test.java @@ -66,6 +66,7 @@ import com.amazon.dlic.util.SettingsBasedSSLConfiguratorV4; import com.amazon.dlic.util.SettingsBasedSSLConfiguratorV4.SSLConfig; +import org.opensearch.common.settings.MockSecureSettings; import org.opensearch.common.settings.Settings; import org.opensearch.security.ssl.util.SSLConfigConstants; import org.opensearch.security.test.helper.file.FileHelper; @@ -73,6 +74,7 @@ import static org.hamcrest.CoreMatchers.either; import static org.hamcrest.CoreMatchers.instanceOf; +import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD; public class SettingsBasedSSLConfiguratorV4Test { @@ -90,7 +92,9 @@ public void testPemTrust() throws Exception { Settings settings = Settings.builder() .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) - .put("prefix.enable_ssl", "true").put("path.home", rootCaPemPath.getParent().toString()).build(); + .put("prefix.enable_ssl", "true") + .put("path.home", rootCaPemPath.getParent().toString()) + .build(); Path configPath = rootCaPemPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); @@ -117,7 +121,9 @@ public void testPemWrongTrust() throws Exception { Settings settings = Settings.builder() .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) - .put("prefix.enable_ssl", "true").put("path.home", rootCaPemPath.getParent().toString()).build(); + .put("prefix.enable_ssl", "true") + .put("path.home", rootCaPemPath.getParent().toString()) + .build(); Path configPath = rootCaPemPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); @@ -146,9 +152,13 @@ public void testPemClientAuth() throws Exception { Settings settings = Settings.builder() .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) - .put("prefix.enable_ssl", "true").put("path.home", rootCaPemPath.getParent().toString()) - .put("prefix.enable_ssl_client_auth", "true").put("prefix.pemcert_filepath", "kirk.pem") - .put("prefix.pemkey_filepath", "kirk.key").put("prefix.pemkey_password", "secret").build(); + .put("prefix.enable_ssl", "true") + .put("path.home", rootCaPemPath.getParent().toString()) + .put("prefix.enable_ssl_client_auth", "true") + .put("prefix.pemcert_filepath", "kirk.pem") + .put("prefix.pemkey_filepath", "kirk.key") + .put("prefix.pemkey_password", "secret") + .build(); Path configPath = rootCaPemPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); @@ -175,9 +185,12 @@ public void testPemClientAuthFailure() throws Exception { Settings settings = Settings.builder() .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) - .put("prefix.enable_ssl", "true").put("path.home", rootCaPemPath.getParent().toString()) - .put("prefix.enable_ssl_client_auth", "true").put("prefix.pemcert_filepath", "wrong-kirk.pem") - .put("prefix.pemkey_filepath", "wrong-kirk.key").put("prefix.pemkey_password", "G0CVtComen4a") + .put("prefix.enable_ssl", "true") + .put("path.home", rootCaPemPath.getParent().toString()) + .put("prefix.enable_ssl_client_auth", "true") + .put("prefix.pemcert_filepath", "wrong-kirk.pem") + .put("prefix.pemkey_filepath", "wrong-kirk.key") + .put("prefix.pemkey_password", "G0CVtComen4a") .build(); Path configPath = rootCaPemPath.getParent(); @@ -211,8 +224,10 @@ public void testPemHostnameVerificationFailure() throws Exception { Settings settings = Settings.builder() .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) - .put("prefix.enable_ssl", "true").put("prefix.verify_hostnames", "true") - .put("path.home", rootCaPemPath.getParent().toString()).build(); + .put("prefix.enable_ssl", "true") + .put("prefix.verify_hostnames", "true") + .put("path.home", rootCaPemPath.getParent().toString()) + .build(); Path configPath = rootCaPemPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); @@ -240,8 +255,10 @@ public void testPemHostnameVerificationOff() throws Exception { Settings settings = Settings.builder() .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) - .put("prefix.enable_ssl", "true").put("prefix.verify_hostnames", "false") - .put("path.home", rootCaPemPath.getParent().toString()).build(); + .put("prefix.enable_ssl", "true") + .put("prefix.verify_hostnames", "false") + .put("path.home", rootCaPemPath.getParent().toString()) + .build(); Path configPath = rootCaPemPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); @@ -265,10 +282,14 @@ public void testJksTrust() throws Exception { "sslConfigurator/jks/node1-keystore.jks", "secret", false)) { Path rootCaJksPath = FileHelper.getAbsoluteFilePathFromClassPath("sslConfigurator/jks/truststore.jks"); + MockSecureSettings mockSecureSettings = new MockSecureSettings(); + mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.propertyName, "secret"); Settings settings = Settings.builder() .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, rootCaJksPath.getFileName().toString()) - .put("plugins.security.ssl.transport.truststore_password", "secret").put("prefix.enable_ssl", "true") - .put("path.home", rootCaJksPath.getParent().toString()).build(); + .put("prefix.enable_ssl", "true") + .put("path.home", rootCaJksPath.getParent().toString()) + .setSecureSettings(mockSecureSettings) + .build(); Path configPath = rootCaJksPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); @@ -293,10 +314,14 @@ public void testJksWrongTrust() throws Exception { "sslConfigurator/jks/node1-keystore.jks", "secret", false)) { Path rootCaJksPath = FileHelper.getAbsoluteFilePathFromClassPath("sslConfigurator/jks/other-root-ca.jks"); + MockSecureSettings mockSecureSettings = new MockSecureSettings(); + mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.propertyName, "secret"); Settings settings = Settings.builder() .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, rootCaJksPath.getFileName().toString()) - .put("plugins.security.ssl.transport.truststore_password", "secret").put("prefix.enable_ssl", "true") - .put("path.home", rootCaJksPath.getParent().toString()).build(); + .put("prefix.enable_ssl", "true") + .put("path.home", rootCaJksPath.getParent().toString()) + .setSecureSettings(mockSecureSettings) + .build(); Path configPath = rootCaJksPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); @@ -321,8 +346,11 @@ public void testTrustAll() throws Exception { "sslConfigurator/jks/node1-keystore.jks", "secret", false)) { Path rootCaJksPath = FileHelper.getAbsoluteFilePathFromClassPath("sslConfigurator/jks/other-root-ca.jks"); - Settings settings = Settings.builder().put("prefix.enable_ssl", "true").put("prefix.trust_all", "true") - .put("path.home", rootCaJksPath.getParent().toString()).build(); + Settings settings = Settings.builder() + .put("prefix.enable_ssl", "true") + .put("prefix.trust_all", "true") + .put("path.home", rootCaJksPath.getParent().toString()) + .build(); Path configPath = rootCaJksPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); From a36802161abc06b77ba82ce8f1083ca2def0ace2 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Thu, 8 Dec 2022 15:09:03 -0500 Subject: [PATCH 095/369] Update to Gradle 7.6 (#2310) Updates gradle to 7.6 alongwith the `gradle-wrapper.jar`, `gradlew.sh` and `gradlew.bat` scripts Signed-off-by: Andriy Redko --- build.gradle | 2 +- bwc-test/gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 61574 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- bwc-test/gradlew | 31 +++++++++--------- bwc-test/gradlew.bat | 27 +++++---------- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 31 +++++++++--------- gradlew.bat | 27 +++++---------- 9 files changed, 51 insertions(+), 71 deletions(-) diff --git a/build.gradle b/build.gradle index 994ca7c24e..81b8c75d03 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,7 @@ plugins { id 'com.diffplug.spotless' version '5.11.0' id 'checkstyle' id 'nebula.ospackage' version "8.3.0" - id "org.gradle.test-retry" version "1.3.1" + id "org.gradle.test-retry" version "1.4.1" id 'eclipse' id "com.github.spotbugs" version "5.0.13" } diff --git a/bwc-test/gradle/wrapper/gradle-wrapper.jar b/bwc-test/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 36900 zcmaI7V{m3&)UKP3ZQHh;j&0kvlMbHPwrx94Y}@X*V>{_2yT4s~SDp9Nsq=5uTw|_Z z*SyDA;~q0%0W54Etby(aY}o0VClxFRhyhkI3lkf_7jK2&%Ygpl=wU>3Rs~ZgXSj(C z9wu-Y1}5%m9g+euEqOU4N$)b6f%GhAiAKT7S{5tUZQ+O8qA*vXC@1j8=Hd@~>p~x- z&X>HDXCKd|8s~KfK;O~X@9)nS-#H{9?;Af5&gdstgNg%}?GllZ=%ag+j&895S#>oj zCkO*T+1@d%!}B4Af42&#LFvJYS1eKc>zxiny{a-5%Ej$3?^j5S_5)6c_G+!8pxufC zd9P-(56q5kbw)>3XQ7K853PQh24-~p}L;HQuyEO+s)M^Gk)Y#4fr1I*ySS6Z>g^ z3j2|yAwKXw?b#D4wNzK4zxeH;LuAJJct5s&k>(Qc2tH}2R3kpSJ)aaz!4*)5Vepww zWc0`u&~Lj*^{+V~D(lFTr?Eemqm3a{8wwF}l_dQsAQURmW$Bm$^?R10r)Xd_(HUYG zN)trq(ix@qb6alE>CCw@_H0*-r?5@|Fbx<6itm$^Qt~aj+h+Vd7l?ycraz%`lP%aB ziO6K|F?9|uUnx$T5aqKdAs74ED7SPSfzocG)~*66q;Yb=gB{=6k{ub6ho3Y`=;SnB z;W96mM@c5#(3(N~i_;u05{yUL8-BBVd|Z@8@(TO#gk&+1Ek#oDaZ?RNw{yG|z+^vm zz_8?GT|RX|oO;EH*3wMsfQTe(p6)G9a)6&yM+tYvZwg;#pZsdueT#%;G9gwXq%a(| zl*TBJYLyjOBS4he@nGA-CofFCVpGz!${(Qa{d?g*Yt zftsoLCHu-*AoZMC;gVx%qEKPVg@Ca2X(0LIQMr5^-B;1b)$5s^R@wa}C&FS9hr_0< zR(PnkT$}=;M;g}bw|7HERCSm?{<0JLnk{!U8*bbod@i#tj?Jr}|IcqMfaed&D?MHW zQQ>7BEPK-|c&@kx4femtLMpewFrq`MVIB%4e_8@IyFi9-$z0o48vnBWlh@E7Lz`C& z{~7u$g;@syjzMCZR|Nm+Jx^T!cp)q9$P*jxSQZ3le#HSIj=wN~)myB;srp0eMln_T z6?=}jUvU5_s4rEcO3k}*z#DQrR;TOvZGc03OR0)P5RI8M<#*B)8fYxxxX(I`Dks;X z_q5?sAs zMlaiDTP-1_XRMwL(q5h(W2yvr9HmtlnR);!9>U%TyViU)t#_5B#W0DnP!P#s!my-T zqbgQRIf%MWo*YUK2vXE8RIy;gJ8p^LU$c6POWt88``5^mIqohk~I!a zv-T{zI?eSLajm^r3>inooK|w$a_2H9J=;|sziKGRQ&FC5CWUF*#N6?n4rD-}S>Eg!tFkOpE7otS)$s3hyim=Ldy&-I$%Yra=M3xIOG{Jc zr8d_wbB301%Zy*8ILfeRiGfeQUIh2N3|41xAR|uvQ%?AIGUkdX*Ymgh z54d1)Igp9~)o7-h8AAH#6DzJ}UPh+srx=B^tGe~_(uwPoOov8sptn}$Rx@&$Ox^8H z!MND`vATA1%mR>+iCrV=b!*TSrj2TDv?Fnmj$=uw{JX1c$tt@zIC9gt)3Inpb+Q~= zh0Y@1o@R7|g+n0^b;v#5cc24{OYlnusF0tun^X?qHRYl#m%6UY?tK9vA zvtPnt7tgpi=qBIQ{v=D|p=4@{^E7)c3MLDCNMKPYec~o)VJ6zmZRE?UqXgYj7O~uG z^YQwQfQr>T!u&NaBfm|PW%g%cDoE8%t<-Ma$wIkMS{3sTS+aWpx=g7(+XtaLt9nqB zrLi<%uH29tuKZ6?`Ka5N0@G{F134GZ+6+RnA|Y+wCs~N*%N4CxyoB6?*{>AMy4w}` z@CMj>CaC}<;Y&#-a6~6AB=v2>)b=&t&D7SK6Vc4p+Tfg{AO(<+v?R1IsPA~@FvGJw z*d@a@6bydfT8{(k2N*D`FO@sUHbUIw4kQ(jrMPa2Mjc&~AK*xoe*c+VfsGx$cnzHQb4bSL2wJvVg>oYR*?s}CgoHMPLwA`Km%5LJm4a&OZ3QL*-+4G0t%;_ zS|DOILXL@I?hGl*3JvMq)Uq;%_B{$ipS*Qkn~F!-P^6Afg;Qf!n-zi$tpUjh9TEgk z$Em>`JJ(>S;8ZLM+$-RWUzFrR!@<;W=Y3ASjLR1`U zRnQ{ZU%JK?(2oo+c(5g;5Ez&I&5{C8{!I?aB34uFL`IQg#2z;=$Si?P0|qnfM1VdS zb6@5YL(+>w;EPEyeuX)yIA~VlFjk5^LQ^)aZ$<1LmDozK0cxH1z>q2*h5eR(*B8Pj6nS=K`)S3FLEV-S*4c;F0<9nRRu$YqiDCFaTc zU2LxT3wJJWeBb8}%B59!#)-W}_%?lSsy~vH3%oytE`j-^9*~SvMr-z3q=A7uy$?X& zf*Ky)z&7X0jy`YDtCs@NJw0+j_3CeDw_I25HR6CPV2t!asKPJV^R_r+u&LUxP)wtR zmFA-~HswLN)Ts=7{YPysG?DY))3+-L*En93o=+v+Kjw;_cUsONDZ!zzk{1O05Wm+3 z*2;}O&??lNOe-V{mDB}Gn<0_7H$ZCa5dWoq#}QCT(~h%=J=n@;@VXR52l^?vcj%GP zh7{kjosPu`1x+iQVU?(TJ^?xlT@AS>a?&FMQRTyRO?(2jczyS@T%&!d8mzxqO0r&;UjTNkbB)J1%*iB$McM0+stU%2(C}f0}_{G?dWaCGjmX7PnOq1 zdRr-MGfS#yqMH&mW5BiJE3#|^%`(niIKQ_BQ7xk`QFp50^I!yunb~0m24`10O=`w3 zc#^=Ae(B8CPKMDwLljERn*+I@7u8~-_2TPH`L# z=1~{&_1Fg{r>4*vu5rRTtDZ3}td&uZ)(p*OD4xfn01zzS+v3c_N~GkBgN$cm$Y%H} z1sPjxf=IxdrC~^)&Pvq1^e`~xXM2! zYU)LU02y$#S?v+CQ~GP{$|nR0d%`>hOlNwPU0Rr{E9ss;_>+ymGd10ASM{eJn+1RF zT}SD!JV-q&r|%0BQcGcRzR&sW)3v$3{tIN=O!JC~9!o8rOP6q=LW3BvlF$48 ziauC6R(9yToYA82viRfL#)tA@_TW;@)DcknleX^H4y+0kpRm zT&&(g50ZC+K(O0ZX6thiJEA8asDxF-J$*PytBYttTHI&)rXY!*0gdA9%@i#Sme5TY z(K6#6E@I~B?eoIu!{?l}dgxBz!rLS{3Q4PhpCSpxt4z#Yux6?y7~I=Yc?6P%bOq~j zI*D}tM^VMu{h6(>+IP|F8QYN`u{ziSK)DC*4*L>I4LoUwdEX_n{knkLwS`D-NRr>0 z&g8^|y3R$61{TgSK6)9&JZFhtApbp$KzF13WaC(QKwAZ|peA@Aol`&*>8RK(2|0%R zyo9nL{gtv}osWeNwLf@YG!wb9H2WRcYhg_DT60dzQGW(y7h7|4U*<;c*4N*sE2sdR zZRP^g;h(t0JLIuv)VNY6gZ)yUD)2d)p?eFznY8$~EZMYTiu%DF*7UeVQPV}h zF*|ls`|a+{u;cd>D@%~dRZBn~-Ac+m&Vg>P=3VY8+$<7Zi7p<~Nq zR^M^jl=zI!T`8H(gK0H945KY=N1J#Up`sWvfY$>1SGEfqEyKIokPVbexYnI`OXJF$ zkMS3dBE8RnB1dK)tJbNSu5Y&$IYBy38luzK-TGMpQcEojhte7Xff-zI50I2qM(i2F2)9DdagoKYlK zz%x8sxFf>5@1bI$-n*}N>o3o#^zP{$d7pf& zf*4SNbn9QDXDCVn;wo6|E0$(wBv*pgxHCA(S3lXJ4HMQW)rU}U7?F zxI}V}W~d>wx97Ozh+^glLBo{*j$o`=hK;idHhi4CG!_fG89V-Ew-^^hhMOWUdu-2< zd(t0O>8BgZ1N<2Xi1G3>r1@d)nBD*K3PsmP{s{&G;tmG_!k=7FNuKO+fCm`SxKP>B zK>mtj;Etn5J%mKvT;yE_zl8vk?q3f9hwea!Dt8yLUCgFO*BnS=YuY}-c!&0jb}J)D zV(s~BTYfVyXK<9y&hpVuS= zc!!wNsFjPgspRhCIw6}w^RvLX#?KnhpM(hB`U3x zg*!~MI$JfAFWhsN7xRdV^%0aygs+rZ;dpWzncKOTAa`0Xq7m(z zS_LwFYW$1KXsfgpFzlw7r#2KOQn(%ww?YQ$bT(GWx*gx2Bsny3J z!6UUPr8>TIGiK`%2m`PSS3Pd36m#OIl#SN?$h?mU25XXidM(*ZGBAelMO)H+;9Uw= z8`vjt5)+09c$b2FAWm3{jId9*ui3~Ihbw`9e-2;@?!T%Dqin&WFbQJt4_m@V=j9P* zbXi|lvH3x49-&)RB5c* zheg*i@5p((w*%DOB8-%Yv2P#-IHB%v>`Y&_9BR4)7ngJze2&>4c~NOkQnJ)jt+X$L z9`^6#2vV*K89hV$gu10|zu~;nKfa?ohox&sMS7NyTlMJCQAe^h{9nZwpoX?uy5xO? zW@PBU$b1{UOpv~AtZ#<+*z+(g?Fjwseh8lsxs5iozi*#gI!;qXBt)G~j z9v5n^MQKOT?2!Dj8;SOO0>6f3orwHJiOFK6`b<|b^4}5n{l-VQ?SoksHS=yv3$O(l zK4aL#0Zq4{g#z$jo$*dAJfuB~zb-n^5(3@{JHT~GGc;Ky(^y99NCxW2rZg%U^gIg; zJ%kBn@NxZn`e|BO6V4* z39i>kJU<7SyAHVHI%uKdcv|~U@W=4e@t=p!S?jnBEq^yQ2E14shzIlXKC?om(H84vN=o^2NtMBm7J~D=rmbm*NWjSVJeDEz-N5UmBk5`GjywWp zZ6s1IpXkUutr~lnCT>!2PPR9DIkuVbt|MCCR|#D(rD%~B zubEU^cc78hxs+x%Vg6$X@16i4ob@ek?PQijQzieZfi>E5NEg`76N6^2(v~ar1-yk2 z{{lAO$SjM{aof;NApyxnbEZnRO}8?!fT!U_<`21g+Y&qC_&99r6|*kDkDETgh-Blb z?9T7UIB}thISUzkw0O~5y~+>wtL{7Fc;gSldH8639yf31)qi4|Wq~g>_I0dfs^OGe z!K&|A^L|jeya>y7<>8(f3SXza9%^rl#3_31Neefn#Uk7*_^}IkM)e_&Fg~Ughu3}B zG0}?Kod{eb?94;$6dD4YV>n9mC5+Hy8M_h+bQmvUNvJ>0P#9a~pPDU9l#NrDP39Z> z7R3hA*IMVAod6Yl=s=BNyrblFv9ahxsA&Gst+0`2T@WSesGH1hRhw z#t7Smp){oxPiCm!XedMT9Xls`K+YKLV>+PC>98;G(5Lw*eBS5`f9B8Y2br|#y@jcz z`ddmVevy*mwN3@%YsE|Fsj!mu|5S)>5)wx;dbtMZ6Z1juCz$0kMS5-C{B5qnD{7ViiFNTv<&?w+5J7 zOvuImg^_o-ySHEQGAp-85!m8;Kjq_i-SzRFWcdAdj|VdIswTnUkggogN4`x{jEyG? zQ*_r9na<4wW8fySLr;PuoDVKKN@|y=99HWqBR+2kiH1prFkUgL{}*5_>twEG!W=|` z!(x}*NZ|P}Bf#p=-xK3y2>!x$6v(pYq)(6dQWk)$ZWSp%-^30dq``oVSfEWcTXE)1aMtpTQ;FW3e5ffMASm16(q#bJ}PAM2+l8m-{ z*nkDPH}ha-U3r{s>8XetSzpDN&nlc>|Er_gOMq?H8gtx5_)=$=rKn8D)UFKeitTF< zrA6>w`_sOEN&t!qEx|Pjw>cpv6y3zP58py3u%=88_f1w?Dh6qHi_=ps1{zKT3c+AJ z-CHtS&YwELV7i&XOXFt+doDFc=HdO@cjpeR_V#?~+=e|BdnS5C#8DCu@>*3!I9V9< zW8$!NLpp)$6Dt$s16B6U0ukr;dz~cWFIBq~D_Il@v4E@wH%Sf#P50K?&Z#GHc^JwQ5QyPaJatDTEbA97~OHLu)q6tU>srf)aJKx!w!`g-`+$hp=yl`47e};Vme|`Otn|zcuTh4TQZ6IKVT7?o{08_qzzuC#0N+` zUL{|(2B|=83J;W>uqDA61!wZ8=lN%B^2FGwkZO!2?1c;bDLELF1bQ^Y?Y+7uH}!W` z^`^=K4S@v^Hf0N&e`kde(pQ;BIt`1ze5~`Nn*fETHo^-|6KuqPj||YZ}sKX zV?ZxRbyMRcdpZnDH1-C5U5;4JguMyzlQm)=l~l=@z2)laaTx@kKq5APotoUE)xH#J z6)(ramD2fUHPdL793*l5S06`4Z3{&?tnR3xfYKS3B*A9}jW9$!H?R6_%7X{4+i!*D z*)40tp!3LCaUi_0jXN?z7Y6AEkZ^eIVyo1w;KO5iZg~7 zHCM5Jk&G}NQwK`~bXb=f#j!xIJJ#ETt7@1qhw9lR(hEuxbrv?Ct!{87z|%xN)YC*i zx*N?__cB*&7kQ_BKkH|g0C{L*XHjv2;aHF<^+m0ch@q*5qw}L{NLOF~Wij{R7GRxv zl5Ne^rT$D06;D(gWfiTsBRtZy(NY}48_YzA+&O?{^mT^%=g%f;Ze*H{?}d8=k;bAO*Q1?nvfP#$3|aI1lz{jcLWDIa9v7R}*UUhVLB> z?TDq)NCcJE9S%g0rVmhrf>=Nw6kt8m!lpu=;6aU-%{(-cj)pA`DiK5kE7&tX-cAxk zV7ZG}Y!Ot|OEx!qA%%(cHP{?eqT&8(26rmJ5#`!FG&0ynY|*(Kz?poEylYbT zipX*&ApQikP2)eD@Cw5>GKY=XH&1uQkIwKs&xAMXwn91ntk9#gnYz6e93PIWrmt>FDJ!k43qNZXPf6WzmzXnJHc=iBBr{8^QV3P3jBjzp1TS;KxA;CN~^( z+=W87)Xjkhvi+QF4Lx^aaWOqm(0Y9CO0GFZR8z&yMefP`|0m~2!!3xZ8Lm2Rvv@2r^&{YhR@ zw^UuX9c)b@B%u83iCNC~IC#%5yDEAF)=sG2Ixi3%m!~JwM$*P5x2h-9J*IpQSa~@J zrrr`+ovQAga*z#m7tsT{r|u?Zhxkhp{;cu*=@#(3`WZu}iQhp)>uS`C#CQB#V0r*V zTe2;aKaHbKz)(xpB<;4XJks+e6S0l-xv_|GDdg@Di2SHte&&#+NZ(2^BxzTs#s&{h zT+P^yaLR3Ngh&SYr_pGSlo1CA2wot^gmLX*Kry~2|D>4C=?)BOyuKoq!#CwNE>=xz z@B8_S`HEpn&6xHL%`uv=rD%h>RB_zhRU&TJz}mn5F1e&^ASo;(3ppRY={cnp``a?A zC0wiV5$%pZ!_*FuGrqYzT=2e770vS1j+=c~|zjkE7i4Y4E(NTKXd-je8>=6q<+#B7yc*NLp6Yi7`s>jG~xBpI-ljN3WLT@-~ z1>TEAk)dHU%i@jw-oY^D2AAb|%)}JjA7Bt{nKOF_Hp_!A9$XYm%X^ ztmK?aV&I-7@30n?X3rXfNuWHp0#VN~t=DRNoaeHi)w&{-K@k@5vgoq(MtF*-_fe2= zYChH0%?FP}6|_HapKK0kzEY{&1ar1-#X(o*HA;tY509Qp>zLBfP;v#}!^mV5J)dZ^ z>BgG%+gA^6~) zZIvs|p~pM!mkV)(Wj^@{;btztU>>X7r>wpDwmCLZ-ovAvPh4@D&-`&>!9aQ4ozB$& zp5iU5W6N}(oJL1>m258VY_?OHJtQ4roUQ9xnhBhaxRO?2T*pfCJ;?Y5nAyb%ZmWeQdtfRjFHZ{sZX3=>dcPZA7K6U&rrSMJ3 z23`Lst@rcgM;A*bOBZ7^yX5>5bBMmNiu{;nn9^8K@J#x?!{n@TH!x&BoMx1Y zpdS!C^i-FX$r+VWfUDF)D_ay~adG-ZLIz0`K#)}p3kzvR0rp=Om7M8tl78YAV0KgX{bGW4+cEG<+t|p2oXOxm#xNQfN z8f%1y6(O6G{7C}RnVfKJuiXZaj0W?HdU$68{-jOybhcswAmTI)jig>@#_t4FFbU=& z)3D3#bDeYZ26=;Z?rb?le{I}drsj^85p*AB*D=t(sbAMU^rLueRZ8e8j2qQV1~Fi> z8hYmusOb@gaqj3$`75=b|ETY1Q+Fq*KH$RLu8u@?^hVwkzBUu&NT}LcfTObO{CffG zsFXYPCekhefLbLr_#$o*i+-Y*PU)i`#x}$R}_=G*KKA8Od zg?&d1E5yBkIi!?6gDJR}d@@sZwG!db9)PIXWr=&{#YBo-o^KfC-w7L=Y$2_q5tA_s zd_)K$q}9eV8#$HB4v)xO`cRrV5M0lbBS^BQ?N_Uyj}uJ$8D))4`RzrAKn8@Bl20*K zK?_9(EL!7Tu@<%jia$Ut+x-QJbj1FEus=kWHhxabUvLKbdZYo9sf_2ZyUzTtQ`H9634fzfh{>IZs*n7#nJFjd~cRk}k{P;z%|sOnYp)rqs0 zMntK7EEh?ZW;Dj{ezME8Ko#w`;YZB7WQfu8Cl3?Ixic3l%&`v9SfHWm2pdd-N*w#6 z>pThQ1uF0rDpJ1vzbcK8Z)NAyf7p9L{2y_q0+dc+(u%0J1ZfqPj;s8HrXflA*Q%+? zSWY;#r_OEyUMB4@+!+QYb20UJ1&W~+YkpIj`Znt-)9V}-KKM^_-T2*HO#8n*e~|@< z*PKcjON29GAwVEB^Quix92bUpcgU|UHxv~9a~In6`L>OeU`GfbThFhw;fLI}TJzeF z0G!n|WK%ep~kHJws&s(en>DFZ0)ld zbX&L4=&DqT55oSDXVOUIOCNtJ?&o_+z|RdgGV~cu#bIU7P1)FXPox?Pt^Wzf#Uyju zHJ-wt;Q{pYCwybEi&h!8>!GxjB3=MYmJsd7{?h#Zb#sZQCgbR3-)Ak*c5Jng=kai# z@B_>mOjhgPQ7~?18moe?$->ieFbaQeT=5~Jd?z*=lLj*#XEpObnQ3^>$2tY5G-}a@ zEmSX?WSoC1&Qmzkw_{vO&V@N_n)R`16?m2h8z&f4!ZL=IT1Aj1)01Uq2tWZO5y$=s zaORP;**KR8NS$#Cee%5<5+F>(+o;+NQrr(r-VaWFBjbZZN76SSb_b1o zc^0aIX`Kg^LWGJ>O)L_3w-hi3`3e%|1sEYkdcfy++pC_P2+`cQV&+tAkLXej;;z$0P<*&mKBafg$S*@#Iivr!)FZxfykAAa& zl+J;luT&!5ym{m^r_*pS9j1jMnop!C&aB@CGMetbC}E6!cJ5#tE)p{Eerq_dc}p;( zrX=B=qAHr%w2o-7rgx<`E+s|9@rhVcgE~DvjDj#@ST0A8q{kD=UCuJ&zxFA}DVC+G za|Tc}KzT+i3WcdDzc_ZvU9+aGyS#D$I1Z}`a7V_(Oe4LSTyu*)ut(@ewfH*g6qn0b z5B!c7#hijdWXoSr@(n%%p}4>se!uezwv4nqN+dY#Aawu%=d-Rn+zkJ-QcHv4x~>H$ z;nl83-22HjF)2QMpNEM1ozq$th2#KRj5s^@lA)tHO0f36Asv{XHuEFwPv8h3aVTxQ z%oEW6IvV#QJ0B;vgw^Hp1Px?Mz2A(2dQ^;}4MsY<8eV>fzO;Af@2_ABvNCN&Vi@_$ zRA;E+5L+M~+U^kL3Cv6VGRI-YP4;A4S&FiV_IwHwRVdRsZgQhV)RgM4Ma^G}ULm!> z8q`CgL(VPvlGhnd4Y_Q(w#EU{=fE(mCcuyXqOz6x9k}xk63wR%n2?k=jbfx8KC{_QVW? z2ys94)HvxzFg3~`E+&TzC@%OAsX|h=**G(r1*OP#MUZ>t$ZBnnJ56m_n+*g-@o>wMN)L+r|C7%OU{k&i7w!T&(lEg>(Lm5?YI)Z zMu*56HN&c15ADmoxo6=V1AoJDxTx;8r_dWba= z34d+4zF0+J$*d`EgH=4aGD~iWMN?r-nPLgUypU3y7jqF-rKVVCMolJ?vXnQCHq3E? zygp@tR;A8@wwqP-$|X$GqUu>re>O?GO0#leqeF|PxrbFUnRX?&+9UTQ^-bmx!a%#? zHr;DWVKXE_Vk>kZU zv>7s5$dTD>2U*zg;YNegvp*xjy`Rq?-EF}S83Bmx;bgi)&qtF#*)1e44g-Oe6BOHb zLCMn`&=S1x^%&^OkftmS_H!DNy0tXtDm$oL#m`o9$?ic5tK&QaR`dqD8&VydP=hmO z4eNH1Vl)1SSv86{1;1>GZ7eRkgcGt^oM^b@+S81dqf)DFG?wjas_XRIoXwxA)TbD$ z&;YM#{~CaV6{j&!q8Q4}E87~4tjOhR`yD|jD7xz-`qG4CixswD1SJ!dNNr(YceB(S zdTBg-bN&brgS8l(!5vd%3#(D9Rs}p}8tkD#7%)3&P(x)5m)j6WJgmsD;%%#t?U^$$ zt}rR)lG=wjUkB3_m9)G?t6Pgk^z+!P)&Q}&ZX<4NL*j8pdJ{Kbnpl=Rg^*{}#rC$9 zgeHxM@YlVRDsc-hGD6kMZ~@(KO!AY7e3CkQJJ^eBC4qsB&hMFE~sc=K_u%p7dodffBw1U*#b6=_ylpuw)MUa&2g24IPnQkKD+p8Kjt| zBrA0e{WbCdZ9sUUwkn@$zfRSJdC;+_fgm}R!nrJph!|;r$;y6jNTv>VK%(mFIc71& zbYEKGXaibyqWmY@Tk{fC;#Flu0igd4Olz3+NBQp<*MZDTvWGBG8rigCLOH%o>>M6OIYwohsAYg2z8B&M~f7N=iLOPie+-I#!D&YrLJ#*|r zk`%QWr}mFM^d&^%W6EKt!Jense)RQoMqrAg_=q!e_ky9mt-vXrEWn`?scHMlBa@%fis_I33 zTO#Cq>!AB*P3)GH3GO0kE#&p6ALzGH1785t(r5xFj0@C83E@@HBtSSGZ|q#57SXzC zBcVYI{w#qZOiY|a25^Fdny!G``ENdD%DlS3Zk}KXPO%lG*^rJ-*YoTz0!5gcbUBIU zcxsp)g(jX$tR0mbI%5n51@)hFEWCS&4h~-C>z+e9XP2#9L=w6n0&{JJOi_tKFjBOmkydTxF?{=r~Z0SZ zQ!+?)lb|XW*a39dgeKjifBjqg6C6^fO>>mhlO5^a!?k@%Fm%OcR)0o}*qm6=$;a85F~$*LPd>M4+h=KK^p< zUTLr~iZCJ`#!sTSSP?A25d9$@jEe9}IiHO>I(cU!JV|?&>({{a8~_Oyc02#bw!fyZ z@HrqJOcWp<_mvL~UYdVG%AR6M@$eurF>ywq!qkU^T{D$%{9=rQK{Mr0e$Ev<4Z5_S zNnwMk`o5QFbqF(j*?kTXXP`Tk>0tE2420%Wbv=sgM}= zFD&odG<``_Nk$!;UUlNa@pUE;@K9l8cg(6Zp^76 zHSY4thE?HEz;V#!D}=e137fguh3sSu$@cn(U(I~bzJ+UcXJ=Q1O00`zY_m-#grEj4 zEGB@jzU304JM9hH$ewewKoi}a*G)7>aprL9L{@#&E63^!f5;GKKdIcz3u zIX?;8Hm+myU<%}TY{&)aehJtE{bUL5REqCLEv$}$XOuvB|LmWM={@UM30}Tc@D;(g zGwu3b=?d;_K`#|5(k3D+azz2#*`b*#(L%u7Pt3A#1qc<-_e7jCTL6jjvyRPZR?)zb zWgFrXi*Z})op{VWcX)K(M?p| z^}a9&&u8|iSNZT&G=-;Z1>0&GKleLMJk=huD4Vlz{zHe^OpLbVZE?7JHGRxRVhX@R zX#DjtFQ~S{-S678C8X4#M?IY@6Nj@YeQh)P53f_5{5@XcsQhQG$hZ}!=|IIsPG@-~ z_{~ws>hNg`<7R&15+VS9kG-XsFaWQ-qAIYaR{NtS)$_Kp8Ny;9bOV?yFjO|C|BAb1>)p63 z4?AKjs4JeWs^@~NgVY^gp5av^K1B~{YF7jfwz3uM!~O04tZ#R7eB-b!IWW%tVX4NF zZl~8XZhad1Tj?)(6C#PG6UgWf`0A^X+pq%_o&XegitvOnypX9A-jKwgoqIsk`7vDH zPz9}L=G;#3Lf5f!K3`t}l&J?TXKzH~Uzk?{5_k9H9xWw9crd@!v&1VY zsOuRn#7S^4j73)ETazCqI7bwNo$t{cZ&ry=x*Xgs76A|6USJp|n$Y_yB zDC2KGY3x!h=P8)>V7&ntYvVVK`hxw4Z_sN~Bp#BR6^2R37pGT z1Dj`(PM$x)t^Bc$%_kZgDbs?_&wIue+uUzpy}>uET;=1A)F*)A>Ata~GY4hAc!A?U z?{U63R0JMe536-g^k(*$`+N?+OJ(#XPk0Vrn^Rty$T*_`6p2GBZiWkJ{>w7+4g|H2 z4M328#NL_h?{$DR4^iA=7M|n{ahQctX<$tp*M$UZN+xz_oI{cx8*`dJ7 zuF=LPSVu%73wwaH{>HwHrblU4zy99llp3ScT+Mw7rR)7PJ^rA!wpR1f3=q)%h-?9K zK52(MxZVT~sZMJ~do{4JL-m{KI{J9x5!DKd$(}V4$Q5i);pa(WYKq|3lh&(wpC>*+ zMJlvE1NX)k5PT%eqpH=J7er0}#EOfJJqW;C+V(XcP_4kkIdOF!3{~9L+ z48Ix^+H}>9X`82&#cyS?k1$qbwT4ZbD>dvelVc$YL!v08DPS3-|GFX_@L!9d*r0D=CD`8m24nd4 zMFjft2!0|nj%z%!`PTgn`g{CLS1g*#*(w8|sFV~Bqc{^=k(H{#0Ah@*tQgwCd0N@ON!OYy9LF`#s=)zI0>F&P85;TXwk#VAWS+GnLle5w zSz<>g3hqrf#qGfiyY=*_G1~|k*h-g(AA+NbC~N@AVhf6A6qXmVY2Temx2|X$S0UFw z%*D3^qpS5e`ZtH#e-p_hv3bYtz!vUA56&MBhN4*snI=g8YNZ{TYX{~dPZ=Z_gk$3Z?0ZR{D-aliB#|SEnR`T;N3$!}02ZQ(F`K#y94FLke@r>i04JrfBacpWL!tC&p$j#%e~c zG0Oa(wM# zM(Mn!CQ&`w@usAmfZg29h)&o{r_NeX64w5N5WxG6q(-s6n3+LYQoV!fQdogT)Mf~f zrQ*(MSoLcIu2Zpl1bcHm-1-=no;nuG(Rr?&=9Dia+wfu8KmGNY@a~FBD`eM%#b5IC zn=aI`v<7i^08qgeb@EmZ1l73Fe^)VHH>vwnl#LfZYM}d!X*vZ=X-Kmm)|p~g8rR~7 zTHpjqRDXxKte4N;M7->5uZ?~X`;`Oeoq;87kGDaWGMa(5g9dgC3{EpOF1o}w3Ms0+ z270RrL{cUBU0=kwNClDNSwY!Lm!3n$dY&svjk#S0d>tPZn?&G%Bdtl_HV)BD3T&C$JTZ)yChEr+){ zP!q~(%s;6J22$ep1;aq;vT%}A@4H_e%j*18G#k|8R4HfuOLp~*H8ydsM!zd^J6-{I z0L19#cSH6Ztna?VS=NwT9B)9MqJAc(Hd_EwUk?-sA$*+!uqnSkia#g=*o}g> z+r%Me7rkks(=8I_1ku94GwiBA%18pKMzhP#Af0}Seaw|!n{!*P9TQbotzCQLm5EQN z>{zN@{lSM;n`U!Q*p-J1;p{VH`75=x^d=n#jJ1K1%%tgPj|GD0Xz zq9fV3Ma?HtM@!DivcDoBi|RXcCu&(8=pz_F%Qq#Kd@NT0|MtB&yqr?e&x3@7k^qX=q=oz=wvkChK5$_^jhq9 zhI+$s(bJ#2(25kdPfP>T<$A@3xOU9Xu;*O>W zPlGz<+y;?kBjzc;6Cx`rv_6DV)$7dgS>VSX3u8DBYT4@c~$tokVRZKT>AAJcn zM`3)eO!3jw64$ia2bI*ky%;JvZAew%gfzr@2z=cx-FW{@F2|Z2yJ)(40FvA_tyb$4 zHp-iN;@m7h0Wd7=&Re6T*H*wT&g*@8FgUyIHK5&0SUQ1)UCLemXi3}48~TLSgCCyk zrp@aYZmn?H^Jl<7jH)47mR8%{zw5cawx$r(oP>dTGqsxPPP=R8-^vbHS!I{bImH+d8&wJ9%Q;wmq?JKe27wwv&l7u{E(hv31^a>U`O|>aMzfL3gd{Uh8TtBa3!a zM{Iu}AI>-WSaizNSJ-FtewydP57^1>j^mNBnaaxoQn&p9y9&-_w4i7^xOT?7NKl?lKxm79T1T;#zGve! z^z&y}PFN96@n!`suxGzHHb%{=V`PLBTAb6YsDu-M5z|b*X1U-HtKvIeCp^%4PTA_v zr^@B{_qoGaW6!xov5Prol9ez6kdqH&(Vd~>o$?gruojX(F}osv#OuA9XCm{BA{HQ6 z7I#HXLktMs2!{a#?(wMAlBNdNxg}5ft0q4}Erg)PFo+~m7-_8kEk4%&n`n!qprR3_ zRKcyO67pN^HTAedB<#V{RM6J$?2A+0nwfZkx z)#H~>#TqYNMDy~b^!AI9>aavY_!YH!u%px+~ zAR_r);-C5#UfvaZNPmjHSuC39+iWbb>#uq)ntooMYNm#v%L5gx`qHNM^>O%V(&=$_ z)SkW9)C`tI#lQ5oYR4|5rnABn0GHiGa>kIEA)V)lr~lGU5$|u7S!kwV34&t z#Znst?`+H+{F>XL5Ihe`v2bcY2LZjt7?Bt^Q*1(5Xcp&jtGCX0X8@7GN*e>1pKz{? zTsY$-TL0JWaic5zP>F zBpD0yg8$LFD8iM^) zk-SPvJ|)^m$UbXDe<1>130Xcxq=9HeXVixa5li>o3bOiCmS8->t{1==s+|s)1#Fxf z`>r33c=P^?sE%sIN{nLrVKP2=8#A#L4aVF0&5hX+277!PfIi#w^-B=A(-v7xyZMmjc^*yX$#oLqK zZ9ANck>T6&l`fxVTgmj2FMyTGi}%N@9p_{)5@W~|eKY+}O(1Eb@~8MeO%U*3OJV&~O!Y|BfsbcWre3Qam04<^Ox8b7rmU*W?BC?5tQ&Maqv&(zE=o#*zFyM3A~aLQx(BIxtIGzX$s zVzx&kS;C&nIUnJf=0g?za@(IQ$b3sWi-$AZ35<7zDuzQDl|s$cdI)pS9|?_@L&YG= zTz1|NMy|(^-ZMSEMkmyA*Ec=8U#qiWonuyZ>vO5Uib@8!;^$YYmuBR+aS?1{mN|pv zw-8JT%`sus&h{q!ics^;33&wOgzyRooPenPBHseN0(uMGO0M=K4B# zfGQ7bWrup@w+0D8zuXDVG3`|9WQUIU2=lfs0}uW&$pO=+x%3;BTP?egh9}g!y|nxQ zF7c19A0dClYKuSr+0{^h;p=f9Z}r~jC}s(xg1yzB|3z2;`K_IX0kqq}KEYNiMmwrL zR11gCd%Misw-RpfU}^|g2}g%6#Etdt0G?#sN0(*BU)z~$KoK{Kq`9iHM72 zx#?+K`4Y8`;N;NJ+f!qAkK#UXrFMqzBWj;wJTv=9yxWXYj<=2W?S}YbPJurHi zQ($FF9S}jGm#Ch5G_{9=G&4K1rES6e)EtmgOi_(}8r`}~fLVtU&2@>eeNlYH>3oCK z-!_xrX%uzAB(J7fGqJ$WVfFlaX$_^-S(u6ywL|Ek8l5*sT z8D9aA(LyK~&|Ms@$?%C~OSUB8zJuyoz!y2nEHMk4VjBmJdxc06{ee>417r_Zx8M_f zQv&2&0cujOd<5@MSTY9gXQR_E^F$=~C=15`95Ht{YHmdLk$@3n#NUOMK$};s*lX~Z zj-hg?05PqDKaXM*=@C*FUgq$9FSP4gH_)(EMoJ6Vkgs{7exk&Q6_1EM;VrM=HLvKN zx7hNZad6+T$rH*0HD{xnW|(A;fL<{)@*L+A~DI2+a&j9;VV7>2~< zOwYgnm%NW?RDa+8Z;c&Dn}UQ!4V=-1_4~gI?EYyNM=CB-ToUF;W;(fN7&0R;6*M#$ zvq5<4o!#$u zL;H83)18fEmc^I%kG9Y0u2a8LzSGT&l-IvE1-?m<>GyN@RiOc=MG0pwK%(g}7UrlR z%-M&;96}o7L1r8apQ&v zS?_M`X_R4kkwW!jor7h&G=I3cyLo=WiDB0_Gi1V3Z<9=>`A-w>Q89bJ>Y)nS-T|=~ z@1h8-J2K?H;h0g6ESyOVVEyg9o<40j9gBKQkt9MJkx!1&%PpEAT{s(tVflR)k?!o2 z0mU~aI_52$;dv3)8$;S9zy4g!NYM&dv+h1r*xa)+IiI?ql;2upk;*aEok5LD%PUqS zz8;1l^|}F5xF(Ao%CIC$YgCZ|0wJ6yU9ZfstHAOwKs1ms4V(xMc;b-etG-ivj|D2A zWYxMR_SLI#Y)|w~S9~nxto669sc=HX zbX$_ZzOwkuE=C*zP%=)t7J$QsNW$t3`nShXVT*uu$f8k+iyTDp@_c=Lp{vaFBc^0&k4p3rk*Y7Zi_uzwrjSgca zMtjp&+ZrhxKyKW{K)&dq@Gfe!?G-`-PBLfo;s&_z5DRcM(+!N~fXTq|3O~PQbs=qA-pTg2l^u+d z%ds=eY1sNyehE&1F?Kp*1nt?h_p`OIU`aFI@{{AP0W(he39BQ}N&Fxr(_Nn9C@|Fv zF2CjVJpZj*KW06pkPfYefvVkXhPmEzhB0ZpvW78P+6b`(DXmx4XD$i@yG6uVoa7U_hH3k2Py`({xw)s6nAe(f(@W-J| zz@YAV6gVhtFUM>qy-n`}{EY%a%Z!g{Uc4KbHQ4Cysq(A?;rg&6Xew@Z;N+ZaVY|*= zY%CB8ewT@Az-G0c2It&IF33z$Exgk%iGnm9(StB(7KF?4q@06F#2&%w!1|s-vJ<$R z#XzNy)JYP=0BaD~u#sigQN$gNdTInmz#5sK4BSByfA_#G&)Zj<2A?Bk3$T_QnC;|2 z<0|qNBOdcGWX_efUbjcIbf9DLA2^E&r#fq>Gu)@g=vUoWqV-D~(xUfMfaCeY?ig%5 zNlo{2#2{?+Ykm2};*J1&Ep^Bz&WB;0YXN=I6)&JUITYUOUDcL5p;6b?izK++B7%r5 z9mr&h^fGbKR>>e`KebYXfs9w~PV?6xQw%lJOA*R&83!gvx2_G^Zzl1NjQ*&uWXlIJ zA5d%t%)`R6RVN`l7|hlJO0zti;vgD9yyKBh-oiXL(LgU}D{!LToK9roJSM_z=}gA@ zV0mkG5=+m9kztd>9U`MRFOYqw_R@@-88|~TY&n;wx0Y%6<;}H~Vhw9l)<<3|O$g znOS~HbBeb++hP5w^R9fzH*%%;O@OyRJ2HQ!`5r6TvCxLMt;lTth4BYout)}a_|rR1 zP|nlJjcdDbp~VeGki#sSoP(U~1 zzvfGSEi^1h$ayZla(pu`eFFiu-MqSdt8cz0qRmg++c}@ChaW9!{X)T1I}H&3h$C+b&J+B z&WGhay#y)vpbmts^9+1um2a^f=rUg9gc(vaIvdu9{ z=g~Ari+YZ*_9#%du+x0Tj|uG&ivk6<0W0(z->5&_@J!xrKJh+-N7(ay9KI1^9DKq1 z-`Q>5RXJWR>^gJg=ceSH1FhP&;-(b&yx3;%21tElpT5B-^B5lRW1stx=Lw@yl4K-H zH_&#(_w~Tx6OXfPTcCLo9$$?1c^Nx?=R`f{P#LiJu7|AN{H=1s9vgkea6`f*yNy6m zELFO8tlEHRx_O|Rftnf+yTTazHib2IaSS}hRg2p_EFj}MmiDQ$RqH#OP&*!>JX=+E zhHHTXEmdmJGX}fFret#wSWMoxwfs%78tQ;lJ+%#EPSxrJ1@y5{w3>3s`&VRTmheQ7 zm(`N@=UL#bJ3J63M84cI!+dq8*0Pa~cm)*vOH>96OZZ8rI+@#sxvX%J;j#2UyoI-P zoHw?w+>h2y0-i8E=E{R&#ky4YXy`dpzp?LN@i=(bZ>Ps)txu1NjX9j_ZqK;J7FkwVRy|k|*99~?Y z`*dy80oA`CJ_$tFQGtxLJfj|?%k{~!rK(wP%(jJ&e^AP#2mSmhEOc8GXcC^~u~)IG z&bB&9qn$v@0V@7Z+WqyCihnp!(NDz!v+(tZ6+efxni(EuvIZgq!%Q;IG-q zqF8&i9!)wS_%M!tY{yK|t}-+MVeB2X)^xwo4U+^n6ZT(3n^9s0^N~ZpVA-p-|=@^inh<~GA#G0Fb6cqg`G}K)*o{T5?_kIK6JI}m$v_ol&8oO4P_zX{TbEI^ zP4gy_X(a!@XOe=(Mp}U0!7ra+gbWnl2qGN(SI*+{5}&-NnMCpgbIjJJMM#>k=g30^ zDbJL&s-oi`3YUeZ9y-BZu65hbFPz;5@(6>;XEhacr$vW+pjdI#rGBriL|0cF)|$5S?ZhrZRY7Vy{kdqRI7&X0dtGtm6}Z)oRm-4;l8Ds`lB z1{;=7P~qZ2_n6wIDqX_QLr64UbcGnv7W5MkBQOQpPgUnUuZmy*Y1;{C(bD+H71WwI zFxkY4N6=#*ys|B0K*aJKZ-tf_Feu|x0wGE^{ za6HB=IjXDV7hj^UMqY@8D*!&A%+%g?A)#u;s#rUkuh7i!inq{PbR#Dr|8ZT+Wh(ZI z1r+upwLB#jrdiBGjm$~v%G;|eT(?4SqN&z(RF;+MW+&TN%T|}sR;8Dh>e|RrS`1xo z;obvgl5Z|wz0;94M2z-Y2WT6-(${?#QL}TPndp;hQjRZh6!1&D`+%7IvJc29LIBMq zvwi(+IZ(P1qKSTq#x08<=kru=S9oc!%gVY%A{T9{D%p8jSYCIzFy$TV^U4-RLFD+w zn77r`QwzNhX2Pbr7lOF`qlaW1HJk_R3Xg`iqZN?BZle86?}o%OyRW zEc|gt<9{tSk0Td&`c-N?)$%jzYaJhoOAjaF;6Z6r1}Rm!15{WMTw!4o5~)Fo-HoU_ z-&ujRx$TNix^SgDySgxKt>YCrB`EyID}h2#B6*Zab@La310Ghd_ma8AO#8-ulwSnj zZ<5BIUzZE;5*FP#&vkvaG!H~2tU$Jkd%gFw`T!S{2mp9?Vh1R?kv;~X`YAwb63>)? znkAD~i^l250{N2CJV<@SZeNTq!pqthV6F>e_QO<+Mykoxd5^JzHJaZeQZ zhJkUxQe7WRdWlz!MRJxF0W`KL@`p~)x5J(z5M;XocV_|rgnnd1%sW+|yq!Q`G&7GP zY07mPEwX@!LGr!_kNsDN#hMPL7#l zlc=pE5aWH28%^Dr5#obbnK@SMPeMr&YC`p^e?y)lV?@3LQVmf_yWw)b$Jl&Of#Rp# z&|KH+IbPYoU^~mj`IAFEK^Z{Gyzpb8*3I%bzXzl%M=>mC%Q2%)jr6JJ(KPB8q85*d zB`H_bk5V~4&VPE&gUAO>5~Zr82#kI9vNGHonE(8&8C(Hj-eU@GWQ@M~+4I^wF?8-BT6Km@x@%lir9`u3T}u<#oKmr!E| z2--yCX0m;Giv$T$>#E8290L1S=M=3CD`(J9s?1X>SX6lZ4GocaWFnHAC)t1T^hkf* zUD3KeM&diP@80N9p%T&fLe$oqvOhhZt`JxBO+^LSf?Q@z_`9Vr$Q6~<0L2-m>O(g4 zOan%-sNta~Xk*}&{@r#)usawmHs1u<1GjQ|b56{BDO&snX)z?_ zAankXRi*W~FHQC%{R2T17EVv=NN_~B7>6qS8-oRfDB^`%jRb@OLn=Vxce}tFY;7n@ zj#*voq%N#N>y$Y|*HtC2U!S=)^IxgQ0-7$v2yiqNXRM zwteC_-%jMY93pATf5JRZt)5Ay&cMar+UEM%P_tH6YH%!8xM83G_bjXj(q~&xt5EB% z3%t+9ys%^4AWWnRiJ*K6xjY*LNS|#O;pS)*K=AB^uJVW_JHF`#iYDK!(>=WUhh6%c zX>sTwaqCCJrW6nIY`0WWbIIb}bAzF+1oH!VTEEkh=Zo6npGn$x%=adz9iX3#tW4ZG zd<(6Uxn#z9!I5&G|DBlUn~4sC6q09u=rux4?hdLGj!_7Cw~W?;w)!zdM>lGL9?iJ}t$XPovsz-)cS-!LHv0ZC zb4AsYLrHn^FyZ^K^RfN==H_K5|Kmms8C*LII4c6rK%~mwn+cs0!Hx`!kJU7zAV@+T zY78x5H8b;aj{WU`xKGLdJJr*0Ydv@5KHQ6gH)}c2!V)JwlsWfdsGezcK zvNM+<{?KLS;}dCbka?fVSkA4*j<+1;zd^mMTl-!=UrG}%Dar#cYGiWKt*OnI2`}s& zKuJNJ^nn0>uh!6qs230jLkzPYLh2_ii7q$|O>AsUP2s0Lrn|+I5<#4D>kLax=_gwF z9%;kCQJZOVwWh{(5l+S2;i@c9Ea^@^d5H*?CXc?hq}byCKRwrA*C%v%mfkhaNtGo( z6ZP->A4&OCCWA#*#FO}#W|pFnPK7yjF|1x3zOLK4rW)-`{Id_xRgaYRE<$eQ5uvhX zwf1^~0@8-xJluw=SU}u}Dw6aJ;q1JO9ug~KY0 zc4j+Rx)`6g89&yl&N%L(+7`jSN#4N90mygg2v-%B)UllG#o_hk%4qb{}DFugg+wjSK#BF}Y6uqK(T} z?kzHTS{^k4!@fD4XcX#W(^8wah zxhMD99Ne&1gVtZZcgbC`hyPk0Duv+(pFsD@Nk!o&HRyRK5G1T7+eQevJC6LPk{?9c zQ-J=nD3qA?mBsZ7LMZK)4N_>F2_tu$3G)*!f%X;15m2(%QTyX5jbibaL(DZZ?^X)6 z6IQe1C)xidS(*m&S%Nxg6*Wvr#c_5a;M1(O#!UP zK|w*!f?nnepYPN2Q*1CL6QwdI+R$^%?Xi@THq}&u@#=_#DZffv#+TLtqCOXu9c<0O zBsjTGdF-y+Z@mK*MKeXymw+sY=m5iC_W;0f&xoJ>Z_(Nj$u*A&fs%=i& zXib;4XQuQ`Jk*=)+;=g|>19uWnY|Fm@!=U93(mB|GesI4Wr=-T+cXbcT)0}e zk9@N7!pP7X;)b3=9w&;zB8_zwDYIgysR+6MlJV2JZgTIABOgT$H7|24>D8+#;3xzh zyKY%iqA_a64CM6~S%7)I77x*&ho@z-+9T$)J3p7ZAAvXTlleQ)85O-Aovu)#(nBFp zlZv+~J@s!EXPC?AV2Qe2x8xWM@qgW+EK=kDvM;^m-$jX%#8X}}_^WbZAFz~n4^?Xl zj%R5)@O^*Xqwo3nF0=1jxhKO#Xm|5ZH%Ot*~o~Quw z_cI`0zS0)qV;eDMqE&yp@f(f!aI}g#JA3@l8p?CR&@Kv6EZIB?Qasr@Gt@Z{w77Nv z-U{;yNYdDIL049ee>V>Tr3Z~994}6y+LfVe( zL~*qRBcjeUeu*d3^?P%t9mHjZr3zcH#b1=(bHZuj@nb&CSkplmQTCO5-ncOKUr7>~ zXO}(#MI0}p_XUBw9Z{>_&I}hoUH;%ATm@}@Ytb5^tGOt&!%kKyT~|z0b_-_?RCARZ zLcxg9h%d{=k%-3K6b}W*odahEdv~P*`guGU=-EBpAXK}9hD!(mCb7CfG)h!eG^FI5 zd=4Io{XOpVr+hC9GHRYg2{EiG9pbO0{pc-`u!{CO2&6VBS#c?uQcF@Ge1pz8z`x7f zHE9T}UBeEQwl^S|gy7HSeu)=DMQEd|gKT=|>Z0d0x2Brl>e0Q*+NDE2Z%mv2r~4?* zs)BH22pO&FW692q$)y8BkuyA5=q{G1BlUhq1an)0@}`oN?EEaV#~%0orHAOc%vR{q z*;tAA6OP9cdMCD$ae+24Qm~2WV^os>Wz#8!J5r1cHjce&Nb+|lF^e;j^Bs&p-JGc~ zKav4|l*k}_e7EyWNLxyMK5|AW7)i^q2!*m2O?(+3 zqby+A^sT-jtH~dn3!P$OMc{Pqj?n#pg7Crsn{p4bJZ}i!``h8~b}(@ZpyEJ+ZW^DyE{7Z#gl4O)5m zjbk$DMFbl+chBv*PFd^V$J6J}hZ+3qBvi5k!tI_S>L$TzcJ^*G+St!ob6TYl)tfN? z;`rk9+C7v-`K&b^3?Dx02XH;WA*noz_@;rr@7b?!{e&;*zzHX(n!PtW~ul z&|=dUNrRvwc>mRXpQk5&-8k|D{su?2jk5!p^G#(vbx?!4tIQ>Il)tb9 znC3VL0&yIpl}_;L7*w91$b^Glb%SBKJYJjTcuN?=rjSt#n#loPeNN^GB|4QV6#|9A z))*lnJ%TH?o7n-B!{luw>GsRBh3~I*pndrHkLfbiN>UjYod}a51nzmD1+I0(7{u`r zlA9>4UXUc)z-!bi7JWd-w@wwKTI>{`9hR1r15}NZ1`EQ*5she490`UZDi{~)hLQAo zF@x+OMp^;QY=JO+x+2Qg;;>mIgf=Xmo^UY0Bv}V83(+id3?Mv1kz18z$0;fV^tm_A z!e*cJtvb-M`dwsOP$-dbF6uU5Yd&C02k~DDA0g?;H9dbopc?PCHW8bAv+1xXzXd!O z=bs!>6tU4sZ00nAP~*Y@frV6L2{yXW)wS2JPr{^!5n9UpOZ(@-%sgtOXPyQVQ0umj z#|bhR`~OAdK?1RqGv8gu00994KtM=RP(+H`^)6R6>^1s-x*RQ7 zWr)DO1*QM_-!NK!6}Zmzcz=fY-cT3weAX9u+-qCImEls)cv({&mB31~sTfkfRfSU9 z@{dXYKVzUjk4~#tJ(Jl*gbJoBq+P2EDx8xF>QB!Xr{_D@l}x+DS2Jw%PYzv#wr4Q$ z<{p>C>mQc{_~j%mrj`i2vup17g&@6~3r-)vgjQ}vy$vX4OsqwR&q%c1yrRY`CLUFV z{F5^#_Qw760bedcYqxO3Ym?KmN#AZdos&wy!>-x!nld4=Lmwf)5eFXEt2N8Iu~QxU zWhsx^S#3sLoZt=#IX=fu>74~JaBEzFwQ*Ew%DaZW;C2b#FMZ6?)-Rqv|FVK@{dUR5 zVYPEq$u{iW#^I@nmdSoGl-=QFN%G%3_toixR}MR>kbQbmWkLJB8S!{&f*kt2D|G?z z<}kD%#qQWOx+6xG&u@#;zXQfCXpHY`nN;(7PYJ1{<4tW*zw)l)3*&h1^^I(YQps}i zB8H=1{BZ7_mKGn)uj;B>p1prd=_Znix70hLVg6M%uEAvS(nMw|Qrw1jI^F()!-C3& zOp?`_DhrI>MoZJNcGqb(x_b=q@-iLhxTW0DzMt#9g0IPfxm;jr$3;gjS=-mVARB6W ztsy^bdmzeWVb4lNyELxF=1qS0?7=q3UL}}s)nKQDQ-|8(A~ke&#g3l#WP`@%Uw22? zB)w&2o_*2U=pf-^*y)C+Da9ck%PAFlPpgQ(dR#wP9%Z2=N0El$$fXrdZs87;i^-C& zXE6y+u3L-}y;k80%=MJv#%fPz%`^BU_3`hd8prA}Lr>|U+Oc7ct3@844p(p8khf!I zrX`B(z)4b&BxATa7wK3*4L_ygb7}WSJpTf~E;UYL?w5|XuB(L1cpyi#hi$6C4#SO` zYEZT>4d2N&MRgWadgfOhb;v4S%whUtMwPiTS75Z!$IWInA)SZHK%ixRWree_0x^?4tck^;}2eX5ll} zQ$3s;24vdFNEq!91S!!HNtcb#`rsV65H_yl+SsCNpV%AB9$hf^FcSg89XBzCduf8r zq7_K2+e^`mYkFJ|=V7htVLEbT;9K?W!9s=@*1EMVC&8$fB4t}SJcmER&6$rwdI6wI zp`@w+t>nlOd_al$CSHl!zWkvr`**OUFZ(yyQs=b=+16^F?cmcLccS|kNnHfpbz}y+ zV#VD(^0}rdw)0xQx65Nxyo*)MydMApuvD4itFO5-(yK$pMmDYQ5qC z>YI+^l$RA5o+1+kGO}l6qs*?<$W6-U5He|J;D}e}!K$EJcbA$rT4U13njeXmUWV04 zE*(&~v=J+wZ#wNB)meIcT;()U9*UkehG0O#b`t2MofG%By7p%!z8goIN;Qw!=U?(Z zXQIu)LM5u$=Q&UtL#ebx@zBKd?u#VPLds9n#p!FWEHr*k{0WtXAA}6?Sr9T{ntB zlb-DYLh__hEgQ+wY$KAZh& zt&aS4yp;Kg{@0JZhqpmXX%=86H-Ppe3S$=9LlRDkaf6p$%&H$n*X1D8<+2f>4syKQ zecCRqs12xWrI8C$2l&dto;YDkFnx%!xah6#`qIaO&!|S16m{T6l1s@JxC~txbpV#| zk}fu78*-_opFd&<)Ghrw*T^F(gm!-i?<-v*^%1X_TP))>kk2?ud zS>ABr25C^WWbW2A_G`(T>sQ0W+8b1yW9omVy?$VpN{_*i_DXgI#L9*`=02#eRg;M=HgS}J9^gh_9dw?cM2yCSonba zrkM9~Z@{}d^CI1%bV}4Oa%$+4biTEe);qYRO3qzE!$ZD~$CWauy#-f%&=%{&U^UX+ z!~hIB60(p$6*T*D_k~Bi{0173X#Ld0fwhJUOPakRaMlQ)3YkVBx# zg5knbl=(sY@Tiu8tx-ohlpN;g$h{F79#p!7C8)Le%inWP^DOB~p4DHV-J z%iRm{p|f<1+6U9e;@N};bY3A^C8fb2H*J%lU4r)6`S8^JoA7txgYiV(VZ=#hE3B;TL6vk(G(qY_W z!POO0YKZ-vI1SC)sYD#G;emLBMVFt4Ej(J~FvIPe{CDkLfm=Y>Pwm66S71Ztj`3Os z@9#@NqkqMB9WAzSs(>z(#CrZ*|UuT27M@1;t zZUYh8EeBojHewBZ)>j|%p+X5BY%J3l!Ume)@n*gy9%`4o$E1H2a8OZo{WZ-OPrsI5 zn;3l+TqmR$*P(Q;JJVe2Df%Se2%sR- zpqj9(xHtFlijQ#C#2pH2HE!G7y`#4H%Xsw=0o=d(?;->v=_AAEo%HI?v2MZNOLFm)M@RZds19xmfL+ z*|#nYtu=Hgcjw7Gy&}%1%S2>>v$8wAJ2R~+M-kNn21-)ocgfmrC-ArQ-Xh%l!S}+Nf=QLbte! zep3kGSahTxx~WCY-IbL{MyGt_qY%(_XX3GeEA)%;x8`3hU0@05AgN7g3Oy?a+V;Hg`*-ss>O+;-AIeMN=up-v9_UVbSd##|#j*F#DP!Td`gd@>xDb?WLvhVQ0Fq+?C?warby;8PufI~? z<-x`!=fDNS#g~QK#b*D~wDcQtN9$2Rye2K@SN^|IM-qJaeDu}~GeHQh)^sx^YSw}V zA^$P=sr-ZbrAzb0sWg?yH1d7Wy7Y0r&gI)2GCJvUs`81g$EIuze3XV*Y#w3&Y`S0VSRR_xr|q6*|QwRQZgI{ z9k@Jpq6J>dJD&D?SWbqg-67GR)r=H~73}CP%VZGiA^$CuoJsX3R?O#lvMJQVc==e} zg8@B@KFY}*)1dk5MQM1<=aMq$eXK5s7R3y`VZ4yjU*=^)`#4Wc#G3axQ-1-lGwk7V)I^lqBYBxsT0Kx2?zkRV8*_ar!tkJt z=|F*IsI*-eOxopCqFj4awt>@kgXY2S9RTy((EO7v<|`_58AtjJm`_I6+hS}M8iGyn z_x{c}*|HIA!gjiYJ7I&`Xc=AMJrz_UQUMCj9}(ZFV$nfn92bZ(o6+ZX!;3inf}!|B zw;Xg|HrIE>_rr^k*9sr|x^slE$-fv|GTpFfHzJBNIzcBecC?-;DJCA5;0Tmo0D zDkKj%y8mPQYnS+kI@VXwb6ni{3zyv0t0eB0oa3$Z$_+zzHe)BYf*-?J`G|k3dd)8> zI|o`Y-!iusuKN?Gv3E`4zo?xD(Dk6R9skkdGOaebO}zw}nI;!jpYJW8BOWZ)3Bj5e zx#CMhIEXnU~ZtFn%w%zMBj{~So6hLKHD34vBImBB6|rr=k_Ov9TDKb zjHv8x?aep|-NHo6bZw~E7&z;lfqdX7)6_9d!3T%O%i+h2Qy8eO#Jzu97y_0DR%Boi zZskbi)tz4_p5?G3RN}xVz)_VC7q~7k757;4Jkcm*1b>l{oR8B5A(n(aqU2MYFPpVB z6h&y5q*B8!@;^PIV@`WkEl>P_59)go7fUVT5s5G*^>im-k*|s-$5wkRp}EQ76+Ugj zIq!eLU!gEOZb?$hz0Nd=-2hv+OEaKb!CToAt`hn51=q`0DETbq)jvAF-4q1sk#2!_$hgUltLx=?;T2fk9Gvi^`h@3j zR&uPc^HEtoq0tCt$W$3NxBs3N*XP!q*QZ75Oa8EYU7qIO+Fg|}YnA-+Zm7E?he&Gn z(AN0GyFR}uX2}`m7h&ZmOt0-I_21pyb+NddB+Stfe7xs*vz#j`{sX^tCE}YRD%^E4 zBDjOl`FAUNnt63d#O!&I>x*cPXld<~b;(78#6_cVXV_SgKgMbR!m}^f z>2Zqo9XrXZ8r%X~!OMUxcEMkb4&r zAnz}M7jly&d4ZP}*|0Wqm5KCVeU^iDA?5RPpo+xYb z6%IN{rz>_6!{12CoCs)<+eX?XBJ8i zR`WZ_Fx(qnx%dyy(NMo?28O; z-Z+y)dMKc{Y(WBe0QS2<<+6vl>x$12LGh3Av;PrYZn-p;M6MM4hQ!pmLfci5##IU6 zs)BR1Xu&DENU7-N0JSwmYN5iL{aO^r^Ip>_oaH0nWGEizG-=y7Cz?v!P{V5jfANQF z4-avR%xP{HbGBg?@5|<0>Rq}g`@701KjGl;*CWuelQ!k)D(`1d(OH4R8inw#Y+>_e zi7c*o;0cv^4iPe|)so#OLYe%rSM2Slj9-JoEFm(^=!Nl%%U^sek|oG`!HP?^E1Y%R z!(|EVWzAaLJB)6RaozREJGc*39Tlm~n943AQZ} zxZ&%U!!a$wR#p0hG)dkF;NeG9AwCww8KmbS#%b09Y%L|}A!8ti-} zaK3ggH3Jg7HK+O&nyt|aYOmF+`N0s&Y~xbzzzLFjnPtxjQ=jm(yg5^D=vb+kTl=j>XHlhNK5n z2XGxTQ^(Nk(5Yn1$99jxX4jp^;DLcclXrG#h1(96y*!pJr@c3V8%vLKyT5*e8bLmb zqJ&d}@gokjki-s!gXDm&7f+qCn^~`8?Lp4)v0p7FqLVNQ2L);`F>Edas{wj!ZeS&4 zuE#B8m(>8`w3r+Svb-mQQB~NHt^DxfwPU!|N8ZgB#iltJ3ce0H%gM>VK4mKuBz_Bw z`qbSnzEXE1a>Ji)l^hx+=IA66VBY|RwJV08LAR64Kqkv&Wei5^?(SV1O^pZTDoz5D zLv?Ec`f|yFK7|7RavcaDE9G$Ql)G9Lhx*&1IwPaHTENXoZV_<#0-#nD_=>dOZFAaF zPo6y6h>h01UT)Rh6VW_|OaJ1JuH~`qiQVBfGvVgQH21epcy)N2(9(ymoY~oca|Kpis{4TTYxkX}3){rPMoy_j)Au0Fk}LiD`tK{%8G41l z!}o9ErvR}jd*hiP#QCVAKQO!%PM&!FmW^cH`A+y2Ea;{A53?yOOMep|!ABg|!UHT_ z%fq>&Z6dvcusl7km06wysty^a|6TcdtUeojF$w}dFcrb-B#B8p z33}B=f#s0%7e1>!8^mRd90+D`6`>IP@2@SiXhW7B0@pbRj%_5l)KC2IOGL#o1Lw%` z7fvSn1I{QN2sz;*lKw^lie-k)(IrSii!6Q;455=K!1zZ@P&yIPJ1(2cUwDi^QHp!O zFmb;D;SZM}wizbTOQ5{F{|KWrE=QUm$s=+IQSXV>>i?`G5s(h;T<=X-5Rh6-5D=RG zUq8?(3Jxg$aaA#nF@F@Ab2boCj5sM!V7g6G%{@t@RZvilVaz$ST433YauhjJ%*P9tfk zK~UTVHD+vRo2UoD@7{c&h}XTZPj7IwU7VpDFF&@M-Y`o?#C>~y!GVH~h+8D0-H9V; zZx8NJ&%0L?;11!CuNVLSY3t16q3RkqJ|?nOV;e?SmN7JzELqA{$U2m*tn(=QzLYGX zX+(N5QC-=xuaPZ-NGODalET;-G+EL-l~Ufk*F0@{-}Cv*=PdVowtLV0W9~io_iN3L z(+iVNTydGm*NiyQ@m23L>`pLAEm6ic7JK4cx`$NQ>LbJ+w~GY#)M-7XJ=CB}PgvbF zD^Bh>sGV?l%+8YiP)aY%Qupb+t9QNieMc<@i@oj9wD<2>^#MyorDx1al}A;YbeWKy5iM_g|DkJ`>%5{()W ztgM<67>~4rMx0%{Y9QGQh0$;`K*ejnhC2xoxOTIr zE>n|L)B8t1+1e-c)dqxim_-+#^r}1M{>Ge|>UBNi*2kJA0;P)PWB*km_{h^o**ou^ zsm$8btMa+AGb)RuvQw2QRW-Ue!jRmkq)wiTSytqmv0H;@Dp=vGF**qW8i#mqK`+t< zWTVK}i!*j(6$o89ZbtQ@_j|any;@#<^i6_QA^=$yjJ3vGv9uPIr&_t@75e1EUjQ{q z!J;nS`B7OlY$&_#Ap9-a5gh|5azpg8Z{^q*B{tYRd zD?aRkDFrotu<`BswHuCcX(V~Se6Nv$?BvD4;eEZ;&?}C1Y>pk()h|Dh%d$046jP&} zd6@mZLFBt<7RcsO^9w*-`Md;0Gj8nl_KV)sYMSp{^4gm__xT$u4PBC6X}|6h@Uj*e z;7B8zl~Y);4YI~wM_YXQa6LPn4vOJg3J>E?Cgp?}vAuNWhjkA^E}B6^A@yk{->SjMlvizuS|jYZcY{TyXS6c6|_`N|D0iu4K=6SU=P*Pu6_!MAp?HR-mCpfA#Z$F(s+k zHk&Fb0-?e=BZ|(6T*s}OJgy91-Ayu2*)6yD5QQY%y3!alN^w0sDmUIeG4_wL8Itb6 z-_o{ne4V%-6VHtzSktA}?K+&S*ZB!nbZE~}$D!lvoE{RsG(~itw0Hzpgm^V>@^yis zc5(4lMLm(Lf_6@geUdzGed3iNB~f+`ql-ZV%lu=Z@@HrdW8B^b`M2@}RI*M-cXuZT z{=H&mHyC>R>j}d(2egu=eDX_XZ<=$~OW%!-ndO0_{GZjTBwHZ6t@(MG%F;`oYxpOQ zSNR2mim^8%U)or^Oe8k&MDw0gtt2<*MBlSLaHKmMEO=fbY|zJDJln(>H*=wp&!hiv z5+SSFgy*l~B)_g_Ma+4|s|HJNc1J2|#VmRo>q=|ozGt!S9D;n`tLp|_;^mWH@K%>} zWu4|xH)Ayley*yIQL%33T+mmE40HHqorHuW$KX>UCLS@#B=-!bIe*OiO^)b>u;A5FUzxo?HC!@vPnv0m4=6-T>(jY$TEZ?c- zaL+ySPYp@I!u__#2rHI?qJ28{e!4q)FC?Rk^!DEtx)OV*m^)P`&{Ifd;94R_z2Aqk z1i=(%ji}?V5m}fVA4O|sAWqiv?_oaOPcDzRyyIF;rWAWnr3r;c4`&*TL*E6-q*%zg zz8qj{XGarHl)dXRsdryOJg}765&TI*w-69!d)`+vth~S;wvWjv5ZH0IJt)S7PW2># zs&Vg5Y6ijIJ9l1Ix>|%)j`s@F-eqO0K)9NWl?`4+9*ih=4!BDW%_WC&hwoL2jnC}G z^vz?U@Ags}Us4)Pm*mc_=JicfdtLLGiMv~6Snu9IO+V1+zNUO4BQnPK%9I!&1_~GZ z>THXu6y+SH?fPia({^+A%g&km=`+n7DK08=gDQL^mDG0orA~FAy*4IDE4Qq(jZmNP z?P365ABnrW&9j3{2c{RS1Ut?!DY~%YoIBF2FplG-(qguP^l0gPlcJVYWl7Hz5v31v z*BoN(^j&rztZjV1__D*^b_Z;J076Jr z!?xlt9mg1D17rC?N#-|P$z87Gql7!K9J6xnI_-s?*3yZB_q* zj}SE3mH1TO+{gHYmBriGr0N_yx!Ce7*BET(El)=y7a1aX4|ndUv)cRc4kF=HLAXL7 zS?!1!AfAv&!UK7xW)|bdU;3$?<WNZas@@+6uTG=e2qc>=e`PYj*jdmEs9{p4>F}mh@nn}D?EB(S+oig zq?=b0d#zNsAV%bc|1pFIn!dEAe1|7Bv_4ghNA3O4FAZwAx1JBPzyi zjK2(1(HMVfA^*#iRe2uHpW{CM^xlVNb4yy5(Jxju3WFBTTWryoaeWNpB~+zEhe zI*4KdF42ZUr8r=)zXV_~X-ItRM<^f)Gl4;}yTPduF<`V~UywX>WIyyn{~(~afJov5 zBPWi**Ezx7iQ{m6E>L1p10Ku;o|?qNH+Di13ZzUPg;(){xg`MjfFJ-mPD#TJ_!(Ir z8aKExxf8q`jo|vxY5}nb$vF6RN)^5YKuI*XahVmwPa~LVpS@bZplKw0NSIMxHZ2Wo zy0qs(ZUT~!P|D`;euM&Igct)#xXJ^@jUj+7_SiotC@vuSOEAEY85w|KjSIE50;xF} zY=Iu{Wk6FiDgeXabW^L18wS(b0tL%}iqvDk7Mr*&K%Nq#l@_WD^QQe4_?C)<=cqts zSjc-z68O{X=ttcGV&MTWXx8{&lcVNYB)nFGQE6jV3}DzCL1V6C`ST1^YeA3-WA?xN zWd0m;*o}mX7qQS~aZZMFFVBWNB0L|x-aJoLDJbr#3@XMXy zU)8!_W0f(6AaU^1yaK$>0VF;X2XU_z;G-^3avya05n$tMA^3(nIP}^bKHv!+qG>T! z!QnwJ@l8R!e**%xtW)Iuo8QxSdA-e*%aGUmg$@26?5EhCIgSa=w+&k0Y|sM(m=5eu zvAyrzLCav5&;R!JvzaZ@dz)tzlwtaP(f0d;#32XxP#_dxLDpdfxK0Rk`|yK-6gKe0 zupqESBkV_~P+UNi2>l6`uuFoy!w6uD`p*`)HsU9&xf2D-QxL!}eGwQ;YztgM_zoX{ zKfdv^UIRN464;i8*Mf{90!9?n9+8GWNQbiWVA==*`ZDA9sa?oqa9RgCQWg0XFHff%59CjAh5zR|&066m+{l``Lbm0wQbicUTBq8bttGcD?h``a_(MU|_#sz`#V)mi$T5NH3^>3e7!r0!_>>r|)?YmKbU>w3vD# z+xXyAnhfx^_WGpw_;OU35_JnyJxJTkechWP|00E6er64vrLE!^^HGR-RtB!-d{KP) zE#nm|yGjW@qX&7w^AM#?_i#V&xDVX)onHQ?0f0}~A%>SJ323qi_ zUW`-V&I%*7n^c=Qw>x~9I^J|gWMN33y3~i?&6N0$Ie8MCEi*wjr_1;druf($Jr;<= z16yD)wdSS&GJ39dF)J&gh>q4ev!sNPP!$wn!qc%a!REZ?DPT14#~;gBqYkPMA67ep z*yw3I_G+zm+dteG-Dzm(J{(y0y4n{QJ^l%NgDga7b&Q1?>_7`p0TwOdTad> zD$c+J)ihS1d%b-R1hNq_ZfQndv$=+CHwdaxP-5bc^V}|R)VV?sQ zG`MpON9^Y5sB&G@uWp8}YHprga>ERzXU9BnKh^Ve94m5f(oQ#Xr}q_owr7v3CY-az z+)VtLTWqS*nAQmYq*{+?7}0yH??dfumg4P|baz-_|G*zVa+qfC&9GJh*E<{0L~!JB zC?O)kPApy>p+iKk6NR|Z$(C9kfy)Ql&w6~(s^>nu&_xXUom17|NQJ zC!W#J`GShp z{)gR21Y#3FrI5xcJFz4~Y=Mo`#nr7e&&QLS!6V0^xW_}UrI5erSoP7xqV8g1sghvh zN-O20s{OXLL^}_k7@xYAN6%4T*3|WEN+;B5BHDZl~&} z^&cC!{>r83p4b2)mRfEWLm}E^u?J%nc?d{&FfdqHu>Up+SYc?xc1hZlzbNqAU0o9M z-<9H-q7yggm|Trc4LY0bHl^f8v1D<1vB{h1U~xP6c3#2b!QWjUck^@MBM!dY(m5WX zb3~Lmo?t$q7wwmQjM2^Q_O$W>O#bt0-o8Qir~EzMzUSqKq9AA&d@2ZOHv9@udx%hf z-A@kH{;21S$B+;d*YzRX2~QxO164DaRw#DAKbOVhkeu4XAhsBFxIA$d+RtTN1e}Dy zx#+CB_7Gn@YtTtE%{MZn^diIEQaRlrXZu#7g8au$c^~LkBW(i4ZT_*&mv7{-hO~uW z44Hw8d}>LR4X<18({b)2_E@eWLrkeXyuYkZ<_bZaDHizEyx;YY`4}K~keO(YJ>td> z@uT)orpYAEP7|Ga@BHk@2nN#|(0yyO7y$WIR0_^|;wn|HjQ1Vbr?{6FZIeh4n_(S$ zTkBJy{rWXRcX|@I=r#ixi#p}4xM39y{W4x#{$lLWwoi|@P{UI!37}Y22a*ZO}b((VF*`8paErO^WCTp%N z<>FN$pHBV+K8IX9p2Is6LJ}3&!_{Kncsy70KWeG#EZUoORe|!(^O}=NJ6_7o(DDOH zW9Ug28!xAm3HH&NtiRisRH{FCw96|_s%;`v`gN_(v~VoDV*I^t8ytiBA>=gx)7(}) z#l({u(KeWVjO}at0n5{~plTc`GD0_w)GhzVT^sy{s_Vj=YfjDjaXQU}RPuvdqJ{e3 z8I^kn%`FmyFMyM&p$|qO&G&Otxe9IgpO5e1ZE7+srpdb?A-_6Zfkr1ZSu&eHYN|AY zN?Uj%RL;~%!Irg)-2wts;VR0l=}%^XN{`mw$X-V^kqOIMPR zw+INRO)}`8{ZJkr@DrAif%1aH-(HSr54jVK%aMrk0PF9En zH%MNT!mPugh>L{*x{ijH)TKet#zMAshp#goVhm!_p0~i|d=b zKX7*^*a-1xuCQu`L9M{HiekBiSQ0yn`J$*EPfRJ5xty~Qm)yRw2Dbcz`oGhg0uX|1lABxTc^AgGQH#C~UWis6c^j@uoY% z5%W9q98fvVAT}DuiIJ>>vg{baVd$R_*It34ZyL{HL7T6j=ZXD zKGVCZcj{bZlHWA0wSDWvXs~uqKy|(%$5&z#$PrDdK2o&w5ts!UVaKN#7Ztt9Z`11g}{ zcd{hS(ApwuI{YHb3KQC~^mFnZ@0!Up62{`MAJ3d9HmhzD@kf^LL)2q)w%}XS*^~qS%%ns#qGIN=NbuLV#TR|pEGSRY(K;zUkUVM%e zd!=*>X#socMI;hG0N&8IDlSeAmvLz`KGE`M(?pj3nCq&ZQ1SginfsILm|eS zH@kIU+X7XJ-5G53@UV6*F_ZZ1hYCDC`*%TSH$F^~9sBIS6jh4C@9r~Uiy^MeGcH4g z?Kv`etoI%EL8;x-skig=DTOOurPqz}J`I$goshX~=SFDnq6`?7Z3u|C3if z-*`tqVlp!`ZkoQHn$!ajh*^DsADebD$yGPh2$f#y#BXWtF865&F`QwbsdD4=7O=$n zT=AhV>SpHUA$I}?!opy)s2EuKlWR(B{ASlW&pm68z_fhD?mXOEG`|*EE z8mqiOCkRh)+dW$P$&~q@%j&Djt3?&!hj6mpwNG&0&BO1N-jNMx9wt3F;sc>59P`X- zMVw!hBqY&r#{O5n=Rzd$eb<>an8LGvr?NvZ^y% z6U#A93?#Ue|GpZ|F98zK1+GjremNb1@6@cz z7V_ywkBWBAo1>I1)h&AV6h5MC_rVk-cUbkht>BYOwEBVkIp>4fUpez)BPtm14(Z#fEq|jjBK#7&zc4OF1<&#B8gHm3f~};t!6o*nbFq z3B@xY|0V_RD$!hrO8|zNzpW823?jnPp~tz8_>(T?O9T2ahz_ zec%rwzyE!9tR9p&hZzsOlF1 z1;Kz9-<+FbPv@}5xU;}3FJtCpVG#x&Lh&khYWz)?k-B@_E&+TC4M`La=?JOu`Rm%N zWamCs)eN`k)X;cwYcN9j3Anl}F&B`^p`!WCf8FIki?6h*HvytD0Nr8Ike3=J;yH0A zV+P5P8*ixF?qoy>YJQ-LAN{~DK=$ur#VVcTvGbd-zd_7Jt+|elsV|mkHc`5t%(NembP<$4=Gb1pKp5sg^O!rh**7qbcT&jeu;haDMQQE7iCS#+w6MCo znvrj`4uwQG2YaQluyN&~X;}bvxNl1qvXbgMzX+CEYX(pFTdGn=f=F(%kpGOi*`XBK zc873Gx75)Ar>HH*zo-dBMAQTdDZ{X3A31^gaSO!Ki^V@NR(plHRkt{Br8OU19Oh(M zbQK+PpsuC;XfnHm&>(36OT8cS)qs~W&NXI_mHZZ}=6c+9WVw(4{T?72(>Ai}A$JRO zDcD>=fBm(wgNJSH+;pO2NE^Jh7-*qv*$nj(^}JQKZX?NOO$Cc)aypmxVd)EDb$DtC zuuS3NuWXpkV!wJ7{5N`H5-;Om9KiD7ZHs1pnT^Na1IdWE?zfaaIK}8Cb~jrrx#q|L zQYtpP=ej12rIGe@j|H?Ok^hxMJ5@eZCnB2lh6o&0>7Sv#b)l=m1?FQfIX=ehys%Cb z%@F|bhsvi3!eMvT2opkg8j^c7Ms@f8eV^lD>Ops2(Eom?{v%#l8q6Aqev&V~B<1G4 zV`{27?tR11a0?|gKMIgy--}ugV_BBujMG~EJX_Pbd;}Au{Ril2Fn3vRV!)?Q6{-w} zbokVSg(mz8Y0>HN%{PEBKf11;PIgPxsBG*_)0jaWfF?p&l|Q;_Y!H^kKLqJTE-+Sd z_)HK{&Ep6ArOptwU!9HRY?&vYr{`*=yu7dJshy+i$z`oj+m$-mW$M8+zpLp<8J9Gb z!Z4lLKY9je{sD@eWgY~`snUNL>_KL6d83>Vj~fv10*XQriS&=ZAR9=l#FF$WBKkGR z`%>T->GNH5Fkb%2&*=*Ji23cy&a(0(APAAx*5Q@K=58Ho=&A$x0bD_+uDOPX-b6Hw zcvZX*9iHZ#&petTj)g8s;>2$OGE{aUaE--kz35JQ(tvw47OidBaeJX%jUj&V_!h-! zXK()YA4(-Ti<@YVyfZi$K1=1|Nvip>%@6NkTIP4gy^%%r$Mytj2z$uI*j($Fzz5~j zLCD6s^fD+nkKCC_TaXA+;c%SN5^owz4i)!xv1EHnZH+p;qht4o)|=}2d8(w5%An$; z!^7V+aiEd0X?E!Vv7oO(3YVT0&P3h?<+2^`lZlrHGxP=TEfMM9W~EKX*T89_9p+QP zi(`^lNA;t{5zE^>t?mi3AgkmdZ|Bfsc!-AyZ)ie((nhyyub||=OOdNL=pJ7SYQ|EG z-Gj@b#{+M0^OcPJbLAYims2u9t!>FA*z~=|4DbNqE1&B*pKq}b&Nf-u91rELq(<4E z!s%s{#9ddly6Oq;_xZ%H=hxmZFbUQ-{ng5tcGlJ0B-G>A^IH@zH=S{RDTJ{JDaW&) z-4CzTTdM7+IalL;(k613=lJR2aUiOo`IgJ!k+bKSt1-wRp0!a_S@?$7L0FMUE$P6c z1Za~xY`p4m{G?v!+TBPriv0eP!PfgnL*3VvEEe^EMffiwqfp##<#UL7Ko9y;V3GA~ z6I3t^s?SIPRXfsIFTTOHE!&lZ$Tj#$W0__-MYcD@Mi}fB>tAq32+sH%G!=4ANaLLL zET>Z1Rx844r6FtCF@yzNC4)x33V)^-;^poN@n4;5>qz6Wk zH1`8L-x!w%1NV|+Kl-MY$%&AOITrdB?mFEsUPT(%SA;$T`Nfbb%-k^>LP3H z@V%U>P^u|el)68Y zHRfPclv6g}53DhQBoxm_l%H|`5&{>5RZI{AyIXAV1*s)OB6zz7$&OAi$H?VN{1su6 zPr@WsK{-K`uNUXf`=|^z-7%g}b@F330#|bnnE9k?7V=0>XBUmaVXfyEO%Y0XTW?^t z?4+G!q<;dmt;?*z*wod9rM4S>iSlL71;;^=s^IR>E)ZYtM`%5OC4q@}^8$a)EdDx9 zQ#EE99N3izLyE{XzoEZT_LePFIFo^G)rUQO+(X&&3Xp*n~#pW5rDe*%X$V{*^!4s3IYyJvIFM!qv zl}{<`8bba7n}-Iuz{K;XL1t^jXk!TcVfb$HktTU5c<5dIF~4|D8vVuH#|83xr%hMs z?g!K-mER8;P9UOiXeuSYAxWn1ATmaNOZlv+q^#M6DMP`;KPsFJ{0yifhkjB36I>vK zgOnXlEh0PBk-^ST=V?>an#`_GY?jC(oM;=p?p^g@zCRNq5UqA|#8SkQ`>7Ah2iv!F1;=MSG_PjzE9Z@Ihk0{-CiM3(Nu|DR6MCsw1By)R$53g5 z#m^3N8fF;Z*7_=Hr-Ay~0=H~>f#@9mXu`@iaSds<-7JE>BOk!&@`3ImsZR_dc8>^O#aza>KF7OPJNFbBpU5oQa=xTw~Kg5qa`qDG5KVr;V zvd%Jb9y*iFOlpZgKfPB*<5G718R?Z1^ZpIAO_{Z2_zdgE^i*AjF25CL9Z}K~{}*1^ zCsqMe0xd+_(M{1ZzNNAeJE`5AH)e;WKn6k9(%|&do@&8Z!h$Rb##hJ^Z*>6ow|j)U zA9#dDd~zs#@&LmBlBTqe3;edj)H--16}R4;Iyf*eCTuV;`u}_=>@=ls_<#@QB-R&9 zL3`C&sat6bd66W447mcE&Il?Q9AyBh2)e{RSX_H5^0m|WE-{tTfk#!UR4h>y4vj0k zQhr)9_?VKn-_6?jkF*1xSLhm(1RfBp}!&W62uV{8+sIp^h(gXNbNw;NmE8IFLE*VeMV&tjeq3Dx7ySe(L!VuACxIEUqWVk3Eo5-ULbj0C!@Z#i2M1Uf$(|=WR$t2vLIm$kD|q+s&H&prb@UFUX*7CDW3j4iT&QwM;?T)`FVr zAoBOGzNR$$P+F!LGOwb9?YEqG^CLJb%N?gSu38#&M_^*#ivy3uri&3KI_G!iE?|}= zbU-;6+JsP#q)4<2uHL0&zxvm##w$;@ZqMZ*KxtT1p9zbdL_nfFr|M8uon)yQto?rO22a!{f)QsCJr5#CP%*YhG?2B^GG|4jGNjDN`v7jb<+0c*G1csqlK zwUNL+{l(bT9D;p}i0(oraA54VH;5(B2om-Y8wR-eC^6Z@F(gN-qRkZ3U1Fg&cts`b z*lC`q4!tO?EU@W}U$|818*Y(Sd=#ro6-?yoh?DZXT!xC%*dkefu`K?Ey@N;2)nZKm zWRszUd2Di8OoaVc*#u1?vse@vjSJGE3?~x_K0B#7+0<(pv?U^_=_NDB!E>vj)oY&K zU<@$YTr|;9pg8fll%FS* z$9!@7sPV^BRX#m>)njt7dzagyjHD$1?aH5uljSyD(qHcS2YT=QyB^FtnBIS z+4=Gab_OLJtsgl24Zgj*K2Hnvj!Ld3CB*EPmtJhnrG}VZ>Quikp*j`I=&fZMh8%)GX+z@gc?v?uzt*1tXSgn`q$APMC@hR2J&L~=;A9-S{ zu^m}+$E(|N8uZjPO2?jtRjc2DxbJn+dFMiif2iY?SD)JZ_Vr=umGD0aP)kBD-rW3f^0sdjmVw3&&0ZM#eGu|RmLzDDl6TbtXzLw3HSusL zciNsdFQ=E1jh=(|Ff00G&nqm4h|wo>&OesTO>4-`+=xM~Wp+0sD0)yT$H7fnvAm^c z2&}ecDki1fAmA4U#rPX;dmRbPj8yuP^N!3aotbk*sipoyd_rVJ1_S7Ch zq&?lb`Bkcx<$~;yrMIzcFJ7*+yMl?S1FE!&1Ng@9Ul3da2lBL64Djim&#&Nm-tZji zv_+KKGHw-=B)HO8-q5+R_OZvifAEdP;oEZMCRqDqYgA>J@Fod?);UE}BX}+@gPgsi z(^y~)7klb_q;e(0T<2%`dNtBv^;I1mQPe(eHyJA7c*0@z1;qm`c9PjNPo~;>D`uv$ z-vGw9#926x=z;YzLIzeGh8EbmX5zZ#5H83^YO|Kan*tk+Gb^Xvt4 z24bnYu-)i5RAdm~MH7(qYQ(1?A@7PN{lXQ7Ph4I;N?Tg^UUG=r^K?M@#wPMJ$<4_m z8I7&m9d=Zux-P?edKB@Pcgus2hW1LpF^+s9dW=XAoOP`aBHxf}FL#{9C0}ZVCoTd@Qscs~AwyA% zj&Wsh+!?kwBXwGNf{ttoeNW{X*X8mqw2FmmwEy6nZHiFf@%~%$Q5Wi56q=A!rZG%3 ztP~-q`HHQ`zjJB<1wmjj4Q z3n`=rbbJFay|Mm%wN5goeOplx!?DTJb8u$?(T9(UiLp7Nlahr)mKR(i=aIE>TwF4S z_^CKHNdLIV@GH`htoY?1wmk7JV*kT=S*t->@Pgz?T{6(wihJ`nBOP1O;@5)r=kEK! z^Sk20=V?jQxB3y`6H^FAr_`PPWP-drOzy;Z0K1%uFa>QSI=qbCqTJUlUb-vlmi*dy zj)4VqQn5pLdV-7x*RLSOZL~07@Zf@DG+fqa*^l02ma0ALgLDlC>QH#=MKxM%-6cIt z@WE*6?;(6XU{ZL|DjaAaRPFyk$krd0w~TsycKg7+8uxi5b#w7y zv!6u5nO68I0n|(mb!Aol_utq$>3N%PCR@u)Z5!V!vlZrJ9=*CSRxK5QljrMW@Ww{TK8JD2=pW2QKzZJL;Ipv&^+&dW*v}{*1 zSUzz-yK%XYM+8n8D!*HqqTM4Lc_-gI;eE7Rm!`_Tsd3LA9k5(^){8_@3QECWKC&h zCr@|mbxH@a?XoFck%y&nlL4g-@8)YcrGgjwG#%lq86u8o*|@sgwzrco{#xoL?kwCI z@w!7&z(9>{i$)%o8Ga@{#l*J}JvqVh4lHv;*LsU6F9{CVB##$(Wxgwd6y#E>Va-_arru~T^%DM0)SC}t=>%lJyH+;qKTSZHpLz?X%Wvr?H)0zy>%QPY(d&NOjBWY* z!SAuVhR-(dr(=O^vNf2cG^gWs?zx2CbWD9?xS(57MrT>>X}N(zZg#v#+wXXMt=Qt9 zHN4_l3L{lm0?}+x+pcM$iofbj5V#jd6W}||@3)SEPS0ppm=N{>keQg`9{PIR zX1NU};MSM|;cb{3)b={V);NP^*yVIJKQcQEp4>zcN3-h5moc59y zDtyQyVE~>TUaiI8I997TTcecMbun!xS8O*~s>BHw-pj>hnZrc+w<%zM5Of1yI8r{e zVteCRr6{dzqb|0o?GavZd34-H#bC=a5kHjC7Am#>CazJJfzyI7G`A{8PJt{x3jN3JZT(?OwH)DNXS<$3g9xJJe}mS&YG!ux)&++&B|Sh zZF711Zn8<8kus5sZs|RthJ7-I>&ECTyT6sIW;xg$lyy@+(I@lrbzH;*JYR>8NWmfpc zndd}Z7MjyZm(}f5ZF+q{wZti%EWL7arC9&9TkrQ>$VDJ)sSZaLQ%kjm2Kly>;%o5!S(7tXZ-*hlmEM zS!2UZ$Ey_eXDc0Z`)sdxqa6BW3i7;kXuosy_fDBd41q|)X`ku#o^>8u8RcdJq8t6a z+TyaUg^0!8G(dH=(|e0p5~V4TKQ*$v((Us0Jo@s#aW{WUaAz|q_IPF1B>Lg^A8DTP zUzrcz@B=z6pQ(POCcVhh`SL;$=nPN%d&j$qErsw*W#m$V(-JZ)Klvj$K+(@oB~JjN z(pb$>LYNYQWT1bcgH#!$+FlKtx;j@pdU|AZ^Y`Ok<}OVN;=c_zaH?7cn;}&N3=KbV zB@9P#Xa3+%?$;r_PwqD%z)YZ4Bfw0e))PcMf&r?TAS=7DF_ii-rk`5N__87}yg?IZJ;Aw%*omusSz3X32H#`< z{>9TsEX~1&Wbq@2qjvGN9)-kCB9|~+t69|%`^3Tvj|s9ZqG`VulKH~8egD3?BOGFB zI15O#3Dm*ORw>xrMSbe3nt^Lu$ucyNhfW|iQkNpu{+PGd3HSv-FW!+|K9?JAXSMl& zGwAL7K80_G90}p*Rx-iN^Y!>qd}>)urBhxWnI0bIp|F@+U+Url-VsRi#h;TwI91FX z=C>{_yyYNqPwc@N|ypzNQ7+oK4-KMcR&hx<(fw^s%CI|+S&gknxmwmJy^$_&m4`vP!{ z`xS}YLS%SA>JT^Ls_>R& z%Kd~Is;s8;H`Pmcx^dD7A4+y5=rP6do0KQ^JJ*5h<7(qjba$4Uz3?3|&htK)?&aue zDLTuLXsR1AQsWVrEd*xi^OF;Way8Jtg7^ylBnvBh76grOvM1xkD>kwZ#h8hjf$9(4 z5JkoLi2(DJ0IMoW@m&~>PopJch55RIh};Q3)QuBoRXRgnAgz$`ymDjs0l4EXRP8~V4a&p%-U<(H-UIN=o?l>H4#tha`*Nd``l?S%`?`+yAIv< zaD+y^u1o!Dbe?OqOh(@J?^e}8x@1(_ie-FTNO9jAbD3+d?!f+8<Idi}L_YObnei1w_ z%6Vp(8SI*>cT2f*=tNw^nod!}pxrxwnN~)jcE?OXi;oCds^ZgBf9M3g66ysV6E3qj zD&)!q&x@J6%QPdZIT(>~gdnbFfBUI0l9M}aMezuf(U4^NDwXwT%>fZl1iepidXMqU z5`Fzvef`wpw~U|W(ec9OY3A8wwci%uec4)x_%AMae~-tQ8o9{?;2_|PSycWDLBh6n zbq?m?%YO;-pX5Kdi8i2CqQ5iqZ|fVsWOr>|I}$|{%&36z zumlqfOq>Y}jP(D3&aWB*fSe35j{<#4?pKybi!3ZUVhDOBwBBDTUs)-uhk1guB}sj( ztj_iIl~_ZEhK$ZqtPDs+$%Zw(u5~A`wXMKaCu1Cay*J_Kc?Ife@u9s*mYw(AAE$-> zng4j7`}vhWpNGvQ+Oz-Rm;W%JoY!4ZNU7Axt%PT zu12AZaBQ105f_GeaxQ8#A|Lj1X!gjnhm)aPmp3u-t`=;=u3xWm1M-~cgBs6(VE>^U za8JJI78*igZ&NCF1~5ndiqeA~Ao@k$s1vxMZJ~^dUEPzlO!*O=QY$5M=SQsL7z5>l zyJlqSCbl_uiT8=V?b1OwBdG~?$+j`b2%r4MA5=W-nmvpV?G0vuUy&NnF{hBpi+GoE zLUD=e_mFE-Gv|=m?vX#dCVh61$dwOmSC@K%wB=StanX3o1~?hQ2u~$~(?kc-8^n}a znCL4Y0&*UIkgF6;e2V@-t9!cLb$#RxisHQa`C=#oFn@|WNO1ig7~28fVv91F90U3i)`7JUGYECJD=%M|GT{tFB=nuk}v)Yc{Fy)-)hPJ zSz^B@r;(q3Ao6h-d6v_`-H_6fqrq*>q-u4v#4zQ$-SSt8M1W_{;iF8clmmI=*;J7= zy|AO!5>Sn?t)KGL-tXL1s(?ZGH~sn0`}B2$;x{UTC+ zt$l}NA}#3lr>v1uHcMNV@!n}(#r|&W1Hc=Z*MBQ6SLka&`PDWatgpa;En7hejv7|h zBf1Pee9*qr4ME@LUT5pUH_d73O}*lU++=t07mmT|S10+cRLaK?&1RxRq4gY-me`70 zARoFXk8A3AeG4SJc_M7od{4Du!NZ{5GUjBa79U*MXd!F^JL;c=^XKhSIfI_>k1{fDe49P5NnAuUZ98$_|~)A3~OZ$+4;WtuH=92N+& z=4k85L+euotP<`#=H@EAlF(`5!D^_f`%#skcLZU;$U1R^h_c2dF=x8)39~_Wa?SSNfH~sIe?@qW#m*(1apk%K zjN@u4BcJIDa-d%M#_kz*J?j6AdET;*1BO}q*Bajfc1cU$22`Up>k<2nTi_t0^@XXb z!ZK z9IYToj^*N!N3dj7)1yP_rh>r}zgV=O@f5}Ukb~aSa#@kjP=4dQJ*jc|g@W(qH0jR= z+koyN#JyYG0?DcJ*@x^GBmlp-A^J{k`b1aYe5@=U5rC9JsmJ|OvrKR0l_P+FUGmGp z2sI4C<9PA@iVsM~RtXs~-viWKR2DoC*fVo@Ly1PW@l43U119 za+rmTrwJCCSVkV?)gML+;5e`nX)al347Q`kMy2{mEU*`j!jFca0MNwTH=<4q5Oevz z=FO-!fh`iF^s)=%;1vsrJu_wQ_OGJD1W~ zN89e%V0ZpSx`eC=U>nRyJ2!ioV(;tx_ z0k81pZJ1R!za3r2<~gcFdhqgCq@53987jvYmy^*_ohLPPD^mxB`6ivpbTrf^M*!BN z=8AoG)KH5Y`u&#{A620XeK%C84$mMxa#?j9QdXth;bu5KkojM1Cm)p0!p}Z#*>Dg4 zEBrzug2zhibn?XtQ*!iWD>rdFB|C?~i1KV8R?Up(eO)(mnT1a0bn;xXplHA8{G(hT zkO;ZFNJas2o8nG^5FxBeg)hJU5 zEU4C>cM8)D;O#HqEf}0$L@0BXeYirCJD!m&7^J|yixs4r8OWm|(0w}p5G2d{e9I`B zU^)8;{0dnRPT$dG|2}Dq%oU`2T6DMQ`2|%rvFcY)s&;A&+%k?P$0fU+p6|E5MhrnkB+8-t^Z@8R=|5C?~e)EG#;i8W+j@g8fF(0~euF=cv=^V^W&#KQG0XSUR+2V`9#FIs=@+d$Q)hv!-E&TO=#7`J6Ht%F(OG+}j$F`W7qLATqzZ7@_2+NT$sK#QX;( zEre^&v(sKXE#Q4BeXBZ-|1i>=hG&LJGNX2NodosFbjTW*#1ub$ofrDG~tPY zgl6;Pc+Ce_nfG(ea%MRB!qBLiaZjJZd71hNw?+|e)*(KZtsAO^mD%ZOGiPJ@Ynlob z>BQ}t=(9y|Vcy3ESJ#|*(C*$7Aab4bVuyYAbM4ReK)$MQBfnRT-c`)PSjF;TD1KH+ z+2P&qkzpp)7))wZ{p|1{dTSH$7yN;8^?v6C#pAQQ*nnF;5=#c(iItG2pp2Xv6h5J? zK}^Hm^fH{{U|4Yf< z;)h-X|1)jsc=#;pY!nyGHc>5^^UiJNoFvpUU}2G+fA zY{^l57)_9>phz1^s?kMORPsMi?Ki%@b$$s@rzl_5`l;?U%TrW8FzHklk#;UIrGIIB ze_h5|rG;P%;nDcK%E^3`*X|O0a*gw|<(I_1 zjZ81K4b{;riuTQeIVA3RX%n;J6*G+NP{(>1U(Pf`GU1F{C0DOH%S(-zJf0BYpA4GvS;qPdnqm+)!s=OYv@ zzG*}X%SwUVQ=mumb?6+EhtO{%W~0l2%mIn#;G$qpI$N5d^`>Q`1Ub%L?Xq{BviBIH zvds%FKJ*tB#fd&CQz4}XPCK83i6oa}FeIyDUvPmyasWyIIJ2(_3O?Z=DyEaP+>NU4 zpI2Y=OQ%m%I~L5Y5j*L@QeP{p55nqkht*P@_W*T zFw_Yik*HK3(=M~v7;f$-1O<0>^4~*2nIth`l4|WGK>L>Ryo$^^3ffPhLdG}Mg-J!( zSkp96hf4K}8~4Qig-0;OJs>0&lpx*?ud2;pYy0<`UYL_2Lc5U~(}Fk6rBV zhA}gqs#G-b&-zUF^jGk=Pr1iQ7l(ZB;Qpwn>hgxxv-vQMt{DBu>Vf%xs9f#7vFpPZ zk_orG27?2h$qU~1FVIJ>N5z#8?LpDsJCT;50LS}X0hv7LnhI>+Kn{l=P~RU>mh`vm zAe2>PWf->pjLFe1@rg9>r;v<~ZR;VgC`4T$3mla5$T<`J4_Dt5omtc^n~rVUwr$(C z)3Kc|wr$(CZL_0}(XpMIbH*L#-v7L>v7hE%HCN4=Rr%~#>ty)Q2i5bTmK>bDHK&&# zE(QIF+dz7(f*1s$>?4r%)>d8T_QJ@HhV4IeYM zOVDU~aP_BtoV2C2hOex@53IlsSTBcJf1hamKX7Mb?EmU|;P-!`tNTfKvO=|A4O>0n z9+SRE3w`st{VUMQ@5J?{FQ|F2RrGGy1$)qY!}oFKvoy%RHn9=leFy#&4ESuo1;S1C!d=IqLgWna1UnCfn3qH zeN$qFRONo5TnwPuRk2hEtJ5Gy3@N}gPJWs~eae1_V53PV0<1zs2KUu#{l$WQ43o)_ zVGSLki!mb0BqKt_U=p8Xz$X9*%eZVtB+p1@2Mp&xazB4*(JpFFDZ##9(!}Vw1cfq4 zlIok`9YWG@i7`%6DVS&RfOz_(^m9JRgPhZII4cAKUPlzS%Oq(MLWBaK#)dTd;SPHt z_9&Ybj6st3`D>8j=c7bTn0)aEYV+@4(kBel^S(h@fJnuoyXgrazY*|)!HEY^_pJ<+oq#-vC;*ov@jjQC3BDw zoOHe^=N&fMR}{4BOgw;xqSd4bFfYJz5{z2{JhnK&sSHAwQhzYrdbAU_6kPdRZSIkP z_ZHfp181Ym{iRxkjN0wSIiCEUGjjq(F-EqygO}=BmSN^hJMzyFeTg;I#akrzQV#Yc zh-B(~pPHVlrj?$9?(e+!I29%Y7(OZ>gAWQ47ZUXeq(U{-{R;p*tj4Tg%Lpu)@H$bz zCN2^y=NwZTIsI_t)&v(-Kdc7#&vm0;?vn`E*7^q@FoYe&cj2maA<#3z|73x_W{#X_ zfM$JFl@ok0XLaP>3``IMV&~HxHXE-%q%V?(yUH>jbYmFb(f7O&2Ecu6zCnrg9)la6X06HGjjM zAcmlx2l-`NmGM`1|C9Vinvegc+>;Eiu#=X&QIfK*V4Dd0IuM~N`6>|Vf2el>h@@)= zti&5^KunUY0*Vmgm_@25>Otp zd%PK7%nIYYWKHD*iQsdXm=Li99`Z#foVIBL0L9C2z;UWI#Ol*3_$tfxBiq#`Y@?Dw zRF_;;EL$7ZbI-{DQIN2ErQbNsJ^t0Xd{VM!3u6C3uEvJhQ_>uOewYFRwL9@-js4)e3o4G$RA5pFE zfC(!%UU}N^EW1AgZzV|<(q^w0Rt9$1^mt@QoT)~i!{ZvD4X)3cUk52yk+HB28!7w+79`(@vPSv<@9kn##{YP9ap zn*p3bB#9GWM5Xfmszx|ALSn-nd+`ZGep8n?_^pBaW=SmW8;t%|eZ#ePKZqfm2P}Rf z!4p`eH_h_EF_YInZSzevJZZ{HxhB+^F~<{^w1|7%Cu`4{$)# z4Z}Ib5^ozONB63POBWFQcH^g|2gTSAaK5$0#Mno>xGJ)9enWkLLFJp4&p(#uEWmV) zfI?m9nIA=2cSIv450a%8x*Fs|lavLgDjL1`C5#|~qd+ahie)Me%KUhx1l z0Ub|8Hl7d5Tn9>3Ap~v~FSbnks0cIx72k+VN)*Ja5t#lvJ{Yz!GP4Dr(DN5_4XD&4 zp&HpZ2%Drb_=ez27Cs@^FJ_eA=HI{mfA(GoNaCX$0qsYnjQd02Q~noupLhe2WV(b1 zcm|-HV14J(y&fKDGK1T|B8~dT+rWZC(iE?!@2`rq*n|_+aLHJ_3$9X?q5MV7Tv&7| zrm@Y8zjB$+NJqE9<|sh<<8s~eZgIHuS3;r0VH&nI0&A?yZr?!?oBJvi>>Lx~&^twDgWhr$a;3{wcX z!JW%H-eY0r#~D1)41k&b@&t1~fT`Zc@O&iG_vH$%tACqg8G>Oh_4Lb~P#A9qlpFH& zP9D}#Ngf~v>8mpaX@P0nJR<5R&)4_yaB99MV zYP%_sDAI$RigzX-O$zZ2(MgR2;7f+)B(uoi+HQp7V=$^H@)}@gzKq!Cs_4rfcI_XJ z|AN7lAF?^&b6hT-zDQ@HHxh}nifN0}(dI5{%WG`L-L@9En9d0-Gqh?oGCxz^PPa

yHlr~Qj z%`kgh<2P>C>fTYE?E#Zh!{+2Qw=75K)1B;8ZJ3zCdDjI$qG`W%*$ojvA?sB=lZvgK zCFeTxA=XpCI{8fHWVEwdoN>)8KI3>wS1$ku!D@vDi!H##`d8bvA;7sf3*MOzNT&#^ z6;g_U-7z1Ji^{Am0x$ju^_X3VOn#pQQ_u;Ery^^ukw>}3FKln<4!Fg-PrZajr)_E1<>}I=v!q+(^ic#+0V+3yx3Z0nrya_ z9ic5(Ikj|7NP?0XaV4ST+E6HsCdv`M=q3j>e)^RmxA|<+tdj)5`<9`iZFSU6^%l5* zuUeaN*&D0)#-8)Fe8S>ey88ImsV>hoi8l7tzto01!b%xWUi?smIhTFWrN(* z72BPsG2KQLsTev>OM7u4F?%B<)XaC6+c>m+gLJt14bLXKdsoBql`8Ch7U`e5&WtBI z{7_XNoZW&^y+%(!etb)eRFCFwWNp11VzQfYOez$uKK4HTM0Tqzw##t8%t{NA6gj9W zKr&BClpUjOKiNRO!TZ#1dGtT= zB`TCkrZO!<(Z~t%LVQWIwqm8~$~fG4edEMFghmK%DbN7NvY2B^SOBG4jSsoeU9}I8 z@8tTrx#)0!Xk0e)MZ`Fi?_`7re_2^HlZb*ubafpShf`3ZQHVytq3Y_Yy!VIl$x_mk z4=1NlMp^cA)$r!Ekfy3uHS+39uf5rJpqII8@)&kPvu8s|XKlfWi*nPacSu_ocf{qc z+xaIq-h_5~osS{9#FPQ&ab=Z9DCd27WKnP7`JEqNIt4Mih~u8SY>LJssztE)gH8&1 zo7?yh*HL<>%aIbkUB;2UVY6-5xHtskHxzkB=KL#I`rI|7FOR8h83?)nmh`T}qu5h% zQWjOGpb_k!((<5@6aw=PODD3#6s27RkYmVFX7bHtkAD_PHnK>4bo@4=f40un2ISaZ zT*dnU7O4-Dn}eO`yK#}wA`O{eMAJn8;TFq&{Vj>EwfS1;EX%&RCIj(z_&GnYOCG*= zwdURH4UVPWsV0Lc#x`s1unv=`3@^@^dnq>ruZX5Nx190n~xHjIs1bmta%p3XQ;HW;dWus-?1PTxQh) zTo&#LVZXaVb-7~QO>QaTsjo9s|JE5c@9J1V{ndcBAc|v8VreFNW38yh^~0^ z0b;Cn#MZ0x-y<`c!rvJ&GLS)L$Mi~j!FC?X^IYlY~!7^!u=K`S0asx?9WJ`VOnME#>b-Xb@JrQG- zr5(}9i1&C=%^H_Ir3HO~9k{JaV}g?f_~p{Avg8mkb53wO!3WfW>>Wz1=%~{p^gcbW zKS!c|wH)MPm1XM06~_X-U>V7%5x}_>GOUo5M0~&DJ&YVY1tkdWOzZo_G^87HWV^JUE$HO3acF-XQ z+MH^-f^k$^xO}KuQ=&*qC}otWrr=C6BX_8~NKU4eX}OjoV4!&HCUn?2Bv4W`bMK@xJVgK%Up<|o zBI0#8S^-@%7*f5za7q*^w2;)zZmZru;SI7)F(0tJL5+UVAZg=|vfGSk$631oW1Ut^ z1_L6E*=(dzpt-5w0=T$QdW{hNfA|H7-D2&%m-u0XU)OVLJ&a5?T|?A!4O2Ucm%5Q9Qea6=O|vm?(voLlGudNwwm}k{+C`LbTmF=T z5rS3bW*+k13AaxniDC5b;o$6Rk=33KK+@qxqhe|?zt%m1$`}STyM7B z21-TZyt3Ga)$UF!(yzp{>Eps~TVLqdG1#n=M6lV0(P~-8o`^^y@=&2rLAn#nVm05f zaY~j-$-G$RtY3~A{LO&9Km@;LC*E5l@FrYm{^ zKJAg#f$PL%jYUBr)Hir5sGn@)={bU`+9f(d)>5!kp?iSJ25sX;KKaYZP$%Zn-;o1N z7;s0u&geOrpsh$p8QBw*A;N~N(pucAB1R7zW}POLuaIgf<@Ep*VCs`>W9Elsw`f%_ zk%{y$3mGxospU5L;HOsQI<7D$T3hZG^lM=`-#YbXg4t(pVt@h&J$w7NE7M+6eqof~ zDc!?A3%@=~jpoWA85f3mg#AW=s7u-qAf1MCP+JNKRdNTIZBe0WyQN97 zUtvi7c!Os|Rv_yPpq#vZ0UJ7`S;RH{d+HAtoL+JM#w^-owJ!-YvHZXmtJIbw4C+Kq z6jyD#gP8qhnPn5UEPPGeQcgj~S$0tFV8ML>^23b4x4n@>@VD!cNUpccQAU3*2Z3j# z+8+KxiX;S7f+bp%6hkBjXf7w@*8mNmaqy2M9u>VIB1Myn7xyq~Y_{O)xyraKctQH0 z?~NBFTNp<88^%1VKj*ZV2x5|XF*`l`Wp3_n_kO?DMgU~)xal9O1Y#BKn#5XLWJwqy z1)@^#BKt4hXk4}1D<|sr1QPp@;zSZ#6}jh1OHJfIO@$7d^_3D|Kpt4=GM)tImtJT> zgU9nNvxw6~6*6xbEY0SloDTm%7QL2yayPX5lwXp9tK%8JqSy63_6^)TkzL%3o} zc-?8@C?-^{(v{JP)I2^IH}&v*o5VO0I(I^@-Yw_!g*V8!%n(y&3r z_V%_g!9~|ZlYbCz%)}y)f8MQhMNp5!Cz%d*w6cwk=1D~2aYQg{F1eC13byfgd#)G< zEZz@&Y;tD3-*U4P0k6T~v7Q*oRCZvF-o`k`=vfVJn$9^3*kGB)?_)c?j}cG{U1-JO zyXb{>^n)efW_trzrdtwxS$Enxp4}g3lKV;0=o9npPXnMaaz zS3vrg8MfvefljB-XdU2Mwob`m%S_oOr_#1o`Mak!=}#fUxQB)as+A^>;-#>>1uZN{ zs+NoDCKaz6?9|~)u+hAZckk&uk&aH%tHgQR@6yW56xoFaxTeH^$+E8^*Y$Fkft7kl z%dYE1_7)v)qKR!c@RmB3o914w-S!^!A(g^QV@ex`XOM%CEv*1&3EvAp-B{wGS)2)) zZ$$I$Eg0S$q@ileW6b@YEtB{t^`TWt3sGTs_fuJzE41v9@Ia&Nz4ozqe)O{aJ72J@ zm*fK$Fftpa;g1*98=yQE+E=em`>XU-lqMPTT)qp*0j_8$RRbnc1owJl4Q#e;ms)|9 z2Xp*v>&$32XHtM3SxouMyghcezJH^W zIFx)fU|kyWBy}VOPVyC6DiNtA^qd5^Gs}Kw_~%XPBTWhcgNxh|b%gvDyoL;<3B$x=6@kASCN-9KVH$I;`3F?2+8j2rri z(6i_VCTT$HUTt}5V)PzJw!QWz46ZM0m3O@K1nQ>PuK2zLXl{|fBZ~(R1Ja~4$>MeT z<1j_9gbRWbmDHv~;6sXqHzuW+f^^@$Dpfi?zl1495W^E9U5P}ohPFMQGYGQcE=ii9 z3@A&KQtA+QYNI!E`@msN(Ts%37irtKZTr zcJTpy2?z06PMxVAXO3&Mf1AB7r-nWAqw+m_f4q$87#k) z6Tfl)mrG?cb(OZ<57m7A<6|wJWQ2y7gn$o`q&}>ndr&jcYTajGI zj0#HtKCeFWyGdRW7oOQvZGo{jZXxQ&+2l}zNDl}h z=t}ue@=MPpb{@pAWEi|wV4WvV&8J?AmmZU5HU=+xOOGY<1pbx} z<^0(d?6zBR10*GO%Q5$>S+2rI2J^wUt>>@A*qFCEfJ}2ls=3dj_0{^nwx!g~K>=6e zWs{OwSijrMBXLn3CI+x|A^tf)mF!mF${J6CzrURVzBimNA_xbU#eUqPinfVmORr4< z6qZjPf-*~ajJ^X|Obn(UuyUH1Vsm!uA0dut0B0@DQ3`%8A15y4G2KhPYWMC2#X~mx z#0Ri6&uda3+5G8*=n$(0bC*;TPqRnRjLVL;@fo}<->3AZjPwc{#0NA_Zn1#gfdT?1 zYq|6&GN6#^?(de2X<@tA7p;Uq8)zO)QmpB(~UT3Tfd@q&lr&dVTkzz z{ZB;lxlo>+|5+^{M*;%k`=7#_J-|(xqrn4IH;dJv)6m0C#KRY}xSB5p;#_rwM@lL= zh&W>KDp&vY+CumaJ$d2q;5_ePNh-Dlwt78Gd*0b{e|{tbeB3{_0cqccM0;(K75#FT zX_pYEVoyd9Juo9-aMVZcK8@~_5@rtk1r-`CwoY3Ftn-o_X;=?TPAiU`s1)V>x|9m| zJ6S&J07}AayiRR`b9IpQZnhN-fq6RsiEljq1icj)=IJRqSmg7GX&|5y}w+=U&V@wtyFqN1aaCU{7LusiK zW&i=rjQYp@D^Cq?RoSYwvC+DTy}G4Xk7Q-hjFWylUpaoSYI z&>g2q$0|K^liVTSFI1oAs$xGjBjXm%7q|ePMrbu>gp%)UAg0r|s+CDBzLFk5Q(N-J zy7~7S2-67y)=BLVdkLG#w}#yF`)(f^m7HvDB6Y)#VkxNe3|dzw?|LURBb2?+>{ack z2_;=D{FZL}kD}qWO>BsH7vGzDnktf}wtz`SQ&OjQ(D5NHRgHc75KAm&m@>C_#k369 zr0x{n{AG(!1*M2SCrh5^SrP`|l8}b9o6smM7z51j{rg1M@xn}BKh;KWa*A1B+f!?H z3c7a4%7HNKS=)-I*1+DuudI|%wbe1=enkeFe#8vA&{BOq zumn1_KyAQDxA3ocHBxwvc8)A^^&jlDpmKVI+AL+4x;H)L8lC;+3Md(XyXumYn#N{f zRc3{GVq1o`3ccr=-B$IOR8!h5bXA+oK-D^3edD(3;{cJnPO2>40T8N<7LCF zs1n%wZE0{DYIlq~YIhW18yfyEAK0}s>7ULesZzTTQ zL)SiCRG&fkZ`3@g7hOR*bzW%rz54zVi**z*?J}*Ir0`=@f3}%&I!M;p;!?2RWown? za3_`3ODncBEjHLMBQVXxSlInzu|fR_mI&{&##0LDGGk*r#K%Sd|{b3l))N z*=_TwbRdE(IpOQ@+~lpdpG>Wq<*VPp65tkF~I&r-rK2T ze5ag!qh}8VOin*$e^_&;jf^U(1-cGfUJ>nUo@*(I?D%_NBytL7_Qh#CBHHeYxJ1VB z!c_X6X~B5aL$4*-Rh{7qPk_Ok`G9bP*m8LM0g;i+WeshTV9FzlOLAt6)EZOVp3~<) znKvafZ+hK#R*e!-9Kpyn9I-%!)W6(=PVs+mfhukREY3zkiSP#aM4|Iwq{zWo? z0G6k3dANxSFaY?z+n~iS%bwiJ$r`A-Gzx)ix%%4&SZv@u zSypcZ;O=uCN7^Hz?5d~&`uX-HqQmp*Wj>;nZee;7{e~QGdHj$8e>EHj?=_Nr8l&!7 zv-Wi(4-Pxp`p?RpP;55My%=Db{8vl<4f3S}05C@QxVym#Eh&uM|jG8R1P&8hDniW$T*;Zu{xc3 zg>KJNcpGE?u=FB~95RgI2PBYuyVW}VO9p%@@hW@M+3%#`GOw@C4$Sy#66>)wuJNE8PNQ{8S^7ddoadRBf)RbmxSCU3#$; zL%W1hV++9DCkw-t9(zPhA#qdLE{AB+OytP@kbEeg1fFoUi?CDh{h!|?5>4znLJBwI zF2uIeHQuqIe=`ZUEPe#{O72X}2-Db2XmcNX2v)s5HwoM_HY^SD?19gsGd7>pZ){Sl@N%ey z2}Uag$*6e%_1qKU1co1Rr^xT%X`y4KyRAVWZ-gAF?1H9+eq0NwKn5z>qFt`&koghB zACn50u5e%Ld)7{b*6o3XKe%uwjsqw2slnM6sCmr&hF=hcU6_=z*TV09kk1oiX23)2 zc8tSRQWR9ecV^LHf4z+YrNByY55fxac${Qg3ntuRv2@{-&X)UuTqL20#s4a*|;( zJ%Z5~fu6ss4Wcblpc3Z1{4f4X6;y`5@~5JQe=7R_b#J?DWQ4_z`|YI3?7EX=#Z+?J zGJgcAdK{?G#Lx-|!NjQTamJEJ+35hoJ)Fqn74wYL?rW-E(G}w+x*@SpU`f=dvNV+C z;U?-rN&~K;!F#M(TeT^)o2KKbxJnGmV0CQMfeZD}3LOqJf6fV}kwuohtvWg~@K51& z-}B>7&8Awrd0-Ll2W|{sZ=pp@S1ObmrOwtZ*{VuCMyufNV3To!IH+|s7oPw*NE!4Z zZxgK+Tu+nm7`@sX2lyi`uAA&5zk|AJrP@RKX`OpAPW4pezFL1Ll6CvS4k`9NMD`tr zfVce%X{4a->Sg`PCYl!0Bi}+RPUUS!v~mm5J%!8!+IRCnLVHkd=L(X>_i zr5n|!=~Ql;r*q?<`1OsIi)Z$ayB#HT){Ow~FoI+rWG1hRdy-MQ9u2Op9jyUPJ0)&TwKk0O zi3M{d;slF`;72|n70KBicfm*nMA$$>SdG%bkV~116mA19PiREGP8fR%Ut058kxjI! z?17|HM&UkIkqcPbb0C*F%aBMXV6gAgQKmAgs(CMg<6$Dblp_Ooc)SZDxs>$#$Rk+v zBnS5w`E@bW=XprvmHYth4Gz&=q8VnWjIkY(j) z5s~e}I`5PxXyKwbRBC<54Yx%SPKhdcE7DU>cI3kJSQ@0)?*%5YaLyVQQl}!lsP+Fv zdZm;7o$mT6(#oGA<@lMF*gIJ;SU4G(+9cVcA^rC|cb5%3>6}vn?0dA_Af}0(D+U=zJF5eN_v=l|T*|8?+ZR8$Ems##)6X*iD%+gdgnlAIF!TchtaXlfs{i_e@McHfOjwmNinCu7t7Z0Gk%BiJKKQgc61+ zZP0d)r*5w{)EgEGe-*QFYV(7njrVG;x&^@L^7#i?L}5OByT5Fv@L$(0@{nrpcHOqJ zriCJn(25bJrkk&YSy}H{u>DKvNw{plOphymr?5TNipNw8X0%#HJ(S2f%&z-jR3q_sNTq1s%7&0Gt$P|xgVrQ~g9SOUti{HV&WvrH5L=c3Rtfw~*+qmFb27ivH= zfbRGyOrx9V%(8thJ~HUIAru0ZVNTWE-Op?T=V+-K(TwOA)5#*jN|Aa8wXINSK$E(I1wHAqAG!Fu~{$uvNxWtKljP z5?62fmwOZwlgnTrJ#-AV#QD~I`~xs#u)XDW@sfNtZe8e&a8`RF_WnqDY=qn6d_Wgk z0G~wHT}Cs912@ym)IT$|yg_Ag7>F;HJ!Am4-%F%0^`ylpiJi2iyuu z8)907bo$J<+}x4CMj;e_f)UN|!7DvbKUFZZ0+amRg9VnP9dh zQ4CL;xtnjE1abNr*g!DP4xfPhn_&Zs4r0E~_~A7FdU=3;go3mTKVXD)V#sp8)kC+W z58UjoMx210{7Nj!U#!YOHWPx;Ew0L%7>go4QLZ?;{6n0^Bjv6Vcq5x0UwDHDFLsxC z%cc{TLv%>AiU`|oGBjKdK8Z`xRJlE*g56y8%ueEz#2f`#TS$KrSp3Kb75foSH&C9X zz<~S_<3Ae}3n9nG~F~j_GCFNUAKv= z)R(&ciL5mJZo$Hcg(^T2Q}0GCC3?;6yr;l%)^qQ(t9hS~_cu~MvAWBHiFg=22AtQ1ul!T8?^=_u=ziBoscx#)IMjB~#4BzI$`c&p8+uK#8UVZD_*3W#jboPlb6h zN7^2BPwblV4VBZPb1dZU9KNJ0D&*hqAj=pRz!Ag+ zNw(C5qA_D)rklIcI_7xQNQG=P+^??H*L`iuCq74zV7ca{6U&+O_iDwMCjti*v~zTjmCt7 z;=T8z7`&v$Su@8#n{c9a2Y=5cUG2S^{;fnX{_9){ScC~36hNO`x@ENzFVmN#?8cyW zQ4>H$qKLXKc2QfyFgm@Pa$`_5v8Wy%ch4!f=Gr!7Msh0VA$5IJ^$b(Y3}*mIBSFLS zjqVmiUd8EQxs~GVjW;PHpi+qCnL!cWfngxTDj3y1f{m?59!JdzAuq^&(QwI|wqh>3 z+;=nwv}=hF#fJrSBffj>@XB0M#Z!&ra5dJ;tXt6@d#)}>*!uWMmwzK<8a@X(v$^bg zy)AQ?GuraWA)()aR^3wDT(#+-Yl~eJ*cj#2w@usd{^`5Kg`3?n66MtNyA1xbzgNpD z6B}re9&YJT*|&2}4Bj-^rw;$tXn2a|?+`=+2%~G5x%%?Ijllz97jWj5B12tgAO~u# z@}H1ajE$hSK}m$yz{>1YoA3#HeZ-#8mTgK9M9y6A3SmP;sXdUF^})!>rr7FIU5hm7 zt)tnLrYZ_a!xO;h%2O!I2=@DFp;VjC40lxxizzsa(#PG{G!Ibh!; zqJv{N`rq0JhZ#+{?H^>e{z+vN_#b3u6xV=C!7+g0u-iIiXo?rF0ER;>;)6i{323sR z`e7me??G??y@`#HvvZD?m7(rP!k2Vr28WkdtJy{)pP|hj$iGyk*7_qAejqFv_SA+1 zglSE$L~;DN@C>9@PT}@Jq*%mQLlocu!!Xdm4pW$b4Y~F~=&&MRx^vHCHv)m9-UxIy~ONLQl-w}Z^G5B}mm}VmcJ(Ck040Km z^ais%LteX4umg2>GT{YD6=L+rW`?M%Q|Qsa2us-{*T9LXK*uJ2WDb&BMPiqT3^`H& zWqrre>nw&Wr$8eg@-|ij#u})JBg<+sB)P2Is`Hq$LVc?c;~%p(U?C+DO8k@6r{8+j z+uDV6uC`Dt=5wQLR_M_!=CjZv`w^vAw#(KMjEmC0WM*0|r>8U5Oid<#x$*=tv6$@2 z1%5jW}YtyNbUY`3>G)EbTas9|0It=4F6QbJar!|EefU&#j#t}r!iZ>jZ= zr{}9Dyap;M>1>qnNnsT&mg5BK6;D`0w@3s=Tw&7bCUkW6e__Fk|EaS5b*~|2a=CKZ zU}(KwZ3h)riMOd9LR?yN@gbJX#f=Fs;m#iHmQfSi1v>f0wCXeJ>1a01iiXDo__uba z$lFe5vl!6}Rv<~)AQ`WtJn8&E8`YXA4Y*of?=i{3(kX)k3#lrk8@PEhq%HR2Ny-(K z2v02Y3F&NYs;F+0i2=1pwZXQrw`v8As$r9ZCp&C|{V3+5Hx8GgacfDRnBO2y*GUvt zo4Z$zM6l->QeMBUHhhW~m&ZW`oFwnFkkmxm;>+>{5oSiS9w}lxl9A5a6fRBRxIWFo zQA3$*%Nn7&n9*E25!->EqZcK)s)=N!S*^EE`=6dkgNI~|=?UwC-9SQHZ_J|BYqE7H z*8g6=7~&qD0HG2NcL1i;$H0P3Wcx;LM@guRi?26LU(rqi&WfNkVplloB-B;0}m<}+~i=cE-p+n|TXh3#Mm%z&Ug}vODE}%L+ zHA%v#J6ch<%NeHE11u3)70N?xHC;7wc(cJmICL%Q%Wk&kfpgt}00>ZeN|ju#3%dku z+)^b2o)VRe3J4wTX%C-2*%>TgOERJ20m}LdTwUhy4zp_67O-K?idqS%ObQV<41`&} zS^wk~t~6n+NkYaCz@;jconW^jbzryrap1P9#dilTMau)|W}!xT+GEJ+LYpJ4{(847 zDDt9Sz$XqgGZo7L{&WPnl!vzI&cv_9Si6?B^RR8$Nou-bA}5p+={YeWk-gu*MnDZQ zmNhQM2fM&fhix(S+^FK{39r{wZ@KIZ(jA3fB)1cF6_3Ts95IW~r_n&-kwqPpz>f@8 zGK=&QX;2s1V>_kj%6T-et~6?o*tUnLMYCvhlvGAL=7H-1CeCfdXwhS^oMM!{KK?dC zhUln`LSA;N*RmYyIQ0;5P)cl3YG67g`E15#9sL%u8@LSJqHe>w!y}`9-vS?LBx;*- z*V63hFOH1CV4ii=n`ZT_4O|M-LWkp}NVdLKoXH8@B6FvRaj9o%+_rHAj??0j-P?%6 z6zQdSHceLsU_|{y%rLW%Qb)pd2LTvO+jJTHiM$W>MS2;YEuHcLIF2AfxAI1EfvrXG z759!a@bmB|!ntvN!M*-$(TxY)AwFl=;Vr~rirwxTj~I>*QICvvnB3Uu zz$*=u8cEZ}iVyOQ&@D(3V@4`2)W#YH9}f%DjnLuoHlT-UX5UskHFnmpRQ56(UJk7t zI{qZ#(uk3#+UWbd9@kEt4<>t$lrEP${Y!0B7RimLI9nz%i6DDUB#H?2;h)1%9*)po z9Exy%c5gLYT?6F6LIf+^i085J(&9as64>!u2yB6&8Ju`B6UF6Bo&wGF_-Ana67(axgbJ{ET9OESa1Ez60$&?0iMij*+#C10&6I)I}3q1;r1d zu9|;A)$%Lm^!lu$UD#FRTYK%NaYuQ$|Dgo_ zfLdnPa?l@SBPjqI8Khh;GnwiLc$fLI2rNys8Yo1V~= zm0iOL`g%uq1{UvSgQfdgX#AftM!tV5X~1X}ETQthDTtc{Nj(2)S@YYeW55Hz8X5Uq zu;aa~;$|fc-n&BX)|^;&kYUIK{9G$2zH~8?!p=Z<-I~UP4--J5;DnA~>moS-o!j=l zw)K`DTYf#CaD!t%AVJ?XZclSMwbJeQZ3qMk?OJ$-H!bwMKH{+IQOc@4jdEq;cEfi$IlJ9ddzYtFQGcWZ83btpIhaB}+pK_;p}IEa8uR zIf`GqJJk^O`TRP@!HZTjzr|r`%s=Asmaw*k(9>~Yb@)JJ-~crGE86mOZ2Y(pn#*4) z=E#@wFU%my&4W?1VOw{tct~L1V7j)wS^s8KL)TG*e_MSy#(`T=KEXj2+P~mYUnhbx zkRDDe4tZj;ewqCwZ>EM-0LIPZJ}R=Ve4rG%kXpY^eLY5!wGX=)5>+Hx4f;Ir$5F@l zK3|HgMUqwIh)bo|zgzBNRGgbPWtXJ9;blHb;zw5HYau^@(tApI?*LlT%15dukY4`j z@q(^VDlL8s2^pU5qw(4mTIrdB?#f02GE`M<&DAI;G2NXg=oN)(z$3&*Px)5Npud0> zz1o1>@6O5vog|IqGF|mg!sA8iFJ(8hwet*OSBc_WWUUns+uRGDuYG>nQu@T&+NNHF zrLaXAq_fq88JjJ48*?)T`MPy`vGB+;3Z;Q3URgtASuvFJdUzT~{>?{7W02MZ;D>xH z4P%leLlhHR7W`3k0B;P;?b>>z!2xl%%;a-DTwW2_*a9_);iO0N1eIl)v5O=X_mQkk z8hNl8ikl=w;bI7V2QbEzT=<0k@R8D&A2`nu*TeW!yXwv`$DxQW6`-H(4y!gv;J}M3 z6vx>qJ(c>2V8rtLXb8bUV6%%6>qi!f%NMP*nk_y9>z&dGSa-p8&kBUNMRbWUVe%7= z<^A0dpR1H;fQib!W)>! z$Wb=={zAnzGh#B~(pK&_x^R%KtOAcavllH4T{C?T>ooObQ7~Vl`qj#cx`@jX zOjAp28XwL>xi61_q`}0V+aMO6_TwY9S$%U1WX_h%p^jg9d${Tm)h(6_kufQ@qt((I zX)2$a5X3({I}mE!6aBuc_Fxp7->?Wy6kX@SST0TkP!VI8-E#j3Y7EfK9aI7S+@m;_ z+pm~0H5h8=j63NLIO$EWD1FG0o1rL}=bE{HS(AZ%pyX50?8JhgqkUvSdAp&dlg};S zTbjdi4OQ9WnpJ$TI$gfW4n5g`-o6DZ#Zzi}M=&AIfZqe#B`lL%j&V}@{7?#esBh~7b9gkx}G zi}TJ2Orz~&E8dvGy>TQM5|)hV(hW}oLRW()lAf>WPZ>w&Ft)5b6QND{-3VSJsPS!4&eILoa8y> zF^rq?+#14qbZA2ADAAf^IW3_{LsA(@Lzd}wiX4wxztrw}ZSCx8dXP{#r@BOmN>tl( zjWJ9zCMIpt1N)mB+Pn9k-}n2Q&-Z)popbN~4c*<4qQA*Qwdpx=`=ar`MyjA)=TPVj(d-n08Z;$`OZaF0^yEZ&JDd+g%Zn=l$&+uh@K{Pw$6<)HL^Gt>_MJCo8fd|H80eCo5~iE+~0ScyWCJ* z!+v&WM_=34an9!x+DU;UjWraLi%E)4b$r$(3B9xtb^*Gg1;hEmqH>TE>f%mBYQN8g`;?eizdzJqapW8M zn0Iws_;WqzB4Jj?b(+qAo&8K$EMY)B#cE(R6LzE-A<+;D6;2>e6ILnQu+*CHdRJ6^ z`4q*gd{CBZ>JZ`lIfyrh3kTe=(gWvToJ1L^3-n+?Av^HRxS#0CfiG z7-h-VX;gjV!M>BQE({xF0p~DMEgD=3B%4UFzQG3S4za+E$VpWfh7UObtr${Ow$6vd z5FPuv)&klHyc#S}u`o*OI)yRX^@W)|+c$+5oxCRj@}&%Hx;+cARurBufTy)> zpjj6Svp-T84nJaaovD+G@cP5(M=RLg&A`+>VFBnNB2X7Tdx}7# z2tS)mLPumYXeYD5)ZHzoPzco)J#8)&kdrqFT4H2N0rHltjfz?*(8{AEq>|au$ns*i zu*V4ed<;$cL17Oaqm+J9EZ3eOE!%qRX=Kd|oIsX)O36u&UOS9Zc0jRAItd%x7ejHc zE%yJk?-VD(Q$z^zAg_Uv=A9zYD8dhy!w&W`Nc7TaWRe$_$&J7vG3j2N+m*|WX=I+P z;H443&rQzTVq{hV{b^UwyX;Ky$gd=C;Ki!BYOfe2KurOgsz}gjwK)k=0@M_6yas`m zFtN`GY;1;#@I~-W9}DpABheC?zFG>hAHbkjF(Bd*L>*Sf>jP*g1+M;bxN7*L*VE~- GTKgBj+ffbx diff --git a/bwc-test/gradle/wrapper/gradle-wrapper.properties b/bwc-test/gradle/wrapper/gradle-wrapper.properties index 8fad3f5a98..f42e62f372 100644 --- a/bwc-test/gradle/wrapper/gradle-wrapper.properties +++ b/bwc-test/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/bwc-test/gradlew b/bwc-test/gradlew index 9794aa4f45..65dcd68d65 100755 --- a/bwc-test/gradlew +++ b/bwc-test/gradlew @@ -1,18 +1,7 @@ #!/bin/sh # -# 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. -# - -# -# Copyright 2015-2021 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -66,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -91,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -154,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -216,6 +209,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/bwc-test/gradlew.bat b/bwc-test/gradlew.bat index 5d049c13ef..6689b85bee 100644 --- a/bwc-test/gradlew.bat +++ b/bwc-test/gradlew.bat @@ -1,15 +1,3 @@ - -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem -@rem The OpenSearch Contributors require contributions made to -@rem this file be licensed under the Apache-2.0 license or a -@rem compatible open source license. -@rem -@rem Modifications Copyright OpenSearch Contributors. See -@rem GitHub history for details. -@rem - @rem @rem Copyright 2015 the original author or authors. @rem @@ -26,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -37,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -52,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -87,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 36900 zcmaI7V{m3&)UKP3ZQHh;j&0kvlMbHPwrx94Y}@X*V>{_2yT4s~SDp9Nsq=5uTw|_Z z*SyDA;~q0%0W54Etby(aY}o0VClxFRhyhkI3lkf_7jK2&%Ygpl=wU>3Rs~ZgXSj(C z9wu-Y1}5%m9g+euEqOU4N$)b6f%GhAiAKT7S{5tUZQ+O8qA*vXC@1j8=Hd@~>p~x- z&X>HDXCKd|8s~KfK;O~X@9)nS-#H{9?;Af5&gdstgNg%}?GllZ=%ag+j&895S#>oj zCkO*T+1@d%!}B4Af42&#LFvJYS1eKc>zxiny{a-5%Ej$3?^j5S_5)6c_G+!8pxufC zd9P-(56q5kbw)>3XQ7K853PQh24-~p}L;HQuyEO+s)M^Gk)Y#4fr1I*ySS6Z>g^ z3j2|yAwKXw?b#D4wNzK4zxeH;LuAJJct5s&k>(Qc2tH}2R3kpSJ)aaz!4*)5Vepww zWc0`u&~Lj*^{+V~D(lFTr?Eemqm3a{8wwF}l_dQsAQURmW$Bm$^?R10r)Xd_(HUYG zN)trq(ix@qb6alE>CCw@_H0*-r?5@|Fbx<6itm$^Qt~aj+h+Vd7l?ycraz%`lP%aB ziO6K|F?9|uUnx$T5aqKdAs74ED7SPSfzocG)~*66q;Yb=gB{=6k{ub6ho3Y`=;SnB z;W96mM@c5#(3(N~i_;u05{yUL8-BBVd|Z@8@(TO#gk&+1Ek#oDaZ?RNw{yG|z+^vm zz_8?GT|RX|oO;EH*3wMsfQTe(p6)G9a)6&yM+tYvZwg;#pZsdueT#%;G9gwXq%a(| zl*TBJYLyjOBS4he@nGA-CofFCVpGz!${(Qa{d?g*Yt zftsoLCHu-*AoZMC;gVx%qEKPVg@Ca2X(0LIQMr5^-B;1b)$5s^R@wa}C&FS9hr_0< zR(PnkT$}=;M;g}bw|7HERCSm?{<0JLnk{!U8*bbod@i#tj?Jr}|IcqMfaed&D?MHW zQQ>7BEPK-|c&@kx4femtLMpewFrq`MVIB%4e_8@IyFi9-$z0o48vnBWlh@E7Lz`C& z{~7u$g;@syjzMCZR|Nm+Jx^T!cp)q9$P*jxSQZ3le#HSIj=wN~)myB;srp0eMln_T z6?=}jUvU5_s4rEcO3k}*z#DQrR;TOvZGc03OR0)P5RI8M<#*B)8fYxxxX(I`Dks;X z_q5?sAs zMlaiDTP-1_XRMwL(q5h(W2yvr9HmtlnR);!9>U%TyViU)t#_5B#W0DnP!P#s!my-T zqbgQRIf%MWo*YUK2vXE8RIy;gJ8p^LU$c6POWt88``5^mIqohk~I!a zv-T{zI?eSLajm^r3>inooK|w$a_2H9J=;|sziKGRQ&FC5CWUF*#N6?n4rD-}S>Eg!tFkOpE7otS)$s3hyim=Ldy&-I$%Yra=M3xIOG{Jc zr8d_wbB301%Zy*8ILfeRiGfeQUIh2N3|41xAR|uvQ%?AIGUkdX*Ymgh z54d1)Igp9~)o7-h8AAH#6DzJ}UPh+srx=B^tGe~_(uwPoOov8sptn}$Rx@&$Ox^8H z!MND`vATA1%mR>+iCrV=b!*TSrj2TDv?Fnmj$=uw{JX1c$tt@zIC9gt)3Inpb+Q~= zh0Y@1o@R7|g+n0^b;v#5cc24{OYlnusF0tun^X?qHRYl#m%6UY?tK9vA zvtPnt7tgpi=qBIQ{v=D|p=4@{^E7)c3MLDCNMKPYec~o)VJ6zmZRE?UqXgYj7O~uG z^YQwQfQr>T!u&NaBfm|PW%g%cDoE8%t<-Ma$wIkMS{3sTS+aWpx=g7(+XtaLt9nqB zrLi<%uH29tuKZ6?`Ka5N0@G{F134GZ+6+RnA|Y+wCs~N*%N4CxyoB6?*{>AMy4w}` z@CMj>CaC}<;Y&#-a6~6AB=v2>)b=&t&D7SK6Vc4p+Tfg{AO(<+v?R1IsPA~@FvGJw z*d@a@6bydfT8{(k2N*D`FO@sUHbUIw4kQ(jrMPa2Mjc&~AK*xoe*c+VfsGx$cnzHQb4bSL2wJvVg>oYR*?s}CgoHMPLwA`Km%5LJm4a&OZ3QL*-+4G0t%;_ zS|DOILXL@I?hGl*3JvMq)Uq;%_B{$ipS*Qkn~F!-P^6Afg;Qf!n-zi$tpUjh9TEgk z$Em>`JJ(>S;8ZLM+$-RWUzFrR!@<;W=Y3ASjLR1`U zRnQ{ZU%JK?(2oo+c(5g;5Ez&I&5{C8{!I?aB34uFL`IQg#2z;=$Si?P0|qnfM1VdS zb6@5YL(+>w;EPEyeuX)yIA~VlFjk5^LQ^)aZ$<1LmDozK0cxH1z>q2*h5eR(*B8Pj6nS=K`)S3FLEV-S*4c;F0<9nRRu$YqiDCFaTc zU2LxT3wJJWeBb8}%B59!#)-W}_%?lSsy~vH3%oytE`j-^9*~SvMr-z3q=A7uy$?X& zf*Ky)z&7X0jy`YDtCs@NJw0+j_3CeDw_I25HR6CPV2t!asKPJV^R_r+u&LUxP)wtR zmFA-~HswLN)Ts=7{YPysG?DY))3+-L*En93o=+v+Kjw;_cUsONDZ!zzk{1O05Wm+3 z*2;}O&??lNOe-V{mDB}Gn<0_7H$ZCa5dWoq#}QCT(~h%=J=n@;@VXR52l^?vcj%GP zh7{kjosPu`1x+iQVU?(TJ^?xlT@AS>a?&FMQRTyRO?(2jczyS@T%&!d8mzxqO0r&;UjTNkbB)J1%*iB$McM0+stU%2(C}f0}_{G?dWaCGjmX7PnOq1 zdRr-MGfS#yqMH&mW5BiJE3#|^%`(niIKQ_BQ7xk`QFp50^I!yunb~0m24`10O=`w3 zc#^=Ae(B8CPKMDwLljERn*+I@7u8~-_2TPH`L# z=1~{&_1Fg{r>4*vu5rRTtDZ3}td&uZ)(p*OD4xfn01zzS+v3c_N~GkBgN$cm$Y%H} z1sPjxf=IxdrC~^)&Pvq1^e`~xXM2! zYU)LU02y$#S?v+CQ~GP{$|nR0d%`>hOlNwPU0Rr{E9ss;_>+ymGd10ASM{eJn+1RF zT}SD!JV-q&r|%0BQcGcRzR&sW)3v$3{tIN=O!JC~9!o8rOP6q=LW3BvlF$48 ziauC6R(9yToYA82viRfL#)tA@_TW;@)DcknleX^H4y+0kpRm zT&&(g50ZC+K(O0ZX6thiJEA8asDxF-J$*PytBYttTHI&)rXY!*0gdA9%@i#Sme5TY z(K6#6E@I~B?eoIu!{?l}dgxBz!rLS{3Q4PhpCSpxt4z#Yux6?y7~I=Yc?6P%bOq~j zI*D}tM^VMu{h6(>+IP|F8QYN`u{ziSK)DC*4*L>I4LoUwdEX_n{knkLwS`D-NRr>0 z&g8^|y3R$61{TgSK6)9&JZFhtApbp$KzF13WaC(QKwAZ|peA@Aol`&*>8RK(2|0%R zyo9nL{gtv}osWeNwLf@YG!wb9H2WRcYhg_DT60dzQGW(y7h7|4U*<;c*4N*sE2sdR zZRP^g;h(t0JLIuv)VNY6gZ)yUD)2d)p?eFznY8$~EZMYTiu%DF*7UeVQPV}h zF*|ls`|a+{u;cd>D@%~dRZBn~-Ac+m&Vg>P=3VY8+$<7Zi7p<~Nq zR^M^jl=zI!T`8H(gK0H945KY=N1J#Up`sWvfY$>1SGEfqEyKIokPVbexYnI`OXJF$ zkMS3dBE8RnB1dK)tJbNSu5Y&$IYBy38luzK-TGMpQcEojhte7Xff-zI50I2qM(i2F2)9DdagoKYlK zz%x8sxFf>5@1bI$-n*}N>o3o#^zP{$d7pf& zf*4SNbn9QDXDCVn;wo6|E0$(wBv*pgxHCA(S3lXJ4HMQW)rU}U7?F zxI}V}W~d>wx97Ozh+^glLBo{*j$o`=hK;idHhi4CG!_fG89V-Ew-^^hhMOWUdu-2< zd(t0O>8BgZ1N<2Xi1G3>r1@d)nBD*K3PsmP{s{&G;tmG_!k=7FNuKO+fCm`SxKP>B zK>mtj;Etn5J%mKvT;yE_zl8vk?q3f9hwea!Dt8yLUCgFO*BnS=YuY}-c!&0jb}J)D zV(s~BTYfVyXK<9y&hpVuS= zc!!wNsFjPgspRhCIw6}w^RvLX#?KnhpM(hB`U3x zg*!~MI$JfAFWhsN7xRdV^%0aygs+rZ;dpWzncKOTAa`0Xq7m(z zS_LwFYW$1KXsfgpFzlw7r#2KOQn(%ww?YQ$bT(GWx*gx2Bsny3J z!6UUPr8>TIGiK`%2m`PSS3Pd36m#OIl#SN?$h?mU25XXidM(*ZGBAelMO)H+;9Uw= z8`vjt5)+09c$b2FAWm3{jId9*ui3~Ihbw`9e-2;@?!T%Dqin&WFbQJt4_m@V=j9P* zbXi|lvH3x49-&)RB5c* zheg*i@5p((w*%DOB8-%Yv2P#-IHB%v>`Y&_9BR4)7ngJze2&>4c~NOkQnJ)jt+X$L z9`^6#2vV*K89hV$gu10|zu~;nKfa?ohox&sMS7NyTlMJCQAe^h{9nZwpoX?uy5xO? zW@PBU$b1{UOpv~AtZ#<+*z+(g?Fjwseh8lsxs5iozi*#gI!;qXBt)G~j z9v5n^MQKOT?2!Dj8;SOO0>6f3orwHJiOFK6`b<|b^4}5n{l-VQ?SoksHS=yv3$O(l zK4aL#0Zq4{g#z$jo$*dAJfuB~zb-n^5(3@{JHT~GGc;Ky(^y99NCxW2rZg%U^gIg; zJ%kBn@NxZn`e|BO6V4* z39i>kJU<7SyAHVHI%uKdcv|~U@W=4e@t=p!S?jnBEq^yQ2E14shzIlXKC?om(H84vN=o^2NtMBm7J~D=rmbm*NWjSVJeDEz-N5UmBk5`GjywWp zZ6s1IpXkUutr~lnCT>!2PPR9DIkuVbt|MCCR|#D(rD%~B zubEU^cc78hxs+x%Vg6$X@16i4ob@ek?PQijQzieZfi>E5NEg`76N6^2(v~ar1-yk2 z{{lAO$SjM{aof;NApyxnbEZnRO}8?!fT!U_<`21g+Y&qC_&99r6|*kDkDETgh-Blb z?9T7UIB}thISUzkw0O~5y~+>wtL{7Fc;gSldH8639yf31)qi4|Wq~g>_I0dfs^OGe z!K&|A^L|jeya>y7<>8(f3SXza9%^rl#3_31Neefn#Uk7*_^}IkM)e_&Fg~Ughu3}B zG0}?Kod{eb?94;$6dD4YV>n9mC5+Hy8M_h+bQmvUNvJ>0P#9a~pPDU9l#NrDP39Z> z7R3hA*IMVAod6Yl=s=BNyrblFv9ahxsA&Gst+0`2T@WSesGH1hRhw z#t7Smp){oxPiCm!XedMT9Xls`K+YKLV>+PC>98;G(5Lw*eBS5`f9B8Y2br|#y@jcz z`ddmVevy*mwN3@%YsE|Fsj!mu|5S)>5)wx;dbtMZ6Z1juCz$0kMS5-C{B5qnD{7ViiFNTv<&?w+5J7 zOvuImg^_o-ySHEQGAp-85!m8;Kjq_i-SzRFWcdAdj|VdIswTnUkggogN4`x{jEyG? zQ*_r9na<4wW8fySLr;PuoDVKKN@|y=99HWqBR+2kiH1prFkUgL{}*5_>twEG!W=|` z!(x}*NZ|P}Bf#p=-xK3y2>!x$6v(pYq)(6dQWk)$ZWSp%-^30dq``oVSfEWcTXE)1aMtpTQ;FW3e5ffMASm16(q#bJ}PAM2+l8m-{ z*nkDPH}ha-U3r{s>8XetSzpDN&nlc>|Er_gOMq?H8gtx5_)=$=rKn8D)UFKeitTF< zrA6>w`_sOEN&t!qEx|Pjw>cpv6y3zP58py3u%=88_f1w?Dh6qHi_=ps1{zKT3c+AJ z-CHtS&YwELV7i&XOXFt+doDFc=HdO@cjpeR_V#?~+=e|BdnS5C#8DCu@>*3!I9V9< zW8$!NLpp)$6Dt$s16B6U0ukr;dz~cWFIBq~D_Il@v4E@wH%Sf#P50K?&Z#GHc^JwQ5QyPaJatDTEbA97~OHLu)q6tU>srf)aJKx!w!`g-`+$hp=yl`47e};Vme|`Otn|zcuTh4TQZ6IKVT7?o{08_qzzuC#0N+` zUL{|(2B|=83J;W>uqDA61!wZ8=lN%B^2FGwkZO!2?1c;bDLELF1bQ^Y?Y+7uH}!W` z^`^=K4S@v^Hf0N&e`kde(pQ;BIt`1ze5~`Nn*fETHo^-|6KuqPj||YZ}sKX zV?ZxRbyMRcdpZnDH1-C5U5;4JguMyzlQm)=l~l=@z2)laaTx@kKq5APotoUE)xH#J z6)(ramD2fUHPdL793*l5S06`4Z3{&?tnR3xfYKS3B*A9}jW9$!H?R6_%7X{4+i!*D z*)40tp!3LCaUi_0jXN?z7Y6AEkZ^eIVyo1w;KO5iZg~7 zHCM5Jk&G}NQwK`~bXb=f#j!xIJJ#ETt7@1qhw9lR(hEuxbrv?Ct!{87z|%xN)YC*i zx*N?__cB*&7kQ_BKkH|g0C{L*XHjv2;aHF<^+m0ch@q*5qw}L{NLOF~Wij{R7GRxv zl5Ne^rT$D06;D(gWfiTsBRtZy(NY}48_YzA+&O?{^mT^%=g%f;Ze*H{?}d8=k;bAO*Q1?nvfP#$3|aI1lz{jcLWDIa9v7R}*UUhVLB> z?TDq)NCcJE9S%g0rVmhrf>=Nw6kt8m!lpu=;6aU-%{(-cj)pA`DiK5kE7&tX-cAxk zV7ZG}Y!Ot|OEx!qA%%(cHP{?eqT&8(26rmJ5#`!FG&0ynY|*(Kz?poEylYbT zipX*&ApQikP2)eD@Cw5>GKY=XH&1uQkIwKs&xAMXwn91ntk9#gnYz6e93PIWrmt>FDJ!k43qNZXPf6WzmzXnJHc=iBBr{8^QV3P3jBjzp1TS;KxA;CN~^( z+=W87)Xjkhvi+QF4Lx^aaWOqm(0Y9CO0GFZR8z&yMefP`|0m~2!!3xZ8Lm2Rvv@2r^&{YhR@ zw^UuX9c)b@B%u83iCNC~IC#%5yDEAF)=sG2Ixi3%m!~JwM$*P5x2h-9J*IpQSa~@J zrrr`+ovQAga*z#m7tsT{r|u?Zhxkhp{;cu*=@#(3`WZu}iQhp)>uS`C#CQB#V0r*V zTe2;aKaHbKz)(xpB<;4XJks+e6S0l-xv_|GDdg@Di2SHte&&#+NZ(2^BxzTs#s&{h zT+P^yaLR3Ngh&SYr_pGSlo1CA2wot^gmLX*Kry~2|D>4C=?)BOyuKoq!#CwNE>=xz z@B8_S`HEpn&6xHL%`uv=rD%h>RB_zhRU&TJz}mn5F1e&^ASo;(3ppRY={cnp``a?A zC0wiV5$%pZ!_*FuGrqYzT=2e770vS1j+=c~|zjkE7i4Y4E(NTKXd-je8>=6q<+#B7yc*NLp6Yi7`s>jG~xBpI-ljN3WLT@-~ z1>TEAk)dHU%i@jw-oY^D2AAb|%)}JjA7Bt{nKOF_Hp_!A9$XYm%X^ ztmK?aV&I-7@30n?X3rXfNuWHp0#VN~t=DRNoaeHi)w&{-K@k@5vgoq(MtF*-_fe2= zYChH0%?FP}6|_HapKK0kzEY{&1ar1-#X(o*HA;tY509Qp>zLBfP;v#}!^mV5J)dZ^ z>BgG%+gA^6~) zZIvs|p~pM!mkV)(Wj^@{;btztU>>X7r>wpDwmCLZ-ovAvPh4@D&-`&>!9aQ4ozB$& zp5iU5W6N}(oJL1>m258VY_?OHJtQ4roUQ9xnhBhaxRO?2T*pfCJ;?Y5nAyb%ZmWeQdtfRjFHZ{sZX3=>dcPZA7K6U&rrSMJ3 z23`Lst@rcgM;A*bOBZ7^yX5>5bBMmNiu{;nn9^8K@J#x?!{n@TH!x&BoMx1Y zpdS!C^i-FX$r+VWfUDF)D_ay~adG-ZLIz0`K#)}p3kzvR0rp=Om7M8tl78YAV0KgX{bGW4+cEG<+t|p2oXOxm#xNQfN z8f%1y6(O6G{7C}RnVfKJuiXZaj0W?HdU$68{-jOybhcswAmTI)jig>@#_t4FFbU=& z)3D3#bDeYZ26=;Z?rb?le{I}drsj^85p*AB*D=t(sbAMU^rLueRZ8e8j2qQV1~Fi> z8hYmusOb@gaqj3$`75=b|ETY1Q+Fq*KH$RLu8u@?^hVwkzBUu&NT}LcfTObO{CffG zsFXYPCekhefLbLr_#$o*i+-Y*PU)i`#x}$R}_=G*KKA8Od zg?&d1E5yBkIi!?6gDJR}d@@sZwG!db9)PIXWr=&{#YBo-o^KfC-w7L=Y$2_q5tA_s zd_)K$q}9eV8#$HB4v)xO`cRrV5M0lbBS^BQ?N_Uyj}uJ$8D))4`RzrAKn8@Bl20*K zK?_9(EL!7Tu@<%jia$Ut+x-QJbj1FEus=kWHhxabUvLKbdZYo9sf_2ZyUzTtQ`H9634fzfh{>IZs*n7#nJFjd~cRk}k{P;z%|sOnYp)rqs0 zMntK7EEh?ZW;Dj{ezME8Ko#w`;YZB7WQfu8Cl3?Ixic3l%&`v9SfHWm2pdd-N*w#6 z>pThQ1uF0rDpJ1vzbcK8Z)NAyf7p9L{2y_q0+dc+(u%0J1ZfqPj;s8HrXflA*Q%+? zSWY;#r_OEyUMB4@+!+QYb20UJ1&W~+YkpIj`Znt-)9V}-KKM^_-T2*HO#8n*e~|@< z*PKcjON29GAwVEB^Quix92bUpcgU|UHxv~9a~In6`L>OeU`GfbThFhw;fLI}TJzeF z0G!n|WK%ep~kHJws&s(en>DFZ0)ld zbX&L4=&DqT55oSDXVOUIOCNtJ?&o_+z|RdgGV~cu#bIU7P1)FXPox?Pt^Wzf#Uyju zHJ-wt;Q{pYCwybEi&h!8>!GxjB3=MYmJsd7{?h#Zb#sZQCgbR3-)Ak*c5Jng=kai# z@B_>mOjhgPQ7~?18moe?$->ieFbaQeT=5~Jd?z*=lLj*#XEpObnQ3^>$2tY5G-}a@ zEmSX?WSoC1&Qmzkw_{vO&V@N_n)R`16?m2h8z&f4!ZL=IT1Aj1)01Uq2tWZO5y$=s zaORP;**KR8NS$#Cee%5<5+F>(+o;+NQrr(r-VaWFBjbZZN76SSb_b1o zc^0aIX`Kg^LWGJ>O)L_3w-hi3`3e%|1sEYkdcfy++pC_P2+`cQV&+tAkLXej;;z$0P<*&mKBafg$S*@#Iivr!)FZxfykAAa& zl+J;luT&!5ym{m^r_*pS9j1jMnop!C&aB@CGMetbC}E6!cJ5#tE)p{Eerq_dc}p;( zrX=B=qAHr%w2o-7rgx<`E+s|9@rhVcgE~DvjDj#@ST0A8q{kD=UCuJ&zxFA}DVC+G za|Tc}KzT+i3WcdDzc_ZvU9+aGyS#D$I1Z}`a7V_(Oe4LSTyu*)ut(@ewfH*g6qn0b z5B!c7#hijdWXoSr@(n%%p}4>se!uezwv4nqN+dY#Aawu%=d-Rn+zkJ-QcHv4x~>H$ z;nl83-22HjF)2QMpNEM1ozq$th2#KRj5s^@lA)tHO0f36Asv{XHuEFwPv8h3aVTxQ z%oEW6IvV#QJ0B;vgw^Hp1Px?Mz2A(2dQ^;}4MsY<8eV>fzO;Af@2_ABvNCN&Vi@_$ zRA;E+5L+M~+U^kL3Cv6VGRI-YP4;A4S&FiV_IwHwRVdRsZgQhV)RgM4Ma^G}ULm!> z8q`CgL(VPvlGhnd4Y_Q(w#EU{=fE(mCcuyXqOz6x9k}xk63wR%n2?k=jbfx8KC{_QVW? z2ys94)HvxzFg3~`E+&TzC@%OAsX|h=**G(r1*OP#MUZ>t$ZBnnJ56m_n+*g-@o>wMN)L+r|C7%OU{k&i7w!T&(lEg>(Lm5?YI)Z zMu*56HN&c15ADmoxo6=V1AoJDxTx;8r_dWba= z34d+4zF0+J$*d`EgH=4aGD~iWMN?r-nPLgUypU3y7jqF-rKVVCMolJ?vXnQCHq3E? zygp@tR;A8@wwqP-$|X$GqUu>re>O?GO0#leqeF|PxrbFUnRX?&+9UTQ^-bmx!a%#? zHr;DWVKXE_Vk>kZU zv>7s5$dTD>2U*zg;YNegvp*xjy`Rq?-EF}S83Bmx;bgi)&qtF#*)1e44g-Oe6BOHb zLCMn`&=S1x^%&^OkftmS_H!DNy0tXtDm$oL#m`o9$?ic5tK&QaR`dqD8&VydP=hmO z4eNH1Vl)1SSv86{1;1>GZ7eRkgcGt^oM^b@+S81dqf)DFG?wjas_XRIoXwxA)TbD$ z&;YM#{~CaV6{j&!q8Q4}E87~4tjOhR`yD|jD7xz-`qG4CixswD1SJ!dNNr(YceB(S zdTBg-bN&brgS8l(!5vd%3#(D9Rs}p}8tkD#7%)3&P(x)5m)j6WJgmsD;%%#t?U^$$ zt}rR)lG=wjUkB3_m9)G?t6Pgk^z+!P)&Q}&ZX<4NL*j8pdJ{Kbnpl=Rg^*{}#rC$9 zgeHxM@YlVRDsc-hGD6kMZ~@(KO!AY7e3CkQJJ^eBC4qsB&hMFE~sc=K_u%p7dodffBw1U*#b6=_ylpuw)MUa&2g24IPnQkKD+p8Kjt| zBrA0e{WbCdZ9sUUwkn@$zfRSJdC;+_fgm}R!nrJph!|;r$;y6jNTv>VK%(mFIc71& zbYEKGXaibyqWmY@Tk{fC;#Flu0igd4Olz3+NBQp<*MZDTvWGBG8rigCLOH%o>>M6OIYwohsAYg2z8B&M~f7N=iLOPie+-I#!D&YrLJ#*|r zk`%QWr}mFM^d&^%W6EKt!Jense)RQoMqrAg_=q!e_ky9mt-vXrEWn`?scHMlBa@%fis_I33 zTO#Cq>!AB*P3)GH3GO0kE#&p6ALzGH1785t(r5xFj0@C83E@@HBtSSGZ|q#57SXzC zBcVYI{w#qZOiY|a25^Fdny!G``ENdD%DlS3Zk}KXPO%lG*^rJ-*YoTz0!5gcbUBIU zcxsp)g(jX$tR0mbI%5n51@)hFEWCS&4h~-C>z+e9XP2#9L=w6n0&{JJOi_tKFjBOmkydTxF?{=r~Z0SZ zQ!+?)lb|XW*a39dgeKjifBjqg6C6^fO>>mhlO5^a!?k@%Fm%OcR)0o}*qm6=$;a85F~$*LPd>M4+h=KK^p< zUTLr~iZCJ`#!sTSSP?A25d9$@jEe9}IiHO>I(cU!JV|?&>({{a8~_Oyc02#bw!fyZ z@HrqJOcWp<_mvL~UYdVG%AR6M@$eurF>ywq!qkU^T{D$%{9=rQK{Mr0e$Ev<4Z5_S zNnwMk`o5QFbqF(j*?kTXXP`Tk>0tE2420%Wbv=sgM}= zFD&odG<``_Nk$!;UUlNa@pUE;@K9l8cg(6Zp^76 zHSY4thE?HEz;V#!D}=e137fguh3sSu$@cn(U(I~bzJ+UcXJ=Q1O00`zY_m-#grEj4 zEGB@jzU304JM9hH$ewewKoi}a*G)7>aprL9L{@#&E63^!f5;GKKdIcz3u zIX?;8Hm+myU<%}TY{&)aehJtE{bUL5REqCLEv$}$XOuvB|LmWM={@UM30}Tc@D;(g zGwu3b=?d;_K`#|5(k3D+azz2#*`b*#(L%u7Pt3A#1qc<-_e7jCTL6jjvyRPZR?)zb zWgFrXi*Z})op{VWcX)K(M?p| z^}a9&&u8|iSNZT&G=-;Z1>0&GKleLMJk=huD4Vlz{zHe^OpLbVZE?7JHGRxRVhX@R zX#DjtFQ~S{-S678C8X4#M?IY@6Nj@YeQh)P53f_5{5@XcsQhQG$hZ}!=|IIsPG@-~ z_{~ws>hNg`<7R&15+VS9kG-XsFaWQ-qAIYaR{NtS)$_Kp8Ny;9bOV?yFjO|C|BAb1>)p63 z4?AKjs4JeWs^@~NgVY^gp5av^K1B~{YF7jfwz3uM!~O04tZ#R7eB-b!IWW%tVX4NF zZl~8XZhad1Tj?)(6C#PG6UgWf`0A^X+pq%_o&XegitvOnypX9A-jKwgoqIsk`7vDH zPz9}L=G;#3Lf5f!K3`t}l&J?TXKzH~Uzk?{5_k9H9xWw9crd@!v&1VY zsOuRn#7S^4j73)ETazCqI7bwNo$t{cZ&ry=x*Xgs76A|6USJp|n$Y_yB zDC2KGY3x!h=P8)>V7&ntYvVVK`hxw4Z_sN~Bp#BR6^2R37pGT z1Dj`(PM$x)t^Bc$%_kZgDbs?_&wIue+uUzpy}>uET;=1A)F*)A>Ata~GY4hAc!A?U z?{U63R0JMe536-g^k(*$`+N?+OJ(#XPk0Vrn^Rty$T*_`6p2GBZiWkJ{>w7+4g|H2 z4M328#NL_h?{$DR4^iA=7M|n{ahQctX<$tp*M$UZN+xz_oI{cx8*`dJ7 zuF=LPSVu%73wwaH{>HwHrblU4zy99llp3ScT+Mw7rR)7PJ^rA!wpR1f3=q)%h-?9K zK52(MxZVT~sZMJ~do{4JL-m{KI{J9x5!DKd$(}V4$Q5i);pa(WYKq|3lh&(wpC>*+ zMJlvE1NX)k5PT%eqpH=J7er0}#EOfJJqW;C+V(XcP_4kkIdOF!3{~9L+ z48Ix^+H}>9X`82&#cyS?k1$qbwT4ZbD>dvelVc$YL!v08DPS3-|GFX_@L!9d*r0D=CD`8m24nd4 zMFjft2!0|nj%z%!`PTgn`g{CLS1g*#*(w8|sFV~Bqc{^=k(H{#0Ah@*tQgwCd0N@ON!OYy9LF`#s=)zI0>F&P85;TXwk#VAWS+GnLle5w zSz<>g3hqrf#qGfiyY=*_G1~|k*h-g(AA+NbC~N@AVhf6A6qXmVY2Temx2|X$S0UFw z%*D3^qpS5e`ZtH#e-p_hv3bYtz!vUA56&MBhN4*snI=g8YNZ{TYX{~dPZ=Z_gk$3Z?0ZR{D-aliB#|SEnR`T;N3$!}02ZQ(F`K#y94FLke@r>i04JrfBacpWL!tC&p$j#%e~c zG0Oa(wM# zM(Mn!CQ&`w@usAmfZg29h)&o{r_NeX64w5N5WxG6q(-s6n3+LYQoV!fQdogT)Mf~f zrQ*(MSoLcIu2Zpl1bcHm-1-=no;nuG(Rr?&=9Dia+wfu8KmGNY@a~FBD`eM%#b5IC zn=aI`v<7i^08qgeb@EmZ1l73Fe^)VHH>vwnl#LfZYM}d!X*vZ=X-Kmm)|p~g8rR~7 zTHpjqRDXxKte4N;M7->5uZ?~X`;`Oeoq;87kGDaWGMa(5g9dgC3{EpOF1o}w3Ms0+ z270RrL{cUBU0=kwNClDNSwY!Lm!3n$dY&svjk#S0d>tPZn?&G%Bdtl_HV)BD3T&C$JTZ)yChEr+){ zP!q~(%s;6J22$ep1;aq;vT%}A@4H_e%j*18G#k|8R4HfuOLp~*H8ydsM!zd^J6-{I z0L19#cSH6Ztna?VS=NwT9B)9MqJAc(Hd_EwUk?-sA$*+!uqnSkia#g=*o}g> z+r%Me7rkks(=8I_1ku94GwiBA%18pKMzhP#Af0}Seaw|!n{!*P9TQbotzCQLm5EQN z>{zN@{lSM;n`U!Q*p-J1;p{VH`75=x^d=n#jJ1K1%%tgPj|GD0Xz zq9fV3Ma?HtM@!DivcDoBi|RXcCu&(8=pz_F%Qq#Kd@NT0|MtB&yqr?e&x3@7k^qX=q=oz=wvkChK5$_^jhq9 zhI+$s(bJ#2(25kdPfP>T<$A@3xOU9Xu;*O>W zPlGz<+y;?kBjzc;6Cx`rv_6DV)$7dgS>VSX3u8DBYT4@c~$tokVRZKT>AAJcn zM`3)eO!3jw64$ia2bI*ky%;JvZAew%gfzr@2z=cx-FW{@F2|Z2yJ)(40FvA_tyb$4 zHp-iN;@m7h0Wd7=&Re6T*H*wT&g*@8FgUyIHK5&0SUQ1)UCLemXi3}48~TLSgCCyk zrp@aYZmn?H^Jl<7jH)47mR8%{zw5cawx$r(oP>dTGqsxPPP=R8-^vbHS!I{bImH+d8&wJ9%Q;wmq?JKe27wwv&l7u{E(hv31^a>U`O|>aMzfL3gd{Uh8TtBa3!a zM{Iu}AI>-WSaizNSJ-FtewydP57^1>j^mNBnaaxoQn&p9y9&-_w4i7^xOT?7NKl?lKxm79T1T;#zGve! z^z&y}PFN96@n!`suxGzHHb%{=V`PLBTAb6YsDu-M5z|b*X1U-HtKvIeCp^%4PTA_v zr^@B{_qoGaW6!xov5Prol9ez6kdqH&(Vd~>o$?gruojX(F}osv#OuA9XCm{BA{HQ6 z7I#HXLktMs2!{a#?(wMAlBNdNxg}5ft0q4}Erg)PFo+~m7-_8kEk4%&n`n!qprR3_ zRKcyO67pN^HTAedB<#V{RM6J$?2A+0nwfZkx z)#H~>#TqYNMDy~b^!AI9>aavY_!YH!u%px+~ zAR_r);-C5#UfvaZNPmjHSuC39+iWbb>#uq)ntooMYNm#v%L5gx`qHNM^>O%V(&=$_ z)SkW9)C`tI#lQ5oYR4|5rnABn0GHiGa>kIEA)V)lr~lGU5$|u7S!kwV34&t z#Znst?`+H+{F>XL5Ihe`v2bcY2LZjt7?Bt^Q*1(5Xcp&jtGCX0X8@7GN*e>1pKz{? zTsY$-TL0JWaic5zP>F zBpD0yg8$LFD8iM^) zk-SPvJ|)^m$UbXDe<1>130Xcxq=9HeXVixa5li>o3bOiCmS8->t{1==s+|s)1#Fxf z`>r33c=P^?sE%sIN{nLrVKP2=8#A#L4aVF0&5hX+277!PfIi#w^-B=A(-v7xyZMmjc^*yX$#oLqK zZ9ANck>T6&l`fxVTgmj2FMyTGi}%N@9p_{)5@W~|eKY+}O(1Eb@~8MeO%U*3OJV&~O!Y|BfsbcWre3Qam04<^Ox8b7rmU*W?BC?5tQ&Maqv&(zE=o#*zFyM3A~aLQx(BIxtIGzX$s zVzx&kS;C&nIUnJf=0g?za@(IQ$b3sWi-$AZ35<7zDuzQDl|s$cdI)pS9|?_@L&YG= zTz1|NMy|(^-ZMSEMkmyA*Ec=8U#qiWonuyZ>vO5Uib@8!;^$YYmuBR+aS?1{mN|pv zw-8JT%`sus&h{q!ics^;33&wOgzyRooPenPBHseN0(uMGO0M=K4B# zfGQ7bWrup@w+0D8zuXDVG3`|9WQUIU2=lfs0}uW&$pO=+x%3;BTP?egh9}g!y|nxQ zF7c19A0dClYKuSr+0{^h;p=f9Z}r~jC}s(xg1yzB|3z2;`K_IX0kqq}KEYNiMmwrL zR11gCd%Misw-RpfU}^|g2}g%6#Etdt0G?#sN0(*BU)z~$KoK{Kq`9iHM72 zx#?+K`4Y8`;N;NJ+f!qAkK#UXrFMqzBWj;wJTv=9yxWXYj<=2W?S}YbPJurHi zQ($FF9S}jGm#Ch5G_{9=G&4K1rES6e)EtmgOi_(}8r`}~fLVtU&2@>eeNlYH>3oCK z-!_xrX%uzAB(J7fGqJ$WVfFlaX$_^-S(u6ywL|Ek8l5*sT z8D9aA(LyK~&|Ms@$?%C~OSUB8zJuyoz!y2nEHMk4VjBmJdxc06{ee>417r_Zx8M_f zQv&2&0cujOd<5@MSTY9gXQR_E^F$=~C=15`95Ht{YHmdLk$@3n#NUOMK$};s*lX~Z zj-hg?05PqDKaXM*=@C*FUgq$9FSP4gH_)(EMoJ6Vkgs{7exk&Q6_1EM;VrM=HLvKN zx7hNZad6+T$rH*0HD{xnW|(A;fL<{)@*L+A~DI2+a&j9;VV7>2~< zOwYgnm%NW?RDa+8Z;c&Dn}UQ!4V=-1_4~gI?EYyNM=CB-ToUF;W;(fN7&0R;6*M#$ zvq5<4o!#$u zL;H83)18fEmc^I%kG9Y0u2a8LzSGT&l-IvE1-?m<>GyN@RiOc=MG0pwK%(g}7UrlR z%-M&;96}o7L1r8apQ&v zS?_M`X_R4kkwW!jor7h&G=I3cyLo=WiDB0_Gi1V3Z<9=>`A-w>Q89bJ>Y)nS-T|=~ z@1h8-J2K?H;h0g6ESyOVVEyg9o<40j9gBKQkt9MJkx!1&%PpEAT{s(tVflR)k?!o2 z0mU~aI_52$;dv3)8$;S9zy4g!NYM&dv+h1r*xa)+IiI?ql;2upk;*aEok5LD%PUqS zz8;1l^|}F5xF(Ao%CIC$YgCZ|0wJ6yU9ZfstHAOwKs1ms4V(xMc;b-etG-ivj|D2A zWYxMR_SLI#Y)|w~S9~nxto669sc=HX zbX$_ZzOwkuE=C*zP%=)t7J$QsNW$t3`nShXVT*uu$f8k+iyTDp@_c=Lp{vaFBc^0&k4p3rk*Y7Zi_uzwrjSgca zMtjp&+ZrhxKyKW{K)&dq@Gfe!?G-`-PBLfo;s&_z5DRcM(+!N~fXTq|3O~PQbs=qA-pTg2l^u+d z%ds=eY1sNyehE&1F?Kp*1nt?h_p`OIU`aFI@{{AP0W(he39BQ}N&Fxr(_Nn9C@|Fv zF2CjVJpZj*KW06pkPfYefvVkXhPmEzhB0ZpvW78P+6b`(DXmx4XD$i@yG6uVoa7U_hH3k2Py`({xw)s6nAe(f(@W-J| zz@YAV6gVhtFUM>qy-n`}{EY%a%Z!g{Uc4KbHQ4Cysq(A?;rg&6Xew@Z;N+ZaVY|*= zY%CB8ewT@Az-G0c2It&IF33z$Exgk%iGnm9(StB(7KF?4q@06F#2&%w!1|s-vJ<$R z#XzNy)JYP=0BaD~u#sigQN$gNdTInmz#5sK4BSByfA_#G&)Zj<2A?Bk3$T_QnC;|2 z<0|qNBOdcGWX_efUbjcIbf9DLA2^E&r#fq>Gu)@g=vUoWqV-D~(xUfMfaCeY?ig%5 zNlo{2#2{?+Ykm2};*J1&Ep^Bz&WB;0YXN=I6)&JUITYUOUDcL5p;6b?izK++B7%r5 z9mr&h^fGbKR>>e`KebYXfs9w~PV?6xQw%lJOA*R&83!gvx2_G^Zzl1NjQ*&uWXlIJ zA5d%t%)`R6RVN`l7|hlJO0zti;vgD9yyKBh-oiXL(LgU}D{!LToK9roJSM_z=}gA@ zV0mkG5=+m9kztd>9U`MRFOYqw_R@@-88|~TY&n;wx0Y%6<;}H~Vhw9l)<<3|O$g znOS~HbBeb++hP5w^R9fzH*%%;O@OyRJ2HQ!`5r6TvCxLMt;lTth4BYout)}a_|rR1 zP|nlJjcdDbp~VeGki#sSoP(U~1 zzvfGSEi^1h$ayZla(pu`eFFiu-MqSdt8cz0qRmg++c}@ChaW9!{X)T1I}H&3h$C+b&J+B z&WGhay#y)vpbmts^9+1um2a^f=rUg9gc(vaIvdu9{ z=g~Ari+YZ*_9#%du+x0Tj|uG&ivk6<0W0(z->5&_@J!xrKJh+-N7(ay9KI1^9DKq1 z-`Q>5RXJWR>^gJg=ceSH1FhP&;-(b&yx3;%21tElpT5B-^B5lRW1stx=Lw@yl4K-H zH_&#(_w~Tx6OXfPTcCLo9$$?1c^Nx?=R`f{P#LiJu7|AN{H=1s9vgkea6`f*yNy6m zELFO8tlEHRx_O|Rftnf+yTTazHib2IaSS}hRg2p_EFj}MmiDQ$RqH#OP&*!>JX=+E zhHHTXEmdmJGX}fFret#wSWMoxwfs%78tQ;lJ+%#EPSxrJ1@y5{w3>3s`&VRTmheQ7 zm(`N@=UL#bJ3J63M84cI!+dq8*0Pa~cm)*vOH>96OZZ8rI+@#sxvX%J;j#2UyoI-P zoHw?w+>h2y0-i8E=E{R&#ky4YXy`dpzp?LN@i=(bZ>Ps)txu1NjX9j_ZqK;J7FkwVRy|k|*99~?Y z`*dy80oA`CJ_$tFQGtxLJfj|?%k{~!rK(wP%(jJ&e^AP#2mSmhEOc8GXcC^~u~)IG z&bB&9qn$v@0V@7Z+WqyCihnp!(NDz!v+(tZ6+efxni(EuvIZgq!%Q;IG-q zqF8&i9!)wS_%M!tY{yK|t}-+MVeB2X)^xwo4U+^n6ZT(3n^9s0^N~ZpVA-p-|=@^inh<~GA#G0Fb6cqg`G}K)*o{T5?_kIK6JI}m$v_ol&8oO4P_zX{TbEI^ zP4gy_X(a!@XOe=(Mp}U0!7ra+gbWnl2qGN(SI*+{5}&-NnMCpgbIjJJMM#>k=g30^ zDbJL&s-oi`3YUeZ9y-BZu65hbFPz;5@(6>;XEhacr$vW+pjdI#rGBriL|0cF)|$5S?ZhrZRY7Vy{kdqRI7&X0dtGtm6}Z)oRm-4;l8Ds`lB z1{;=7P~qZ2_n6wIDqX_QLr64UbcGnv7W5MkBQOQpPgUnUuZmy*Y1;{C(bD+H71WwI zFxkY4N6=#*ys|B0K*aJKZ-tf_Feu|x0wGE^{ za6HB=IjXDV7hj^UMqY@8D*!&A%+%g?A)#u;s#rUkuh7i!inq{PbR#Dr|8ZT+Wh(ZI z1r+upwLB#jrdiBGjm$~v%G;|eT(?4SqN&z(RF;+MW+&TN%T|}sR;8Dh>e|RrS`1xo z;obvgl5Z|wz0;94M2z-Y2WT6-(${?#QL}TPndp;hQjRZh6!1&D`+%7IvJc29LIBMq zvwi(+IZ(P1qKSTq#x08<=kru=S9oc!%gVY%A{T9{D%p8jSYCIzFy$TV^U4-RLFD+w zn77r`QwzNhX2Pbr7lOF`qlaW1HJk_R3Xg`iqZN?BZle86?}o%OyRW zEc|gt<9{tSk0Td&`c-N?)$%jzYaJhoOAjaF;6Z6r1}Rm!15{WMTw!4o5~)Fo-HoU_ z-&ujRx$TNix^SgDySgxKt>YCrB`EyID}h2#B6*Zab@La310Ghd_ma8AO#8-ulwSnj zZ<5BIUzZE;5*FP#&vkvaG!H~2tU$Jkd%gFw`T!S{2mp9?Vh1R?kv;~X`YAwb63>)? znkAD~i^l250{N2CJV<@SZeNTq!pqthV6F>e_QO<+Mykoxd5^JzHJaZeQZ zhJkUxQe7WRdWlz!MRJxF0W`KL@`p~)x5J(z5M;XocV_|rgnnd1%sW+|yq!Q`G&7GP zY07mPEwX@!LGr!_kNsDN#hMPL7#l zlc=pE5aWH28%^Dr5#obbnK@SMPeMr&YC`p^e?y)lV?@3LQVmf_yWw)b$Jl&Of#Rp# z&|KH+IbPYoU^~mj`IAFEK^Z{Gyzpb8*3I%bzXzl%M=>mC%Q2%)jr6JJ(KPB8q85*d zB`H_bk5V~4&VPE&gUAO>5~Zr82#kI9vNGHonE(8&8C(Hj-eU@GWQ@M~+4I^wF?8-BT6Km@x@%lir9`u3T}u<#oKmr!E| z2--yCX0m;Giv$T$>#E8290L1S=M=3CD`(J9s?1X>SX6lZ4GocaWFnHAC)t1T^hkf* zUD3KeM&diP@80N9p%T&fLe$oqvOhhZt`JxBO+^LSf?Q@z_`9Vr$Q6~<0L2-m>O(g4 zOan%-sNta~Xk*}&{@r#)usawmHs1u<1GjQ|b56{BDO&snX)z?_ zAankXRi*W~FHQC%{R2T17EVv=NN_~B7>6qS8-oRfDB^`%jRb@OLn=Vxce}tFY;7n@ zj#*voq%N#N>y$Y|*HtC2U!S=)^IxgQ0-7$v2yiqNXRM zwteC_-%jMY93pATf5JRZt)5Ay&cMar+UEM%P_tH6YH%!8xM83G_bjXj(q~&xt5EB% z3%t+9ys%^4AWWnRiJ*K6xjY*LNS|#O;pS)*K=AB^uJVW_JHF`#iYDK!(>=WUhh6%c zX>sTwaqCCJrW6nIY`0WWbIIb}bAzF+1oH!VTEEkh=Zo6npGn$x%=adz9iX3#tW4ZG zd<(6Uxn#z9!I5&G|DBlUn~4sC6q09u=rux4?hdLGj!_7Cw~W?;w)!zdM>lGL9?iJ}t$XPovsz-)cS-!LHv0ZC zb4AsYLrHn^FyZ^K^RfN==H_K5|Kmms8C*LII4c6rK%~mwn+cs0!Hx`!kJU7zAV@+T zY78x5H8b;aj{WU`xKGLdJJr*0Ydv@5KHQ6gH)}c2!V)JwlsWfdsGezcK zvNM+<{?KLS;}dCbka?fVSkA4*j<+1;zd^mMTl-!=UrG}%Dar#cYGiWKt*OnI2`}s& zKuJNJ^nn0>uh!6qs230jLkzPYLh2_ii7q$|O>AsUP2s0Lrn|+I5<#4D>kLax=_gwF z9%;kCQJZOVwWh{(5l+S2;i@c9Ea^@^d5H*?CXc?hq}byCKRwrA*C%v%mfkhaNtGo( z6ZP->A4&OCCWA#*#FO}#W|pFnPK7yjF|1x3zOLK4rW)-`{Id_xRgaYRE<$eQ5uvhX zwf1^~0@8-xJluw=SU}u}Dw6aJ;q1JO9ug~KY0 zc4j+Rx)`6g89&yl&N%L(+7`jSN#4N90mygg2v-%B)UllG#o_hk%4qb{}DFugg+wjSK#BF}Y6uqK(T} z?kzHTS{^k4!@fD4XcX#W(^8wah zxhMD99Ne&1gVtZZcgbC`hyPk0Duv+(pFsD@Nk!o&HRyRK5G1T7+eQevJC6LPk{?9c zQ-J=nD3qA?mBsZ7LMZK)4N_>F2_tu$3G)*!f%X;15m2(%QTyX5jbibaL(DZZ?^X)6 z6IQe1C)xidS(*m&S%Nxg6*Wvr#c_5a;M1(O#!UP zK|w*!f?nnepYPN2Q*1CL6QwdI+R$^%?Xi@THq}&u@#=_#DZffv#+TLtqCOXu9c<0O zBsjTGdF-y+Z@mK*MKeXymw+sY=m5iC_W;0f&xoJ>Z_(Nj$u*A&fs%=i& zXib;4XQuQ`Jk*=)+;=g|>19uWnY|Fm@!=U93(mB|GesI4Wr=-T+cXbcT)0}e zk9@N7!pP7X;)b3=9w&;zB8_zwDYIgysR+6MlJV2JZgTIABOgT$H7|24>D8+#;3xzh zyKY%iqA_a64CM6~S%7)I77x*&ho@z-+9T$)J3p7ZAAvXTlleQ)85O-Aovu)#(nBFp zlZv+~J@s!EXPC?AV2Qe2x8xWM@qgW+EK=kDvM;^m-$jX%#8X}}_^WbZAFz~n4^?Xl zj%R5)@O^*Xqwo3nF0=1jxhKO#Xm|5ZH%Ot*~o~Quw z_cI`0zS0)qV;eDMqE&yp@f(f!aI}g#JA3@l8p?CR&@Kv6EZIB?Qasr@Gt@Z{w77Nv z-U{;yNYdDIL049ee>V>Tr3Z~994}6y+LfVe( zL~*qRBcjeUeu*d3^?P%t9mHjZr3zcH#b1=(bHZuj@nb&CSkplmQTCO5-ncOKUr7>~ zXO}(#MI0}p_XUBw9Z{>_&I}hoUH;%ATm@}@Ytb5^tGOt&!%kKyT~|z0b_-_?RCARZ zLcxg9h%d{=k%-3K6b}W*odahEdv~P*`guGU=-EBpAXK}9hD!(mCb7CfG)h!eG^FI5 zd=4Io{XOpVr+hC9GHRYg2{EiG9pbO0{pc-`u!{CO2&6VBS#c?uQcF@Ge1pz8z`x7f zHE9T}UBeEQwl^S|gy7HSeu)=DMQEd|gKT=|>Z0d0x2Brl>e0Q*+NDE2Z%mv2r~4?* zs)BH22pO&FW692q$)y8BkuyA5=q{G1BlUhq1an)0@}`oN?EEaV#~%0orHAOc%vR{q z*;tAA6OP9cdMCD$ae+24Qm~2WV^os>Wz#8!J5r1cHjce&Nb+|lF^e;j^Bs&p-JGc~ zKav4|l*k}_e7EyWNLxyMK5|AW7)i^q2!*m2O?(+3 zqby+A^sT-jtH~dn3!P$OMc{Pqj?n#pg7Crsn{p4bJZ}i!``h8~b}(@ZpyEJ+ZW^DyE{7Z#gl4O)5m zjbk$DMFbl+chBv*PFd^V$J6J}hZ+3qBvi5k!tI_S>L$TzcJ^*G+St!ob6TYl)tfN? z;`rk9+C7v-`K&b^3?Dx02XH;WA*noz_@;rr@7b?!{e&;*zzHX(n!PtW~ul z&|=dUNrRvwc>mRXpQk5&-8k|D{su?2jk5!p^G#(vbx?!4tIQ>Il)tb9 znC3VL0&yIpl}_;L7*w91$b^Glb%SBKJYJjTcuN?=rjSt#n#loPeNN^GB|4QV6#|9A z))*lnJ%TH?o7n-B!{luw>GsRBh3~I*pndrHkLfbiN>UjYod}a51nzmD1+I0(7{u`r zlA9>4UXUc)z-!bi7JWd-w@wwKTI>{`9hR1r15}NZ1`EQ*5she490`UZDi{~)hLQAo zF@x+OMp^;QY=JO+x+2Qg;;>mIgf=Xmo^UY0Bv}V83(+id3?Mv1kz18z$0;fV^tm_A z!e*cJtvb-M`dwsOP$-dbF6uU5Yd&C02k~DDA0g?;H9dbopc?PCHW8bAv+1xXzXd!O z=bs!>6tU4sZ00nAP~*Y@frV6L2{yXW)wS2JPr{^!5n9UpOZ(@-%sgtOXPyQVQ0umj z#|bhR`~OAdK?1RqGv8gu00994KtM=RP(+H`^)6R6>^1s-x*RQ7 zWr)DO1*QM_-!NK!6}Zmzcz=fY-cT3weAX9u+-qCImEls)cv({&mB31~sTfkfRfSU9 z@{dXYKVzUjk4~#tJ(Jl*gbJoBq+P2EDx8xF>QB!Xr{_D@l}x+DS2Jw%PYzv#wr4Q$ z<{p>C>mQc{_~j%mrj`i2vup17g&@6~3r-)vgjQ}vy$vX4OsqwR&q%c1yrRY`CLUFV z{F5^#_Qw760bedcYqxO3Ym?KmN#AZdos&wy!>-x!nld4=Lmwf)5eFXEt2N8Iu~QxU zWhsx^S#3sLoZt=#IX=fu>74~JaBEzFwQ*Ew%DaZW;C2b#FMZ6?)-Rqv|FVK@{dUR5 zVYPEq$u{iW#^I@nmdSoGl-=QFN%G%3_toixR}MR>kbQbmWkLJB8S!{&f*kt2D|G?z z<}kD%#qQWOx+6xG&u@#;zXQfCXpHY`nN;(7PYJ1{<4tW*zw)l)3*&h1^^I(YQps}i zB8H=1{BZ7_mKGn)uj;B>p1prd=_Znix70hLVg6M%uEAvS(nMw|Qrw1jI^F()!-C3& zOp?`_DhrI>MoZJNcGqb(x_b=q@-iLhxTW0DzMt#9g0IPfxm;jr$3;gjS=-mVARB6W ztsy^bdmzeWVb4lNyELxF=1qS0?7=q3UL}}s)nKQDQ-|8(A~ke&#g3l#WP`@%Uw22? zB)w&2o_*2U=pf-^*y)C+Da9ck%PAFlPpgQ(dR#wP9%Z2=N0El$$fXrdZs87;i^-C& zXE6y+u3L-}y;k80%=MJv#%fPz%`^BU_3`hd8prA}Lr>|U+Oc7ct3@844p(p8khf!I zrX`B(z)4b&BxATa7wK3*4L_ygb7}WSJpTf~E;UYL?w5|XuB(L1cpyi#hi$6C4#SO` zYEZT>4d2N&MRgWadgfOhb;v4S%whUtMwPiTS75Z!$IWInA)SZHK%ixRWree_0x^?4tck^;}2eX5ll} zQ$3s;24vdFNEq!91S!!HNtcb#`rsV65H_yl+SsCNpV%AB9$hf^FcSg89XBzCduf8r zq7_K2+e^`mYkFJ|=V7htVLEbT;9K?W!9s=@*1EMVC&8$fB4t}SJcmER&6$rwdI6wI zp`@w+t>nlOd_al$CSHl!zWkvr`**OUFZ(yyQs=b=+16^F?cmcLccS|kNnHfpbz}y+ zV#VD(^0}rdw)0xQx65Nxyo*)MydMApuvD4itFO5-(yK$pMmDYQ5qC z>YI+^l$RA5o+1+kGO}l6qs*?<$W6-U5He|J;D}e}!K$EJcbA$rT4U13njeXmUWV04 zE*(&~v=J+wZ#wNB)meIcT;()U9*UkehG0O#b`t2MofG%By7p%!z8goIN;Qw!=U?(Z zXQIu)LM5u$=Q&UtL#ebx@zBKd?u#VPLds9n#p!FWEHr*k{0WtXAA}6?Sr9T{ntB zlb-DYLh__hEgQ+wY$KAZh& zt&aS4yp;Kg{@0JZhqpmXX%=86H-Ppe3S$=9LlRDkaf6p$%&H$n*X1D8<+2f>4syKQ zecCRqs12xWrI8C$2l&dto;YDkFnx%!xah6#`qIaO&!|S16m{T6l1s@JxC~txbpV#| zk}fu78*-_opFd&<)Ghrw*T^F(gm!-i?<-v*^%1X_TP))>kk2?ud zS>ABr25C^WWbW2A_G`(T>sQ0W+8b1yW9omVy?$VpN{_*i_DXgI#L9*`=02#eRg;M=HgS}J9^gh_9dw?cM2yCSonba zrkM9~Z@{}d^CI1%bV}4Oa%$+4biTEe);qYRO3qzE!$ZD~$CWauy#-f%&=%{&U^UX+ z!~hIB60(p$6*T*D_k~Bi{0173X#Ld0fwhJUOPakRaMlQ)3YkVBx# zg5knbl=(sY@Tiu8tx-ohlpN;g$h{F79#p!7C8)Le%inWP^DOB~p4DHV-J z%iRm{p|f<1+6U9e;@N};bY3A^C8fb2H*J%lU4r)6`S8^JoA7txgYiV(VZ=#hE3B;TL6vk(G(qY_W z!POO0YKZ-vI1SC)sYD#G;emLBMVFt4Ej(J~FvIPe{CDkLfm=Y>Pwm66S71Ztj`3Os z@9#@NqkqMB9WAzSs(>z(#CrZ*|UuT27M@1;t zZUYh8EeBojHewBZ)>j|%p+X5BY%J3l!Ume)@n*gy9%`4o$E1H2a8OZo{WZ-OPrsI5 zn;3l+TqmR$*P(Q;JJVe2Df%Se2%sR- zpqj9(xHtFlijQ#C#2pH2HE!G7y`#4H%Xsw=0o=d(?;->v=_AAEo%HI?v2MZNOLFm)M@RZds19xmfL+ z*|#nYtu=Hgcjw7Gy&}%1%S2>>v$8wAJ2R~+M-kNn21-)ocgfmrC-ArQ-Xh%l!S}+Nf=QLbte! zep3kGSahTxx~WCY-IbL{MyGt_qY%(_XX3GeEA)%;x8`3hU0@05AgN7g3Oy?a+V;Hg`*-ss>O+;-AIeMN=up-v9_UVbSd##|#j*F#DP!Td`gd@>xDb?WLvhVQ0Fq+?C?warby;8PufI~? z<-x`!=fDNS#g~QK#b*D~wDcQtN9$2Rye2K@SN^|IM-qJaeDu}~GeHQh)^sx^YSw}V zA^$P=sr-ZbrAzb0sWg?yH1d7Wy7Y0r&gI)2GCJvUs`81g$EIuze3XV*Y#w3&Y`S0VSRR_xr|q6*|QwRQZgI{ z9k@Jpq6J>dJD&D?SWbqg-67GR)r=H~73}CP%VZGiA^$CuoJsX3R?O#lvMJQVc==e} zg8@B@KFY}*)1dk5MQM1<=aMq$eXK5s7R3y`VZ4yjU*=^)`#4Wc#G3axQ-1-lGwk7V)I^lqBYBxsT0Kx2?zkRV8*_ar!tkJt z=|F*IsI*-eOxopCqFj4awt>@kgXY2S9RTy((EO7v<|`_58AtjJm`_I6+hS}M8iGyn z_x{c}*|HIA!gjiYJ7I&`Xc=AMJrz_UQUMCj9}(ZFV$nfn92bZ(o6+ZX!;3inf}!|B zw;Xg|HrIE>_rr^k*9sr|x^slE$-fv|GTpFfHzJBNIzcBecC?-;DJCA5;0Tmo0D zDkKj%y8mPQYnS+kI@VXwb6ni{3zyv0t0eB0oa3$Z$_+zzHe)BYf*-?J`G|k3dd)8> zI|o`Y-!iusuKN?Gv3E`4zo?xD(Dk6R9skkdGOaebO}zw}nI;!jpYJW8BOWZ)3Bj5e zx#CMhIEXnU~ZtFn%w%zMBj{~So6hLKHD34vBImBB6|rr=k_Ov9TDKb zjHv8x?aep|-NHo6bZw~E7&z;lfqdX7)6_9d!3T%O%i+h2Qy8eO#Jzu97y_0DR%Boi zZskbi)tz4_p5?G3RN}xVz)_VC7q~7k757;4Jkcm*1b>l{oR8B5A(n(aqU2MYFPpVB z6h&y5q*B8!@;^PIV@`WkEl>P_59)go7fUVT5s5G*^>im-k*|s-$5wkRp}EQ76+Ugj zIq!eLU!gEOZb?$hz0Nd=-2hv+OEaKb!CToAt`hn51=q`0DETbq)jvAF-4q1sk#2!_$hgUltLx=?;T2fk9Gvi^`h@3j zR&uPc^HEtoq0tCt$W$3NxBs3N*XP!q*QZ75Oa8EYU7qIO+Fg|}YnA-+Zm7E?he&Gn z(AN0GyFR}uX2}`m7h&ZmOt0-I_21pyb+NddB+Stfe7xs*vz#j`{sX^tCE}YRD%^E4 zBDjOl`FAUNnt63d#O!&I>x*cPXld<~b;(78#6_cVXV_SgKgMbR!m}^f z>2Zqo9XrXZ8r%X~!OMUxcEMkb4&r zAnz}M7jly&d4ZP}*|0Wqm5KCVeU^iDA?5RPpo+xYb z6%IN{rz>_6!{12CoCs)<+eX?XBJ8i zR`WZ_Fx(qnx%dyy(NMo?28O; z-Z+y)dMKc{Y(WBe0QS2<<+6vl>x$12LGh3Av;PrYZn-p;M6MM4hQ!pmLfci5##IU6 zs)BR1Xu&DENU7-N0JSwmYN5iL{aO^r^Ip>_oaH0nWGEizG-=y7Cz?v!P{V5jfANQF z4-avR%xP{HbGBg?@5|<0>Rq}g`@701KjGl;*CWuelQ!k)D(`1d(OH4R8inw#Y+>_e zi7c*o;0cv^4iPe|)so#OLYe%rSM2Slj9-JoEFm(^=!Nl%%U^sek|oG`!HP?^E1Y%R z!(|EVWzAaLJB)6RaozREJGc*39Tlm~n943AQZ} zxZ&%U!!a$wR#p0hG)dkF;NeG9AwCww8KmbS#%b09Y%L|}A!8ti-} zaK3ggH3Jg7HK+O&nyt|aYOmF+`N0s&Y~xbzzzLFjnPtxjQ=jm(yg5^D=vb+kTl=j>XHlhNK5n z2XGxTQ^(Nk(5Yn1$99jxX4jp^;DLcclXrG#h1(96y*!pJr@c3V8%vLKyT5*e8bLmb zqJ&d}@gokjki-s!gXDm&7f+qCn^~`8?Lp4)v0p7FqLVNQ2L);`F>Edas{wj!ZeS&4 zuE#B8m(>8`w3r+Svb-mQQB~NHt^DxfwPU!|N8ZgB#iltJ3ce0H%gM>VK4mKuBz_Bw z`qbSnzEXE1a>Ji)l^hx+=IA66VBY|RwJV08LAR64Kqkv&Wei5^?(SV1O^pZTDoz5D zLv?Ec`f|yFK7|7RavcaDE9G$Ql)G9Lhx*&1IwPaHTENXoZV_<#0-#nD_=>dOZFAaF zPo6y6h>h01UT)Rh6VW_|OaJ1JuH~`qiQVBfGvVgQH21epcy)N2(9(ymoY~oca|Kpis{4TTYxkX}3){rPMoy_j)Au0Fk}LiD`tK{%8G41l z!}o9ErvR}jd*hiP#QCVAKQO!%PM&!FmW^cH`A+y2Ea;{A53?yOOMep|!ABg|!UHT_ z%fq>&Z6dvcusl7km06wysty^a|6TcdtUeojF$w}dFcrb-B#B8p z33}B=f#s0%7e1>!8^mRd90+D`6`>IP@2@SiXhW7B0@pbRj%_5l)KC2IOGL#o1Lw%` z7fvSn1I{QN2sz;*lKw^lie-k)(IrSii!6Q;455=K!1zZ@P&yIPJ1(2cUwDi^QHp!O zFmb;D;SZM}wizbTOQ5{F{|KWrE=QUm$s=+IQSXV>>i?`G5s(h;T<=X-5Rh6-5D=RG zUq8?(3Jxg$aaA#nF@F@Ab2boCj5sM!V7g6G%{@t@RZvilVaz$ST433YauhjJ%*P9tfk zK~UTVHD+vRo2UoD@7{c&h}XTZPj7IwU7VpDFF&@M-Y`o?#C>~y!GVH~h+8D0-H9V; zZx8NJ&%0L?;11!CuNVLSY3t16q3RkqJ|?nOV;e?SmN7JzELqA{$U2m*tn(=QzLYGX zX+(N5QC-=xuaPZ-NGODalET;-G+EL-l~Ufk*F0@{-}Cv*=PdVowtLV0W9~io_iN3L z(+iVNTydGm*NiyQ@m23L>`pLAEm6ic7JK4cx`$NQ>LbJ+w~GY#)M-7XJ=CB}PgvbF zD^Bh>sGV?l%+8YiP)aY%Qupb+t9QNieMc<@i@oj9wD<2>^#MyorDx1al}A;YbeWKy5iM_g|DkJ`>%5{()W ztgM<67>~4rMx0%{Y9QGQh0$;`K*ejnhC2xoxOTIr zE>n|L)B8t1+1e-c)dqxim_-+#^r}1M{>Ge|>UBNi*2kJA0;P)PWB*km_{h^o**ou^ zsm$8btMa+AGb)RuvQw2QRW-Ue!jRmkq)wiTSytqmv0H;@Dp=vGF**qW8i#mqK`+t< zWTVK}i!*j(6$o89ZbtQ@_j|any;@#<^i6_QA^=$yjJ3vGv9uPIr&_t@75e1EUjQ{q z!J;nS`B7OlY$&_#Ap9-a5gh|5azpg8Z{^q*B{tYRd zD?aRkDFrotu<`BswHuCcX(V~Se6Nv$?BvD4;eEZ;&?}C1Y>pk()h|Dh%d$046jP&} zd6@mZLFBt<7RcsO^9w*-`Md;0Gj8nl_KV)sYMSp{^4gm__xT$u4PBC6X}|6h@Uj*e z;7B8zl~Y);4YI~wM_YXQa6LPn4vOJg3J>E?Cgp?}vAuNWhjkA^E}B6^A@yk{->SjMlvizuS|jYZcY{TyXS6c6|_`N|D0iu4K=6SU=P*Pu6_!MAp?HR-mCpfA#Z$F(s+k zHk&Fb0-?e=BZ|(6T*s}OJgy91-Ayu2*)6yD5QQY%y3!alN^w0sDmUIeG4_wL8Itb6 z-_o{ne4V%-6VHtzSktA}?K+&S*ZB!nbZE~}$D!lvoE{RsG(~itw0Hzpgm^V>@^yis zc5(4lMLm(Lf_6@geUdzGed3iNB~f+`ql-ZV%lu=Z@@HrdW8B^b`M2@}RI*M-cXuZT z{=H&mHyC>R>j}d(2egu=eDX_XZ<=$~OW%!-ndO0_{GZjTBwHZ6t@(MG%F;`oYxpOQ zSNR2mim^8%U)or^Oe8k&MDw0gtt2<*MBlSLaHKmMEO=fbY|zJDJln(>H*=wp&!hiv z5+SSFgy*l~B)_g_Ma+4|s|HJNc1J2|#VmRo>q=|ozGt!S9D;n`tLp|_;^mWH@K%>} zWu4|xH)Ayley*yIQL%33T+mmE40HHqorHuW$KX>UCLS@#B=-!bIe*OiO^)b>u;A5FUzxo?HC!@vPnv0m4=6-T>(jY$TEZ?c- zaL+ySPYp@I!u__#2rHI?qJ28{e!4q)FC?Rk^!DEtx)OV*m^)P`&{Ifd;94R_z2Aqk z1i=(%ji}?V5m}fVA4O|sAWqiv?_oaOPcDzRyyIF;rWAWnr3r;c4`&*TL*E6-q*%zg zz8qj{XGarHl)dXRsdryOJg}765&TI*w-69!d)`+vth~S;wvWjv5ZH0IJt)S7PW2># zs&Vg5Y6ijIJ9l1Ix>|%)j`s@F-eqO0K)9NWl?`4+9*ih=4!BDW%_WC&hwoL2jnC}G z^vz?U@Ags}Us4)Pm*mc_=JicfdtLLGiMv~6Snu9IO+V1+zNUO4BQnPK%9I!&1_~GZ z>THXu6y+SH?fPia({^+A%g&km=`+n7DK08=gDQL^mDG0orA~FAy*4IDE4Qq(jZmNP z?P365ABnrW&9j3{2c{RS1Ut?!DY~%YoIBF2FplG-(qguP^l0gPlcJVYWl7Hz5v31v z*BoN(^j&rztZjV1__D*^b_Z;J076Jr z!?xlt9mg1D17rC?N#-|P$z87Gql7!K9J6xnI_-s?*3yZB_q* zj}SE3mH1TO+{gHYmBriGr0N_yx!Ce7*BET(El)=y7a1aX4|ndUv)cRc4kF=HLAXL7 zS?!1!AfAv&!UK7xW)|bdU;3$?<WNZas@@+6uTG=e2qc>=e`PYj*jdmEs9{p4>F}mh@nn}D?EB(S+oig zq?=b0d#zNsAV%bc|1pFIn!dEAe1|7Bv_4ghNA3O4FAZwAx1JBPzyi zjK2(1(HMVfA^*#iRe2uHpW{CM^xlVNb4yy5(Jxju3WFBTTWryoaeWNpB~+zEhe zI*4KdF42ZUr8r=)zXV_~X-ItRM<^f)Gl4;}yTPduF<`V~UywX>WIyyn{~(~afJov5 zBPWi**Ezx7iQ{m6E>L1p10Ku;o|?qNH+Di13ZzUPg;(){xg`MjfFJ-mPD#TJ_!(Ir z8aKExxf8q`jo|vxY5}nb$vF6RN)^5YKuI*XahVmwPa~LVpS@bZplKw0NSIMxHZ2Wo zy0qs(ZUT~!P|D`;euM&Igct)#xXJ^@jUj+7_SiotC@vuSOEAEY85w|KjSIE50;xF} zY=Iu{Wk6FiDgeXabW^L18wS(b0tL%}iqvDk7Mr*&K%Nq#l@_WD^QQe4_?C)<=cqts zSjc-z68O{X=ttcGV&MTWXx8{&lcVNYB)nFGQE6jV3}DzCL1V6C`ST1^YeA3-WA?xN zWd0m;*o}mX7qQS~aZZMFFVBWNB0L|x-aJoLDJbr#3@XMXy zU)8!_W0f(6AaU^1yaK$>0VF;X2XU_z;G-^3avya05n$tMA^3(nIP}^bKHv!+qG>T! z!QnwJ@l8R!e**%xtW)Iuo8QxSdA-e*%aGUmg$@26?5EhCIgSa=w+&k0Y|sM(m=5eu zvAyrzLCav5&;R!JvzaZ@dz)tzlwtaP(f0d;#32XxP#_dxLDpdfxK0Rk`|yK-6gKe0 zupqESBkV_~P+UNi2>l6`uuFoy!w6uD`p*`)HsU9&xf2D-QxL!}eGwQ;YztgM_zoX{ zKfdv^UIRN464;i8*Mf{90!9?n9+8GWNQbiWVA==*`ZDA9sa?oqa9RgCQWg0XFHff%59CjAh5zR|&066m+{l``Lbm0wQbicUTBq8bttGcD?h``a_(MU|_#sz`#V)mi$T5NH3^>3e7!r0!_>>r|)?YmKbU>w3vD# z+xXyAnhfx^_WGpw_;OU35_JnyJxJTkechWP|00E6er64vrLE!^^HGR-RtB!-d{KP) zE#nm|yGjW@qX&7w^AM#?_i#V&xDVX)onHQ?0f0}~A%>SJ323qi_ zUW`-V&I%*7n^c=Qw>x~9I^J|gWMN33y3~i?&6N0$Ie8MCEi*wjr_1;druf($Jr;<= z16yD)wdSS&GJ39dF)J&gh>q4ev!sNPP!$wn!qc%a!REZ?DPT14#~;gBqYkPMA67ep z*yw3I_G+zm+dteG-Dzm(J{(y0y4n{QJ^l%NgDga7b&Q1?>_7`p0TwOdTad> zD$c+J)ihS1d%b-R1hNq_ZfQndv$=+CHwdaxP-5bc^V}|R)VV?sQ zG`MpON9^Y5sB&G@uWp8}YHprga>ERzXU9BnKh^Ve94m5f(oQ#Xr}q_owr7v3CY-az z+)VtLTWqS*nAQmYq*{+?7}0yH??dfumg4P|baz-_|G*zVa+qfC&9GJh*E<{0L~!JB zC?O)kPApy>p+iKk6NR|Z$(C9kfy)Ql&w6~(s^>nu&_xXUom17|NQJ zC!W#J`GShp z{)gR21Y#3FrI5xcJFz4~Y=Mo`#nr7e&&QLS!6V0^xW_}UrI5erSoP7xqV8g1sghvh zN-O20s{OXLL^}_k7@xYAN6%4T*3|WEN+;B5BHDZl~&} z^&cC!{>r83p4b2)mRfEWLm}E^u?J%nc?d{&FfdqHu>Up+SYc?xc1hZlzbNqAU0o9M z-<9H-q7yggm|Trc4LY0bHl^f8v1D<1vB{h1U~xP6c3#2b!QWjUck^@MBM!dY(m5WX zb3~Lmo?t$q7wwmQjM2^Q_O$W>O#bt0-o8Qir~EzMzUSqKq9AA&d@2ZOHv9@udx%hf z-A@kH{;21S$B+;d*YzRX2~QxO164DaRw#DAKbOVhkeu4XAhsBFxIA$d+RtTN1e}Dy zx#+CB_7Gn@YtTtE%{MZn^diIEQaRlrXZu#7g8au$c^~LkBW(i4ZT_*&mv7{-hO~uW z44Hw8d}>LR4X<18({b)2_E@eWLrkeXyuYkZ<_bZaDHizEyx;YY`4}K~keO(YJ>td> z@uT)orpYAEP7|Ga@BHk@2nN#|(0yyO7y$WIR0_^|;wn|HjQ1Vbr?{6FZIeh4n_(S$ zTkBJy{rWXRcX|@I=r#ixi#p}4xM39y{W4x#{$lLWwoi|@P{UI!37}Y22a*ZO}b((VF*`8paErO^WCTp%N z<>FN$pHBV+K8IX9p2Is6LJ}3&!_{Kncsy70KWeG#EZUoORe|!(^O}=NJ6_7o(DDOH zW9Ug28!xAm3HH&NtiRisRH{FCw96|_s%;`v`gN_(v~VoDV*I^t8ytiBA>=gx)7(}) z#l({u(KeWVjO}at0n5{~plTc`GD0_w)GhzVT^sy{s_Vj=YfjDjaXQU}RPuvdqJ{e3 z8I^kn%`FmyFMyM&p$|qO&G&Otxe9IgpO5e1ZE7+srpdb?A-_6Zfkr1ZSu&eHYN|AY zN?Uj%RL;~%!Irg)-2wts;VR0l=}%^XN{`mw$X-V^kqOIMPR zw+INRO)}`8{ZJkr@DrAif%1aH-(HSr54jVK%aMrk0PF9En zH%MNT!mPugh>L{*x{ijH)TKet#zMAshp#goVhm!_p0~i|d=b zKX7*^*a-1xuCQu`L9M{HiekBiSQ0yn`J$*EPfRJ5xty~Qm)yRw2Dbcz`oGhg0uX|1lABxTc^AgGQH#C~UWis6c^j@uoY% z5%W9q98fvVAT}DuiIJ>>vg{baVd$R_*It34ZyL{HL7T6j=ZXD zKGVCZcj{bZlHWA0wSDWvXs~uqKy|(%$5&z#$PrDdK2o&w5ts!UVaKN#7Ztt9Z`11g}{ zcd{hS(ApwuI{YHb3KQC~^mFnZ@0!Up62{`MAJ3d9HmhzD@kf^LL)2q)w%}XS*^~qS%%ns#qGIN=NbuLV#TR|pEGSRY(K;zUkUVM%e zd!=*>X#socMI;hG0N&8IDlSeAmvLz`KGE`M(?pj3nCq&ZQ1SginfsILm|eS zH@kIU+X7XJ-5G53@UV6*F_ZZ1hYCDC`*%TSH$F^~9sBIS6jh4C@9r~Uiy^MeGcH4g z?Kv`etoI%EL8;x-skig=DTOOurPqz}J`I$goshX~=SFDnq6`?7Z3u|C3if z-*`tqVlp!`ZkoQHn$!ajh*^DsADebD$yGPh2$f#y#BXWtF865&F`QwbsdD4=7O=$n zT=AhV>SpHUA$I}?!opy)s2EuKlWR(B{ASlW&pm68z_fhD?mXOEG`|*EE z8mqiOCkRh)+dW$P$&~q@%j&Djt3?&!hj6mpwNG&0&BO1N-jNMx9wt3F;sc>59P`X- zMVw!hBqY&r#{O5n=Rzd$eb<>an8LGvr?NvZ^y% z6U#A93?#Ue|GpZ|F98zK1+GjremNb1@6@cz z7V_ywkBWBAo1>I1)h&AV6h5MC_rVk-cUbkht>BYOwEBVkIp>4fUpez)BPtm14(Z#fEq|jjBK#7&zc4OF1<&#B8gHm3f~};t!6o*nbFq z3B@xY|0V_RD$!hrO8|zNzpW823?jnPp~tz8_>(T?O9T2ahz_ zec%rwzyE!9tR9p&hZzsOlF1 z1;Kz9-<+FbPv@}5xU;}3FJtCpVG#x&Lh&khYWz)?k-B@_E&+TC4M`La=?JOu`Rm%N zWamCs)eN`k)X;cwYcN9j3Anl}F&B`^p`!WCf8FIki?6h*HvytD0Nr8Ike3=J;yH0A zV+P5P8*ixF?qoy>YJQ-LAN{~DK=$ur#VVcTvGbd-zd_7Jt+|elsV|mkHc`5t%(NembP<$4=Gb1pKp5sg^O!rh**7qbcT&jeu;haDMQQE7iCS#+w6MCo znvrj`4uwQG2YaQluyN&~X;}bvxNl1qvXbgMzX+CEYX(pFTdGn=f=F(%kpGOi*`XBK zc873Gx75)Ar>HH*zo-dBMAQTdDZ{X3A31^gaSO!Ki^V@NR(plHRkt{Br8OU19Oh(M zbQK+PpsuC;XfnHm&>(36OT8cS)qs~W&NXI_mHZZ}=6c+9WVw(4{T?72(>Ai}A$JRO zDcD>=fBm(wgNJSH+;pO2NE^Jh7-*qv*$nj(^}JQKZX?NOO$Cc)aypmxVd)EDb$DtC zuuS3NuWXpkV!wJ7{5N`H5-;Om9KiD7ZHs1pnT^Na1IdWE?zfaaIK}8Cb~jrrx#q|L zQYtpP=ej12rIGe@j|H?Ok^hxMJ5@eZCnB2lh6o&0>7Sv#b)l=m1?FQfIX=ehys%Cb z%@F|bhsvi3!eMvT2opkg8j^c7Ms@f8eV^lD>Ops2(Eom?{v%#l8q6Aqev&V~B<1G4 zV`{27?tR11a0?|gKMIgy--}ugV_BBujMG~EJX_Pbd;}Au{Ril2Fn3vRV!)?Q6{-w} zbokVSg(mz8Y0>HN%{PEBKf11;PIgPxsBG*_)0jaWfF?p&l|Q;_Y!H^kKLqJTE-+Sd z_)HK{&Ep6ArOptwU!9HRY?&vYr{`*=yu7dJshy+i$z`oj+m$-mW$M8+zpLp<8J9Gb z!Z4lLKY9je{sD@eWgY~`snUNL>_KL6d83>Vj~fv10*XQriS&=ZAR9=l#FF$WBKkGR z`%>T->GNH5Fkb%2&*=*Ji23cy&a(0(APAAx*5Q@K=58Ho=&A$x0bD_+uDOPX-b6Hw zcvZX*9iHZ#&petTj)g8s;>2$OGE{aUaE--kz35JQ(tvw47OidBaeJX%jUj&V_!h-! zXK()YA4(-Ti<@YVyfZi$K1=1|Nvip>%@6NkTIP4gy^%%r$Mytj2z$uI*j($Fzz5~j zLCD6s^fD+nkKCC_TaXA+;c%SN5^owz4i)!xv1EHnZH+p;qht4o)|=}2d8(w5%An$; z!^7V+aiEd0X?E!Vv7oO(3YVT0&P3h?<+2^`lZlrHGxP=TEfMM9W~EKX*T89_9p+QP zi(`^lNA;t{5zE^>t?mi3AgkmdZ|Bfsc!-AyZ)ie((nhyyub||=OOdNL=pJ7SYQ|EG z-Gj@b#{+M0^OcPJbLAYims2u9t!>FA*z~=|4DbNqE1&B*pKq}b&Nf-u91rELq(<4E z!s%s{#9ddly6Oq;_xZ%H=hxmZFbUQ-{ng5tcGlJ0B-G>A^IH@zH=S{RDTJ{JDaW&) z-4CzTTdM7+IalL;(k613=lJR2aUiOo`IgJ!k+bKSt1-wRp0!a_S@?$7L0FMUE$P6c z1Za~xY`p4m{G?v!+TBPriv0eP!PfgnL*3VvEEe^EMffiwqfp##<#UL7Ko9y;V3GA~ z6I3t^s?SIPRXfsIFTTOHE!&lZ$Tj#$W0__-MYcD@Mi}fB>tAq32+sH%G!=4ANaLLL zET>Z1Rx844r6FtCF@yzNC4)x33V)^-;^poN@n4;5>qz6Wk zH1`8L-x!w%1NV|+Kl-MY$%&AOITrdB?mFEsUPT(%SA;$T`Nfbb%-k^>LP3H z@V%U>P^u|el)68Y zHRfPclv6g}53DhQBoxm_l%H|`5&{>5RZI{AyIXAV1*s)OB6zz7$&OAi$H?VN{1su6 zPr@WsK{-K`uNUXf`=|^z-7%g}b@F330#|bnnE9k?7V=0>XBUmaVXfyEO%Y0XTW?^t z?4+G!q<;dmt;?*z*wod9rM4S>iSlL71;;^=s^IR>E)ZYtM`%5OC4q@}^8$a)EdDx9 zQ#EE99N3izLyE{XzoEZT_LePFIFo^G)rUQO+(X&&3Xp*n~#pW5rDe*%X$V{*^!4s3IYyJvIFM!qv zl}{<`8bba7n}-Iuz{K;XL1t^jXk!TcVfb$HktTU5c<5dIF~4|D8vVuH#|83xr%hMs z?g!K-mER8;P9UOiXeuSYAxWn1ATmaNOZlv+q^#M6DMP`;KPsFJ{0yifhkjB36I>vK zgOnXlEh0PBk-^ST=V?>an#`_GY?jC(oM;=p?p^g@zCRNq5UqA|#8SkQ`>7Ah2iv!F1;=MSG_PjzE9Z@Ihk0{-CiM3(Nu|DR6MCsw1By)R$53g5 z#m^3N8fF;Z*7_=Hr-Ay~0=H~>f#@9mXu`@iaSds<-7JE>BOk!&@`3ImsZR_dc8>^O#aza>KF7OPJNFbBpU5oQa=xTw~Kg5qa`qDG5KVr;V zvd%Jb9y*iFOlpZgKfPB*<5G718R?Z1^ZpIAO_{Z2_zdgE^i*AjF25CL9Z}K~{}*1^ zCsqMe0xd+_(M{1ZzNNAeJE`5AH)e;WKn6k9(%|&do@&8Z!h$Rb##hJ^Z*>6ow|j)U zA9#dDd~zs#@&LmBlBTqe3;edj)H--16}R4;Iyf*eCTuV;`u}_=>@=ls_<#@QB-R&9 zL3`C&sat6bd66W447mcE&Il?Q9AyBh2)e{RSX_H5^0m|WE-{tTfk#!UR4h>y4vj0k zQhr)9_?VKn-_6?jkF*1xSLhm(1RfBp}!&W62uV{8+sIp^h(gXNbNw;NmE8IFLE*VeMV&tjeq3Dx7ySe(L!VuACxIEUqWVk3Eo5-ULbj0C!@Z#i2M1Uf$(|=WR$t2vLIm$kD|q+s&H&prb@UFUX*7CDW3j4iT&QwM;?T)`FVr zAoBOGzNR$$P+F!LGOwb9?YEqG^CLJb%N?gSu38#&M_^*#ivy3uri&3KI_G!iE?|}= zbU-;6+JsP#q)4<2uHL0&zxvm##w$;@ZqMZ*KxtT1p9zbdL_nfFr|M8uon)yQto?rO22a!{f)QsCJr5#CP%*YhG?2B^GG|4jGNjDN`v7jb<+0c*G1csqlK zwUNL+{l(bT9D;p}i0(oraA54VH;5(B2om-Y8wR-eC^6Z@F(gN-qRkZ3U1Fg&cts`b z*lC`q4!tO?EU@W}U$|818*Y(Sd=#ro6-?yoh?DZXT!xC%*dkefu`K?Ey@N;2)nZKm zWRszUd2Di8OoaVc*#u1?vse@vjSJGE3?~x_K0B#7+0<(pv?U^_=_NDB!E>vj)oY&K zU<@$YTr|;9pg8fll%FS* z$9!@7sPV^BRX#m>)njt7dzagyjHD$1?aH5uljSyD(qHcS2YT=QyB^FtnBIS z+4=Gab_OLJtsgl24Zgj*K2Hnvj!Ld3CB*EPmtJhnrG}VZ>Quikp*j`I=&fZMh8%)GX+z@gc?v?uzt*1tXSgn`q$APMC@hR2J&L~=;A9-S{ zu^m}+$E(|N8uZjPO2?jtRjc2DxbJn+dFMiif2iY?SD)JZ_Vr=umGD0aP)kBD-rW3f^0sdjmVw3&&0ZM#eGu|RmLzDDl6TbtXzLw3HSusL zciNsdFQ=E1jh=(|Ff00G&nqm4h|wo>&OesTO>4-`+=xM~Wp+0sD0)yT$H7fnvAm^c z2&}ecDki1fAmA4U#rPX;dmRbPj8yuP^N!3aotbk*sipoyd_rVJ1_S7Ch zq&?lb`Bkcx<$~;yrMIzcFJ7*+yMl?S1FE!&1Ng@9Ul3da2lBL64Djim&#&Nm-tZji zv_+KKGHw-=B)HO8-q5+R_OZvifAEdP;oEZMCRqDqYgA>J@Fod?);UE}BX}+@gPgsi z(^y~)7klb_q;e(0T<2%`dNtBv^;I1mQPe(eHyJA7c*0@z1;qm`c9PjNPo~;>D`uv$ z-vGw9#926x=z;YzLIzeGh8EbmX5zZ#5H83^YO|Kan*tk+Gb^Xvt4 z24bnYu-)i5RAdm~MH7(qYQ(1?A@7PN{lXQ7Ph4I;N?Tg^UUG=r^K?M@#wPMJ$<4_m z8I7&m9d=Zux-P?edKB@Pcgus2hW1LpF^+s9dW=XAoOP`aBHxf}FL#{9C0}ZVCoTd@Qscs~AwyA% zj&Wsh+!?kwBXwGNf{ttoeNW{X*X8mqw2FmmwEy6nZHiFf@%~%$Q5Wi56q=A!rZG%3 ztP~-q`HHQ`zjJB<1wmjj4Q z3n`=rbbJFay|Mm%wN5goeOplx!?DTJb8u$?(T9(UiLp7Nlahr)mKR(i=aIE>TwF4S z_^CKHNdLIV@GH`htoY?1wmk7JV*kT=S*t->@Pgz?T{6(wihJ`nBOP1O;@5)r=kEK! z^Sk20=V?jQxB3y`6H^FAr_`PPWP-drOzy;Z0K1%uFa>QSI=qbCqTJUlUb-vlmi*dy zj)4VqQn5pLdV-7x*RLSOZL~07@Zf@DG+fqa*^l02ma0ALgLDlC>QH#=MKxM%-6cIt z@WE*6?;(6XU{ZL|DjaAaRPFyk$krd0w~TsycKg7+8uxi5b#w7y zv!6u5nO68I0n|(mb!Aol_utq$>3N%PCR@u)Z5!V!vlZrJ9=*CSRxK5QljrMW@Ww{TK8JD2=pW2QKzZJL;Ipv&^+&dW*v}{*1 zSUzz-yK%XYM+8n8D!*HqqTM4Lc_-gI;eE7Rm!`_Tsd3LA9k5(^){8_@3QECWKC&h zCr@|mbxH@a?XoFck%y&nlL4g-@8)YcrGgjwG#%lq86u8o*|@sgwzrco{#xoL?kwCI z@w!7&z(9>{i$)%o8Ga@{#l*J}JvqVh4lHv;*LsU6F9{CVB##$(Wxgwd6y#E>Va-_arru~T^%DM0)SC}t=>%lJyH+;qKTSZHpLz?X%Wvr?H)0zy>%QPY(d&NOjBWY* z!SAuVhR-(dr(=O^vNf2cG^gWs?zx2CbWD9?xS(57MrT>>X}N(zZg#v#+wXXMt=Qt9 zHN4_l3L{lm0?}+x+pcM$iofbj5V#jd6W}||@3)SEPS0ppm=N{>keQg`9{PIR zX1NU};MSM|;cb{3)b={V);NP^*yVIJKQcQEp4>zcN3-h5moc59y zDtyQyVE~>TUaiI8I997TTcecMbun!xS8O*~s>BHw-pj>hnZrc+w<%zM5Of1yI8r{e zVteCRr6{dzqb|0o?GavZd34-H#bC=a5kHjC7Am#>CazJJfzyI7G`A{8PJt{x3jN3JZT(?OwH)DNXS<$3g9xJJe}mS&YG!ux)&++&B|Sh zZF711Zn8<8kus5sZs|RthJ7-I>&ECTyT6sIW;xg$lyy@+(I@lrbzH;*JYR>8NWmfpc zndd}Z7MjyZm(}f5ZF+q{wZti%EWL7arC9&9TkrQ>$VDJ)sSZaLQ%kjm2Kly>;%o5!S(7tXZ-*hlmEM zS!2UZ$Ey_eXDc0Z`)sdxqa6BW3i7;kXuosy_fDBd41q|)X`ku#o^>8u8RcdJq8t6a z+TyaUg^0!8G(dH=(|e0p5~V4TKQ*$v((Us0Jo@s#aW{WUaAz|q_IPF1B>Lg^A8DTP zUzrcz@B=z6pQ(POCcVhh`SL;$=nPN%d&j$qErsw*W#m$V(-JZ)Klvj$K+(@oB~JjN z(pb$>LYNYQWT1bcgH#!$+FlKtx;j@pdU|AZ^Y`Ok<}OVN;=c_zaH?7cn;}&N3=KbV zB@9P#Xa3+%?$;r_PwqD%z)YZ4Bfw0e))PcMf&r?TAS=7DF_ii-rk`5N__87}yg?IZJ;Aw%*omusSz3X32H#`< z{>9TsEX~1&Wbq@2qjvGN9)-kCB9|~+t69|%`^3Tvj|s9ZqG`VulKH~8egD3?BOGFB zI15O#3Dm*ORw>xrMSbe3nt^Lu$ucyNhfW|iQkNpu{+PGd3HSv-FW!+|K9?JAXSMl& zGwAL7K80_G90}p*Rx-iN^Y!>qd}>)urBhxWnI0bIp|F@+U+Url-VsRi#h;TwI91FX z=C>{_yyYNqPwc@N|ypzNQ7+oK4-KMcR&hx<(fw^s%CI|+S&gknxmwmJy^$_&m4`vP!{ z`xS}YLS%SA>JT^Ls_>R& z%Kd~Is;s8;H`Pmcx^dD7A4+y5=rP6do0KQ^JJ*5h<7(qjba$4Uz3?3|&htK)?&aue zDLTuLXsR1AQsWVrEd*xi^OF;Way8Jtg7^ylBnvBh76grOvM1xkD>kwZ#h8hjf$9(4 z5JkoLi2(DJ0IMoW@m&~>PopJch55RIh};Q3)QuBoRXRgnAgz$`ymDjs0l4EXRP8~V4a&p%-U<(H-UIN=o?l>H4#tha`*Nd``l?S%`?`+yAIv< zaD+y^u1o!Dbe?OqOh(@J?^e}8x@1(_ie-FTNO9jAbD3+d?!f+8<Idi}L_YObnei1w_ z%6Vp(8SI*>cT2f*=tNw^nod!}pxrxwnN~)jcE?OXi;oCds^ZgBf9M3g66ysV6E3qj zD&)!q&x@J6%QPdZIT(>~gdnbFfBUI0l9M}aMezuf(U4^NDwXwT%>fZl1iepidXMqU z5`Fzvef`wpw~U|W(ec9OY3A8wwci%uec4)x_%AMae~-tQ8o9{?;2_|PSycWDLBh6n zbq?m?%YO;-pX5Kdi8i2CqQ5iqZ|fVsWOr>|I}$|{%&36z zumlqfOq>Y}jP(D3&aWB*fSe35j{<#4?pKybi!3ZUVhDOBwBBDTUs)-uhk1guB}sj( ztj_iIl~_ZEhK$ZqtPDs+$%Zw(u5~A`wXMKaCu1Cay*J_Kc?Ife@u9s*mYw(AAE$-> zng4j7`}vhWpNGvQ+Oz-Rm;W%JoY!4ZNU7Axt%PT zu12AZaBQ105f_GeaxQ8#A|Lj1X!gjnhm)aPmp3u-t`=;=u3xWm1M-~cgBs6(VE>^U za8JJI78*igZ&NCF1~5ndiqeA~Ao@k$s1vxMZJ~^dUEPzlO!*O=QY$5M=SQsL7z5>l zyJlqSCbl_uiT8=V?b1OwBdG~?$+j`b2%r4MA5=W-nmvpV?G0vuUy&NnF{hBpi+GoE zLUD=e_mFE-Gv|=m?vX#dCVh61$dwOmSC@K%wB=StanX3o1~?hQ2u~$~(?kc-8^n}a znCL4Y0&*UIkgF6;e2V@-t9!cLb$#RxisHQa`C=#oFn@|WNO1ig7~28fVv91F90U3i)`7JUGYECJD=%M|GT{tFB=nuk}v)Yc{Fy)-)hPJ zSz^B@r;(q3Ao6h-d6v_`-H_6fqrq*>q-u4v#4zQ$-SSt8M1W_{;iF8clmmI=*;J7= zy|AO!5>Sn?t)KGL-tXL1s(?ZGH~sn0`}B2$;x{UTC+ zt$l}NA}#3lr>v1uHcMNV@!n}(#r|&W1Hc=Z*MBQ6SLka&`PDWatgpa;En7hejv7|h zBf1Pee9*qr4ME@LUT5pUH_d73O}*lU++=t07mmT|S10+cRLaK?&1RxRq4gY-me`70 zARoFXk8A3AeG4SJc_M7od{4Du!NZ{5GUjBa79U*MXd!F^JL;c=^XKhSIfI_>k1{fDe49P5NnAuUZ98$_|~)A3~OZ$+4;WtuH=92N+& z=4k85L+euotP<`#=H@EAlF(`5!D^_f`%#skcLZU;$U1R^h_c2dF=x8)39~_Wa?SSNfH~sIe?@qW#m*(1apk%K zjN@u4BcJIDa-d%M#_kz*J?j6AdET;*1BO}q*Bajfc1cU$22`Up>k<2nTi_t0^@XXb z!ZK z9IYToj^*N!N3dj7)1yP_rh>r}zgV=O@f5}Ukb~aSa#@kjP=4dQJ*jc|g@W(qH0jR= z+koyN#JyYG0?DcJ*@x^GBmlp-A^J{k`b1aYe5@=U5rC9JsmJ|OvrKR0l_P+FUGmGp z2sI4C<9PA@iVsM~RtXs~-viWKR2DoC*fVo@Ly1PW@l43U119 za+rmTrwJCCSVkV?)gML+;5e`nX)al347Q`kMy2{mEU*`j!jFca0MNwTH=<4q5Oevz z=FO-!fh`iF^s)=%;1vsrJu_wQ_OGJD1W~ zN89e%V0ZpSx`eC=U>nRyJ2!ioV(;tx_ z0k81pZJ1R!za3r2<~gcFdhqgCq@53987jvYmy^*_ohLPPD^mxB`6ivpbTrf^M*!BN z=8AoG)KH5Y`u&#{A620XeK%C84$mMxa#?j9QdXth;bu5KkojM1Cm)p0!p}Z#*>Dg4 zEBrzug2zhibn?XtQ*!iWD>rdFB|C?~i1KV8R?Up(eO)(mnT1a0bn;xXplHA8{G(hT zkO;ZFNJas2o8nG^5FxBeg)hJU5 zEU4C>cM8)D;O#HqEf}0$L@0BXeYirCJD!m&7^J|yixs4r8OWm|(0w}p5G2d{e9I`B zU^)8;{0dnRPT$dG|2}Dq%oU`2T6DMQ`2|%rvFcY)s&;A&+%k?P$0fU+p6|E5MhrnkB+8-t^Z@8R=|5C?~e)EG#;i8W+j@g8fF(0~euF=cv=^V^W&#KQG0XSUR+2V`9#FIs=@+d$Q)hv!-E&TO=#7`J6Ht%F(OG+}j$F`W7qLATqzZ7@_2+NT$sK#QX;( zEre^&v(sKXE#Q4BeXBZ-|1i>=hG&LJGNX2NodosFbjTW*#1ub$ofrDG~tPY zgl6;Pc+Ce_nfG(ea%MRB!qBLiaZjJZd71hNw?+|e)*(KZtsAO^mD%ZOGiPJ@Ynlob z>BQ}t=(9y|Vcy3ESJ#|*(C*$7Aab4bVuyYAbM4ReK)$MQBfnRT-c`)PSjF;TD1KH+ z+2P&qkzpp)7))wZ{p|1{dTSH$7yN;8^?v6C#pAQQ*nnF;5=#c(iItG2pp2Xv6h5J? zK}^Hm^fH{{U|4Yf< z;)h-X|1)jsc=#;pY!nyGHc>5^^UiJNoFvpUU}2G+fA zY{^l57)_9>phz1^s?kMORPsMi?Ki%@b$$s@rzl_5`l;?U%TrW8FzHklk#;UIrGIIB ze_h5|rG;P%;nDcK%E^3`*X|O0a*gw|<(I_1 zjZ81K4b{;riuTQeIVA3RX%n;J6*G+NP{(>1U(Pf`GU1F{C0DOH%S(-zJf0BYpA4GvS;qPdnqm+)!s=OYv@ zzG*}X%SwUVQ=mumb?6+EhtO{%W~0l2%mIn#;G$qpI$N5d^`>Q`1Ub%L?Xq{BviBIH zvds%FKJ*tB#fd&CQz4}XPCK83i6oa}FeIyDUvPmyasWyIIJ2(_3O?Z=DyEaP+>NU4 zpI2Y=OQ%m%I~L5Y5j*L@QeP{p55nqkht*P@_W*T zFw_Yik*HK3(=M~v7;f$-1O<0>^4~*2nIth`l4|WGK>L>Ryo$^^3ffPhLdG}Mg-J!( zSkp96hf4K}8~4Qig-0;OJs>0&lpx*?ud2;pYy0<`UYL_2Lc5U~(}Fk6rBV zhA}gqs#G-b&-zUF^jGk=Pr1iQ7l(ZB;Qpwn>hgxxv-vQMt{DBu>Vf%xs9f#7vFpPZ zk_orG27?2h$qU~1FVIJ>N5z#8?LpDsJCT;50LS}X0hv7LnhI>+Kn{l=P~RU>mh`vm zAe2>PWf->pjLFe1@rg9>r;v<~ZR;VgC`4T$3mla5$T<`J4_Dt5omtc^n~rVUwr$(C z)3Kc|wr$(CZL_0}(XpMIbH*L#-v7L>v7hE%HCN4=Rr%~#>ty)Q2i5bTmK>bDHK&&# zE(QIF+dz7(f*1s$>?4r%)>d8T_QJ@HhV4IeYM zOVDU~aP_BtoV2C2hOex@53IlsSTBcJf1hamKX7Mb?EmU|;P-!`tNTfKvO=|A4O>0n z9+SRE3w`st{VUMQ@5J?{FQ|F2RrGGy1$)qY!}oFKvoy%RHn9=leFy#&4ESuo1;S1C!d=IqLgWna1UnCfn3qH zeN$qFRONo5TnwPuRk2hEtJ5Gy3@N}gPJWs~eae1_V53PV0<1zs2KUu#{l$WQ43o)_ zVGSLki!mb0BqKt_U=p8Xz$X9*%eZVtB+p1@2Mp&xazB4*(JpFFDZ##9(!}Vw1cfq4 zlIok`9YWG@i7`%6DVS&RfOz_(^m9JRgPhZII4cAKUPlzS%Oq(MLWBaK#)dTd;SPHt z_9&Ybj6st3`D>8j=c7bTn0)aEYV+@4(kBel^S(h@fJnuoyXgrazY*|)!HEY^_pJ<+oq#-vC;*ov@jjQC3BDw zoOHe^=N&fMR}{4BOgw;xqSd4bFfYJz5{z2{JhnK&sSHAwQhzYrdbAU_6kPdRZSIkP z_ZHfp181Ym{iRxkjN0wSIiCEUGjjq(F-EqygO}=BmSN^hJMzyFeTg;I#akrzQV#Yc zh-B(~pPHVlrj?$9?(e+!I29%Y7(OZ>gAWQ47ZUXeq(U{-{R;p*tj4Tg%Lpu)@H$bz zCN2^y=NwZTIsI_t)&v(-Kdc7#&vm0;?vn`E*7^q@FoYe&cj2maA<#3z|73x_W{#X_ zfM$JFl@ok0XLaP>3``IMV&~HxHXE-%q%V?(yUH>jbYmFb(f7O&2Ecu6zCnrg9)la6X06HGjjM zAcmlx2l-`NmGM`1|C9Vinvegc+>;Eiu#=X&QIfK*V4Dd0IuM~N`6>|Vf2el>h@@)= zti&5^KunUY0*Vmgm_@25>Otp zd%PK7%nIYYWKHD*iQsdXm=Li99`Z#foVIBL0L9C2z;UWI#Ol*3_$tfxBiq#`Y@?Dw zRF_;;EL$7ZbI-{DQIN2ErQbNsJ^t0Xd{VM!3u6C3uEvJhQ_>uOewYFRwL9@-js4)e3o4G$RA5pFE zfC(!%UU}N^EW1AgZzV|<(q^w0Rt9$1^mt@QoT)~i!{ZvD4X)3cUk52yk+HB28!7w+79`(@vPSv<@9kn##{YP9ap zn*p3bB#9GWM5Xfmszx|ALSn-nd+`ZGep8n?_^pBaW=SmW8;t%|eZ#ePKZqfm2P}Rf z!4p`eH_h_EF_YInZSzevJZZ{HxhB+^F~<{^w1|7%Cu`4{$)# z4Z}Ib5^ozONB63POBWFQcH^g|2gTSAaK5$0#Mno>xGJ)9enWkLLFJp4&p(#uEWmV) zfI?m9nIA=2cSIv450a%8x*Fs|lavLgDjL1`C5#|~qd+ahie)Me%KUhx1l z0Ub|8Hl7d5Tn9>3Ap~v~FSbnks0cIx72k+VN)*Ja5t#lvJ{Yz!GP4Dr(DN5_4XD&4 zp&HpZ2%Drb_=ez27Cs@^FJ_eA=HI{mfA(GoNaCX$0qsYnjQd02Q~noupLhe2WV(b1 zcm|-HV14J(y&fKDGK1T|B8~dT+rWZC(iE?!@2`rq*n|_+aLHJ_3$9X?q5MV7Tv&7| zrm@Y8zjB$+NJqE9<|sh<<8s~eZgIHuS3;r0VH&nI0&A?yZr?!?oBJvi>>Lx~&^twDgWhr$a;3{wcX z!JW%H-eY0r#~D1)41k&b@&t1~fT`Zc@O&iG_vH$%tACqg8G>Oh_4Lb~P#A9qlpFH& zP9D}#Ngf~v>8mpaX@P0nJR<5R&)4_yaB99MV zYP%_sDAI$RigzX-O$zZ2(MgR2;7f+)B(uoi+HQp7V=$^H@)}@gzKq!Cs_4rfcI_XJ z|AN7lAF?^&b6hT-zDQ@HHxh}nifN0}(dI5{%WG`L-L@9En9d0-Gqh?oGCxz^PPa

yHlr~Qj z%`kgh<2P>C>fTYE?E#Zh!{+2Qw=75K)1B;8ZJ3zCdDjI$qG`W%*$ojvA?sB=lZvgK zCFeTxA=XpCI{8fHWVEwdoN>)8KI3>wS1$ku!D@vDi!H##`d8bvA;7sf3*MOzNT&#^ z6;g_U-7z1Ji^{Am0x$ju^_X3VOn#pQQ_u;Ery^^ukw>}3FKln<4!Fg-PrZajr)_E1<>}I=v!q+(^ic#+0V+3yx3Z0nrya_ z9ic5(Ikj|7NP?0XaV4ST+E6HsCdv`M=q3j>e)^RmxA|<+tdj)5`<9`iZFSU6^%l5* zuUeaN*&D0)#-8)Fe8S>ey88ImsV>hoi8l7tzto01!b%xWUi?smIhTFWrN(* z72BPsG2KQLsTev>OM7u4F?%B<)XaC6+c>m+gLJt14bLXKdsoBql`8Ch7U`e5&WtBI z{7_XNoZW&^y+%(!etb)eRFCFwWNp11VzQfYOez$uKK4HTM0Tqzw##t8%t{NA6gj9W zKr&BClpUjOKiNRO!TZ#1dGtT= zB`TCkrZO!<(Z~t%LVQWIwqm8~$~fG4edEMFghmK%DbN7NvY2B^SOBG4jSsoeU9}I8 z@8tTrx#)0!Xk0e)MZ`Fi?_`7re_2^HlZb*ubafpShf`3ZQHVytq3Y_Yy!VIl$x_mk z4=1NlMp^cA)$r!Ekfy3uHS+39uf5rJpqII8@)&kPvu8s|XKlfWi*nPacSu_ocf{qc z+xaIq-h_5~osS{9#FPQ&ab=Z9DCd27WKnP7`JEqNIt4Mih~u8SY>LJssztE)gH8&1 zo7?yh*HL<>%aIbkUB;2UVY6-5xHtskHxzkB=KL#I`rI|7FOR8h83?)nmh`T}qu5h% zQWjOGpb_k!((<5@6aw=PODD3#6s27RkYmVFX7bHtkAD_PHnK>4bo@4=f40un2ISaZ zT*dnU7O4-Dn}eO`yK#}wA`O{eMAJn8;TFq&{Vj>EwfS1;EX%&RCIj(z_&GnYOCG*= zwdURH4UVPWsV0Lc#x`s1unv=`3@^@^dnq>ruZX5Nx190n~xHjIs1bmta%p3XQ;HW;dWus-?1PTxQh) zTo&#LVZXaVb-7~QO>QaTsjo9s|JE5c@9J1V{ndcBAc|v8VreFNW38yh^~0^ z0b;Cn#MZ0x-y<`c!rvJ&GLS)L$Mi~j!FC?X^IYlY~!7^!u=K`S0asx?9WJ`VOnME#>b-Xb@JrQG- zr5(}9i1&C=%^H_Ir3HO~9k{JaV}g?f_~p{Avg8mkb53wO!3WfW>>Wz1=%~{p^gcbW zKS!c|wH)MPm1XM06~_X-U>V7%5x}_>GOUo5M0~&DJ&YVY1tkdWOzZo_G^87HWV^JUE$HO3acF-XQ z+MH^-f^k$^xO}KuQ=&*qC}otWrr=C6BX_8~NKU4eX}OjoV4!&HCUn?2Bv4W`bMK@xJVgK%Up<|o zBI0#8S^-@%7*f5za7q*^w2;)zZmZru;SI7)F(0tJL5+UVAZg=|vfGSk$631oW1Ut^ z1_L6E*=(dzpt-5w0=T$QdW{hNfA|H7-D2&%m-u0XU)OVLJ&a5?T|?A!4O2Ucm%5Q9Qea6=O|vm?(voLlGudNwwm}k{+C`LbTmF=T z5rS3bW*+k13AaxniDC5b;o$6Rk=33KK+@qxqhe|?zt%m1$`}STyM7B z21-TZyt3Ga)$UF!(yzp{>Eps~TVLqdG1#n=M6lV0(P~-8o`^^y@=&2rLAn#nVm05f zaY~j-$-G$RtY3~A{LO&9Km@;LC*E5l@FrYm{^ zKJAg#f$PL%jYUBr)Hir5sGn@)={bU`+9f(d)>5!kp?iSJ25sX;KKaYZP$%Zn-;o1N z7;s0u&geOrpsh$p8QBw*A;N~N(pucAB1R7zW}POLuaIgf<@Ep*VCs`>W9Elsw`f%_ zk%{y$3mGxospU5L;HOsQI<7D$T3hZG^lM=`-#YbXg4t(pVt@h&J$w7NE7M+6eqof~ zDc!?A3%@=~jpoWA85f3mg#AW=s7u-qAf1MCP+JNKRdNTIZBe0WyQN97 zUtvi7c!Os|Rv_yPpq#vZ0UJ7`S;RH{d+HAtoL+JM#w^-owJ!-YvHZXmtJIbw4C+Kq z6jyD#gP8qhnPn5UEPPGeQcgj~S$0tFV8ML>^23b4x4n@>@VD!cNUpccQAU3*2Z3j# z+8+KxiX;S7f+bp%6hkBjXf7w@*8mNmaqy2M9u>VIB1Myn7xyq~Y_{O)xyraKctQH0 z?~NBFTNp<88^%1VKj*ZV2x5|XF*`l`Wp3_n_kO?DMgU~)xal9O1Y#BKn#5XLWJwqy z1)@^#BKt4hXk4}1D<|sr1QPp@;zSZ#6}jh1OHJfIO@$7d^_3D|Kpt4=GM)tImtJT> zgU9nNvxw6~6*6xbEY0SloDTm%7QL2yayPX5lwXp9tK%8JqSy63_6^)TkzL%3o} zc-?8@C?-^{(v{JP)I2^IH}&v*o5VO0I(I^@-Yw_!g*V8!%n(y&3r z_V%_g!9~|ZlYbCz%)}y)f8MQhMNp5!Cz%d*w6cwk=1D~2aYQg{F1eC13byfgd#)G< zEZz@&Y;tD3-*U4P0k6T~v7Q*oRCZvF-o`k`=vfVJn$9^3*kGB)?_)c?j}cG{U1-JO zyXb{>^n)efW_trzrdtwxS$Enxp4}g3lKV;0=o9npPXnMaaz zS3vrg8MfvefljB-XdU2Mwob`m%S_oOr_#1o`Mak!=}#fUxQB)as+A^>;-#>>1uZN{ zs+NoDCKaz6?9|~)u+hAZckk&uk&aH%tHgQR@6yW56xoFaxTeH^$+E8^*Y$Fkft7kl z%dYE1_7)v)qKR!c@RmB3o914w-S!^!A(g^QV@ex`XOM%CEv*1&3EvAp-B{wGS)2)) zZ$$I$Eg0S$q@ileW6b@YEtB{t^`TWt3sGTs_fuJzE41v9@Ia&Nz4ozqe)O{aJ72J@ zm*fK$Fftpa;g1*98=yQE+E=em`>XU-lqMPTT)qp*0j_8$RRbnc1owJl4Q#e;ms)|9 z2Xp*v>&$32XHtM3SxouMyghcezJH^W zIFx)fU|kyWBy}VOPVyC6DiNtA^qd5^Gs}Kw_~%XPBTWhcgNxh|b%gvDyoL;<3B$x=6@kASCN-9KVH$I;`3F?2+8j2rri z(6i_VCTT$HUTt}5V)PzJw!QWz46ZM0m3O@K1nQ>PuK2zLXl{|fBZ~(R1Ja~4$>MeT z<1j_9gbRWbmDHv~;6sXqHzuW+f^^@$Dpfi?zl1495W^E9U5P}ohPFMQGYGQcE=ii9 z3@A&KQtA+QYNI!E`@msN(Ts%37irtKZTr zcJTpy2?z06PMxVAXO3&Mf1AB7r-nWAqw+m_f4q$87#k) z6Tfl)mrG?cb(OZ<57m7A<6|wJWQ2y7gn$o`q&}>ndr&jcYTajGI zj0#HtKCeFWyGdRW7oOQvZGo{jZXxQ&+2l}zNDl}h z=t}ue@=MPpb{@pAWEi|wV4WvV&8J?AmmZU5HU=+xOOGY<1pbx} z<^0(d?6zBR10*GO%Q5$>S+2rI2J^wUt>>@A*qFCEfJ}2ls=3dj_0{^nwx!g~K>=6e zWs{OwSijrMBXLn3CI+x|A^tf)mF!mF${J6CzrURVzBimNA_xbU#eUqPinfVmORr4< z6qZjPf-*~ajJ^X|Obn(UuyUH1Vsm!uA0dut0B0@DQ3`%8A15y4G2KhPYWMC2#X~mx z#0Ri6&uda3+5G8*=n$(0bC*;TPqRnRjLVL;@fo}<->3AZjPwc{#0NA_Zn1#gfdT?1 zYq|6&GN6#^?(de2X<@tA7p;Uq8)zO)QmpB(~UT3Tfd@q&lr&dVTkzz z{ZB;lxlo>+|5+^{M*;%k`=7#_J-|(xqrn4IH;dJv)6m0C#KRY}xSB5p;#_rwM@lL= zh&W>KDp&vY+CumaJ$d2q;5_ePNh-Dlwt78Gd*0b{e|{tbeB3{_0cqccM0;(K75#FT zX_pYEVoyd9Juo9-aMVZcK8@~_5@rtk1r-`CwoY3Ftn-o_X;=?TPAiU`s1)V>x|9m| zJ6S&J07}AayiRR`b9IpQZnhN-fq6RsiEljq1icj)=IJRqSmg7GX&|5y}w+=U&V@wtyFqN1aaCU{7LusiK zW&i=rjQYp@D^Cq?RoSYwvC+DTy}G4Xk7Q-hjFWylUpaoSYI z&>g2q$0|K^liVTSFI1oAs$xGjBjXm%7q|ePMrbu>gp%)UAg0r|s+CDBzLFk5Q(N-J zy7~7S2-67y)=BLVdkLG#w}#yF`)(f^m7HvDB6Y)#VkxNe3|dzw?|LURBb2?+>{ack z2_;=D{FZL}kD}qWO>BsH7vGzDnktf}wtz`SQ&OjQ(D5NHRgHc75KAm&m@>C_#k369 zr0x{n{AG(!1*M2SCrh5^SrP`|l8}b9o6smM7z51j{rg1M@xn}BKh;KWa*A1B+f!?H z3c7a4%7HNKS=)-I*1+DuudI|%wbe1=enkeFe#8vA&{BOq zumn1_KyAQDxA3ocHBxwvc8)A^^&jlDpmKVI+AL+4x;H)L8lC;+3Md(XyXumYn#N{f zRc3{GVq1o`3ccr=-B$IOR8!h5bXA+oK-D^3edD(3;{cJnPO2>40T8N<7LCF zs1n%wZE0{DYIlq~YIhW18yfyEAK0}s>7ULesZzTTQ zL)SiCRG&fkZ`3@g7hOR*bzW%rz54zVi**z*?J}*Ir0`=@f3}%&I!M;p;!?2RWown? za3_`3ODncBEjHLMBQVXxSlInzu|fR_mI&{&##0LDGGk*r#K%Sd|{b3l))N z*=_TwbRdE(IpOQ@+~lpdpG>Wq<*VPp65tkF~I&r-rK2T ze5ag!qh}8VOin*$e^_&;jf^U(1-cGfUJ>nUo@*(I?D%_NBytL7_Qh#CBHHeYxJ1VB z!c_X6X~B5aL$4*-Rh{7qPk_Ok`G9bP*m8LM0g;i+WeshTV9FzlOLAt6)EZOVp3~<) znKvafZ+hK#R*e!-9Kpyn9I-%!)W6(=PVs+mfhukREY3zkiSP#aM4|Iwq{zWo? z0G6k3dANxSFaY?z+n~iS%bwiJ$r`A-Gzx)ix%%4&SZv@u zSypcZ;O=uCN7^Hz?5d~&`uX-HqQmp*Wj>;nZee;7{e~QGdHj$8e>EHj?=_Nr8l&!7 zv-Wi(4-Pxp`p?RpP;55My%=Db{8vl<4f3S}05C@QxVym#Eh&uM|jG8R1P&8hDniW$T*;Zu{xc3 zg>KJNcpGE?u=FB~95RgI2PBYuyVW}VO9p%@@hW@M+3%#`GOw@C4$Sy#66>)wuJNE8PNQ{8S^7ddoadRBf)RbmxSCU3#$; zL%W1hV++9DCkw-t9(zPhA#qdLE{AB+OytP@kbEeg1fFoUi?CDh{h!|?5>4znLJBwI zF2uIeHQuqIe=`ZUEPe#{O72X}2-Db2XmcNX2v)s5HwoM_HY^SD?19gsGd7>pZ){Sl@N%ey z2}Uag$*6e%_1qKU1co1Rr^xT%X`y4KyRAVWZ-gAF?1H9+eq0NwKn5z>qFt`&koghB zACn50u5e%Ld)7{b*6o3XKe%uwjsqw2slnM6sCmr&hF=hcU6_=z*TV09kk1oiX23)2 zc8tSRQWR9ecV^LHf4z+YrNByY55fxac${Qg3ntuRv2@{-&X)UuTqL20#s4a*|;( zJ%Z5~fu6ss4Wcblpc3Z1{4f4X6;y`5@~5JQe=7R_b#J?DWQ4_z`|YI3?7EX=#Z+?J zGJgcAdK{?G#Lx-|!NjQTamJEJ+35hoJ)Fqn74wYL?rW-E(G}w+x*@SpU`f=dvNV+C z;U?-rN&~K;!F#M(TeT^)o2KKbxJnGmV0CQMfeZD}3LOqJf6fV}kwuohtvWg~@K51& z-}B>7&8Awrd0-Ll2W|{sZ=pp@S1ObmrOwtZ*{VuCMyufNV3To!IH+|s7oPw*NE!4Z zZxgK+Tu+nm7`@sX2lyi`uAA&5zk|AJrP@RKX`OpAPW4pezFL1Ll6CvS4k`9NMD`tr zfVce%X{4a->Sg`PCYl!0Bi}+RPUUS!v~mm5J%!8!+IRCnLVHkd=L(X>_i zr5n|!=~Ql;r*q?<`1OsIi)Z$ayB#HT){Ow~FoI+rWG1hRdy-MQ9u2Op9jyUPJ0)&TwKk0O zi3M{d;slF`;72|n70KBicfm*nMA$$>SdG%bkV~116mA19PiREGP8fR%Ut058kxjI! z?17|HM&UkIkqcPbb0C*F%aBMXV6gAgQKmAgs(CMg<6$Dblp_Ooc)SZDxs>$#$Rk+v zBnS5w`E@bW=XprvmHYth4Gz&=q8VnWjIkY(j) z5s~e}I`5PxXyKwbRBC<54Yx%SPKhdcE7DU>cI3kJSQ@0)?*%5YaLyVQQl}!lsP+Fv zdZm;7o$mT6(#oGA<@lMF*gIJ;SU4G(+9cVcA^rC|cb5%3>6}vn?0dA_Af}0(D+U=zJF5eN_v=l|T*|8?+ZR8$Ems##)6X*iD%+gdgnlAIF!TchtaXlfs{i_e@McHfOjwmNinCu7t7Z0Gk%BiJKKQgc61+ zZP0d)r*5w{)EgEGe-*QFYV(7njrVG;x&^@L^7#i?L}5OByT5Fv@L$(0@{nrpcHOqJ zriCJn(25bJrkk&YSy}H{u>DKvNw{plOphymr?5TNipNw8X0%#HJ(S2f%&z-jR3q_sNTq1s%7&0Gt$P|xgVrQ~g9SOUti{HV&WvrH5L=c3Rtfw~*+qmFb27ivH= zfbRGyOrx9V%(8thJ~HUIAru0ZVNTWE-Op?T=V+-K(TwOA)5#*jN|Aa8wXINSK$E(I1wHAqAG!Fu~{$uvNxWtKljP z5?62fmwOZwlgnTrJ#-AV#QD~I`~xs#u)XDW@sfNtZe8e&a8`RF_WnqDY=qn6d_Wgk z0G~wHT}Cs912@ym)IT$|yg_Ag7>F;HJ!Am4-%F%0^`ylpiJi2iyuu z8)907bo$J<+}x4CMj;e_f)UN|!7DvbKUFZZ0+amRg9VnP9dh zQ4CL;xtnjE1abNr*g!DP4xfPhn_&Zs4r0E~_~A7FdU=3;go3mTKVXD)V#sp8)kC+W z58UjoMx210{7Nj!U#!YOHWPx;Ew0L%7>go4QLZ?;{6n0^Bjv6Vcq5x0UwDHDFLsxC z%cc{TLv%>AiU`|oGBjKdK8Z`xRJlE*g56y8%ueEz#2f`#TS$KrSp3Kb75foSH&C9X zz<~S_<3Ae}3n9nG~F~j_GCFNUAKv= z)R(&ciL5mJZo$Hcg(^T2Q}0GCC3?;6yr;l%)^qQ(t9hS~_cu~MvAWBHiFg=22AtQ1ul!T8?^=_u=ziBoscx#)IMjB~#4BzI$`c&p8+uK#8UVZD_*3W#jboPlb6h zN7^2BPwblV4VBZPb1dZU9KNJ0D&*hqAj=pRz!Ag+ zNw(C5qA_D)rklIcI_7xQNQG=P+^??H*L`iuCq74zV7ca{6U&+O_iDwMCjti*v~zTjmCt7 z;=T8z7`&v$Su@8#n{c9a2Y=5cUG2S^{;fnX{_9){ScC~36hNO`x@ENzFVmN#?8cyW zQ4>H$qKLXKc2QfyFgm@Pa$`_5v8Wy%ch4!f=Gr!7Msh0VA$5IJ^$b(Y3}*mIBSFLS zjqVmiUd8EQxs~GVjW;PHpi+qCnL!cWfngxTDj3y1f{m?59!JdzAuq^&(QwI|wqh>3 z+;=nwv}=hF#fJrSBffj>@XB0M#Z!&ra5dJ;tXt6@d#)}>*!uWMmwzK<8a@X(v$^bg zy)AQ?GuraWA)()aR^3wDT(#+-Yl~eJ*cj#2w@usd{^`5Kg`3?n66MtNyA1xbzgNpD z6B}re9&YJT*|&2}4Bj-^rw;$tXn2a|?+`=+2%~G5x%%?Ijllz97jWj5B12tgAO~u# z@}H1ajE$hSK}m$yz{>1YoA3#HeZ-#8mTgK9M9y6A3SmP;sXdUF^})!>rr7FIU5hm7 zt)tnLrYZ_a!xO;h%2O!I2=@DFp;VjC40lxxizzsa(#PG{G!Ibh!; zqJv{N`rq0JhZ#+{?H^>e{z+vN_#b3u6xV=C!7+g0u-iIiXo?rF0ER;>;)6i{323sR z`e7me??G??y@`#HvvZD?m7(rP!k2Vr28WkdtJy{)pP|hj$iGyk*7_qAejqFv_SA+1 zglSE$L~;DN@C>9@PT}@Jq*%mQLlocu!!Xdm4pW$b4Y~F~=&&MRx^vHCHv)m9-UxIy~ONLQl-w}Z^G5B}mm}VmcJ(Ck040Km z^ais%LteX4umg2>GT{YD6=L+rW`?M%Q|Qsa2us-{*T9LXK*uJ2WDb&BMPiqT3^`H& zWqrre>nw&Wr$8eg@-|ij#u})JBg<+sB)P2Is`Hq$LVc?c;~%p(U?C+DO8k@6r{8+j z+uDV6uC`Dt=5wQLR_M_!=CjZv`w^vAw#(KMjEmC0WM*0|r>8U5Oid<#x$*=tv6$@2 z1%5jW}YtyNbUY`3>G)EbTas9|0It=4F6QbJar!|EefU&#j#t}r!iZ>jZ= zr{}9Dyap;M>1>qnNnsT&mg5BK6;D`0w@3s=Tw&7bCUkW6e__Fk|EaS5b*~|2a=CKZ zU}(KwZ3h)riMOd9LR?yN@gbJX#f=Fs;m#iHmQfSi1v>f0wCXeJ>1a01iiXDo__uba z$lFe5vl!6}Rv<~)AQ`WtJn8&E8`YXA4Y*of?=i{3(kX)k3#lrk8@PEhq%HR2Ny-(K z2v02Y3F&NYs;F+0i2=1pwZXQrw`v8As$r9ZCp&C|{V3+5Hx8GgacfDRnBO2y*GUvt zo4Z$zM6l->QeMBUHhhW~m&ZW`oFwnFkkmxm;>+>{5oSiS9w}lxl9A5a6fRBRxIWFo zQA3$*%Nn7&n9*E25!->EqZcK)s)=N!S*^EE`=6dkgNI~|=?UwC-9SQHZ_J|BYqE7H z*8g6=7~&qD0HG2NcL1i;$H0P3Wcx;LM@guRi?26LU(rqi&WfNkVplloB-B;0}m<}+~i=cE-p+n|TXh3#Mm%z&Ug}vODE}%L+ zHA%v#J6ch<%NeHE11u3)70N?xHC;7wc(cJmICL%Q%Wk&kfpgt}00>ZeN|ju#3%dku z+)^b2o)VRe3J4wTX%C-2*%>TgOERJ20m}LdTwUhy4zp_67O-K?idqS%ObQV<41`&} zS^wk~t~6n+NkYaCz@;jconW^jbzryrap1P9#dilTMau)|W}!xT+GEJ+LYpJ4{(847 zDDt9Sz$XqgGZo7L{&WPnl!vzI&cv_9Si6?B^RR8$Nou-bA}5p+={YeWk-gu*MnDZQ zmNhQM2fM&fhix(S+^FK{39r{wZ@KIZ(jA3fB)1cF6_3Ts95IW~r_n&-kwqPpz>f@8 zGK=&QX;2s1V>_kj%6T-et~6?o*tUnLMYCvhlvGAL=7H-1CeCfdXwhS^oMM!{KK?dC zhUln`LSA;N*RmYyIQ0;5P)cl3YG67g`E15#9sL%u8@LSJqHe>w!y}`9-vS?LBx;*- z*V63hFOH1CV4ii=n`ZT_4O|M-LWkp}NVdLKoXH8@B6FvRaj9o%+_rHAj??0j-P?%6 z6zQdSHceLsU_|{y%rLW%Qb)pd2LTvO+jJTHiM$W>MS2;YEuHcLIF2AfxAI1EfvrXG z759!a@bmB|!ntvN!M*-$(TxY)AwFl=;Vr~rirwxTj~I>*QICvvnB3Uu zz$*=u8cEZ}iVyOQ&@D(3V@4`2)W#YH9}f%DjnLuoHlT-UX5UskHFnmpRQ56(UJk7t zI{qZ#(uk3#+UWbd9@kEt4<>t$lrEP${Y!0B7RimLI9nz%i6DDUB#H?2;h)1%9*)po z9Exy%c5gLYT?6F6LIf+^i085J(&9as64>!u2yB6&8Ju`B6UF6Bo&wGF_-Ana67(axgbJ{ET9OESa1Ez60$&?0iMij*+#C10&6I)I}3q1;r1d zu9|;A)$%Lm^!lu$UD#FRTYK%NaYuQ$|Dgo_ zfLdnPa?l@SBPjqI8Khh;GnwiLc$fLI2rNys8Yo1V~= zm0iOL`g%uq1{UvSgQfdgX#AftM!tV5X~1X}ETQthDTtc{Nj(2)S@YYeW55Hz8X5Uq zu;aa~;$|fc-n&BX)|^;&kYUIK{9G$2zH~8?!p=Z<-I~UP4--J5;DnA~>moS-o!j=l zw)K`DTYf#CaD!t%AVJ?XZclSMwbJeQZ3qMk?OJ$-H!bwMKH{+IQOc@4jdEq;cEfi$IlJ9ddzYtFQGcWZ83btpIhaB}+pK_;p}IEa8uR zIf`GqJJk^O`TRP@!HZTjzr|r`%s=Asmaw*k(9>~Yb@)JJ-~crGE86mOZ2Y(pn#*4) z=E#@wFU%my&4W?1VOw{tct~L1V7j)wS^s8KL)TG*e_MSy#(`T=KEXj2+P~mYUnhbx zkRDDe4tZj;ewqCwZ>EM-0LIPZJ}R=Ve4rG%kXpY^eLY5!wGX=)5>+Hx4f;Ir$5F@l zK3|HgMUqwIh)bo|zgzBNRGgbPWtXJ9;blHb;zw5HYau^@(tApI?*LlT%15dukY4`j z@q(^VDlL8s2^pU5qw(4mTIrdB?#f02GE`M<&DAI;G2NXg=oN)(z$3&*Px)5Npud0> zz1o1>@6O5vog|IqGF|mg!sA8iFJ(8hwet*OSBc_WWUUns+uRGDuYG>nQu@T&+NNHF zrLaXAq_fq88JjJ48*?)T`MPy`vGB+;3Z;Q3URgtASuvFJdUzT~{>?{7W02MZ;D>xH z4P%leLlhHR7W`3k0B;P;?b>>z!2xl%%;a-DTwW2_*a9_);iO0N1eIl)v5O=X_mQkk z8hNl8ikl=w;bI7V2QbEzT=<0k@R8D&A2`nu*TeW!yXwv`$DxQW6`-H(4y!gv;J}M3 z6vx>qJ(c>2V8rtLXb8bUV6%%6>qi!f%NMP*nk_y9>z&dGSa-p8&kBUNMRbWUVe%7= z<^A0dpR1H;fQib!W)>! z$Wb=={zAnzGh#B~(pK&_x^R%KtOAcavllH4T{C?T>ooObQ7~Vl`qj#cx`@jX zOjAp28XwL>xi61_q`}0V+aMO6_TwY9S$%U1WX_h%p^jg9d${Tm)h(6_kufQ@qt((I zX)2$a5X3({I}mE!6aBuc_Fxp7->?Wy6kX@SST0TkP!VI8-E#j3Y7EfK9aI7S+@m;_ z+pm~0H5h8=j63NLIO$EWD1FG0o1rL}=bE{HS(AZ%pyX50?8JhgqkUvSdAp&dlg};S zTbjdi4OQ9WnpJ$TI$gfW4n5g`-o6DZ#Zzi}M=&AIfZqe#B`lL%j&V}@{7?#esBh~7b9gkx}G zi}TJ2Orz~&E8dvGy>TQM5|)hV(hW}oLRW()lAf>WPZ>w&Ft)5b6QND{-3VSJsPS!4&eILoa8y> zF^rq?+#14qbZA2ADAAf^IW3_{LsA(@Lzd}wiX4wxztrw}ZSCx8dXP{#r@BOmN>tl( zjWJ9zCMIpt1N)mB+Pn9k-}n2Q&-Z)popbN~4c*<4qQA*Qwdpx=`=ar`MyjA)=TPVj(d-n08Z;$`OZaF0^yEZ&JDd+g%Zn=l$&+uh@K{Pw$6<)HL^Gt>_MJCo8fd|H80eCo5~iE+~0ScyWCJ* z!+v&WM_=34an9!x+DU;UjWraLi%E)4b$r$(3B9xtb^*Gg1;hEmqH>TE>f%mBYQN8g`;?eizdzJqapW8M zn0Iws_;WqzB4Jj?b(+qAo&8K$EMY)B#cE(R6LzE-A<+;D6;2>e6ILnQu+*CHdRJ6^ z`4q*gd{CBZ>JZ`lIfyrh3kTe=(gWvToJ1L^3-n+?Av^HRxS#0CfiG z7-h-VX;gjV!M>BQE({xF0p~DMEgD=3B%4UFzQG3S4za+E$VpWfh7UObtr${Ow$6vd z5FPuv)&klHyc#S}u`o*OI)yRX^@W)|+c$+5oxCRj@}&%Hx;+cARurBufTy)> zpjj6Svp-T84nJaaovD+G@cP5(M=RLg&A`+>VFBnNB2X7Tdx}7# z2tS)mLPumYXeYD5)ZHzoPzco)J#8)&kdrqFT4H2N0rHltjfz?*(8{AEq>|au$ns*i zu*V4ed<;$cL17Oaqm+J9EZ3eOE!%qRX=Kd|oIsX)O36u&UOS9Zc0jRAItd%x7ejHc zE%yJk?-VD(Q$z^zAg_Uv=A9zYD8dhy!w&W`Nc7TaWRe$_$&J7vG3j2N+m*|WX=I+P z;H443&rQzTVq{hV{b^UwyX;Ky$gd=C;Ki!BYOfe2KurOgsz}gjwK)k=0@M_6yas`m zFtN`GY;1;#@I~-W9}DpABheC?zFG>hAHbkjF(Bd*L>*Sf>jP*g1+M;bxN7*L*VE~- GTKgBj+ffbx diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8fad3f5a98..f42e62f372 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 9794aa4f45..65dcd68d65 100755 --- a/gradlew +++ b/gradlew @@ -1,18 +1,7 @@ #!/bin/sh # -# 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. -# - -# -# Copyright 2015-2021 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -66,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -91,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -154,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -216,6 +209,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 5d049c13ef..6689b85bee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,15 +1,3 @@ - -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem -@rem The OpenSearch Contributors require contributions made to -@rem this file be licensed under the Apache-2.0 license or a -@rem compatible open source license. -@rem -@rem Modifications Copyright OpenSearch Contributors. See -@rem GitHub history for details. -@rem - @rem @rem Copyright 2015 the original author or authors. @rem @@ -26,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -37,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -52,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -87,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From efbc48b405ff6ff372c4821aa15c53fc50a409a3 Mon Sep 17 00:00:00 2001 From: rutuja-amazon <110013621+rutuja-amazon@users.noreply.github.com> Date: Sat, 10 Dec 2022 02:41:42 +0530 Subject: [PATCH 096/369] Username validation for special characters (#2277) * Only prevent user creation on colon characters, separate out tests Signed-off-by: Rutuja Surve Signed-off-by: Rutuja Surve <110013621+rutuja-amazon@users.noreply.github.com> Signed-off-by: Peter Nied Co-authored-by: Peter Nied --- .../dlic/rest/api/InternalUsersApiAction.java | 12 ++++++++++ .../security/dlic/rest/api/UserApiTest.java | 24 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java index dbf1a0e800..2394488041 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; +import java.util.stream.Collectors; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -49,6 +50,10 @@ import static org.opensearch.security.dlic.rest.support.Utils.hash; public class InternalUsersApiAction extends PatchableResourceApiAction { + static final List RESTRICTED_FROM_USERNAME = ImmutableList.of( + ":" // Not allowed in basic auth, see https://stackoverflow.com/a/33391003/533057 + ); + private static final List routes = addRoutesPrefix(ImmutableList.of( new Route(Method.GET, "/user/{name}"), new Route(Method.GET, "/user/"), @@ -93,6 +98,13 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C return; } + final List foundRestrictedContents = RESTRICTED_FROM_USERNAME.stream().filter(username::contains).collect(Collectors.toList()); + if (!foundRestrictedContents.isEmpty()) { + final String restrictedContents = foundRestrictedContents.stream().map(s -> "'" + s + "'").collect(Collectors.joining(",")); + badRequestResponse(channel, "Username has restricted characters " + restrictedContents + " that are not permitted."); + return; + } + // TODO it might be sensible to consolidate this with the overridden method in // order to minimize duplicated logic diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index 715c256cb7..f100b4ba7f 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -28,7 +28,12 @@ import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; +import static org.opensearch.security.dlic.rest.api.InternalUsersApiAction.RESTRICTED_FROM_USERNAME; + public class UserApiTest extends AbstractRestApiUnitTest { private final String ENDPOINT; @@ -468,7 +473,7 @@ public void testPasswordRules() throws Exception { addUserWithPassword("$1aAAAAAAAac", "$1aAAAAAAAAC", HttpStatus.SC_BAD_REQUEST); addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%", "UTF-8"), "$1aAAAAAAAAC%", HttpStatus.SC_BAD_REQUEST); addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%!=\"/\\;:test&~@^", "UTF-8").replace("+", "%2B"), "$1aAAAAAAAac%!=\\\"/\\\\;:test&~@^", HttpStatus.SC_BAD_REQUEST); - addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%!=\"/\\;: test&", "UTF-8"), "$1aAAAAAAAac%!=\\\"/\\\\;: test&123", HttpStatus.SC_CREATED); + addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%!=\"/\\;: test&", "UTF-8"), "$1aAAAAAAAac%!=\\\"/\\\\;: test&123", HttpStatus.SC_BAD_REQUEST); response = rh.executeGetRequest(PLUGINS_PREFIX + "/api/internalusers/nothinghthere?pretty", new Header[0]); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); @@ -624,7 +629,24 @@ public void testUserApiForNonSuperAdmin() throws Exception { // Patch multiple hidden users response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/hide/description\", \"value\": \"foo\" }]", new Header[0]); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + } + + @Test + public void restrictedUsernameContents() throws Exception { + setup(); + + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendAdminCertificate = true; + + RESTRICTED_FROM_USERNAME.stream().forEach(restrictedTerm -> { + final String username = "nag" + restrictedTerm + "ilum"; + final String url = ENDPOINT + "/internalusers/" + username; + final String bodyWithDefaultPasswordHash = "{\"hash\": \"456\"}"; + final HttpResponse response = rh.executePutRequest(url, bodyWithDefaultPasswordHash); + assertThat("Expected " + username + " to be rejected", response.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + assertThat(response.getBody(), containsString(restrictedTerm)); + }); } @Test From a44df5c7e6aaaa0687bcbbacc525688e23024b05 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 9 Dec 2022 18:29:02 -0600 Subject: [PATCH 097/369] Remove upgrade tests that keeps breaking on env issues (#2323) The upgrade check tests were a great idea that ultimately exposes some problems with build compabtility between versions. While we could upgrade the test to re-install gradle after the upgrade since we are effectively 'down grading' this is a really complex way to confirm that code we don't often mess with works. Signed-off-by: Peter Nied --- .github/workflows/ci.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b4319f1c1..05d9fe3539 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,17 +154,6 @@ jobs: - run: ./gradlew clean assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip - - name: Verify updateVersion gradle tasks works - env: - ExpectedVersionString: "opensearch_version: 2.1.0-SNAPSHOT" - run: | - ## Make sure the current doesn't match the test version - test "$(./gradlew properties | grep opensearch.version)" != "$ExpectedVersionString" - ## Update the new version to 2.1.0 - ./gradlew clean updateVersion -DnewVersion=2.1.0-SNAPSHOT - ## Make sure the version matches expectation - test "$(./gradlew properties | grep opensearch.version)" = "$ExpectedVersionString" - - name: List files in the build directory if there was an error run: ls -al ./build/distributions/ if: failure() From 7b52ef91cd9a62ad77877af142f0499798364ca9 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Sun, 11 Dec 2022 20:50:35 -0600 Subject: [PATCH 098/369] Remove redudant test suite (#2299) --- .../security/auditlog/AuditLogTestSuite.java | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 src/test/java/org/opensearch/security/auditlog/AuditLogTestSuite.java 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 71b2de6892..0000000000 --- a/src/test/java/org/opensearch/security/auditlog/AuditLogTestSuite.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.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 { - -} From 6ae3941e13e85a6c9c14f6a66af8e2bc2d6788dd Mon Sep 17 00:00:00 2001 From: lukasz-soszynski-eliatra <110241464+lukasz-soszynski-eliatra@users.noreply.github.com> Date: Tue, 13 Dec 2022 18:59:14 +0100 Subject: [PATCH 099/369] Test related to security plugin configuration updates. (#2155) * Test related to security plugin configuration updates. Signed-off-by: Lukasz Soszynski --- .../security/ConfigurationFiles.java | 61 +++++ .../security/DefaultConfigurationTests.java | 80 +++++++ .../IpBruteForceAttacksPreventionTests.java | 2 +- .../security/SecurityAdminLauncher.java | 41 ++++ .../security/SecurityConfigurationTests.java | 215 ++++++++++++++++++ .../UserBruteForceAttacksPreventionTests.java | 2 +- .../test/framework/TestSecurityConfig.java | 80 ++++--- .../certificate/TestCertificates.java | 11 +- .../test/framework/cluster/LocalCluster.java | 64 +++++- ...inimumSecuritySettingsSupplierFactory.java | 1 + .../resources/action_groups.yml | 4 + src/integrationTest/resources/allowlist.yml | 4 + src/integrationTest/resources/config.yml | 17 ++ .../resources/internal_users.yml | 14 ++ src/integrationTest/resources/nodes_dn.yml | 4 + src/integrationTest/resources/roles.yml | 19 ++ .../resources/roles_mapping.yml | 9 + .../resources/security_tenants.yml | 4 + src/integrationTest/resources/tenants.yml | 8 + src/integrationTest/resources/whitelist.yml | 4 + 20 files changed, 595 insertions(+), 49 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/ConfigurationFiles.java create mode 100644 src/integrationTest/java/org/opensearch/security/DefaultConfigurationTests.java create mode 100644 src/integrationTest/java/org/opensearch/security/SecurityAdminLauncher.java create mode 100644 src/integrationTest/java/org/opensearch/security/SecurityConfigurationTests.java create mode 100644 src/integrationTest/resources/action_groups.yml create mode 100644 src/integrationTest/resources/allowlist.yml create mode 100644 src/integrationTest/resources/config.yml create mode 100644 src/integrationTest/resources/internal_users.yml create mode 100644 src/integrationTest/resources/nodes_dn.yml create mode 100644 src/integrationTest/resources/roles.yml create mode 100644 src/integrationTest/resources/roles_mapping.yml create mode 100644 src/integrationTest/resources/security_tenants.yml create mode 100644 src/integrationTest/resources/tenants.yml create mode 100644 src/integrationTest/resources/whitelist.yml diff --git a/src/integrationTest/java/org/opensearch/security/ConfigurationFiles.java b/src/integrationTest/java/org/opensearch/security/ConfigurationFiles.java new file mode 100644 index 0000000000..e77d6a9f73 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/ConfigurationFiles.java @@ -0,0 +1,61 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +class ConfigurationFiles { + + public static void createRoleMappingFile(File destination) { + String resource = "roles_mapping.yml"; + copyResourceToFile(resource, destination); + } + + public static Path createConfigurationDirectory() { + try { + Path tempDirectory = Files.createTempDirectory("test-security-config"); + String[] configurationFiles = { + "config.yml", + "action_groups.yml", + "config.yml", + "internal_users.yml", + "roles.yml", + "roles_mapping.yml", + "security_tenants.yml", + "tenants.yml" + }; + for (String fileName : configurationFiles) { + Path configFileDestination = tempDirectory.resolve(fileName); + copyResourceToFile(fileName, configFileDestination.toFile()); + } + return tempDirectory.toAbsolutePath(); + } catch (IOException ex) { + throw new RuntimeException("Cannot create directory with security plugin configuration.", ex); + } + } + + private static void copyResourceToFile(String resource, File destination) { + try(InputStream input = ConfigurationFiles.class.getClassLoader().getResourceAsStream(resource)) { + Objects.requireNonNull(input, "Cannot find source resource " + resource); + try(OutputStream output = new FileOutputStream(destination)) { + input.transferTo(output); + } + } catch (IOException e) { + throw new RuntimeException("Cannot create file with security plugin configuration", e); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/DefaultConfigurationTests.java b/src/integrationTest/java/org/opensearch/security/DefaultConfigurationTests.java new file mode 100644 index 0000000000..589fe798d5 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/DefaultConfigurationTests.java @@ -0,0 +1,80 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.commons.io.FileUtils; +import org.awaitility.Awaitility; +import org.junit.AfterClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class DefaultConfigurationTests { + + private final static Path configurationFolder = ConfigurationFiles.createConfigurationDirectory(); + public static final String ADMIN_USER_NAME = "admin"; + public static final String DEFAULT_PASSWORD = "secret"; + public static final String NEW_USER = "new-user"; + public static final String LIMITED_USER = "limited-user"; + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.SINGLENODE) + .nodeSettings(Map.of( + "plugins.security.allow_default_init_securityindex", true, + "plugins.security.restapi.roles_enabled", List.of("user_admin__all_access") + )) + .defaultConfigurationInitDirectory(configurationFolder.toString()) + .loadConfigurationIntoIndex(false) + .build(); + + @AfterClass + public static void cleanConfigurationDirectory() throws IOException { + FileUtils.deleteDirectory(configurationFolder.toFile()); + } + + @Test + public void shouldLoadDefaultConfiguration() { + try(TestRestClient client = cluster.getRestClient(NEW_USER, DEFAULT_PASSWORD)) { + Awaitility.await().alias("Load default configuration") + .until(() -> client.getAuthInfo().getStatusCode(), equalTo(200)); + } + try(TestRestClient client = cluster.getRestClient(ADMIN_USER_NAME, DEFAULT_PASSWORD)){ + client.assertCorrectCredentials(ADMIN_USER_NAME); + HttpResponse response = client.get("/_plugins/_security/api/internalusers"); + response.assertStatusCode(200); + Map users = response.getBodyAs(Map.class); + assertThat(users, allOf( + aMapWithSize(3), + hasKey(ADMIN_USER_NAME), + hasKey(NEW_USER), + hasKey(LIMITED_USER))); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java b/src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java index 819f4225e8..f1f9cdf3f8 100644 --- a/src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java +++ b/src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java @@ -120,7 +120,7 @@ public void shouldNotBlockIpWhenFailureAuthenticationCountIsLessThanAllowedTries } @Test - public void shouldBlockIpWhenFailureAuthenticationCountIsGraterThanAllowedTries() { + public void shouldBlockIpWhenFailureAuthenticationCountIsGreaterThanAllowedTries() { authenticateUserWithIncorrectPassword(CLIENT_IP_8, USER_1, ALLOWED_TRIES * 2); try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_8))) { diff --git a/src/integrationTest/java/org/opensearch/security/SecurityAdminLauncher.java b/src/integrationTest/java/org/opensearch/security/SecurityAdminLauncher.java new file mode 100644 index 0000000000..0cd8b23f5d --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/SecurityAdminLauncher.java @@ -0,0 +1,41 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.io.File; + +import org.opensearch.security.tools.SecurityAdmin; +import org.opensearch.test.framework.certificate.TestCertificates; + +import static java.util.Objects.requireNonNull; + +class SecurityAdminLauncher { + + private final TestCertificates certificates; + private int port; + + public SecurityAdminLauncher(int port, TestCertificates certificates) { + this.port = port; + this.certificates = requireNonNull(certificates, "Certificates are required to communicate with cluster."); + } + + public int updateRoleMappings(File roleMappingsConfigurationFile) throws Exception { + String[] commandLineArguments = {"-cacert", certificates.getRootCertificate().getAbsolutePath(), + "-cert", certificates.getAdminCertificate().getAbsolutePath(), + "-key", certificates.getAdminKey(null).getAbsolutePath(), + "-nhnv", + "-p", String.valueOf(port), + "-f", roleMappingsConfigurationFile.getAbsolutePath(), + "-t", "rolesmapping" + }; + + return SecurityAdmin.execute(commandLineArguments); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/SecurityConfigurationTests.java b/src/integrationTest/java/org/opensearch/security/SecurityConfigurationTests.java new file mode 100644 index 0000000000..2caf05536b --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/SecurityConfigurationTests.java @@ -0,0 +1,215 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.awaitility.Awaitility; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; + +import org.opensearch.client.Client; +import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.TestSecurityConfig.User; +import org.opensearch.test.framework.certificate.TestCertificates; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.opensearch.security.support.ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class SecurityConfigurationTests { + + private static final User USER_ADMIN = new User("admin").roles(ALL_ACCESS); + private static final User LIMITED_USER = new User("limited-user") + .roles(new Role("limited-role").indexPermissions("indices:data/read/search", "indices:data/read/get").on("user-${user.name}")); + public static final String LIMITED_USER_INDEX = "user-" + LIMITED_USER.getName(); + public static final String ADDITIONAL_USER_1 = "additional00001"; + public static final String ADDITIONAL_PASSWORD_1 = ADDITIONAL_USER_1; + + public static final String ADDITIONAL_USER_2 = "additional2"; + public static final String ADDITIONAL_PASSWORD_2 = ADDITIONAL_USER_2; + public static final String CREATE_USER_BODY = "{\"password\": \"%s\",\"opendistro_security_roles\": []}"; + public static final String INTERNAL_USERS_RESOURCE = "_plugins/_security/api/internalusers/"; + public static final String ID_1 = "one"; + public static final String PROHIBITED_INDEX = "prohibited"; + public static final String ID_2 = "two"; + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_ADMIN, LIMITED_USER).anonymousAuth(false) + .nodeSettings(Map.of(SECURITY_RESTAPI_ROLES_ENABLED, List.of("user_" + USER_ADMIN.getName() +"__" + ALL_ACCESS.getName()), + SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, true)) + .build(); + + @Rule + public TemporaryFolder configurationDirectory = new TemporaryFolder(); + + @BeforeClass + public static void initData() { + try(Client client = cluster.getInternalNodeClient()){ + client.prepareIndex(LIMITED_USER_INDEX).setId(ID_1).setRefreshPolicy(IMMEDIATE).setSource("foo", "bar").get(); + client.prepareIndex(PROHIBITED_INDEX).setId(ID_2).setRefreshPolicy(IMMEDIATE).setSource("three", "four").get(); + } + } + + @Test + public void shouldCreateUserViaRestApi_success() { + try(TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse httpResponse = client.putJson(INTERNAL_USERS_RESOURCE + ADDITIONAL_USER_1, String.format(CREATE_USER_BODY, + ADDITIONAL_PASSWORD_1)); + + assertThat(httpResponse.getStatusCode(), equalTo(201)); + } + try(TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + client.assertCorrectCredentials(USER_ADMIN.getName()); + } + try(TestRestClient client = cluster.getRestClient(ADDITIONAL_USER_1, ADDITIONAL_PASSWORD_1)) { + client.assertCorrectCredentials(ADDITIONAL_USER_1); + } + } + + @Test + public void shouldCreateUserViaRestApi_failure() { + try(TestRestClient client = cluster.getRestClient(LIMITED_USER)) { + HttpResponse httpResponse = client.putJson(INTERNAL_USERS_RESOURCE + ADDITIONAL_USER_1, String.format(CREATE_USER_BODY, + ADDITIONAL_PASSWORD_1)); + + httpResponse.assertStatusCode(403); + } + } + + @Test + public void shouldAuthenticateAsAdminWithCertificate_positive() { + try(TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + HttpResponse httpResponse = client.get("/_plugins/_security/whoami"); + + httpResponse.assertStatusCode(200); + assertThat(httpResponse.getTextFromJsonBody("/is_admin"), equalTo("true")); + } + } + + @Test + public void shouldAuthenticateAsAdminWithCertificate_negativeSelfSignedCertificate() { + TestCertificates testCertificates = cluster.getTestCertificates(); + try(TestRestClient client = cluster.getRestClient(testCertificates.createSelfSignedCertificate("CN=bond"))) { + HttpResponse httpResponse = client.get("/_plugins/_security/whoami"); + + httpResponse.assertStatusCode(200); + assertThat(httpResponse.getTextFromJsonBody("/is_admin"), equalTo("false")); + } + } + + @Test + public void shouldAuthenticateAsAdminWithCertificate_negativeIncorrectDn() { + TestCertificates testCertificates = cluster.getTestCertificates(); + try(TestRestClient client = cluster.getRestClient(testCertificates.createAdminCertificate("CN=non_admin"))) { + HttpResponse httpResponse = client.get("/_plugins/_security/whoami"); + + httpResponse.assertStatusCode(200); + assertThat(httpResponse.getTextFromJsonBody("/is_admin"), equalTo("false")); + } + } + + @Test + public void shouldCreateUserViaRestApiWhenAdminIsAuthenticatedViaCertificate_positive() { + try(TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + + HttpResponse httpResponse = client.putJson(INTERNAL_USERS_RESOURCE + ADDITIONAL_USER_2, String.format(CREATE_USER_BODY, + ADDITIONAL_PASSWORD_2)); + + httpResponse.assertStatusCode(201); + } + try(TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + client.assertCorrectCredentials(USER_ADMIN.getName()); + } + try(TestRestClient client = cluster.getRestClient(ADDITIONAL_USER_2, ADDITIONAL_PASSWORD_2)) { + client.assertCorrectCredentials(ADDITIONAL_USER_2); + } + } + + @Test + public void shouldCreateUserViaRestApiWhenAdminIsAuthenticatedViaCertificate_negative() { + TestCertificates testCertificates = cluster.getTestCertificates(); + try(TestRestClient client = cluster.getRestClient(testCertificates.createSelfSignedCertificate("CN=attacker"))) { + HttpResponse httpResponse = client.putJson(INTERNAL_USERS_RESOURCE + ADDITIONAL_USER_2, String.format(CREATE_USER_BODY, + ADDITIONAL_PASSWORD_2)); + + httpResponse.assertStatusCode(401); + } + } + + @Test + public void shouldStillWorkAfterUpdateOfSecurityConfig() { + List users = new ArrayList<>(cluster.getConfiguredUsers()); + User newUser = new User("new-user"); + users.add(newUser); + + cluster.updateUserConfiguration(users); + + try(TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + client.assertCorrectCredentials(USER_ADMIN.getName()); + } + try(TestRestClient client = cluster.getRestClient(newUser)) { + client.assertCorrectCredentials(newUser.getName()); + } + } + + @Test + public void shouldAccessIndexWithPlaceholder_positive() { + try(TestRestClient client = cluster.getRestClient(LIMITED_USER)) { + HttpResponse httpResponse = client.get("/" + LIMITED_USER_INDEX + "/_doc/" + ID_1); + + httpResponse.assertStatusCode(200); + } + } + + @Test + public void shouldAccessIndexWithPlaceholder_negative() { + try(TestRestClient client = cluster.getRestClient(LIMITED_USER)) { + HttpResponse httpResponse = client.get("/" + PROHIBITED_INDEX + "/_doc/" + ID_2); + + httpResponse.assertStatusCode(403); + } + } + + @Test + public void shouldUseSecurityAdminTool() throws Exception { + SecurityAdminLauncher securityAdminLauncher = new SecurityAdminLauncher(cluster.getHttpPort(), cluster.getTestCertificates()); + File rolesMapping = configurationDirectory.newFile("roles_mapping.yml"); + ConfigurationFiles.createRoleMappingFile(rolesMapping); + + int exitCode = securityAdminLauncher.updateRoleMappings(rolesMapping); + + assertThat(exitCode, equalTo(0)); + try(TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + Awaitility.await().alias("Waiting for rolemapping 'readall' availability.") + .until(() -> client.get("_plugins/_security/api/rolesmapping/readall").getStatusCode(), equalTo(200)); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java b/src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java index 1c06bd9cff..e0e9d5beb6 100644 --- a/src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java +++ b/src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java @@ -79,7 +79,7 @@ public void shouldBlockUserWhenNumberOfFailureLoginAttemptIsEqualToLimit() { } @Test - public void shouldBlockUserWhenNumberOfFailureLoginAttemptIsGraterThanLimit() { + public void shouldBlockUserWhenNumberOfFailureLoginAttemptIsGreaterThanLimit() { authenticateUserWithIncorrectPassword(USER_3, ALLOWED_TRIES * 2); try(TestRestClient client = cluster.getRestClient(USER_3)) { HttpResponse response = client.getAuthInfo(); diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index ed58a9f60a..15e9e48b19 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -29,6 +29,7 @@ package org.opensearch.test.framework; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.ArrayList; @@ -49,20 +50,18 @@ import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.index.IndexRequest; -import org.opensearch.action.support.WriteRequest.RefreshPolicy; +import org.opensearch.action.update.UpdateRequest; import org.opensearch.client.Client; import org.opensearch.common.Strings; import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.xcontent.ToXContentObject; import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.security.action.configupdate.ConfigUpdateAction; -import org.opensearch.security.action.configupdate.ConfigUpdateRequest; -import org.opensearch.security.action.configupdate.ConfigUpdateResponse; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.test.framework.cluster.OpenSearchClientProvider.UserCredentialsHolder; import static org.apache.http.HttpHeaders.AUTHORIZATION; +import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; /** * This class allows the declarative specification of the security configuration; in particular: @@ -135,6 +134,10 @@ public TestSecurityConfig user(User user) { return this; } + public List getUsers() { + return new ArrayList<>(internalUsers.values()); + } + public TestSecurityConfig roles(Role... roles) { for (Role role : roles) { if(this.roles.containsKey(role.name)) { @@ -596,13 +599,14 @@ public void initIndex(Client client) { writeConfigToIndex(client, CType.ROLESMAPPING, rolesMapping); writeEmptyConfigToIndex(client, CType.ACTIONGROUPS); writeEmptyConfigToIndex(client, CType.TENANTS); + } - ConfigUpdateResponse configUpdateResponse = client.execute(ConfigUpdateAction.INSTANCE, - new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0]))).actionGet(); - - if (configUpdateResponse.hasFailures()) { - throw new RuntimeException("ConfigUpdateResponse produced failures: " + configUpdateResponse.failures()); + public void updateInternalUsersConfiguration(Client client, List users) { + Map userMap = new HashMap<>(); + for(User user : users) { + userMap.put(user.getName(), user); } + updateConfigInIndex(client, CType.INTERNALUSERS, userMap); } @@ -621,33 +625,54 @@ private void writeEmptyConfigToIndex(Client client, CType configType) { private void writeConfigToIndex(Client client, CType configType, Map config) { try { - XContentBuilder builder = XContentFactory.jsonBuilder(); - - builder.startObject(); - builder.startObject("_meta"); - builder.field("type", configType.toLCString()); - builder.field("config_version", 2); - builder.endObject(); - - for (Map.Entry entry : config.entrySet()) { - builder.field(entry.getKey(), entry.getValue()); - } - - builder.endObject(); - - String json = Strings.toString(builder); + String json = configToJson(configType, config); log.info("Writing security configuration into index " + configType + ":\n" + json); + BytesReference bytesReference = toByteReference(json); client.index(new IndexRequest(indexName).id(configType.toLCString()) - .setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(configType.toLCString(), - BytesReference.fromByteBuffer(ByteBuffer.wrap(json.getBytes("utf-8"))))) + .setRefreshPolicy(IMMEDIATE).source(configType.toLCString(), bytesReference)) .actionGet(); } catch (Exception e) { throw new RuntimeException("Error while initializing config for " + indexName, e); } } + private static BytesReference toByteReference(String string) throws UnsupportedEncodingException { + return BytesReference.fromByteBuffer(ByteBuffer.wrap(string.getBytes("utf-8"))); + } + + private void updateConfigInIndex(Client client, CType configType, Map config) { + try { + String json = configToJson(configType, config); + BytesReference bytesReference = toByteReference(json); + log.info("Update configuration of type '{}' in index '{}', new value '{}'.", configType, indexName, json); + UpdateRequest upsert = new UpdateRequest(indexName, configType.toLCString()).doc(configType.toLCString(), bytesReference) + .setRefreshPolicy(IMMEDIATE); + client.update(upsert).actionGet(); + } catch (Exception e) { + throw new RuntimeException("Error while updating config for " + indexName, e); + } + } + + private static String configToJson(CType configType, Map config) throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder(); + + builder.startObject(); + builder.startObject("_meta"); + builder.field("type", configType.toLCString()); + builder.field("config_version", 2); + builder.endObject(); + + for (Map.Entry entry : config.entrySet()) { + builder.field(entry.getKey(), entry.getValue()); + } + + builder.endObject(); + + return Strings.toString(builder); + } + private void writeSingleEntryConfigToIndex(Client client, CType configType, ToXContentObject config) { writeSingleEntryConfigToIndex(client, configType, configType.toLCString(), config); } @@ -671,8 +696,7 @@ private void writeSingleEntryConfigToIndex(Client client, CType configType, Stri log.info("Writing security plugin configuration into index " + configType + ":\n" + json); client.index(new IndexRequest(indexName).id(configType.toLCString()) - .setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(configType.toLCString(), - BytesReference.fromByteBuffer(ByteBuffer.wrap(json.getBytes("utf-8"))))) + .setRefreshPolicy(IMMEDIATE).source(configType.toLCString(), toByteReference(json))) .actionGet(); } catch (Exception e) { throw new RuntimeException("Error while initializing config for " + indexName, e); diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java index 57d8ce012e..14e6357330 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java @@ -73,8 +73,8 @@ public TestCertificates() { this.nodeCertificates = IntStream.range(0, MAX_NUMBER_OF_NODE_CERTIFICATES) .mapToObj(this::createNodeCertificate) .collect(Collectors.toList()); - this.adminCertificate = createAdminCertificate(); this.ldapCertificate = createLdapCertificate(); + this.adminCertificate = createAdminCertificate(ADMIN_DN); log.info("Test certificates successfully generated"); } @@ -86,8 +86,8 @@ private CertificateData createCaCertificate() { .issueSelfSignedCertificate(metadata); } - private CertificateData createAdminCertificate() { - CertificateMetadata metadata = CertificateMetadata.basicMetadata(ADMIN_DN, CERTIFICATE_VALIDITY_DAYS) + public CertificateData createAdminCertificate(String adminDn) { + CertificateMetadata metadata = CertificateMetadata.basicMetadata(adminDn, CERTIFICATE_VALIDITY_DAYS) .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH); return CertificatesIssuerFactory .rsaBaseCertificateIssuer() @@ -175,7 +175,6 @@ public CertificateData getLdapCertificateData() { * @param privateKeyPassword is a password used to encode private key, can be null to retrieve unencrypted key. * @return file which contains private key encoded in PEM format, defined * by RFC 1421 - * @throws IOException */ public File getNodeKey(int node, String privateKeyPassword) { CertificateData certificateData = nodeCertificates.get(node); @@ -186,7 +185,6 @@ public File getNodeKey(int node, String privateKeyPassword) { * Certificate which proofs admin user identity. Certificate is derived from root certificate returned by * method {@link #getRootCertificate()} * @return file which contains certificate in PEM format, defined by RFC 1421 - * @throws IOException */ public File getAdminCertificate() { return createTempFile("admin", CERTIFICATE_FILE_EXTENSION, adminCertificate.certificateInPemFormat()); @@ -202,9 +200,8 @@ public CertificateData getAdminCertificateData() { * @param privateKeyPassword is a password used to encode private key, can be null to retrieve unencrypted key. * @return file which contains private key encoded in PEM format, defined * by RFC 1421 - * @throws IOException */ - public File getAdminKey(String privateKeyPassword) throws IOException { + public File getAdminKey(String privateKeyPassword) { return createTempFile("admin", KEY_FILE_EXTENSION, adminCertificate.privateKeyInPemFormat(privateKeyPassword)); } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index ea27d7a2b1..e9bb7b5be5 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -28,7 +28,6 @@ package org.opensearch.test.framework.cluster; -import java.io.File; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; @@ -39,6 +38,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.rules.ExternalResource; @@ -47,6 +47,10 @@ import org.opensearch.common.settings.Settings; import org.opensearch.node.PluginAwareNode; import org.opensearch.plugins.Plugin; +import org.opensearch.security.action.configupdate.ConfigUpdateAction; +import org.opensearch.security.action.configupdate.ConfigUpdateRequest; +import org.opensearch.security.action.configupdate.ConfigUpdateResponse; +import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.support.ConfigConstants; import org.opensearch.test.framework.AuditConfiguration; import org.opensearch.test.framework.AuthFailureListeners; @@ -57,6 +61,7 @@ import org.opensearch.test.framework.TestSecurityConfig.Role; import org.opensearch.test.framework.XffConfig; import org.opensearch.test.framework.audit.TestRuleAuditLogSink; +import org.opensearch.test.framework.certificate.CertificateData; import org.opensearch.test.framework.certificate.TestCertificates; /** @@ -71,13 +76,11 @@ public class LocalCluster extends ExternalResource implements AutoCloseable, Ope private static final Logger log = LogManager.getLogger(LocalCluster.class); - static { - System.setProperty("security.default_init.dir", new File("./securityconfig").getAbsolutePath()); - } + public static final String INIT_CONFIGURATION_DIR = "security.default_init.dir"; protected static final AtomicLong num = new AtomicLong(); - private boolean sslOnly = false; + private boolean sslOnly; private final List> plugins; private final ClusterManager clusterManager; @@ -96,7 +99,7 @@ public class LocalCluster extends ExternalResource implements AutoCloseable, Ope private LocalCluster(String clusterName, TestSecurityConfig testSgConfig, boolean sslOnly, Settings nodeOverride, ClusterManager clusterManager, List> plugins, TestCertificates testCertificates, List clusterDependencies, Map remotes, List testIndices, - boolean loadConfigurationIntoIndex) { + boolean loadConfigurationIntoIndex, String defaultConfigurationInitDirectory) { this.plugins = plugins; this.testCertificates = testCertificates; this.clusterManager = clusterManager; @@ -109,6 +112,9 @@ private LocalCluster(String clusterName, TestSecurityConfig testSgConfig, boolea this.clusterDependencies = clusterDependencies; this.testIndices = testIndices; this.loadConfigurationIntoIndex = loadConfigurationIntoIndex; + if(StringUtils.isNoneBlank(defaultConfigurationInitDirectory)) { + System.setProperty(INIT_CONFIGURATION_DIR, defaultConfigurationInitDirectory); + } } public String getSnapshotDirPath() { @@ -134,13 +140,13 @@ public void before() throws Throwable { .putList(key, value) .build(); } - start(); } } @Override protected void after() { + System.clearProperty(INIT_CONFIGURATION_DIR); close(); } @@ -207,6 +213,10 @@ public boolean isStarted() { return localOpenSearchCluster != null; } + public List getConfiguredUsers() { + return testSecurityConfig.getUsers(); + } + public Random getRandom() { return localOpenSearchCluster.getRandom(); } @@ -239,9 +249,28 @@ private void initSecurityIndex(TestSecurityConfig testSecurityConfig) { log.info("Initializing OpenSearch Security index"); try(Client client = new ContextHeaderDecoratorClient(this.getInternalNodeClient(), Map.of(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER , "true"))) { testSecurityConfig.initIndex(client); + triggerConfigurationReload(client); + } + } + + public void updateUserConfiguration(List users) { + try(Client client = new ContextHeaderDecoratorClient(this.getInternalNodeClient(), Map.of(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER , "true"))) { + testSecurityConfig.updateInternalUsersConfiguration(client, users); + triggerConfigurationReload(client); + } + } + + private static void triggerConfigurationReload(Client client) { + ConfigUpdateResponse configUpdateResponse = client.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0]))).actionGet(); + if (configUpdateResponse.hasFailures()) { + throw new RuntimeException("ConfigUpdateResponse produced failures: " + configUpdateResponse.failures()); } } + public CertificateData getAdminCertificate() { + return testCertificates.getAdminCertificateData(); + } + public static class Builder { private final Settings.Builder nodeOverrideSettingsBuilder = Settings.builder(); @@ -258,6 +287,8 @@ public static class Builder { private boolean loadConfigurationIntoIndex = true; + private String defaultConfigurationInitDirectory = null; + public Builder() { } @@ -352,17 +383,21 @@ public Builder users(TestSecurityConfig.User... users) { } public Builder audit(AuditConfiguration auditConfiguration) { - if(auditConfiguration != null) { + if (auditConfiguration != null) { testSecurityConfig.audit(auditConfiguration); } - if(auditConfiguration.isEnabled()) { + if (auditConfiguration.isEnabled()) { nodeOverrideSettingsBuilder.put("plugins.security.audit.type", TestRuleAuditLogSink.class.getName()); } else { - nodeOverrideSettingsBuilder.put("plugins.security.audit.type","noop"); + nodeOverrideSettingsBuilder.put("plugins.security.audit.type", "noop"); } return this; } + public List getUsers() { + return testSecurityConfig.getUsers(); + } + public Builder roles(Role... roles) { testSecurityConfig.roles(roles); return this; @@ -422,6 +457,11 @@ public Builder doNotFailOnForbidden(boolean doNotFailOnForbidden) { return this; } + public Builder defaultConfigurationInitDirectory(String defaultConfigurationInitDirectory){ + this.defaultConfigurationInitDirectory = defaultConfigurationInitDirectory; + return this; + } + public LocalCluster build() { try { if(testCertificates == null) { @@ -429,8 +469,8 @@ public LocalCluster build() { } clusterName += "_" + num.incrementAndGet(); Settings settings = nodeOverrideSettingsBuilder.build(); - return new LocalCluster(clusterName, testSecurityConfig, sslOnly, settings, clusterManager, plugins, - testCertificates, clusterDependencies, remoteClusters, testIndices, loadConfigurationIntoIndex); + return new LocalCluster(clusterName, testSecurityConfig, sslOnly, settings, clusterManager, plugins, testCertificates, + clusterDependencies, remoteClusters, testIndices, loadConfigurationIntoIndex, defaultConfigurationInitDirectory); } catch (Exception e) { log.error("Failed to build LocalCluster", e); throw new RuntimeException(e); diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java index cf35962565..318dce63d6 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java @@ -70,6 +70,7 @@ private Settings.Builder minimumOpenSearchSettingsBuilder(int node, boolean sslO builder.putList("plugins.security.authcz.admin_dn", testCertificates.getAdminDNs()); builder.put("plugins.security.compliance.salt", "1234567890123456"); builder.put("plugins.security.audit.type", "noop"); + builder.put("plugins.security.background_init_if_securityindex_not_exist", "false"); } return builder; diff --git a/src/integrationTest/resources/action_groups.yml b/src/integrationTest/resources/action_groups.yml new file mode 100644 index 0000000000..32188f69d0 --- /dev/null +++ b/src/integrationTest/resources/action_groups.yml @@ -0,0 +1,4 @@ +--- +_meta: + type: "actiongroups" + config_version: 2 diff --git a/src/integrationTest/resources/allowlist.yml b/src/integrationTest/resources/allowlist.yml new file mode 100644 index 0000000000..d1b4540d6d --- /dev/null +++ b/src/integrationTest/resources/allowlist.yml @@ -0,0 +1,4 @@ +--- +_meta: + type: "allowlist" + config_version: 2 diff --git a/src/integrationTest/resources/config.yml b/src/integrationTest/resources/config.yml new file mode 100644 index 0000000000..5e929c0e2a --- /dev/null +++ b/src/integrationTest/resources/config.yml @@ -0,0 +1,17 @@ +--- +_meta: + type: "config" + config_version: 2 +config: + dynamic: + authc: + basic: + http_enabled: true + order: 0 + http_authenticator: + type: "basic" + challenge: true + config: {} + authentication_backend: + type: "internal" + config: {} diff --git a/src/integrationTest/resources/internal_users.yml b/src/integrationTest/resources/internal_users.yml new file mode 100644 index 0000000000..866a879165 --- /dev/null +++ b/src/integrationTest/resources/internal_users.yml @@ -0,0 +1,14 @@ +--- +_meta: + type: "internalusers" + config_version: 2 +new-user: + hash: "$2y$12$d2KAKcGE9qoywfu.c.hV/.pHigC7HTZFp2yJzBo8z2w.585t7XDWO" +limited-user: + hash: "$2y$12$fOJAMx0U7e7M4OObVPzm6eUTnAyN/Gtpzfv34M6PL1bfusae43a52" + opendistro_security_roles: + - "user_limited-user__limited-role" +admin: + hash: "$2y$12$53iW.RRy.uumsmU7lrlp7OUCPdxz40Z5uIJo1WcCC2GNFwEWNiTD6" + opendistro_security_roles: + - "user_admin__all_access" diff --git a/src/integrationTest/resources/nodes_dn.yml b/src/integrationTest/resources/nodes_dn.yml new file mode 100644 index 0000000000..437583b160 --- /dev/null +++ b/src/integrationTest/resources/nodes_dn.yml @@ -0,0 +1,4 @@ +--- +_meta: + type: "nodesdn" + config_version: 2 diff --git a/src/integrationTest/resources/roles.yml b/src/integrationTest/resources/roles.yml new file mode 100644 index 0000000000..ef4765e25f --- /dev/null +++ b/src/integrationTest/resources/roles.yml @@ -0,0 +1,19 @@ +--- +_meta: + type: "roles" + config_version: 2 +user_admin__all_access: + cluster_permissions: + - "*" + index_permissions: + - index_patterns: + - "*" + allowed_actions: + - "*" +user_limited-user__limited-role: + index_permissions: + - index_patterns: + - "user-${user.name}" + allowed_actions: + - "indices:data/read/search" + - "indices:data/read/get" diff --git a/src/integrationTest/resources/roles_mapping.yml b/src/integrationTest/resources/roles_mapping.yml new file mode 100644 index 0000000000..193f999176 --- /dev/null +++ b/src/integrationTest/resources/roles_mapping.yml @@ -0,0 +1,9 @@ +--- +_meta: + type: "rolesmapping" + config_version: 2 + +readall: + reserved: false + backend_roles: + - "readall" diff --git a/src/integrationTest/resources/security_tenants.yml b/src/integrationTest/resources/security_tenants.yml new file mode 100644 index 0000000000..93b510dd16 --- /dev/null +++ b/src/integrationTest/resources/security_tenants.yml @@ -0,0 +1,4 @@ +--- +_meta: + type: "tenants" + config_version: 2 diff --git a/src/integrationTest/resources/tenants.yml b/src/integrationTest/resources/tenants.yml new file mode 100644 index 0000000000..add18ebd54 --- /dev/null +++ b/src/integrationTest/resources/tenants.yml @@ -0,0 +1,8 @@ +--- +_meta: + type: "tenants" + config_version: 2 + +admin_tenant: + reserved: false + description: "Test tenant for admin user" diff --git a/src/integrationTest/resources/whitelist.yml b/src/integrationTest/resources/whitelist.yml new file mode 100644 index 0000000000..866ffe9eb3 --- /dev/null +++ b/src/integrationTest/resources/whitelist.yml @@ -0,0 +1,4 @@ +--- +_meta: + type: "whitelist" + config_version: 2 From ff798dde5bda2d42053acc1ba9031a4b7ca2a708 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 13 Dec 2022 13:11:45 -0500 Subject: [PATCH 100/369] Remove jackson-databind and jackson-annotations dependency now coming from core (#2325) * Remove jackson-databind dependency now coming from core Signed-off-by: Craig Perkins Signed-off-by: Craig Perkins --- build.gradle | 5 ----- 1 file changed, 5 deletions(-) diff --git a/build.gradle b/build.gradle index 81b8c75d03..e8978a2c85 100644 --- a/build.gradle +++ b/build.gradle @@ -427,11 +427,6 @@ dependencies { testRuntimeOnly "org.apache.kafka:kafka-metadata:${kafka_version}" testRuntimeOnly "org.apache.kafka:kafka-storage:${kafka_version}" - - - implementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}" - implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" - compileOnly "org.opensearch:opensearch:${opensearch_version}" //integration test framework: From 6bee3e06155a27b034082967c7657beddf9ce3e9 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Tue, 13 Dec 2022 14:04:08 -0600 Subject: [PATCH 101/369] Add @RyanL1997 to maintainers (#2338) Note this was an oversight as it should have happened at the same time as https://github.com/opensearch-project/security-dashboards-plugin/pull/1130 Signed-off-by: Peter Nied --- MAINTAINERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 03860dc6bf..ef0bbe9dfd 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -16,6 +16,7 @@ | Dave Lago | [davidlago](https://github.com/davidlago) | Amazon | | Peter Nied | [peternied](https://github.com/peternied) | Amazon | | Craig Perkins | [cwperks](https://github.com/cwperks) | Amazon | +| Ryan Liang | [RyanL1997](https://github.com/RyanL1997) | Amazon | ### Updating Practices To ensure common practices as maintainers, all practices are expected to be documented here or enforced through github actions. There should be no expectations beyond what is documented in the repo [CONTRIBUTING.md](./CONTRIBUTING.md) and OpenSearch-Project [CONTRIBUTING.md](https://github.com/opensearch-project/.github/blob/main/CONTRIBUTING.md). To modify an existing processes or create a new one, make a pull request on this MAINTAINERS.md for review and merge it after all maintainers approve of it. From 9c7babdacafaaee3ab35b060f5ccc11b79750e1c Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 13 Dec 2022 15:32:50 -0500 Subject: [PATCH 102/369] Add 2.4.1.0 release notes (#2327) Signed-off-by: Craig Perkins --- release-notes/opensearch-security.release-notes-2.4.1.0.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-2.4.1.0.md diff --git a/release-notes/opensearch-security.release-notes-2.4.1.0.md b/release-notes/opensearch-security.release-notes-2.4.1.0.md new file mode 100644 index 0000000000..ae58e917e2 --- /dev/null +++ b/release-notes/opensearch-security.release-notes-2.4.1.0.md @@ -0,0 +1,7 @@ +## 2022-12-13 Version 2.4.1.0 + +Compatible with OpenSearch 2.4.1 + +### Maintenance +* Username validation for special characters ([#2277](https://github.com/opensearch-project/security/pull/2277)) +* [Backport 2.4] Fixes CVE-2022-42920 by forcing bcel version to resovle to 6.6 ([#2303](https://github.com/opensearch-project/security/pull/2303)) From cc45b046a356ffc1ade76f624e171267857a423f Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 13 Dec 2022 15:33:09 -0500 Subject: [PATCH 103/369] Add release notes for 1.3.7.0 (#2328) Signed-off-by: Craig Perkins --- ...pensearch-security.release-notes-1.3.7.0.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-1.3.7.0.md diff --git a/release-notes/opensearch-security.release-notes-1.3.7.0.md b/release-notes/opensearch-security.release-notes-1.3.7.0.md new file mode 100644 index 0000000000..abb08b79e1 --- /dev/null +++ b/release-notes/opensearch-security.release-notes-1.3.7.0.md @@ -0,0 +1,18 @@ +## 2022-12-13 Version 1.3.7.0 + +Compatible with OpenSearch 1.3.7 + +### Bug fixes + +* Conditionally serialize with ODFE package if min version of node in cluster is less than OS 1.0.0 ([#2268](https://github.com/opensearch-project/security/pull/2268)) +* [Backport 1.3] Fix issues with datastream backing indexes ([#2247](https://github.com/opensearch-project/security/pull/2247)) + +### Maintenance + +* [Backport 1.3] Fixes CVE-2022-42920 by forcing bcel version to resovle to 6.6 ([#2304](https://github.com/opensearch-project/security/pull/2304)) +* [Backport 1.3] Add plugin install workflow and action ([#2300](https://github.com/opensearch-project/security/pull/2300)) +* Windows build and test support for 1.3 ([#2291](https://github.com/opensearch-project/security/pull/2291)) +* 1.3 branch version increment of for jackson to match with core ([#2286](https://github.com/opensearch-project/security/pull/2286)) +* Update Dependency Versions: Woodstox 6.4.0, Scala-lang 2.13.9, Jackson-Databind 2.14.0 ([#2270](https://github.com/opensearch-project/security/pull/2270)) +* [Backport 1.3] Add install_demo_configuration Batch script for Windows ([#2273](https://github.com/opensearch-project/security/pull/2273)) +* Address CVE-2022-42889 by updating commons-text ([#2241](https://github.com/opensearch-project/security/pull/2241)) From 5116d9902dde90ce4c16e88ca7bfb51183eca834 Mon Sep 17 00:00:00 2001 From: kt-eliatra <103500997+kt-eliatra@users.noreply.github.com> Date: Thu, 15 Dec 2022 16:41:23 +0100 Subject: [PATCH 104/369] Fls dls field masking tests (#2258) Adds integration tests for FLS DLS field masking Signed-off-by: Kacper Trochimiak --- .../security/CrossClusterSearchTests.java | 12 +- .../security/DoNotFailOnForbiddenTests.java | 8 +- .../security/FlsDlsAndFieldMaskingTest.java | 975 ++++++++++++++++++ .../security/SearchOperationTest.java | 113 +- .../java/org/opensearch/security/Song.java | 35 +- .../http/ExtendedProxyAuthenticationTest.java | 4 +- .../security/http/JwtAuthenticationTests.java | 2 +- .../http/LdapTlsAuthenticationTest.java | 6 +- .../cluster/SearchRequestFactory.java | 9 + .../framework/cluster/TestRestClient.java | 12 + .../ContainsExactlyIndicesMatcher.java | 46 + .../matcher/ContainsFieldWithTypeMatcher.java | 51 + .../FieldCapabilitiesResponseMatchers.java | 32 + ...esponseContainsDocumentWithIdMatcher.java} | 8 +- ...ContainsExactlyFieldsWithNamesMatcher.java | 49 + ...nseDocumentDoesNotContainFieldMatcher.java | 47 + .../matcher/GetResponseMatchers.java | 10 +- .../matcher/MultiGetResponseMatchers.java | 29 + .../matcher/MultiSearchResponseMatchers.java | 29 + .../NumberOfFieldsIsEqualToMatcher.java | 39 + ...berOfGetItemResponsesIsEqualToMatcher.java | 40 + ...OfSearchItemResponsesIsEqualToMatcher.java | 41 + .../SuccessfulMultiGetResponseMatcher.java | 37 + .../SuccessfulMultiSearchResponseMatcher.java | 35 + 24 files changed, 1584 insertions(+), 85 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/FlsDlsAndFieldMaskingTest.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsExactlyIndicesMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsFieldWithTypeMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/FieldCapabilitiesResponseMatchers.java rename src/integrationTest/java/org/opensearch/test/framework/matcher/{GetResponseDocumentIdMatcher.java => GetResponseContainsDocumentWithIdMatcher.java} (83%) create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentContainsExactlyFieldsWithNamesMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentDoesNotContainFieldMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/MultiGetResponseMatchers.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/MultiSearchResponseMatchers.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfFieldsIsEqualToMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfGetItemResponsesIsEqualToMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfSearchItemResponsesIsEqualToMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiGetResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiSearchResponseMatcher.java diff --git a/src/integrationTest/java/org/opensearch/security/CrossClusterSearchTests.java b/src/integrationTest/java/org/opensearch/security/CrossClusterSearchTests.java index 5f812b921b..6762274133 100644 --- a/src/integrationTest/java/org/opensearch/security/CrossClusterSearchTests.java +++ b/src/integrationTest/java/org/opensearch/security/CrossClusterSearchTests.java @@ -155,14 +155,14 @@ public CrossClusterSearchTests(Boolean ccsMinimizeRoundtrips) { @BeforeClass public static void createTestData() { try(Client client = remoteCluster.getInternalNodeClient()){ - client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_1R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0]).get(); - client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_6R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[5]).get(); - client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(SONG_ID_3R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1]).get(); - client.prepareIndex(LIMITED_USER_INDEX_NAME).setId(SONG_ID_5R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[4]).get(); + client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_1R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); + client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_6R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[5].asMap()).get(); + client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(SONG_ID_3R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1].asMap()).get(); + client.prepareIndex(LIMITED_USER_INDEX_NAME).setId(SONG_ID_5R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[4].asMap()).get(); } try(Client client = cluster.getInternalNodeClient()){ - client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_2L).setRefreshPolicy(IMMEDIATE).setSource(SONGS[2]).get(); - client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(SONG_ID_4L).setRefreshPolicy(IMMEDIATE).setSource(SONGS[3]).get(); + client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_2L).setRefreshPolicy(IMMEDIATE).setSource(SONGS[2].asMap()).get(); + client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(SONG_ID_4L).setRefreshPolicy(IMMEDIATE).setSource(SONGS[3].asMap()).get(); } try(TestRestClient client = cluster.getRestClient(ADMIN_USER)) { client.assignRoleToUser(LIMITED_USER.getName(), LIMITED_ROLE.getName()).assertStatusCode(200); diff --git a/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java b/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java index b4e582507e..67d61b7d6f 100644 --- a/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java +++ b/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java @@ -115,11 +115,11 @@ public class DoNotFailOnForbiddenTests { @BeforeClass public static void createTestData() { try(Client client = cluster.getInternalNodeClient()) { - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_1).source(SONGS[0])).actionGet(); - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_2).source(SONGS[1])).actionGet(); - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_3).source(SONGS[2])).actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_1).source(SONGS[0].asMap())).actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_2).source(SONGS[1].asMap())).actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_3).source(SONGS[2].asMap())).actionGet(); - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(HORRIBLE_SONGS).id(ID_4).source(SONGS[3])).actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(HORRIBLE_SONGS).id(ID_4).source(SONGS[3].asMap())).actionGet(); client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD).indices( MARVELOUS_SONGS, HORRIBLE_SONGS).alias(BOTH_INDEX_ALIAS))).actionGet(); diff --git a/src/integrationTest/java/org/opensearch/security/FlsDlsAndFieldMaskingTest.java b/src/integrationTest/java/org/opensearch/security/FlsDlsAndFieldMaskingTest.java new file mode 100644 index 0000000000..4bef7ef4f6 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/FlsDlsAndFieldMaskingTest.java @@ -0,0 +1,975 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.opensearch.action.fieldcaps.FieldCapabilitiesRequest; +import org.opensearch.action.fieldcaps.FieldCapabilitiesResponse; +import org.opensearch.action.get.GetRequest; +import org.opensearch.action.get.GetResponse; +import org.opensearch.action.get.MultiGetItemResponse; +import org.opensearch.action.get.MultiGetRequest; +import org.opensearch.action.get.MultiGetResponse; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.index.IndexResponse; +import org.opensearch.action.search.MultiSearchRequest; +import org.opensearch.action.search.MultiSearchResponse; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.action.search.SearchScrollRequest; +import org.opensearch.client.Client; +import org.opensearch.client.RestHighLevelClient; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.search.aggregations.Aggregation; +import org.opensearch.search.aggregations.metrics.ParsedAvg; +import org.opensearch.search.sort.SortOrder; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.everyItem; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions.Type.ADD; +import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.opensearch.client.RequestOptions.DEFAULT; +import static org.opensearch.security.Song.ARTIST_FIRST; +import static org.opensearch.security.Song.ARTIST_NO; +import static org.opensearch.security.Song.ARTIST_STRING; +import static org.opensearch.security.Song.ARTIST_TWINS; +import static org.opensearch.security.Song.FIELD_ARTIST; +import static org.opensearch.security.Song.FIELD_LYRICS; +import static org.opensearch.security.Song.FIELD_STARS; +import static org.opensearch.security.Song.FIELD_TITLE; +import static org.opensearch.security.Song.QUERY_TITLE_NEXT_SONG; +import static org.opensearch.security.Song.SONGS; +import static org.opensearch.security.Song.TITLE_NEXT_SONG; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.averageAggregationRequest; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.getSearchScrollRequest; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.queryByIdsRequest; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.searchRequestWithScroll; +import static org.opensearch.test.framework.matcher.FieldCapabilitiesResponseMatchers.containsExactlyIndices; +import static org.opensearch.test.framework.matcher.FieldCapabilitiesResponseMatchers.containsFieldWithNameAndType; +import static org.opensearch.test.framework.matcher.FieldCapabilitiesResponseMatchers.numberOfFieldsIsEqualTo; +import static org.opensearch.test.framework.matcher.GetResponseMatchers.containDocument; +import static org.opensearch.test.framework.matcher.GetResponseMatchers.documentContainField; +import static org.opensearch.test.framework.matcher.GetResponseMatchers.documentDoesNotContainField; +import static org.opensearch.test.framework.matcher.MultiGetResponseMatchers.isSuccessfulMultiGetResponse; +import static org.opensearch.test.framework.matcher.MultiGetResponseMatchers.numberOfGetItemResponsesIsEqualTo; +import static org.opensearch.test.framework.matcher.MultiSearchResponseMatchers.isSuccessfulMultiSearchResponse; +import static org.opensearch.test.framework.matcher.MultiSearchResponseMatchers.numberOfSearchItemResponsesIsEqualTo; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.containAggregationWithNameAndType; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.containNotEmptyScrollingId; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.isSuccessfulSearchResponse; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.numberOfTotalHitsIsEqualTo; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitContainsFieldWithValue; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitDoesNotContainField; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitsContainDocumentWithId; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class FlsDlsAndFieldMaskingTest { + + static final String FIRST_INDEX_ID_SONG_1 = "INDEX_1_S1"; + static final String FIRST_INDEX_ID_SONG_2 = "INDEX_1_S2"; + static final String FIRST_INDEX_ID_SONG_3 = "INDEX_1_S3"; + static final String FIRST_INDEX_ID_SONG_4 = "INDEX_1_S4"; + static final String SECOND_INDEX_ID_SONG_1 = "INDEX_2_S1"; + static final String SECOND_INDEX_ID_SONG_2 = "INDEX_2_S2"; + static final String SECOND_INDEX_ID_SONG_3 = "INDEX_2_S3"; + static final String SECOND_INDEX_ID_SONG_4 = "INDEX_2_S4"; + + static final String INDEX_NAME_SUFFIX = "-test-index"; + static final String FIRST_INDEX_NAME = "first".concat(INDEX_NAME_SUFFIX); + static final String SECOND_INDEX_NAME = "second".concat(INDEX_NAME_SUFFIX); + static final String FIRST_INDEX_ALIAS = FIRST_INDEX_NAME.concat("-alias"); + static final String SECOND_INDEX_ALIAS = SECOND_INDEX_NAME.concat("-alias"); + static final String FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE = FIRST_INDEX_NAME.concat("-filtered-by-next-song-title"); + static final String FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST = FIRST_INDEX_NAME.concat("-filtered-by-twins-artist"); + static final String FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST = FIRST_INDEX_NAME.concat("-filtered-by-first-artist"); + static final String ALL_INDICES_ALIAS = "_all"; + + static final String MASK_VALUE = "*"; + + static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + /** + * User who is allowed to see all fields on all indices. Values of the title and artist fields should be masked. + */ + static final TestSecurityConfig.User ALL_INDICES_MASKED_TITLE_ARTIST_READER = new TestSecurityConfig.User("masked_artist_title_reader") + .roles( + new TestSecurityConfig.Role("masked_artist_title_reader") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .maskedFields( + FIELD_TITLE.concat("::/(?<=.{1})./::").concat(MASK_VALUE), + FIELD_ARTIST.concat("::/(?<=.{1})./::").concat(MASK_VALUE) + ) + .on("*") + ); + + /** + * User who is allowed to see all fields on indices {@link #FIRST_INDEX_NAME} and {@link #SECOND_INDEX_NAME}. + *

    + *
  • values of the artist and lyrics fields should be masked on index {@link #FIRST_INDEX_NAME}
  • + *
  • values of the lyrics field should be masked on index {@link #SECOND_INDEX_NAME}
  • + *
+ */ + static final TestSecurityConfig.User MASKED_ARTIST_LYRICS_READER = new TestSecurityConfig.User("masked_title_artist_lyrics_reader") + .roles( + new TestSecurityConfig.Role("masked_title_artist_lyrics_reader") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .maskedFields( + FIELD_ARTIST.concat("::/(?<=.{1})./::").concat(MASK_VALUE), + FIELD_LYRICS.concat("::/(?<=.{1})./::").concat(MASK_VALUE) + ) + .on(FIRST_INDEX_NAME), + new TestSecurityConfig.Role("masked_lyrics_reader") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .maskedFields(FIELD_LYRICS.concat("::/(?<=.{1})./::").concat(MASK_VALUE)) + .on(SECOND_INDEX_NAME) + ); + + /** + * Function that converts field value to value masked with {@link #MASK_VALUE} + */ + static final Function VALUE_TO_MASKED_VALUE = value -> value.substring(0, 1) + .concat(MASK_VALUE.repeat(value.length() - 1)); + + /** + * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_STRING}. + */ + static final TestSecurityConfig.User ALL_INDICES_STRING_ARTIST_READER = new TestSecurityConfig.User("string_artist_reader") + .roles( + new TestSecurityConfig.Role("string_artist_reader") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_STRING)) + .on("*") + ); + + /** + * User who is allowed to see documents on index: + *
    + *
  • {@link #FIRST_INDEX_NAME} where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_TWINS}
  • + *
  • {@link #SECOND_INDEX_NAME} where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_FIRST}
  • + *
+ */ + static final TestSecurityConfig.User TWINS_FIRST_ARTIST_READER = new TestSecurityConfig.User("twins_first_artist_reader") + .roles( + new TestSecurityConfig.Role("twins_artist_reader") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_TWINS)) + .on(FIRST_INDEX_NAME), + new TestSecurityConfig.Role("first_artist_reader") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_FIRST)) + .on(SECOND_INDEX_NAME) + ); + + /** + * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_STARS} is less than zero. + */ + static final TestSecurityConfig.User ALL_INDICES_STARS_LESS_THAN_ZERO_READER = new TestSecurityConfig.User("stars_less_than_zero_reader") + .roles( + new TestSecurityConfig.Role("stars_less_than_zero_reader") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"range\":{\"%s\":{\"lt\":%d}}}", FIELD_STARS, 0)) + .on("*") + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) + .nodeSettings(Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() +"__" + ALL_ACCESS.getName()))) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users( + ADMIN_USER, + ALL_INDICES_MASKED_TITLE_ARTIST_READER, MASKED_ARTIST_LYRICS_READER, + ALL_INDICES_STRING_ARTIST_READER, ALL_INDICES_STARS_LESS_THAN_ZERO_READER, TWINS_FIRST_ARTIST_READER + ) + .build(); + + /** + * Function that returns id assigned to song with title equal to given title or throws {@link RuntimeException} + * when no song matches. + */ + static final BiFunction, String, String> FIND_ID_OF_SONG_WITH_TITLE = (map, title) -> map.entrySet() + .stream().filter(entry -> title.equals(entry.getValue().getTitle())) + .findAny() + .map(Map.Entry::getKey) + .orElseThrow(() -> new RuntimeException("Cannot find id of song with title: " + title)); + + /** + * Function that returns id assigned to song with artist equal to given artist or throws {@link RuntimeException} + * when no song matches. + */ + static final BiFunction, String, String> FIND_ID_OF_SONG_WITH_ARTIST = (map, artist) -> map.entrySet() + .stream().filter(entry -> artist.equals(entry.getValue().getArtist())) + .findAny() + .map(Map.Entry::getKey) + .orElseThrow(() -> new RuntimeException("Cannot find id of song with artist: " + artist)); + + static final TreeMap FIRST_INDEX_SONGS_BY_ID = new TreeMap<>() {{ + put(FIRST_INDEX_ID_SONG_1, SONGS[0]); + put(FIRST_INDEX_ID_SONG_2, SONGS[1]); + put(FIRST_INDEX_ID_SONG_3, SONGS[2]); + put(FIRST_INDEX_ID_SONG_4, SONGS[3]); + }}; + + static final TreeMap SECOND_INDEX_SONGS_BY_ID = new TreeMap<>() {{ + put(SECOND_INDEX_ID_SONG_1, SONGS[3]); + put(SECOND_INDEX_ID_SONG_2, SONGS[2]); + put(SECOND_INDEX_ID_SONG_3, SONGS[1]); + put(SECOND_INDEX_ID_SONG_4, SONGS[0]); + }}; + + @BeforeClass + public static void createTestData() { + try(Client client = cluster.getInternalNodeClient()){ + FIRST_INDEX_SONGS_BY_ID.forEach((id, song) -> { + client.prepareIndex(FIRST_INDEX_NAME).setId(id).setRefreshPolicy(IMMEDIATE).setSource(song.asMap()).get(); + }); + + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) + .indices(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS) + )).actionGet(); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) + .index(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE) + .filter(QueryBuilders.queryStringQuery(QUERY_TITLE_NEXT_SONG)) + )).actionGet(); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) + .index(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST) + .filter(QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, ARTIST_TWINS))) + )).actionGet(); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) + .index(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST) + .filter(QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, ARTIST_FIRST))) + )).actionGet(); + + SECOND_INDEX_SONGS_BY_ID.forEach((id, song) -> { + client.prepareIndex(SECOND_INDEX_NAME).setId(id).setRefreshPolicy(IMMEDIATE).setSource(song.asMap()).get(); + }); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) + .indices(SECOND_INDEX_NAME) + .alias(SECOND_INDEX_ALIAS) + )).actionGet(); + } + } + + @Test + public void flsEnabledFieldsAreHiddenForNormalUsers() throws IOException { + String indexName = "fls_index"; + String indexAlias = "fls_index_alias"; + String indexFilteredAlias = "fls_index_filtered_alias"; + TestSecurityConfig.Role userRole = new TestSecurityConfig.Role("fls_exclude_stars_reader") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .fls("~".concat(FIELD_STARS)) + .on("*"); + TestSecurityConfig.User user = createUserWithRole("fls_user", userRole); + List docIds = createIndexWithDocs(indexName, SONGS[0], SONGS[1]); + addAliasToIndex(indexName, indexAlias); + addAliasToIndex(indexName, indexFilteredAlias, QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, SONGS[0].getArtist()))); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(user)) { + //search + SearchResponse searchResponse = restHighLevelClient.search(new SearchRequest(indexName), DEFAULT); + + assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); + + //search with index pattern + searchResponse = restHighLevelClient.search(new SearchRequest("*".concat(indexName)), DEFAULT); + + assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); + + //search via alias + searchResponse = restHighLevelClient.search(new SearchRequest(indexAlias), DEFAULT); + + assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); + + //search via filtered alias + searchResponse = restHighLevelClient.search(new SearchRequest(indexFilteredAlias), DEFAULT); + + assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); + + //search via all indices alias + searchResponse = restHighLevelClient.search(new SearchRequest(ALL_INDICES_ALIAS), DEFAULT); + + assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); + + //scroll + searchResponse = restHighLevelClient.search(searchRequestWithScroll(indexName, 1), DEFAULT); + + assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); + + SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); + SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); + + assertSearchHitsDoNotContainField(scrollResponse, FIELD_STARS); + + //aggregate data and compute avg + String aggregationName = "averageStars"; + searchResponse = restHighLevelClient.search(averageAggregationRequest(indexName, aggregationName, FIELD_STARS), DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); + assertThat(actualAggregation, instanceOf(ParsedAvg.class)); + assertThat(((ParsedAvg) actualAggregation).getValue(), is(Double.POSITIVE_INFINITY)); //user cannot see the STARS field + + //get document + GetResponse getResponse = restHighLevelClient.get(new GetRequest(indexName, docIds.get(0)), DEFAULT); + + assertThat(getResponse, documentDoesNotContainField(FIELD_STARS)); + + //multi get + for (String index: List.of(indexName, indexAlias)) { + MultiGetRequest multiGetRequest = new MultiGetRequest(); + docIds.forEach(id -> multiGetRequest.add(new MultiGetRequest.Item(index, id))); + + MultiGetResponse multiGetResponse = restHighLevelClient.mget(multiGetRequest, DEFAULT); + + List getResponses = Arrays.stream(multiGetResponse.getResponses()) + .map(MultiGetItemResponse::getResponse) + .collect(Collectors.toList()); + assertThat(getResponses, everyItem(documentDoesNotContainField(FIELD_STARS))); + } + + //multi search + for (String index: List.of(indexName, indexAlias)) { + MultiSearchRequest multiSearchRequest = new MultiSearchRequest(); + docIds.forEach(id -> multiSearchRequest.add(queryByIdsRequest(index, id))); + MultiSearchResponse multiSearchResponse = restHighLevelClient.msearch(multiSearchRequest, DEFAULT); + + assertThat(multiSearchResponse, isSuccessfulMultiSearchResponse()); + List itemResponses = List.of(multiSearchResponse.getResponses()); + itemResponses.forEach(item -> assertSearchHitsDoNotContainField(item.getResponse(), FIELD_STARS)); + } + + //field capabilities + FieldCapabilitiesResponse fieldCapsResponse = restHighLevelClient.fieldCaps( + new FieldCapabilitiesRequest().indices(indexName).fields(FIELD_TITLE, FIELD_STARS), DEFAULT + ); + assertThat(fieldCapsResponse.getField(FIELD_STARS), nullValue()); + } + } + + private static List createIndexWithDocs(String indexName, Song... songs) { + try(Client client = cluster.getInternalNodeClient()){ + return Stream.of(songs).map(song -> { + IndexResponse response = client.index( + new IndexRequest(indexName).setRefreshPolicy(IMMEDIATE).source(song.asMap()) + ).actionGet(); + return response.getId(); + }).collect(Collectors.toList()); + } + } + + private static void addAliasToIndex(String indexName, String alias) { + addAliasToIndex(indexName, alias, QueryBuilders.matchAllQuery()); + } + + private static void addAliasToIndex(String indexName, String alias, QueryBuilder filterQuery) { + try(Client client = cluster.getInternalNodeClient()){ + client.admin().indices() + .aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) + .indices(indexName) + .alias(alias) + .filter(filterQuery) + )).actionGet(); + } + } + + private static TestSecurityConfig.User createUserWithRole(String userName, TestSecurityConfig.Role role) { + TestSecurityConfig.User user = new TestSecurityConfig.User(userName); + try(TestRestClient client = cluster.getRestClient(ADMIN_USER)) { + client.createRole(role.getName(), role).assertStatusCode(201); + client.createUser(user.getName(), user).assertStatusCode(201); + client.assignRoleToUser(user.getName(), role.getName()).assertStatusCode(200); + } + return user; + } + + private static void assertSearchHitsDoNotContainField(SearchResponse response, String excludedField) { + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response.getHits().getHits().length, greaterThan(0)); + IntStream.range(0, response.getHits().getHits().length).boxed() + .forEach(index -> assertThat(response, searchHitDoesNotContainField(index, excludedField))); + } + + @Test + public void searchForDocuments() throws IOException { + //FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String songId = FIRST_INDEX_ID_SONG_1; + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + + SearchRequest searchRequest = queryByIdsRequest(FIRST_INDEX_NAME, songId); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + + songId = SECOND_INDEX_ID_SONG_2; + song = SECOND_INDEX_SONGS_BY_ID.get(songId); + + searchRequest = queryByIdsRequest(SECOND_INDEX_NAME, songId); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, song.getArtist())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + } + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + } + } + + @Test + public void searchForDocumentsWithIndexPattern() throws IOException { + //FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String songId = FIRST_INDEX_ID_SONG_2; + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + + SearchRequest searchRequest = queryByIdsRequest("*".concat(FIRST_INDEX_NAME), songId); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + + + songId = SECOND_INDEX_ID_SONG_3; + song = SECOND_INDEX_SONGS_BY_ID.get(songId); + + searchRequest = queryByIdsRequest("*".concat(SECOND_INDEX_NAME), songId); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, song.getArtist())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + } + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { + SearchRequest searchRequest = new SearchRequest("*".concat(FIRST_INDEX_NAME)); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + + searchRequest = new SearchRequest("*".concat(SECOND_INDEX_NAME)); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + } + } + + @Test + public void searchForDocumentsViaAlias() throws IOException { + //FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String songId = FIRST_INDEX_ID_SONG_3; + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + + SearchRequest searchRequest = queryByIdsRequest(FIRST_INDEX_ALIAS, songId); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + + songId = SECOND_INDEX_ID_SONG_4; + song = SECOND_INDEX_SONGS_BY_ID.get(songId); + + searchRequest = queryByIdsRequest("*".concat(SECOND_INDEX_ALIAS), songId); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, song.getArtist())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + } + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_ALIAS); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + + searchRequest = new SearchRequest(SECOND_INDEX_ALIAS); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + } + } + + @Test + public void searchForDocumentsViaFilteredAlias() throws IOException { + //FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String songId = FIND_ID_OF_SONG_WITH_TITLE.apply(FIRST_INDEX_SONGS_BY_ID, TITLE_NEXT_SONG); + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + } + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + + searchRequest = new SearchRequest(FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(0)); + } + } + + @Test + public void searchForDocumentsViaAllIndicesAlias() throws IOException { + //FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ALL_INDICES_MASKED_TITLE_ARTIST_READER)) { + String songId = FIRST_INDEX_ID_SONG_4; + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + + SearchRequest searchRequest = queryByIdsRequest(ALL_INDICES_ALIAS, songId); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, VALUE_TO_MASKED_VALUE.apply(song.getTitle()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, song.getLyrics())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + + + songId = SECOND_INDEX_ID_SONG_1; + song = SECOND_INDEX_SONGS_BY_ID.get(songId); + + searchRequest = queryByIdsRequest(ALL_INDICES_ALIAS, songId); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, VALUE_TO_MASKED_VALUE.apply(song.getTitle()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, song.getLyrics())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + } + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ALL_INDICES_STRING_ARTIST_READER)) { + SearchRequest searchRequest = new SearchRequest(ALL_INDICES_ALIAS); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_STRING)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); + } + } + + @Test + public void scrollOverSearchResults() throws IOException { + //FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String songId = FIRST_INDEX_SONGS_BY_ID.firstKey(); + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + + SearchRequest searchRequest = searchRequestWithScroll(FIRST_INDEX_NAME, 1); + searchRequest.source().sort("_id", SortOrder.ASC); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containNotEmptyScrollingId()); + + SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); + + SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); + assertThat(scrollResponse, isSuccessfulSearchResponse()); + assertThat(scrollResponse, containNotEmptyScrollingId()); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + } + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { + SearchRequest searchRequest = searchRequestWithScroll(FIRST_INDEX_NAME, 2); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containNotEmptyScrollingId()); + + SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); + + SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); + assertThat(scrollResponse, isSuccessfulSearchResponse()); + assertThat(scrollResponse, containNotEmptyScrollingId()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + } + } + + @Test + public void aggregateDataAndComputeAverage() throws IOException { + //FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String aggregationName = "averageStars"; + Double expectedValue = FIRST_INDEX_SONGS_BY_ID.values() + .stream() + .mapToDouble(Song::getStars) + .average().orElseThrow(() -> new RuntimeException("Cannot compute average stars - list of docs is empty")); + SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); + assertThat(actualAggregation, instanceOf(ParsedAvg.class)); + assertThat(((ParsedAvg) actualAggregation).getValue(), is(expectedValue)); + } + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { + String aggregationName = "averageStars"; + Song song = FIRST_INDEX_SONGS_BY_ID.get(FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_TWINS)); + + SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); + assertThat(actualAggregation, instanceOf(ParsedAvg.class)); + assertThat(((ParsedAvg) actualAggregation).getValue(), is(song.getStars()*1.0)); + } + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ALL_INDICES_STARS_LESS_THAN_ZERO_READER)) { + String aggregationName = "averageStars"; + SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); + assertThat(actualAggregation, instanceOf(ParsedAvg.class)); + assertThat(((ParsedAvg) actualAggregation).getValue(), is(Double.POSITIVE_INFINITY)); + } + } + + @Test + public void getDocument() throws IOException { + //FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String songId = FIRST_INDEX_ID_SONG_4; + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + GetResponse response = restHighLevelClient.get(new GetRequest(FIRST_INDEX_NAME, songId), DEFAULT); + + assertThat(response, containDocument(FIRST_INDEX_NAME, songId)); + assertThat(response, documentContainField(FIELD_TITLE, song.getTitle())); + assertThat(response, documentContainField(FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(response, documentContainField(FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(response, documentContainField(FIELD_STARS, song.getStars())); + + songId = SECOND_INDEX_ID_SONG_1; + song = SECOND_INDEX_SONGS_BY_ID.get(songId); + response = restHighLevelClient.get(new GetRequest(SECOND_INDEX_NAME, songId), DEFAULT); + + assertThat(response, containDocument(SECOND_INDEX_NAME, songId)); + assertThat(response, documentContainField(FIELD_TITLE, song.getTitle())); + assertThat(response, documentContainField(FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(response, documentContainField(FIELD_ARTIST, song.getArtist())); + assertThat(response, documentContainField(FIELD_STARS, song.getStars())); + } + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { + String songId = FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_TWINS); + + GetResponse response = restHighLevelClient.get(new GetRequest(FIRST_INDEX_NAME, songId), DEFAULT); + + assertThat(response, containDocument(FIRST_INDEX_NAME, songId)); + + songId = FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_STRING); + + response = restHighLevelClient.get(new GetRequest(FIRST_INDEX_NAME, songId), DEFAULT); + + assertThat(response, not(containDocument(FIRST_INDEX_NAME, songId))); + } + } + + @Test + public void multiGetDocuments() throws IOException { + //FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + List> indicesToCheck = List.of( + List.of(FIRST_INDEX_NAME, SECOND_INDEX_NAME), + List.of(FIRST_INDEX_ALIAS, SECOND_INDEX_ALIAS) + ); + String firstSongId = FIRST_INDEX_ID_SONG_1; + Song firstSong = FIRST_INDEX_SONGS_BY_ID.get(firstSongId); + String secondSongId = SECOND_INDEX_ID_SONG_2; + Song secondSong = SECOND_INDEX_SONGS_BY_ID.get(secondSongId); + + for (List indices : indicesToCheck) { + MultiGetRequest request = new MultiGetRequest(); + request.add(new MultiGetRequest.Item(indices.get(0), firstSongId)); + request.add(new MultiGetRequest.Item(indices.get(1), secondSongId)); + MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); + + assertThat(response, isSuccessfulMultiGetResponse()); + assertThat(response, numberOfGetItemResponsesIsEqualTo(2)); + + MultiGetItemResponse[] responses = response.getResponses(); + assertThat(responses[0].getResponse(), allOf( + containDocument(FIRST_INDEX_NAME, FIRST_INDEX_ID_SONG_1), + documentContainField(FIELD_TITLE, firstSong.getTitle()), + documentContainField(FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(firstSong.getLyrics())), + documentContainField(FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(firstSong.getArtist())), + documentContainField(FIELD_STARS, firstSong.getStars()) + )); + assertThat(responses[1].getResponse(), allOf( + containDocument(SECOND_INDEX_NAME, secondSongId), + documentContainField(FIELD_TITLE, secondSong.getTitle()), + documentContainField(FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(secondSong.getLyrics())), + documentContainField(FIELD_ARTIST, secondSong.getArtist()), + documentContainField(FIELD_STARS, secondSong.getStars()) + )); + } + } + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { + List indicesToCheck = List.of(FIRST_INDEX_NAME, FIRST_INDEX_ALIAS); + String firstSongId = FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_NO); + String secondSongId = FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_STRING); + + for(String index : indicesToCheck) { + MultiGetRequest request = new MultiGetRequest(); + request.add(new MultiGetRequest.Item(index, firstSongId)); + request.add(new MultiGetRequest.Item(index, secondSongId)); + + MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); + + assertThat(response, isSuccessfulMultiGetResponse()); + assertThat(response, numberOfGetItemResponsesIsEqualTo(2)); + + MultiGetItemResponse[] responses = response.getResponses(); + assertThat(responses[0].getResponse(), allOf( + not(containDocument(FIRST_INDEX_NAME, firstSongId))) + ); + assertThat(responses[1].getResponse(), allOf( + not(containDocument(FIRST_INDEX_NAME, secondSongId))) + ); + } + } + } + + @Test + public void multiSearchDocuments() throws IOException { + //FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + List> indicesToCheck = List.of( + List.of(FIRST_INDEX_NAME, SECOND_INDEX_NAME), + List.of(FIRST_INDEX_ALIAS, SECOND_INDEX_ALIAS) + ); + String firstSongId = FIRST_INDEX_ID_SONG_3; + Song firstSong = FIRST_INDEX_SONGS_BY_ID.get(firstSongId); + String secondSongId = SECOND_INDEX_ID_SONG_4; + Song secondSong = SECOND_INDEX_SONGS_BY_ID.get(secondSongId); + + for (List indices : indicesToCheck) { + MultiSearchRequest request = new MultiSearchRequest(); + request.add(queryByIdsRequest(indices.get(0), firstSongId)); + request.add(queryByIdsRequest(indices.get(1), secondSongId)); + MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); + + assertThat(response, isSuccessfulMultiSearchResponse()); + assertThat(response, numberOfSearchItemResponsesIsEqualTo(2)); + + MultiSearchResponse.Item[] responses = response.getResponses(); + + assertThat(responses[0].getResponse(), allOf( + searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, firstSongId), + searchHitContainsFieldWithValue(0, FIELD_TITLE, firstSong.getTitle()), + searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(firstSong.getLyrics())), + searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(firstSong.getArtist())), + searchHitContainsFieldWithValue(0, FIELD_STARS, firstSong.getStars()) + )); + assertThat(responses[1].getResponse(), allOf( + searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, secondSongId), + searchHitContainsFieldWithValue(0, FIELD_TITLE, secondSong.getTitle()), + searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(secondSong.getLyrics())), + searchHitContainsFieldWithValue(0, FIELD_ARTIST, secondSong.getArtist()), + searchHitContainsFieldWithValue(0, FIELD_STARS, secondSong.getStars()) + )); + } + } + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { + List> indicesToCheck = List.of( + List.of(FIRST_INDEX_NAME, SECOND_INDEX_NAME), + List.of(FIRST_INDEX_ALIAS, SECOND_INDEX_ALIAS) + ); + String firstSongId = FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_TWINS); + String secondSongId = FIND_ID_OF_SONG_WITH_ARTIST.apply(SECOND_INDEX_SONGS_BY_ID, ARTIST_FIRST); + + for (List indices : indicesToCheck) { + MultiSearchRequest request = new MultiSearchRequest(); + indices.forEach(index -> request.add(new SearchRequest(index))); + + MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); + + assertThat(response, isSuccessfulMultiSearchResponse()); + assertThat(response, numberOfSearchItemResponsesIsEqualTo(2)); + + MultiSearchResponse.Item[] responses = response.getResponses(); + + assertThat(responses[0].getResponse(), searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, firstSongId)); + assertThat(responses[0].getResponse(), searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(responses[1].getResponse(), searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, secondSongId)); + assertThat(responses[1].getResponse(), searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + } + } + } + + @Test + public void getFieldCapabilities() throws IOException { + //FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(FIRST_INDEX_NAME).fields(FIELD_ARTIST, FIELD_TITLE, FIELD_LYRICS); + FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); + + assertThat(response, containsExactlyIndices(FIRST_INDEX_NAME)); + assertThat(response, numberOfFieldsIsEqualTo(3)); + assertThat(response, containsFieldWithNameAndType(FIELD_ARTIST, "text")); + assertThat(response, containsFieldWithNameAndType(FIELD_TITLE, "text")); + assertThat(response, containsFieldWithNameAndType(FIELD_LYRICS, "text")); + } + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(FIRST_INDEX_NAME).fields(FIELD_ARTIST, FIELD_TITLE, FIELD_LYRICS); + FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); + + assertThat(response, containsExactlyIndices(FIRST_INDEX_NAME)); + assertThat(response, numberOfFieldsIsEqualTo(3)); + assertThat(response, containsFieldWithNameAndType(FIELD_ARTIST, "text")); + assertThat(response, containsFieldWithNameAndType(FIELD_TITLE, "text")); + assertThat(response, containsFieldWithNameAndType(FIELD_LYRICS, "text")); + } + } + +} diff --git a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java index f664ab47cd..08e8394ec8 100644 --- a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java +++ b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java @@ -19,8 +19,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.awaitility.Awaitility; -import org.hamcrest.Matcher; -import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; @@ -106,14 +104,10 @@ import org.opensearch.test.framework.cluster.LocalCluster; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.aMapWithSize; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.arrayContaining; -import static org.hamcrest.Matchers.arrayContainingInAnyOrder; -import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -171,6 +165,9 @@ import static org.opensearch.test.framework.matcher.ClusterMatchers.snapshotInClusterDoesNotExists; import static org.opensearch.test.framework.matcher.DeleteResponseMatchers.isSuccessfulDeleteResponse; import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy; +import static org.opensearch.test.framework.matcher.FieldCapabilitiesResponseMatchers.containsExactlyIndices; +import static org.opensearch.test.framework.matcher.FieldCapabilitiesResponseMatchers.containsFieldWithNameAndType; +import static org.opensearch.test.framework.matcher.FieldCapabilitiesResponseMatchers.numberOfFieldsIsEqualTo; import static org.opensearch.test.framework.matcher.GetResponseMatchers.containDocument; import static org.opensearch.test.framework.matcher.GetResponseMatchers.documentContainField; import static org.opensearch.test.framework.matcher.IndexResponseMatchers.getIndexResponseContainsIndices; @@ -181,6 +178,10 @@ import static org.opensearch.test.framework.matcher.IndexResponseMatchers.isSuccessfulCreateIndexResponse; import static org.opensearch.test.framework.matcher.IndexResponseMatchers.isSuccessfulOpenIndexResponse; import static org.opensearch.test.framework.matcher.IndexResponseMatchers.isSuccessfulResizeResponse; +import static org.opensearch.test.framework.matcher.MultiGetResponseMatchers.isSuccessfulMultiGetResponse; +import static org.opensearch.test.framework.matcher.MultiGetResponseMatchers.numberOfGetItemResponsesIsEqualTo; +import static org.opensearch.test.framework.matcher.MultiSearchResponseMatchers.isSuccessfulMultiSearchResponse; +import static org.opensearch.test.framework.matcher.MultiSearchResponseMatchers.numberOfSearchItemResponsesIsEqualTo; import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.errorMessageContain; import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.statusException; import static org.opensearch.test.framework.matcher.SearchResponseMatchers.containAggregationWithNameAndType; @@ -225,7 +226,7 @@ public class SearchOperationTest { public static final String UNUSED_SNAPSHOT_REPOSITORY_NAME = "unused-snapshot-repository"; public static final String RESTORED_SONG_INDEX_NAME = "restored_" + WRITE_SONG_INDEX_NAME; - + public static final String UPDATE_DELETE_OPERATION_INDEX_NAME = "update_delete_index"; public static final String DOCUMENT_TO_UPDATE_ID = "doc_to_update"; @@ -327,13 +328,13 @@ public class SearchOperationTest { @BeforeClass public static void createTestData() { try(Client client = cluster.getInternalNodeClient()) { - client.prepareIndex(SONG_INDEX_NAME).setId(ID_S1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0]).get(); + client.prepareIndex(SONG_INDEX_NAME).setId(ID_S1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); client.prepareIndex(UPDATE_DELETE_OPERATION_INDEX_NAME).setId(DOCUMENT_TO_UPDATE_ID).setRefreshPolicy(IMMEDIATE).setSource("field", "value").get(); client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new AliasActions(ADD).indices(SONG_INDEX_NAME).alias(SONG_LYRICS_ALIAS))).actionGet(); - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SONG_INDEX_NAME).id(ID_S2).source(SONGS[1])).actionGet(); - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SONG_INDEX_NAME).id(ID_S3).source(SONGS[2])).actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SONG_INDEX_NAME).id(ID_S2).source(SONGS[1].asMap())).actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SONG_INDEX_NAME).id(ID_S3).source(SONGS[2].asMap())).actionGet(); - client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(ID_P4).setSource(SONGS[3]).setRefreshPolicy(IMMEDIATE).get(); + client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(ID_P4).setSource(SONGS[3].asMap()).setRefreshPolicy(IMMEDIATE).get(); client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new AliasActions(ADD).indices(PROHIBITED_SONG_INDEX_NAME).alias(PROHIBITED_SONG_ALIAS))).actionGet(); client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new AliasActions(ADD).indices(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME).alias(COLLECTIVE_INDEX_ALIAS))).actionGet(); @@ -681,11 +682,10 @@ public void shouldPerformMultiGetDocuments_positive() throws IOException { MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); assertThat(response, is(notNullValue())); - MultiGetItemResponse[] responses = response.getResponses(); - assertThat(responses, arrayWithSize(2)); - Matcher withNullFailureProperty = hasProperty("failure", nullValue()); - assertThat(responses, arrayContaining(withNullFailureProperty, withNullFailureProperty)); + assertThat(response, isSuccessfulMultiGetResponse()); + assertThat(response, numberOfGetItemResponsesIsEqualTo(2)); + MultiGetItemResponse[] responses = response.getResponses(); assertThat(responses[0].getResponse(), allOf( containDocument(SONG_INDEX_NAME, ID_S1), documentContainField(FIELD_TITLE, TITLE_MAGNUM_OPUS)) @@ -722,8 +722,10 @@ public void shouldPerformMultiGetDocuments_partiallyPositive() throws IOExceptio MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); assertThat(request, notNullValue()); + assertThat(response, not(isSuccessfulMultiGetResponse())); + assertThat(response, numberOfGetItemResponsesIsEqualTo(2)); + MultiGetItemResponse[] responses = response.getResponses(); - assertThat(responses, arrayWithSize(2)); assertThat(responses, arrayContaining( hasProperty("failure", nullValue()), hasProperty("failure", notNullValue()) @@ -747,14 +749,10 @@ public void shouldBeAllowedToPerformMulitSearch_positive() throws IOException { MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); assertThat(response, notNullValue()); + assertThat(response, isSuccessfulMultiSearchResponse()); + assertThat(response, numberOfSearchItemResponsesIsEqualTo(2)); + MultiSearchResponse.Item[] responses = response.getResponses(); - assertThat(responses, Matchers.arrayWithSize(2)); - assertThat(responses, arrayContaining( - notNullValue(), - notNullValue() - )); - assertThat(responses[0].getFailure(), nullValue()); - assertThat(responses[1].getFailure(), nullValue()); assertThat(responses[0].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); assertThat(responses[0].getResponse(), searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); @@ -776,9 +774,10 @@ public void shouldBeAllowedToPerformMulitSearch_partiallyPositive() throws IOExc MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); assertThat(response, notNullValue()); + assertThat(response, not(isSuccessfulMultiSearchResponse())); + assertThat(response, numberOfSearchItemResponsesIsEqualTo(2)); + MultiSearchResponse.Item[] responses = response.getResponses(); - assertThat(responses, Matchers.arrayWithSize(2)); - assertThat(responses, arrayContaining(notNullValue(), notNullValue())); assertThat(responses[0].getFailure(), nullValue()); assertThat(responses[1].getFailure(), statusException(INTERNAL_SERVER_ERROR)); assertThat(responses[1].getFailure(), errorMessageContain("security_exception")); @@ -859,8 +858,8 @@ public void shouldPerformStatAggregation_negative() throws IOException { public void shouldIndexDocumentInBulkRequest_positive() throws IOException { try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0])); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); bulkRequest.setRefreshPolicy(IMMEDIATE); BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); @@ -882,8 +881,8 @@ public void shouldIndexDocumentInBulkRequest_positive() throws IOException { public void shouldIndexDocumentInBulkRequest_partiallyPositive() throws IOException { try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("one").source(SONGS[0])); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1])); + bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); bulkRequest.setRefreshPolicy(IMMEDIATE); BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); @@ -907,8 +906,8 @@ public void shouldIndexDocumentInBulkRequest_partiallyPositive() throws IOExcept public void shouldIndexDocumentInBulkRequest_negative() throws IOException { try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("one").source(SONGS[0])); - bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("two").source(SONGS[1])); + bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); bulkRequest.setRefreshPolicy(IMMEDIATE); BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); @@ -932,8 +931,8 @@ public void shouldUpdateDocumentsInBulk_positive() throws IOException { final String titleOne = "shape of my mind"; final String titleTwo = "forgiven"; BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0])); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); restHighLevelClient.bulk(bulkRequest, DEFAULT); bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); bulkRequest.add(new UpdateRequest(WRITE_SONG_INDEX_NAME, "one").doc(Map.of(FIELD_TITLE, titleOne))); @@ -958,7 +957,7 @@ public void shouldUpdateDocumentsInBulk_partiallyPositive() throws IOException { try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { final String titleOne = "shape of my mind"; BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); restHighLevelClient.bulk(bulkRequest, DEFAULT); bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); bulkRequest.add(new UpdateRequest(WRITE_SONG_INDEX_NAME, "one").doc(Map.of(FIELD_TITLE, titleOne))); @@ -1007,10 +1006,10 @@ public void shouldUpdateDocumentsInBulk_negative() throws IOException { public void shouldDeleteDocumentInBulk_positive() throws IOException { try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0])); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1])); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("three").source(SONGS[2])); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("four").source(SONGS[3])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("three").source(SONGS[2].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("four").source(SONGS[3].asMap())); assertThat(restHighLevelClient.bulk(bulkRequest, DEFAULT), successBulkResponse()); bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "one")); @@ -1035,8 +1034,8 @@ public void shouldDeleteDocumentInBulk_positive() throws IOException { public void shouldDeleteDocumentInBulk_partiallyPositive() throws IOException { try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0])); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); assertThat(restHighLevelClient.bulk(bulkRequest, DEFAULT), successBulkResponse()); bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "one")); @@ -1280,7 +1279,7 @@ public void shouldCreateIndexTemplate_positive() throws IOException { assertThat(response.isAcknowledged(), equalTo(true)); assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE )); String documentId = "0001"; - IndexRequest indexRequest = new IndexRequest(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ).id(documentId).source(SONGS[0]) + IndexRequest indexRequest = new IndexRequest(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ).id(documentId).source(SONGS[0].asMap()) .setRefreshPolicy(IMMEDIATE); restHighLevelClient.index(indexRequest, DEFAULT); assertThat(internalClient, clusterContainsDocument(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ, documentId)); @@ -1365,7 +1364,7 @@ public void shouldUpdateTemplate_positive() throws IOException { assertThat(response, notNullValue()); assertThat(response.isAcknowledged(), equalTo(true)); String documentId = "000one"; - IndexRequest indexRequest = new IndexRequest(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ).id(documentId).source(SONGS[0]) + IndexRequest indexRequest = new IndexRequest(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ).id(documentId).source(SONGS[0].asMap()) .setRefreshPolicy(IMMEDIATE); restHighLevelClient.index(indexRequest, DEFAULT); assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE)); @@ -1405,10 +1404,9 @@ public void shouldGetFieldCapabilitiesForAllIndexes_positive() throws IOExceptio FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); assertThat(response, notNullValue()); - assertThat(response.get(), aMapWithSize(1)); - assertThat(response.getIndices(), arrayWithSize(3)); - assertThat(response.getField(FIELD_TITLE), hasKey("text")); - assertThat(response.getIndices(), arrayContainingInAnyOrder(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME, UPDATE_DELETE_OPERATION_INDEX_NAME)); + assertThat(response, containsExactlyIndices(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME, UPDATE_DELETE_OPERATION_INDEX_NAME)); + assertThat(response, numberOfFieldsIsEqualTo(1)); + assertThat(response, containsFieldWithNameAndType(FIELD_TITLE, "text")); } auditLogsRule.assertExactlyOne(userAuthenticated(ADMIN_USER).withRestRequest(GET, "/_field_caps")); auditLogsRule.assertExactlyOne(grantedPrivilege(ADMIN_USER, "FieldCapabilitiesRequest")); @@ -1434,10 +1432,9 @@ public void shouldGetFieldCapabilitiesForParticularIndex_positive() throws IOExc FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); assertThat(response, notNullValue()); - assertThat(response.get(), aMapWithSize(1)); - assertThat(response.getIndices(), arrayWithSize(1)); - assertThat(response.getField(FIELD_TITLE), hasKey("text")); - assertThat(response.getIndices(), arrayContainingInAnyOrder(SONG_INDEX_NAME)); + assertThat(response, containsExactlyIndices(SONG_INDEX_NAME)); + assertThat(response, numberOfFieldsIsEqualTo(1)); + assertThat(response, containsFieldWithNameAndType(FIELD_TITLE, "text")); } auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/song_lyrics/_field_caps")); auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "FieldCapabilitiesRequest")); @@ -1608,8 +1605,8 @@ public void shouldRestoreSnapshot_positive() throws IOException { SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); // 1. create some documents BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0])); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1].asMap())); bulkRequest.setRefreshPolicy(IMMEDIATE); restHighLevelClient.bulk(bulkRequest, DEFAULT); @@ -1624,8 +1621,8 @@ public void shouldRestoreSnapshot_positive() throws IOException { // 5. introduce some changes bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Drei").source(SONGS[2])); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Vier").source(SONGS[3])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Drei").source(SONGS[2].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Vier").source(SONGS[3].asMap())); bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "Eins")); bulkRequest.setRefreshPolicy(IMMEDIATE); restHighLevelClient.bulk(bulkRequest, DEFAULT); @@ -1672,8 +1669,8 @@ public void shouldRestoreSnapshot_failureForbiddenIndex() throws IOException { SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); // 1. create some documents BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0])); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1].asMap())); bulkRequest.setRefreshPolicy(IMMEDIATE); restHighLevelClient.bulk(bulkRequest, DEFAULT); @@ -1718,8 +1715,8 @@ public void shouldRestoreSnapshot_failureOperationForbidden() throws IOException SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); // 1. create some documents BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0])); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1])); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1].asMap())); bulkRequest.setRefreshPolicy(IMMEDIATE); restHighLevelClient.bulk(bulkRequest, DEFAULT); diff --git a/src/integrationTest/java/org/opensearch/security/Song.java b/src/integrationTest/java/org/opensearch/security/Song.java index 14b1ce9131..c0e5749d29 100644 --- a/src/integrationTest/java/org/opensearch/security/Song.java +++ b/src/integrationTest/java/org/opensearch/security/Song.java @@ -50,20 +50,19 @@ public class Song { public static final String QUERY_TITLE_POISON = FIELD_TITLE + ":" + TITLE_POISON; public static final String QUERY_TITLE_MAGNUM_OPUS = FIELD_TITLE + ":" + TITLE_MAGNUM_OPUS; - public static final Map[] SONGS = { - new Song(ARTIST_FIRST, TITLE_MAGNUM_OPUS ,LYRICS_1, 1, GENRE_ROCK).asMap(), - new Song(ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES).asMap(), - new Song(ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ).asMap(), - new Song(ARTIST_NO, TITLE_POISON, LYRICS_4, 4, GENRE_ROCK).asMap(), - new Song(ARTIST_YES, TITLE_AFFIRMATIVE,LYRICS_5, 5, GENRE_BLUES).asMap(), - new Song(ARTIST_UNKNOWN, TITLE_CONFIDENTIAL, LYRICS_6, 6, GENRE_JAZZ).asMap() + public static final Song[] SONGS = { + new Song(ARTIST_FIRST, TITLE_MAGNUM_OPUS ,LYRICS_1, 1, GENRE_ROCK), + new Song(ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES), + new Song(ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ), + new Song(ARTIST_NO, TITLE_POISON, LYRICS_4, 4, GENRE_ROCK), + new Song(ARTIST_YES, TITLE_AFFIRMATIVE,LYRICS_5, 5, GENRE_BLUES), + new Song(ARTIST_UNKNOWN, TITLE_CONFIDENTIAL, LYRICS_6, 6, GENRE_JAZZ) }; private final String artist; private final String title; private final String lyrics; private final Integer stars; - private final String genre; public Song(String artist, String title, String lyrics, Integer stars, String genre) { @@ -74,6 +73,26 @@ public Song(String artist, String title, String lyrics, Integer stars, String ge this.genre = Objects.requireNonNull(genre, "Genre field is required"); } + public String getArtist() { + return artist; + } + + public String getTitle() { + return title; + } + + public String getLyrics() { + return lyrics; + } + + public Integer getStars() { + return stars; + } + + public String getGenre() { + return genre; + } + public Map asMap() { return Map.of(FIELD_ARTIST, artist, FIELD_TITLE, title, diff --git a/src/integrationTest/java/org/opensearch/security/http/ExtendedProxyAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/ExtendedProxyAuthenticationTest.java index 7ba2a6a72e..4278a07a53 100644 --- a/src/integrationTest/java/org/opensearch/security/http/ExtendedProxyAuthenticationTest.java +++ b/src/integrationTest/java/org/opensearch/security/http/ExtendedProxyAuthenticationTest.java @@ -75,8 +75,8 @@ protected LocalCluster getCluster() { @BeforeClass public static void createTestData() { try(Client client = cluster.getInternalNodeClient()){ - client.prepareIndex(PERSONAL_INDEX_NAME_SPOCK).setId(ID_ONE_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0]).get(); - client.prepareIndex(PERSONAL_INDEX_NAME_KIRK).setId(ID_TWO_2).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1]).get(); + client.prepareIndex(PERSONAL_INDEX_NAME_SPOCK).setId(ID_ONE_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); + client.prepareIndex(PERSONAL_INDEX_NAME_KIRK).setId(ID_TWO_2).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1].asMap()).get(); } } diff --git a/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java b/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java index 341a956b4a..5875a120a4 100644 --- a/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java @@ -126,7 +126,7 @@ public class JwtAuthenticationTests { @BeforeClass public static void createTestData() { try (Client client = cluster.getInternalNodeClient()) { - client.prepareIndex(QA_SONG_INDEX_NAME).setId(SONG_ID_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0]).get(); + client.prepareIndex(QA_SONG_INDEX_NAME).setId(SONG_ID_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); } try(TestRestClient client = cluster.getRestClient(ADMIN_USER)){ client.createRoleMapping(ROLE_VP, DEPARTMENT_SONG_LISTENER_ROLE.getName()); diff --git a/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java index ce51e2f920..be17b6c4cf 100644 --- a/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java +++ b/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java @@ -174,9 +174,9 @@ public class LdapTlsAuthenticationTest { @BeforeClass public static void createTestData() { try(Client client = cluster.getInternalNodeClient()){ - client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0]).get(); - client.prepareIndex(PERSONAL_INDEX_NAME_SPOCK).setId(SONG_ID_2).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1]).get(); - client.prepareIndex(PERSONAL_INDEX_NAME_KIRK).setId(SONG_ID_3).setRefreshPolicy(IMMEDIATE).setSource(SONGS[2]).get(); + client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); + client.prepareIndex(PERSONAL_INDEX_NAME_SPOCK).setId(SONG_ID_2).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1].asMap()).get(); + client.prepareIndex(PERSONAL_INDEX_NAME_KIRK).setId(SONG_ID_3).setRefreshPolicy(IMMEDIATE).setSource(SONGS[2].asMap()).get(); } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java index 9e28b3a2fe..781ad4505c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java @@ -26,6 +26,15 @@ public final class SearchRequestFactory { private SearchRequestFactory() { } + + public static SearchRequest queryByIdsRequest(String indexName, String... ids) { + SearchRequest searchRequest = new SearchRequest(indexName); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.idsQuery().addIds(ids)); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } + public static SearchRequest queryStringQueryRequest(String indexName, String queryString) { SearchRequest searchRequest = new SearchRequest(indexName); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java index cf6565ea12..8619421085 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -192,6 +192,18 @@ public HttpResponse assignRoleToUser(String username, String roleName) { return patch("_plugins/_security/api/internalusers/" + username, body); } + public HttpResponse createRole(String roleName, ToXContentObject role) { + Objects.requireNonNull(roleName, "Role name is required"); + Objects.requireNonNull(role, "Role is required"); + return putJson("_plugins/_security/api/roles/" + roleName, role); + } + + public HttpResponse createUser(String userName, ToXContentObject user) { + Objects.requireNonNull(userName, "User name is required"); + Objects.requireNonNull(user, "User is required"); + return putJson("_plugins/_security/api/internalusers/" + userName, user); + } + public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... requestSpecificHeaders) { try(CloseableHttpClient httpClient = getHTTPClient()) { diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsExactlyIndicesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsExactlyIndicesMatcher.java new file mode 100644 index 0000000000..551a1823bf --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsExactlyIndicesMatcher.java @@ -0,0 +1,46 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Set; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.fieldcaps.FieldCapabilitiesResponse; + +import static java.util.Objects.isNull; + +class ContainsExactlyIndicesMatcher extends TypeSafeDiagnosingMatcher { + + private final Set expectedIndices; + + ContainsExactlyIndicesMatcher(String... expectedIndices) { + if (isNull(expectedIndices) || expectedIndices.length == 0) { + throw new IllegalArgumentException("expectedIndices cannot be null or empty"); + } + this.expectedIndices = Set.of(expectedIndices); + } + + @Override + protected boolean matchesSafely(FieldCapabilitiesResponse response, Description mismatchDescription) { + Set actualIndices = Set.of(response.getIndices()); + if (!expectedIndices.equals(actualIndices)) { + mismatchDescription.appendText("Actual indices: ").appendValue(actualIndices); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response contains indices: ").appendValue(expectedIndices); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsFieldWithTypeMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsFieldWithTypeMatcher.java new file mode 100644 index 0000000000..41db8a9ed5 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsFieldWithTypeMatcher.java @@ -0,0 +1,51 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Map; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.fieldcaps.FieldCapabilities; +import org.opensearch.action.fieldcaps.FieldCapabilitiesResponse; + +import static java.util.Objects.requireNonNull; + +class ContainsFieldWithTypeMatcher extends TypeSafeDiagnosingMatcher { + + private final String expectedFieldName; + private final String expectedFieldType; + + ContainsFieldWithTypeMatcher(String expectedFieldName, String expectedFieldType) { + this.expectedFieldName = requireNonNull(expectedFieldName, "Field name is required");; + this.expectedFieldType = requireNonNull(expectedFieldType, "Field type is required");; + } + + @Override + protected boolean matchesSafely(FieldCapabilitiesResponse response, Description mismatchDescription) { + Map> fieldCapabilitiesMap = response.get(); + if (!fieldCapabilitiesMap.containsKey(expectedFieldName)) { + mismatchDescription.appendText("Response does not contain field with name ").appendText(expectedFieldName); + return false; + } + if (!fieldCapabilitiesMap.get(expectedFieldName).containsKey(expectedFieldType)) { + mismatchDescription.appendText("Field type does not match ").appendText(expectedFieldType); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response contains field with name ").appendValue(expectedFieldName) + .appendText(" and type ").appendValue(expectedFieldType); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/FieldCapabilitiesResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/FieldCapabilitiesResponseMatchers.java new file mode 100644 index 0000000000..c5680f6055 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/FieldCapabilitiesResponseMatchers.java @@ -0,0 +1,32 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Matcher; + +import org.opensearch.action.fieldcaps.FieldCapabilitiesResponse; + +public class FieldCapabilitiesResponseMatchers { + + private FieldCapabilitiesResponseMatchers() {} + + public static Matcher containsExactlyIndices(String... expectedIndices) { + return new ContainsExactlyIndicesMatcher(expectedIndices); + } + + public static Matcher containsFieldWithNameAndType(String expectedFieldName, String expectedFieldType) { + return new ContainsFieldWithTypeMatcher(expectedFieldName, expectedFieldType); + } + + public static Matcher numberOfFieldsIsEqualTo(int expectedNumberOfFields) { + return new NumberOfFieldsIsEqualToMatcher(expectedNumberOfFields); + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentIdMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainsDocumentWithIdMatcher.java similarity index 83% rename from src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentIdMatcher.java rename to src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainsDocumentWithIdMatcher.java index 85519a7261..5dd0c72576 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentIdMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainsDocumentWithIdMatcher.java @@ -16,12 +16,12 @@ import static java.util.Objects.requireNonNull; -class GetResponseDocumentIdMatcher extends TypeSafeDiagnosingMatcher { +class GetResponseContainsDocumentWithIdMatcher extends TypeSafeDiagnosingMatcher { private final String indexName; private final String documentId; - public GetResponseDocumentIdMatcher(String indexName, String documentId) { + public GetResponseContainsDocumentWithIdMatcher(String indexName, String documentId) { this.indexName = requireNonNull(indexName, "Index name is required"); this.documentId = requireNonNull(documentId, "Document id is required"); } @@ -40,6 +40,10 @@ protected boolean matchesSafely(GetResponse response, Description mismatchDescri mismatchDescription.appendText("Document does not exist or is inaccessible"); return false; } + if(response.isSourceEmpty()) { + mismatchDescription.appendText("Document source is empty"); + return false; + } return true; } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentContainsExactlyFieldsWithNamesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentContainsExactlyFieldsWithNamesMatcher.java new file mode 100644 index 0000000000..aa8daa0128 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentContainsExactlyFieldsWithNamesMatcher.java @@ -0,0 +1,49 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Map; +import java.util.Set; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.get.GetResponse; + +import static java.util.Objects.isNull; + +class GetResponseDocumentContainsExactlyFieldsWithNamesMatcher extends TypeSafeDiagnosingMatcher { + + private final Set expectedFieldsNames; + + GetResponseDocumentContainsExactlyFieldsWithNamesMatcher(String... expectedFieldsNames) { + if (isNull(expectedFieldsNames) || expectedFieldsNames.length == 0) { + throw new IllegalArgumentException("expectedFieldsNames cannot be null or empty"); + } + this.expectedFieldsNames = Set.of(expectedFieldsNames); + } + + @Override + protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { + Map sourceMap = response.getSourceAsMap(); + Set actualFieldsNames = sourceMap.keySet(); + if (!expectedFieldsNames.equals(actualFieldsNames)) { + mismatchDescription.appendValue("Document with id ").appendValue(response.getId()) + .appendText(" contains fields with names: ").appendValue(actualFieldsNames); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Document contain exactly fields with names: ").appendValue(expectedFieldsNames); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentDoesNotContainFieldMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentDoesNotContainFieldMatcher.java new file mode 100644 index 0000000000..f8f81f0e95 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentDoesNotContainFieldMatcher.java @@ -0,0 +1,47 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Map; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.get.GetResponse; + +import static java.util.Objects.requireNonNull; + +class GetResponseDocumentDoesNotContainFieldMatcher extends TypeSafeDiagnosingMatcher { + + private final String fieldName; + + public GetResponseDocumentDoesNotContainFieldMatcher(String fieldName) { + this.fieldName = requireNonNull(fieldName, "Field name is required."); + } + + @Override + protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { + Map source = response.getSource(); + if(source == null) { + mismatchDescription.appendText("Source is not available in search results"); + return false; + } + if(source.containsKey(fieldName)) { + mismatchDescription.appendText("Document contains field ").appendValue(fieldName); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Document does not contain field ").appendValue(fieldName); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java index 87f346d704..6a494dc9cd 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java @@ -18,7 +18,7 @@ public class GetResponseMatchers { private GetResponseMatchers() {} public static Matcher containDocument(String indexName, String documentId) { - return new GetResponseDocumentIdMatcher(indexName, documentId); + return new GetResponseContainsDocumentWithIdMatcher(indexName, documentId); } public static Matcher containOnlyDocumentId(String indexName, String documentId) { @@ -28,4 +28,12 @@ public static Matcher containOnlyDocumentId(String indexName, Strin public static Matcher documentContainField(String fieldName, Object fieldValue) { return new GetResponseDocumentFieldValueMatcher(fieldName, fieldValue); } + + public static Matcher documentDoesNotContainField(String fieldName) { + return new GetResponseDocumentDoesNotContainFieldMatcher(fieldName); + } + + public static Matcher documentContainsExactlyFieldsWithNames(String... expectedFieldsNames) { + return new GetResponseDocumentContainsExactlyFieldsWithNamesMatcher(expectedFieldsNames); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiGetResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiGetResponseMatchers.java new file mode 100644 index 0000000000..30b3037752 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiGetResponseMatchers.java @@ -0,0 +1,29 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Matcher; + +import org.opensearch.action.get.MultiGetResponse; + +public class MultiGetResponseMatchers { + + private MultiGetResponseMatchers() { + } + + public static Matcher isSuccessfulMultiGetResponse() { + return new SuccessfulMultiGetResponseMatcher(); + } + + public static Matcher numberOfGetItemResponsesIsEqualTo(int expectedNumberOfResponses) { + return new NumberOfGetItemResponsesIsEqualToMatcher(expectedNumberOfResponses); + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiSearchResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiSearchResponseMatchers.java new file mode 100644 index 0000000000..39a2645ce4 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiSearchResponseMatchers.java @@ -0,0 +1,29 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Matcher; + +import org.opensearch.action.search.MultiSearchResponse; + +public class MultiSearchResponseMatchers { + + private MultiSearchResponseMatchers() { + } + + public static Matcher isSuccessfulMultiSearchResponse() { + return new SuccessfulMultiSearchResponseMatcher(); + } + + public static Matcher numberOfSearchItemResponsesIsEqualTo(int expectedNumberOfResponses) { + return new NumberOfSearchItemResponsesIsEqualToMatcher(expectedNumberOfResponses); + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfFieldsIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfFieldsIsEqualToMatcher.java new file mode 100644 index 0000000000..a11d360b98 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfFieldsIsEqualToMatcher.java @@ -0,0 +1,39 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.fieldcaps.FieldCapabilitiesResponse; + +class NumberOfFieldsIsEqualToMatcher extends TypeSafeDiagnosingMatcher { + + private final int expectedNumberOfFields; + + NumberOfFieldsIsEqualToMatcher(int expectedNumberOfFields) { + this.expectedNumberOfFields = expectedNumberOfFields; + } + + @Override + protected boolean matchesSafely(FieldCapabilitiesResponse response, Description mismatchDescription) { + if (expectedNumberOfFields != response.get().size()) { + mismatchDescription.appendText("Actual number of fields: ").appendValue(response.get().size()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response contains information about ") + .appendValue(expectedNumberOfFields).appendText(" fields"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfGetItemResponsesIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfGetItemResponsesIsEqualToMatcher.java new file mode 100644 index 0000000000..141b235f2f --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfGetItemResponsesIsEqualToMatcher.java @@ -0,0 +1,40 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.get.MultiGetResponse; + +class NumberOfGetItemResponsesIsEqualToMatcher extends TypeSafeDiagnosingMatcher { + + private final int expectedNumberOfResponses; + + NumberOfGetItemResponsesIsEqualToMatcher(int expectedNumberOfResponses) { + this.expectedNumberOfResponses = expectedNumberOfResponses; + } + + + @Override + protected boolean matchesSafely(MultiGetResponse response, Description mismatchDescription) { + if (expectedNumberOfResponses != response.getResponses().length) { + mismatchDescription.appendText("Actual number of responses: ").appendValue(response.getResponses().length); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Multi get response contains: ").appendValue(expectedNumberOfResponses) + .appendText(" item responses"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfSearchItemResponsesIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfSearchItemResponsesIsEqualToMatcher.java new file mode 100644 index 0000000000..971473148c --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfSearchItemResponsesIsEqualToMatcher.java @@ -0,0 +1,41 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.MultiSearchResponse; + +class NumberOfSearchItemResponsesIsEqualToMatcher extends TypeSafeDiagnosingMatcher { + + private final int expectedNumberOfResponses; + + NumberOfSearchItemResponsesIsEqualToMatcher(int expectedNumberOfResponses) { + this.expectedNumberOfResponses = expectedNumberOfResponses; + } + + + @Override + protected boolean matchesSafely(MultiSearchResponse response, Description mismatchDescription) { + if (expectedNumberOfResponses != response.getResponses().length) { + mismatchDescription.appendText("Actual number of responses: ").appendValue(response.getResponses().length); + return false; + } + + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Multi search response contains: ").appendValue(expectedNumberOfResponses) + .appendText(" item responses"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiGetResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiGetResponseMatcher.java new file mode 100644 index 0000000000..3aedb0174b --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiGetResponseMatcher.java @@ -0,0 +1,37 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.get.MultiGetItemResponse; +import org.opensearch.action.get.MultiGetResponse; + +class SuccessfulMultiGetResponseMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(MultiGetResponse response, Description mismatchDescription) { + for (MultiGetItemResponse getItemResponse : response.getResponses()) { + if (getItemResponse.isFailed()) { + mismatchDescription.appendValue("Get an item from index: ").appendValue(getItemResponse.getIndex()) + .appendText(" failed: ").appendValue(getItemResponse.getFailure().getMessage()); + return false; + } + } + + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful multi get response"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiSearchResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiSearchResponseMatcher.java new file mode 100644 index 0000000000..b1dbd64c33 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiSearchResponseMatcher.java @@ -0,0 +1,35 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.MultiSearchResponse; + +class SuccessfulMultiSearchResponseMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(MultiSearchResponse response, Description mismatchDescription) { + for (MultiSearchResponse.Item itemResponse : response.getResponses()) { + if (itemResponse.isFailure()) { + mismatchDescription.appendValue("Get an item failed: ").appendValue(itemResponse.getFailureMessage()); + return false; + } + } + + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful multi search response"); + } +} From d14143d315174a23a6ad215b71bd938a16c182ab Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Thu, 15 Dec 2022 10:47:11 -0500 Subject: [PATCH 105/369] OpenSSLTest is not using the OpenSSL Provider (#2301) * OpenSSLTest is not using the OpenSSL Provider Signed-off-by: Andriy Redko * Enable OpenSSLTest on Windows Signed-off-by: Andriy Redko * Extracted OpenSSL test into separate task to eliminate mess with system properties Signed-off-by: Andriy Redko Signed-off-by: Andriy Redko --- build.gradle | 33 ++++++++++++++++++- .../opensearch/security/ssl/OpenSSLTest.java | 1 - .../org/opensearch/security/ssl/SSLTest.java | 4 +-- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index e8978a2c85..85c7f73dca 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,7 @@ plugins { id "org.gradle.test-retry" version "1.4.1" id 'eclipse' id "com.github.spotbugs" version "5.0.13" + id "com.google.osdetector" version "1.7.1" } allprojects { @@ -121,6 +122,7 @@ test { include '**/*.class' filter { excludeTestsMatching "org.opensearch.security.sanity.tests.*" + excludeTestsMatching "org.opensearch.security.ssl.OpenSSL*" } maxParallelForks = 8 jvmArgs += "-Xmx3072m" @@ -148,13 +150,37 @@ test { } } +//add new task that runs OpenSSL tests +task opensslTest(type: Test) { + include '**/OpenSSL*.class' + retry { + failOnPassedAfterRetry = false + maxRetries = 5 + } + jacoco { + excludes = [ + "com.sun.jndi.dns.*", + "com.sun.security.sasl.gsskerb.*", + "java.sql.*", + "javax.script.*", + "org.jcp.xml.dsig.internal.dom.*", + "sun.nio.cs.ext.*", + "sun.security.ec.*", + "sun.security.jgss.*", + "sun.security.pkcs11.*", + "sun.security.smartcardio.*", + "sun.util.resources.provider.*" + ] + } +} + task copyExtraTestResources(dependsOn: testClasses) { copy { from 'src/test/resources' into 'build/testrun/test/src/test/resources' } } -tasks.test.dependsOn(copyExtraTestResources) +tasks.test.dependsOn(copyExtraTestResources, opensslTest) jacoco { reportsDirectory = file("$buildDir/reports/jacoco") @@ -413,6 +439,11 @@ dependencies { testImplementation 'org.springframework:spring-beans:5.3.20' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + // Only osx-x86_64, osx-aarch_64, linux-x86_64, linux-aarch_64, windows-x86_64 are available + if (osdetector.classifier in ["osx-x86_64", "osx-aarch_64", "linux-x86_64", "linux-aarch_64", "windows-x86_64"]) { + testImplementation "io.netty:netty-tcnative-classes:2.0.54.Final" + testImplementation "io.netty:netty-tcnative-boringssl-static:2.0.54.Final:${osdetector.classifier}" + } // JUnit build requirement testCompileOnly 'org.apiguardian:apiguardian-api:1.0.0' // Kafka test execution diff --git a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java index 6990df9ea7..6d473c0160 100644 --- a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java @@ -66,7 +66,6 @@ public static void restoreNettyDefaultAllocator() { @Before public void setup() { - Assume.assumeFalse(PlatformDependent.isWindows()); allowOpenSSL = true; } diff --git a/src/test/java/org/opensearch/security/ssl/SSLTest.java b/src/test/java/org/opensearch/security/ssl/SSLTest.java index d150353aeb..65181d66b9 100644 --- a/src/test/java/org/opensearch/security/ssl/SSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/SSLTest.java @@ -89,9 +89,9 @@ public void testHttps() throws Exception { .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "TLSv1.1", "TLSv1.2") - .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256") + .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.1", "TLSv1.2") - .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256") + .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) .build(); From 93faf7513154d89fbae9ce353de56263e4466a3c Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Thu, 15 Dec 2022 14:37:49 -0500 Subject: [PATCH 106/369] Upgrade CXF to 3.5.5 to address CVE-2022-46363 (#2350) Signed-off-by: Stephen Crawford Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 85c7f73dca..cd172d7ba3 100644 --- a/build.gradle +++ b/build.gradle @@ -347,7 +347,7 @@ dependencies { implementation "org.bouncycastle:bcprov-jdk15on:${versions.bouncycastle}" implementation 'org.ldaptive:ldaptive:1.2.3' implementation 'io.jsonwebtoken:jjwt-api:0.10.8' - implementation('org.apache.cxf:cxf-rt-rs-security-jose:3.4.5') { + implementation('org.apache.cxf:cxf-rt-rs-security-jose:3.5.5') { exclude(group: 'jakarta.activation', module: 'jakarta.activation-api') } implementation 'com.github.wnameless:json-flattener:0.5.0' @@ -358,9 +358,9 @@ dependencies { runtimeOnly 'net.minidev:accessors-smart:2.4.7' - runtimeOnly 'org.apache.cxf:cxf-core:3.4.5' - implementation 'org.apache.cxf:cxf-rt-rs-json-basic:3.4.5' - runtimeOnly 'org.apache.cxf:cxf-rt-security:3.4.5' + runtimeOnly 'org.apache.cxf:cxf-core:3.5.5' + implementation 'org.apache.cxf:cxf-rt-rs-json-basic:3.5.5' + runtimeOnly 'org.apache.cxf:cxf-rt-security:3.5.5' runtimeOnly 'com.sun.activation:jakarta.activation:1.2.2' runtimeOnly 'com.eclipsesource.minimal-json:minimal-json:0.9.5' From 6c54ea8bb3a9cee9bfa22e22599382b4daf9f1a5 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Dec 2022 12:32:11 -0500 Subject: [PATCH 107/369] Remove duplicate block of code in DynamicConfigFactory (#2321) Signed-off-by: Craig Perkins --- .../security/securityconf/DynamicConfigFactory.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java index cdf0eaf366..262eb37cf8 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java @@ -145,16 +145,6 @@ public DynamicConfigFactory(ConfigurationRepository cr, final Settings opensearc log.info("Static resources will not be loaded."); } - if(opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES, true)) { - try { - loadStaticConfig(); - } catch (IOException e) { - throw new StaticResourceException("Unable to load static resources due to "+e, e); - } - } else { - log.info("Static resources will not be loaded."); - } - registerDCFListener(this.iab); this.cr.subscribeOnChange(this); } From a0a71da6995941baed1ec7c6bf83226591ba9b90 Mon Sep 17 00:00:00 2001 From: Abhi Kalra <99718513+abhivka7@users.noreply.github.com> Date: Wed, 21 Dec 2022 19:46:17 +0530 Subject: [PATCH 108/369] Changing logging type to give warning for basic auth with no creds (#2347) Signed-off-by: Abhi Kalra --- src/main/java/org/opensearch/security/auth/BackendRegistry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index 42dc9ede07..79c63bca33 100644 --- a/src/main/java/org/opensearch/security/auth/BackendRegistry.java +++ b/src/main/java/org/opensearch/security/auth/BackendRegistry.java @@ -267,7 +267,7 @@ public boolean authenticate(final RestRequest request, final RestChannel channel if(authDomain.isChallenge() && httpAuthenticator.reRequestAuthentication(channel, null)) { auditLog.logFailedLogin("", false, null, request); - log.trace("No 'Authorization' header, send 401 and 'WWW-Authenticate Basic'"); + log.warn("No 'Authorization' header, send 401 and 'WWW-Authenticate Basic'"); return false; } else { //no reRequest possible From 7a6909f6912e888caa500a281bd7fe5d7e6c1ecf Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Wed, 21 Dec 2022 12:16:25 -0500 Subject: [PATCH 109/369] Updates DlsFlsFilterLeafReader to use new Lucene document() instead of visitDocument() (#2362) * Updates DlsFlsFilterLeafReader to use new Lucene document() instead of visitDocument() Signed-off-by: Stephen Crawford --- .../security/configuration/DlsFlsFilterLeafReader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java index 2b390b7c85..e94bdcb593 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java @@ -394,10 +394,10 @@ public DlsFlsStoredFieldsReader(StoredFieldsReader storedFieldsReader) { } @Override - public void visitDocument(final int docID, StoredFieldVisitor visitor) throws IOException { + public void document(final int docID, StoredFieldVisitor visitor) throws IOException { visitor = getDlsFlsVisitor(visitor); try { - in.visitDocument(docID, visitor); + in.document(docID, visitor); } finally { finishVisitor(visitor); } From 1702d52d14e6a78b9bad33a243bf667216f73d7a Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 22 Dec 2022 15:15:50 -0500 Subject: [PATCH 110/369] Incorporate changes introduced in PR branch when running BWC check for CI (#2366) * Run backwards-compatibility check with changes introduced in PR Signed-off-by: Craig Perkins --- .github/actions/create-bwc-build/action.yaml | 7 +++++++ .github/workflows/ci.yml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/actions/create-bwc-build/action.yaml b/.github/actions/create-bwc-build/action.yaml index 4d2d17a53a..b6ee3d5478 100644 --- a/.github/actions/create-bwc-build/action.yaml +++ b/.github/actions/create-bwc-build/action.yaml @@ -19,7 +19,14 @@ runs: run: git config --system core.longpaths true shell: pwsh + - name: Checkout Branch from Fork + if: ${{ inputs.plugin-branch == 'current_branch' }} + uses: actions/checkout@v2 + with: + path: ${{ inputs.plugin-branch }} + - uses: actions/checkout@v3 + if: ${{ inputs.plugin-branch != 'current_branch' }} with: repository: opensearch-project/security ref: ${{ inputs.plugin-branch }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05d9fe3539..4c859d3615 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: uses: ./.github/actions/run-bwc-suite with: plugin-previous-branch: "2.x" - plugin-next-branch: "main" + plugin-next-branch: "current_branch" report-artifact-name: bwc-${{ matrix.platform }}-jdk${{ matrix.jdk }} code-ql: From 17c2ba1c5c06e4ae627f7ddea1d0d54c86e7f96c Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Sat, 31 Dec 2022 03:58:39 -0500 Subject: [PATCH 111/369] [HTTP/2] Functional Test cases (#2367) Signed-off-by: Andriy Redko --- .../InitializationIntegrationTests.java | 30 ++- .../integration/BasicAuditlogTest.java | 2 +- .../dlsfls/DlsFlsCrossClusterSearchTest.java | 7 +- .../security/dlic/dlsfls/DlsTest.java | 2 +- .../sanity/tests/SecurityRestTestCase.java | 10 +- .../test/AbstractSecurityUnitTest.java | 8 + .../security/test/helper/rest/RestHelper.java | 220 ++++++++++++------ 7 files changed, 203 insertions(+), 76 deletions(-) diff --git a/src/test/java/org/opensearch/security/InitializationIntegrationTests.java b/src/test/java/org/opensearch/security/InitializationIntegrationTests.java index ef8ef9bf86..17ced9e325 100644 --- a/src/test/java/org/opensearch/security/InitializationIntegrationTests.java +++ b/src/test/java/org/opensearch/security/InitializationIntegrationTests.java @@ -33,6 +33,8 @@ import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.HttpVersion; +import org.apache.hc.core5.http2.HttpVersionPolicy; import org.junit.Assert; import org.junit.Test; @@ -120,6 +122,8 @@ public void testWhoAmI() throws Exception { try (RestHighLevelClient restHighLevelClient = getRestClient(clusterInfo, "spock-keystore.jks", "truststore.jks")) { Response whoAmIRes = restHighLevelClient.getLowLevelClient().performRequest(new Request("GET", "/_plugins/_security/whoami")); Assert.assertEquals(whoAmIRes.getStatusLine().getStatusCode(), 200); + // Should be using HTTP/2 by default + Assert.assertEquals(whoAmIRes.getStatusLine().getProtocolVersion(), HttpVersion.HTTP_2); JsonNode whoAmIResNode = DefaultObjectMapper.objectMapper.readTree(whoAmIRes.getEntity().getContent()); String whoAmIResponsePayload = whoAmIResNode.toPrettyString(); Assert.assertEquals(whoAmIResponsePayload, "CN=spock,OU=client,O=client,L=Test,C=DE", whoAmIResNode.get("dn").asText()); @@ -127,7 +131,30 @@ public void testWhoAmI() throws Exception { Assert.assertFalse(whoAmIResponsePayload, whoAmIResNode.get("is_node_certificate_request").asBoolean()); } } - + + @Test + public void testWhoAmIForceHttp1() throws Exception { + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityInternalUsers("internal_empty.yml") + .setSecurityRoles("roles_deny.yml"), settings, true); + + try (RestHighLevelClient restHighLevelClient = getRestClient(clusterInfo, "spock-keystore.jks", "truststore.jks", HttpVersionPolicy.FORCE_HTTP_1)) { + Response whoAmIRes = restHighLevelClient.getLowLevelClient().performRequest(new Request("GET", "/_plugins/_security/whoami")); + Assert.assertEquals(whoAmIRes.getStatusLine().getStatusCode(), 200); + // The HTTP/1.1 is forced and should be used instead + Assert.assertEquals(whoAmIRes.getStatusLine().getProtocolVersion(), HttpVersion.HTTP_1_1); + JsonNode whoAmIResNode = DefaultObjectMapper.objectMapper.readTree(whoAmIRes.getEntity().getContent()); + String whoAmIResponsePayload = whoAmIResNode.toPrettyString(); + Assert.assertEquals(whoAmIResponsePayload, "CN=spock,OU=client,O=client,L=Test,C=DE", whoAmIResNode.get("dn").asText()); + Assert.assertFalse(whoAmIResponsePayload, whoAmIResNode.get("is_admin").asBoolean()); + Assert.assertFalse(whoAmIResponsePayload, whoAmIResNode.get("is_node_certificate_request").asBoolean()); + } + } + @Test public void testConfigHotReload() throws Exception { @@ -234,5 +261,4 @@ public void testDiscoveryWithoutInitialization() throws Exception { Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); } - } 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 30e3e53436..1a99e8cdb0 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java @@ -133,7 +133,7 @@ public void testSSLPlainText() throws Exception { final RuntimeException ex = Assert.assertThrows(RuntimeException.class, () -> nonSslRestHelper().executeGetRequest("_search", encodeBasicHeader("admin", "admin"))); Assert.assertEquals("org.apache.hc.core5.http.NoHttpResponseException", ex.getCause().getClass().getName()); - }, 2); + }, 1); /* no retry on NotSslRecordException exceptions */ // All of the messages should be the same as the http client is attempting multiple times. messages.stream().forEach((message) -> { diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java index 4e8351d7b3..8fcd85f873 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java @@ -125,7 +125,8 @@ public void testCcs() throws Exception { Assert.assertTrue(ccs.getBody().contains("salary1")); Assert.assertFalse(ccs.getBody().contains("secret1")); Assert.assertFalse(ccs.getBody().contains("AnotherSecredField")); - Assert.assertFalse(ccs.getBody().contains("xxx1")); Assert.assertEquals(ccs.getHeaders().toString(), 1, ccs.getHeaders().size()); + Assert.assertFalse(ccs.getBody().contains("xxx1")); + Assert.assertEquals(ccs.getHeaders().toString(), 2, ccs.getHeaders().size()); } @Test @@ -183,7 +184,7 @@ public void testCcsDifferentConfig() throws Exception { Assert.assertTrue(ccs.getBody().contains("__fn__crl2")); Assert.assertFalse(ccs.getBody().contains("secret1")); Assert.assertFalse(ccs.getBody().contains("AnotherSecredField")); - Assert.assertEquals(ccs.getHeaders().toString(), 1, ccs.getHeaders().size()); + Assert.assertEquals(ccs.getHeaders().toString(), 2, ccs.getHeaders().size()); } @Test @@ -265,6 +266,6 @@ public void testCcsDifferentConfigBoth() throws Exception { Assert.assertFalse(ccs.getBody().contains("secret1")); Assert.assertFalse(ccs.getBody().contains("AnotherSecredField")); Assert.assertTrue(ccs.getBody().contains("someoneelse")); - Assert.assertEquals(ccs.getHeaders().toString(), 1, ccs.getHeaders().size()); + Assert.assertEquals(ccs.getHeaders().toString(), 2, ccs.getHeaders().size()); } } 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 18bbef8fef..554500257b 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java @@ -100,7 +100,7 @@ public void testDls() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("dept_manager", "password"))).getStatusCode()); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(res.getHeaders().toString(), 1, res.getHeaders().size()); + Assert.assertEquals(res.getHeaders().toString(), 2, res.getHeaders().size()); Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); diff --git a/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java b/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java index 258c18b384..51c4ddb984 100644 --- a/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java +++ b/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java @@ -77,14 +77,20 @@ protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOE // create adminDN (super-admin) client File file = new File(getClass().getClassLoader().getResource(CERT_FILE_DIRECTORY).getFile()); Path configPath = PathUtils.get(file.toURI()).getParent().toAbsolutePath(); - return new SecureRestClientBuilder(settings, configPath).setSocketTimeout(60000).build(); + return new SecureRestClientBuilder(settings, configPath) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build(); } // create client with passed user String userName = System.getProperty("user"); String password = System.getProperty("password"); - return new SecureRestClientBuilder(hosts, isHttps(), userName, password).setSocketTimeout(60000).build(); + return new SecureRestClientBuilder(hosts, isHttps(), userName, password) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build(); } else { RestClientBuilder builder = RestClient.builder(hosts); diff --git a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java index 592433d5e9..72b05abb91 100644 --- a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java +++ b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java @@ -55,6 +55,7 @@ import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.ssl.SSLContextBuilder; import org.apache.hc.core5.ssl.SSLContexts; @@ -144,6 +145,10 @@ public static Header encodeBasicHeader(final String username, final String passw } protected RestHighLevelClient getRestClient(ClusterInfo info, String keyStoreName, String trustStoreName) { + return getRestClient(info, keyStoreName, trustStoreName, null); + } + + protected RestHighLevelClient getRestClient(ClusterInfo info, String keyStoreName, String trustStoreName, HttpVersionPolicy httpVersionPolicy) { final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; try { @@ -183,6 +188,9 @@ public TlsDetails create(final SSLEngine sslEngine) { .setTlsStrategy(tlsStrategy) .build(); builder.setConnectionManager(cm); + if (httpVersionPolicy != null) { + builder.setVersionPolicy(httpVersionPolicy); + } return builder; }); return new RestHighLevelClient(restClientBuilder); 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 30b549dfa1..ff9a5b536b 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 @@ -28,24 +28,27 @@ import java.io.FileInputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Scanner; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import com.fasterxml.jackson.databind.JsonNode; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; import org.apache.hc.client5.http.classic.methods.HttpDelete; @@ -56,22 +59,32 @@ import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.classic.methods.HttpPut; import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; -import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; +import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.function.Factory; +import org.apache.hc.core5.http.ConnectionClosedException; +import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHeaders; -import org.apache.hc.core5.http.io.SocketConfig; +import org.apache.hc.core5.http.HttpVersion; +import org.apache.hc.core5.http.NoHttpResponseException; +import org.apache.hc.core5.http.ProtocolVersion; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.ssl.SSLContextBuilder; import org.apache.hc.core5.ssl.SSLContexts; +import org.apache.hc.core5.util.Timeout; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -108,24 +121,50 @@ public RestHelper(ClusterInfo clusterInfo, boolean enableHTTPClientSSL, boolean } public String executeSimpleRequest(final String request) throws Exception { - CloseableHttpClient httpClient = null; - CloseableHttpResponse response = null; + CloseableHttpAsyncClient httpClient = null; try { httpClient = getHTTPClient(); - response = httpClient.execute(new HttpGet(getRequestUri(request))); + httpClient.start(); + + final CompletableFuture future = new CompletableFuture<>(); + final SimpleHttpRequest simpleRequest = SimpleRequestBuilder.copy(new HttpGet(getRequestUri(request))).build(); + httpClient.execute(simpleRequest, new FutureCallback() { + @Override + public void completed(SimpleHttpResponse result) { + future.complete(result); + } + + @Override + public void failed(Exception ex) { + future.completeExceptionally(ex); + } + + @Override + public void cancelled() { + future.cancel(true); + } + }); + final SimpleHttpResponse response = future.join(); if (response.getCode() >= 300) { throw new Exception("Statuscode " + response.getCode()); } - return IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); - } finally { - - if (response != null) { - response.close(); + if (enableHTTPClientSSL && !response.getVersion().equals(HttpVersion.HTTP_2)) { + throw new IllegalStateException("HTTP/2 expected for HTTPS communication but " + response.getVersion() + " was used"); } + return response.getBodyText(); + } catch (final CompletionException e) { + final Throwable cause = e.getCause(); + // Make it compatible with DefaultHttpResponseParser::createConnectionClosedException() + if (cause instanceof ConnectionClosedException) { + throw new NoHttpResponseException(cause.getMessage(), cause); + } else { + throw (Exception)cause; + } + } finally { if (httpClient != null) { httpClient.close(); } @@ -145,7 +184,7 @@ public HttpResponse[] executeMultipleAsyncPutRequest(final int numOfRequests, fi } public HttpResponse executeGetRequest(final String request, Header... header) { - return executeRequest(new HttpGet(getRequestUri(request)), header); + return executeRequest(new HttpGet(getRequestUri(request)), header); } public HttpResponse executeGetRequest(final String request, String body, Header... header) { @@ -156,12 +195,12 @@ public HttpResponse executeGetRequest(final String request, String body, Header. } public HttpResponse executeHeadRequest(final String request, Header... header) { - return executeRequest(new HttpHead(getRequestUri(request)), header); - } + return executeRequest(new HttpHead(getRequestUri(request)), header); + } public HttpResponse executeOptionsRequest(final String request) { - return executeRequest(new HttpOptions(getRequestUri(request))); - } + return executeRequest(new HttpOptions(getRequestUri(request))); + } public HttpResponse executePutRequest(final String request, String body, Header... header) { HttpPut uriRequest = new HttpPut(getRequestUri(request)); @@ -192,20 +231,21 @@ public HttpResponse executePostRequest(final String request, String body, Header return executeRequest(uriRequest, header); } - public HttpResponse executePatchRequest(final String request, String body, Header... header) { - HttpPatch uriRequest = new HttpPatch(getRequestUri(request)); - if (body != null && !body.isEmpty()) { - uriRequest.setEntity(createStringEntity(body)); - } - return executeRequest(uriRequest, header); - } + public HttpResponse executePatchRequest(final String request, String body, Header... header) { + HttpPatch uriRequest = new HttpPatch(getRequestUri(request)); + if (body != null && !body.isEmpty()) { + uriRequest.setEntity(createStringEntity(body)); + } + return executeRequest(uriRequest, header); + } public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... header) { - CloseableHttpClient httpClient = null; + CloseableHttpAsyncClient httpClient = null; try { httpClient = getHTTPClient(); + httpClient.start(); if (header != null && header.length > 0) { for (int i = 0; i < header.length; i++) { @@ -215,11 +255,49 @@ public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... header) } if (!uriRequest.containsHeader("Content-Type")) { - uriRequest.addHeader("Content-Type","application/json"); + uriRequest.addHeader("Content-Type","application/json"); + } + + final CompletableFuture future = new CompletableFuture<>(); + final SimpleHttpRequest simpleRequest = SimpleRequestBuilder.copy(uriRequest).build(); + if (uriRequest.getEntity() != null) { + simpleRequest.setBody(EntityUtils.toByteArray(uriRequest.getEntity()), + ContentType.parse(uriRequest.getEntity().getContentType())); } - HttpResponse res = new HttpResponse(httpClient.execute(uriRequest)); + httpClient.execute(simpleRequest, new FutureCallback() { + @Override + public void completed(SimpleHttpResponse result) { + future.complete(result); + } + + @Override + public void failed(Exception ex) { + future.completeExceptionally(ex); + } + + @Override + public void cancelled() { + future.cancel(true); + } + }); + + final HttpResponse res = new HttpResponse(future.join()); + if (enableHTTPClientSSL && !res.getProtocolVersion().equals(HttpVersion.HTTP_2)) { + throw new IllegalStateException("HTTP/2 expected for HTTPS communication but " + res.getProtocolVersion() + " was used"); + } + log.debug(res.getBody()); return res; + } catch (final CompletionException e) { + final Throwable cause = e.getCause(); + // Make it compatible with DefaultHttpResponseParser::createConnectionClosedException() + if (cause instanceof ConnectionClosedException) { + throw new RuntimeException(new NoHttpResponseException(cause.getMessage(), cause)); + } else if (cause instanceof RuntimeException) { + throw (RuntimeException)cause; + } else { + throw new RuntimeException(cause); + } } catch (final Exception e) { throw new RuntimeException(e); } finally { @@ -248,9 +326,9 @@ protected final String getRequestUri(String request) { return getHttpServerUri() + "/" + StringUtils.strip(request, "/"); } - protected final CloseableHttpClient getHTTPClient() throws Exception { + protected final CloseableHttpAsyncClient getHTTPClient() throws Exception { - final HttpClientBuilder hcb = HttpClients.custom(); + final HttpAsyncClientBuilder hcb = HttpAsyncClients.custom(); if (sendHTTPClientCredentials) { UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("sarek", "sarek".toCharArray()); @@ -264,11 +342,11 @@ protected final CloseableHttpClient getHTTPClient() throws Exception { log.debug("Configure HTTP client with SSL"); if(prefix != null && !keystore.contains("/")) { - keystore = prefix+"/"+keystore; + keystore = prefix+"/"+keystore; } - final String keyStorePath = FileHelper.getAbsoluteFilePathFromClassPath(keystore).toFile().getParent(); - + final String keyStorePath = FileHelper.getAbsoluteFilePathFromClassPath(keystore).toFile().getParent(); + final KeyStore myTrustStore = KeyStore.getInstance("JKS"); myTrustStore.load(new FileInputStream(keyStorePath+"/truststore.jks"), "changeit".toCharArray()); @@ -296,40 +374,54 @@ protected final CloseableHttpClient getHTTPClient() throws Exception { protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" }; } - final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, protocols, null, - NoopHostnameVerifier.INSTANCE); + final TlsStrategy tlsStrategy = ClientTlsStrategyBuilder + .create() + .setSslContext(sslContext) + .setTlsVersions(protocols) + .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) + // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 + .setTlsDetailsFactory(new Factory() { + @Override + public TlsDetails create(final SSLEngine sslEngine) { + return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); + } + }) + .build(); - final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslsf) - .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60, TimeUnit.SECONDS).build()) + final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(tlsStrategy) .build(); + hcb.setConnectionManager(cm); } - return hcb.build(); + final RequestConfig.Builder requestConfigBuilder = RequestConfig.custom() + .setResponseTimeout(Timeout.ofSeconds(60)); + + return hcb.setDefaultRequestConfig(requestConfigBuilder.build()).disableAutomaticRetries().build(); } public static class HttpResponse { - private final CloseableHttpResponse inner; + private final SimpleHttpResponse inner; private final String body; private final Header[] header; private final int statusCode; private final String statusReason; + private final ProtocolVersion protocolVersion; - public HttpResponse(CloseableHttpResponse inner) throws IllegalStateException, IOException { + public HttpResponse(SimpleHttpResponse inner) throws IllegalStateException, IOException { super(); this.inner = inner; - final HttpEntity entity = inner.getEntity(); - if(entity == null) { //head request does not have a entity - this.body = ""; + if(inner.getBody() == null) { //head request does not have a entity + this.body = ""; } else { - this.body = IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8); + this.body = inner.getBodyText(); } this.header = inner.getHeaders(); this.statusCode = inner.getCode(); this.statusReason = inner.getReasonPhrase(); - inner.close(); + this.protocolVersion = inner.getVersion(); } public String getContentType() { @@ -348,7 +440,7 @@ public boolean isJsonContentType() { return ct.contains("application/json"); } - public CloseableHttpResponse getInner() { + public SimpleHttpResponse getInner() { return inner; } @@ -372,20 +464,14 @@ public List
getHeaders() { return header==null?Collections.emptyList():Arrays.asList(header); } - @Override - public String toString() { - return "HttpResponse [inner=" + inner + ", body=" + body + ", header=" + Arrays.toString(header) + ", statusCode=" + statusCode - + ", statusReason=" + statusReason + "]"; - } - - private static void findArrayAccessor(String input) { - final Pattern r = Pattern.compile("(.+?)\\[(\\d+)\\]"); - final Matcher m = r.matcher(input); - if(m.find()) { - System.out.println("'" + input + "'\t Name was: " + m.group(1) + ",\t index position: " + m.group(2)); - } else { - System.out.println("'" + input + "'\t No Match"); - } + public ProtocolVersion getProtocolVersion() { + return protocolVersion; + } + + @Override + public String toString() { + return "HttpResponse [inner=" + inner + ", body=" + body + ", header=" + Arrays.toString(header) + ", statusCode=" + statusCode + + ", statusReason=" + statusReason + "]"; } /** From 67e417625242607041fbaaf72683f1b146e99de4 Mon Sep 17 00:00:00 2001 From: kt-eliatra <103500997+kt-eliatra@users.noreply.github.com> Date: Sat, 31 Dec 2022 10:32:30 +0100 Subject: [PATCH 112/369] Pit operations tests (#2359) Signed-off-by: Kacper Trochimiak Signed-off-by: Kacper Trochimiak <103500997+kt-eliatra@users.noreply.github.com> --- .../security/PointInTimeOperationTest.java | 395 ++++++++++++++++++ .../framework/cluster/TestRestClient.java | 10 + ...ePitContainsExactlyIdsResponseMatcher.java | 48 +++ ...PitsContainsExactlyIdsResponseMatcher.java | 48 +++ .../matcher/PitResponseMatchers.java | 38 ++ .../SuccessfulCreatePitResponseMatcher.java | 37 ++ .../SuccessfulDeletePitResponseMatcher.java | 40 ++ 7 files changed, 616 insertions(+) create mode 100644 src/integrationTest/java/org/opensearch/security/PointInTimeOperationTest.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/DeletePitContainsExactlyIdsResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/GetAllPitsContainsExactlyIdsResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/PitResponseMatchers.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreatePitResponseMatcher.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeletePitResponseMatcher.java diff --git a/src/integrationTest/java/org/opensearch/security/PointInTimeOperationTest.java b/src/integrationTest/java/org/opensearch/security/PointInTimeOperationTest.java new file mode 100644 index 0000000000..eda561185d --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/PointInTimeOperationTest.java @@ -0,0 +1,395 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.io.IOException; + +import com.carrotsearch.randomizedtesting.RandomizedRunner; +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.OpenSearchStatusException; +import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.search.CreatePitRequest; +import org.opensearch.action.search.CreatePitResponse; +import org.opensearch.action.search.DeletePitRequest; +import org.opensearch.action.search.DeletePitResponse; +import org.opensearch.action.search.GetAllPitNodesResponse; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.client.Client; +import org.opensearch.client.RestHighLevelClient; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.rest.RestStatus; +import org.opensearch.search.builder.PointInTimeBuilder; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions.Type.ADD; +import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.opensearch.client.RequestOptions.DEFAULT; +import static org.opensearch.rest.RestStatus.FORBIDDEN; +import static org.opensearch.rest.RestStatus.OK; +import static org.opensearch.security.Song.SONGS; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; +import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy; +import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.statusException; +import static org.opensearch.test.framework.matcher.PitResponseMatchers.deleteResponseContainsExactlyPitWithIds; +import static org.opensearch.test.framework.matcher.PitResponseMatchers.getAllResponseContainsExactlyPitWithIds; +import static org.opensearch.test.framework.matcher.PitResponseMatchers.isSuccessfulCreatePitResponse; +import static org.opensearch.test.framework.matcher.PitResponseMatchers.isSuccessfulDeletePitResponse; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.isSuccessfulSearchResponse; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitsContainDocumentsInAnyOrder; + +@RunWith(RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class PointInTimeOperationTest { + + private static final String FIRST_SONG_INDEX = "song-index-1"; + private static final String FIRST_INDEX_ALIAS = "song-index-1-alias"; + private static final String SECOND_SONG_INDEX = "song-index-2"; + private static final String SECOND_INDEX_ALIAS = "song-index-2-alias"; + + private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + /** + * User who is allowed to perform PIT operations only on the {@link #FIRST_SONG_INDEX} + */ + private static final TestSecurityConfig.User LIMITED_POINT_IN_TIME_USER = new TestSecurityConfig.User("limited_point_in_time_user") + .roles(new TestSecurityConfig.Role("limited_point_in_time_user") + .indexPermissions( + "indices:data/read/point_in_time/create", + "indices:data/read/point_in_time/delete", + "indices:data/read/search", + "indices:data/read/point_in_time/readall", //anyway user needs the all indexes permission (*) to find all pits + "indices:monitor/point_in_time/segments" //anyway user needs the all indexes permission (*) to list all pits segments + ) + .on(FIRST_SONG_INDEX)); + /** + * User who is allowed to perform PIT operations on all indices + */ + private static final TestSecurityConfig.User POINT_IN_TIME_USER = new TestSecurityConfig.User("point_in_time_user") + .roles(new TestSecurityConfig.Role("point_in_time_user") + .indexPermissions( + "indices:data/read/point_in_time/create", + "indices:data/read/point_in_time/delete", + "indices:data/read/search", + "indices:data/read/point_in_time/readall", + "indices:monitor/point_in_time/segments" + ) + .on("*")); + + private static final String ID_1 = "1"; + private static final String ID_2 = "2"; + private static final String ID_3 = "3"; + private static final String ID_4 = "4"; + + @BeforeClass + public static void createTestData() { + try(Client client = cluster.getInternalNodeClient()) { + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(FIRST_SONG_INDEX).id(ID_1).source(SONGS[0].asMap())).actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(FIRST_SONG_INDEX).id(ID_2).source(SONGS[1].asMap())).actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(FIRST_SONG_INDEX).id(ID_3).source(SONGS[2].asMap())).actionGet(); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD).indices( + FIRST_SONG_INDEX).alias(FIRST_INDEX_ALIAS))).actionGet(); + + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SECOND_SONG_INDEX).id(ID_4).source(SONGS[3].asMap())).actionGet(); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD).indices( + SECOND_SONG_INDEX).alias(SECOND_INDEX_ALIAS))).actionGet(); + } + } + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER, LIMITED_POINT_IN_TIME_USER, POINT_IN_TIME_USER) + .build(); + + @Test + public void createPit_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, FIRST_SONG_INDEX); + + CreatePitResponse createPitResponse = restHighLevelClient.createPit(createPitRequest, DEFAULT); + + assertThat(createPitResponse, isSuccessfulCreatePitResponse()); + } + } + + @Test + public void createPitWithIndexAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, FIRST_INDEX_ALIAS); + + CreatePitResponse createPitResponse = restHighLevelClient.createPit(createPitRequest, DEFAULT); + + assertThat(createPitResponse, isSuccessfulCreatePitResponse()); + } + } + + @Test + public void createPit_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, SECOND_SONG_INDEX); + + assertThatThrownBy(() -> restHighLevelClient.createPit(createPitRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void createPitWithIndexAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, SECOND_INDEX_ALIAS); + + assertThatThrownBy(() -> restHighLevelClient.createPit(createPitRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void listAllPits_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(POINT_IN_TIME_USER)) { + cleanUpPits(); + String firstIndexPit = createPitForIndices(FIRST_SONG_INDEX); + String secondIndexPit = createPitForIndices(SECOND_SONG_INDEX); + + GetAllPitNodesResponse getAllPitsResponse = restHighLevelClient.getAllPits(DEFAULT); + + assertThat(getAllPitsResponse, getAllResponseContainsExactlyPitWithIds(firstIndexPit, secondIndexPit)); + } + } + + @Test + public void listAllPits_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + assertThatThrownBy(() -> restHighLevelClient.getAllPits(DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void deletePit_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(FIRST_SONG_INDEX); + + DeletePitResponse deletePitResponse = restHighLevelClient.deletePit(new DeletePitRequest(existingPitId), DEFAULT); + assertThat(deletePitResponse, isSuccessfulDeletePitResponse()); + assertThat(deletePitResponse, deleteResponseContainsExactlyPitWithIds(existingPitId)); + } + } + + @Test + public void deletePitCreatedWithIndexAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(FIRST_INDEX_ALIAS); + + DeletePitResponse deletePitResponse = restHighLevelClient.deletePit(new DeletePitRequest(existingPitId), DEFAULT); + assertThat(deletePitResponse, isSuccessfulDeletePitResponse()); + assertThat(deletePitResponse, deleteResponseContainsExactlyPitWithIds(existingPitId)); + } + } + + @Test + public void deletePit_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(SECOND_SONG_INDEX); + + assertThatThrownBy(() -> restHighLevelClient.deletePit(new DeletePitRequest(existingPitId), DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void deletePitCreatedWithIndexAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(SECOND_INDEX_ALIAS); + + assertThatThrownBy(() -> restHighLevelClient.deletePit(new DeletePitRequest(existingPitId), DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void deleteAllPits_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(POINT_IN_TIME_USER)) { + cleanUpPits(); + String firstIndexPit = createPitForIndices(FIRST_SONG_INDEX); + String secondIndexPit = createPitForIndices(SECOND_SONG_INDEX); + + DeletePitResponse deletePitResponse = restHighLevelClient.deleteAllPits(DEFAULT); + assertThat(deletePitResponse, isSuccessfulDeletePitResponse()); + assertThat(deletePitResponse, deleteResponseContainsExactlyPitWithIds(firstIndexPit, secondIndexPit)); + } + } + + @Test + public void deleteAllPits_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + assertThatThrownBy(() -> restHighLevelClient.deleteAllPits(DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void searchWithPit_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(FIRST_SONG_INDEX); + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.source(new SearchSourceBuilder().pointInTimeBuilder(new PointInTimeBuilder(existingPitId))); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, searchHitsContainDocumentsInAnyOrder( + Pair.of(FIRST_SONG_INDEX, ID_1), Pair.of(FIRST_SONG_INDEX, ID_2), Pair.of(FIRST_SONG_INDEX, ID_3) + )); + } + } + + @Test + public void searchWithPitCreatedWithIndexAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(FIRST_INDEX_ALIAS); + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.source(new SearchSourceBuilder().pointInTimeBuilder(new PointInTimeBuilder(existingPitId))); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, searchHitsContainDocumentsInAnyOrder( + Pair.of(FIRST_SONG_INDEX, ID_1), Pair.of(FIRST_SONG_INDEX, ID_2), Pair.of(FIRST_SONG_INDEX, ID_3) + )); + } + } + + @Test + public void searchWithPit_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(SECOND_SONG_INDEX); + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.source(new SearchSourceBuilder().pointInTimeBuilder(new PointInTimeBuilder(existingPitId))); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void searchWithPitCreatedWithIndexAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(SECOND_INDEX_ALIAS); + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.source(new SearchSourceBuilder().pointInTimeBuilder(new PointInTimeBuilder(existingPitId))); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void listPitSegments_positive() throws IOException { + try (TestRestClient restClient = cluster.getRestClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(FIRST_SONG_INDEX); + String body = String.format("{\"pit_id\":[\"%s\"]}", existingPitId); + HttpResponse response = restClient.getWithJsonBody("/_cat/pit_segments", body); + + response.assertStatusCode(OK.getStatus()); + } + } + + @Test + public void listPitSegmentsCreatedWithIndexAlias_positive() throws IOException { + try (TestRestClient restClient = cluster.getRestClient(POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(FIRST_INDEX_ALIAS); + String body = String.format("{\"pit_id\":[\"%s\"]}", existingPitId); + HttpResponse response = restClient.getWithJsonBody("/_cat/pit_segments", body); + + response.assertStatusCode(OK.getStatus()); + } + } + + @Test + public void listPitSegments_negative() throws IOException { + try (TestRestClient restClient = cluster.getRestClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(SECOND_SONG_INDEX); + String body = String.format("{\"pit_id\":[\"%s\"]}", existingPitId); + HttpResponse response = restClient.getWithJsonBody("/_cat/pit_segments", body); + + response.assertStatusCode(FORBIDDEN.getStatus()); + } + } + + @Test + public void listPitSegmentsCreatedWithIndexAlias_negative() throws IOException { + try (TestRestClient restClient = cluster.getRestClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(SECOND_INDEX_ALIAS); + String body = String.format("{\"pit_id\":[\"%s\"]}", existingPitId); + HttpResponse response = restClient.getWithJsonBody("/_cat/pit_segments", body); + + response.assertStatusCode(FORBIDDEN.getStatus()); + } + } + + @Test + public void listAllPitSegments_positive() { + try (TestRestClient restClient = cluster.getRestClient(POINT_IN_TIME_USER)) { + HttpResponse response = restClient.get("/_cat/pit_segments/_all"); + + response.assertStatusCode(OK.getStatus()); + } + } + + @Test + public void listAllPitSegments_negative() { + try (TestRestClient restClient = cluster.getRestClient(LIMITED_POINT_IN_TIME_USER)) { + HttpResponse response = restClient.get("/_cat/pit_segments/_all"); + + response.assertStatusCode(FORBIDDEN.getStatus()); + } + } + + /** + * Creates PIT for given indices. Returns PIT id. + */ + private String createPitForIndices(String... indices) throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, indices); + + CreatePitResponse createPitResponse = restHighLevelClient.createPit(createPitRequest, DEFAULT); + + assertThat(createPitResponse, isSuccessfulCreatePitResponse()); + return createPitResponse.getId(); + } + } + + /** + * Deletes all PITs. + */ + public void cleanUpPits() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + try { + restHighLevelClient.deleteAllPits(DEFAULT); + } catch (OpenSearchStatusException ex) { + if (ex.status() != RestStatus.NOT_FOUND) { + throw ex; + } + //tried to remove pits but no pit exists + } + } + } + +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java index 8619421085..b7be0a5548 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -120,6 +120,16 @@ public HttpResponse get(String path, Header... headers) { return get(path, Collections.emptyList(), headers); } + public HttpResponse getWithJsonBody(String path, String body, Header... headers) { + try { + HttpGet httpGet = new HttpGet( new URIBuilder(getHttpServerUri()).setPath(path).build()); + httpGet.setEntity(toStringEntity(body)); + return executeRequest(httpGet, mergeHeaders(CONTENT_TYPE_JSON, headers)); + } catch (URISyntaxException ex) { + throw new RuntimeException("Incorrect URI syntax", ex); + } + } + public HttpResponse getAuthInfo( Header... headers) { return executeRequest(new HttpGet(getHttpServerUri() + "/_opendistro/_security/authinfo?pretty"), headers); } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/DeletePitContainsExactlyIdsResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/DeletePitContainsExactlyIdsResponseMatcher.java new file mode 100644 index 0000000000..fd921d0d72 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/DeletePitContainsExactlyIdsResponseMatcher.java @@ -0,0 +1,48 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.DeletePitInfo; +import org.opensearch.action.search.DeletePitResponse; + +import static java.util.Objects.isNull; + +class DeletePitContainsExactlyIdsResponseMatcher extends TypeSafeDiagnosingMatcher { + + private final Set expectedPitIds; + + DeletePitContainsExactlyIdsResponseMatcher(String[] expectedPitIds) { + if (isNull(expectedPitIds) || 0 == expectedPitIds.length) { + throw new IllegalArgumentException("expectedPitIds cannot be null or empty"); + } + this.expectedPitIds = Set.of(expectedPitIds); + } + + @Override + protected boolean matchesSafely(DeletePitResponse response, Description mismatchDescription) { + Set actualPitIds = response.getDeletePitResults().stream().map(DeletePitInfo::getPitId).collect(Collectors.toSet()); + if (!actualPitIds.equals(expectedPitIds)) { + mismatchDescription.appendText("Actual pit ids: ").appendValue(actualPitIds); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Should contain exactly pit with ids: ").appendValue(expectedPitIds); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetAllPitsContainsExactlyIdsResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetAllPitsContainsExactlyIdsResponseMatcher.java new file mode 100644 index 0000000000..447224345c --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetAllPitsContainsExactlyIdsResponseMatcher.java @@ -0,0 +1,48 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.GetAllPitNodesResponse; +import org.opensearch.action.search.ListPitInfo; + +import static java.util.Objects.isNull; + +class GetAllPitsContainsExactlyIdsResponseMatcher extends TypeSafeDiagnosingMatcher { + + private final Set expectedPitIds; + + GetAllPitsContainsExactlyIdsResponseMatcher(String[] expectedPitIds) { + if (isNull(expectedPitIds) || 0 == expectedPitIds.length) { + throw new IllegalArgumentException("expectedPitIds cannot be null or empty"); + } + this.expectedPitIds = Set.of(expectedPitIds); + } + + @Override + protected boolean matchesSafely(GetAllPitNodesResponse response, Description mismatchDescription) { + Set actualPitIds = response.getPitInfos().stream().map(ListPitInfo::getPitId).collect(Collectors.toSet()); + if (!actualPitIds.equals(expectedPitIds)) { + mismatchDescription.appendText("Actual pit ids: ").appendValue(actualPitIds); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Should contain exactly pit with ids: ").appendValue(expectedPitIds); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/PitResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/PitResponseMatchers.java new file mode 100644 index 0000000000..3e72866092 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/PitResponseMatchers.java @@ -0,0 +1,38 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Matcher; + +import org.opensearch.action.search.CreatePitResponse; +import org.opensearch.action.search.DeletePitResponse; +import org.opensearch.action.search.GetAllPitNodesResponse; + +public class PitResponseMatchers { + + private PitResponseMatchers() { + } + + public static Matcher isSuccessfulCreatePitResponse() { + return new SuccessfulCreatePitResponseMatcher(); + } + + public static Matcher getAllResponseContainsExactlyPitWithIds(String... expectedPitIds) { + return new GetAllPitsContainsExactlyIdsResponseMatcher(expectedPitIds); + } + + public static Matcher isSuccessfulDeletePitResponse() { + return new SuccessfulDeletePitResponseMatcher(); + } + + public static Matcher deleteResponseContainsExactlyPitWithIds(String... expectedPitIds) { + return new DeletePitContainsExactlyIdsResponseMatcher(expectedPitIds); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreatePitResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreatePitResponseMatcher.java new file mode 100644 index 0000000000..29670334cc --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreatePitResponseMatcher.java @@ -0,0 +1,37 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.CreatePitResponse; +import org.opensearch.rest.RestStatus; + +class SuccessfulCreatePitResponseMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(CreatePitResponse response, Description mismatchDescription) { + if(!RestStatus.OK.equals(response.status())) { + mismatchDescription.appendText("has status ").appendValue(response.status()).appendText(" which denotes failure."); + return false; + } + if(response.getShardFailures().length != 0) { + mismatchDescription.appendText("contains ").appendValue(response.getShardFailures().length).appendText(" shard failures"); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful create pit response"); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeletePitResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeletePitResponseMatcher.java new file mode 100644 index 0000000000..0b0c0af09c --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeletePitResponseMatcher.java @@ -0,0 +1,40 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.test.framework.matcher; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import org.opensearch.action.search.DeletePitInfo; +import org.opensearch.action.search.DeletePitResponse; +import org.opensearch.rest.RestStatus; + +class SuccessfulDeletePitResponseMatcher extends TypeSafeDiagnosingMatcher { + + @Override + protected boolean matchesSafely(DeletePitResponse response, Description mismatchDescription) { + if(!RestStatus.OK.equals(response.status())) { + mismatchDescription.appendText("has status ").appendValue(response.status()).appendText(" which denotes failure."); + return false; + } + for(DeletePitInfo deletePitInfo : response.getDeletePitResults()) { + if (!deletePitInfo.isSuccessful()) { + mismatchDescription.appendValue("Pit: ").appendValue(deletePitInfo.getPitId()).appendText(" - delete result was not successful"); + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful delete pit response"); + } +} From 07c17c2e52f1a941be0876ea4ae758701c0798cc Mon Sep 17 00:00:00 2001 From: kt-eliatra <103500997+kt-eliatra@users.noreply.github.com> Date: Thu, 5 Jan 2023 21:12:25 +0100 Subject: [PATCH 113/369] add anonymous authentication tests (#2370) Signed-off-by: Kacper Trochimiak Signed-off-by: Kacper Trochimiak <103500997+kt-eliatra@users.noreply.github.com> --- .../http/AnonymousAuthenticationTest.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/integrationTest/java/org/opensearch/security/http/AnonymousAuthenticationTest.java diff --git a/src/integrationTest/java/org/opensearch/security/http/AnonymousAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/AnonymousAuthenticationTest.java new file mode 100644 index 0000000000..d50ed081c8 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/AnonymousAuthenticationTest.java @@ -0,0 +1,128 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security.http; + +import java.util.List; + +import com.carrotsearch.randomizedtesting.RandomizedRunner; +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.RolesMapping; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; + +@RunWith(RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class AnonymousAuthenticationTest { + + private static final String DEFAULT_ANONYMOUS_USER_NAME = "opendistro_security_anonymous"; + private static final String DEFAULT_ANONYMOUS_USER_BACKEND_ROLE_NAME = "opendistro_security_anonymous_backendrole"; + + /** + * Custom role assigned to the anonymous user via {@link #ANONYMOUS_USER_CUSTOM_ROLE_MAPPING} + */ + private static final TestSecurityConfig.Role ANONYMOUS_USER_CUSTOM_ROLE = new TestSecurityConfig.Role("anonymous_user_custom_role"); + + /** + * Maps {@link #ANONYMOUS_USER_CUSTOM_ROLE} to {@link #DEFAULT_ANONYMOUS_USER_BACKEND_ROLE_NAME} + */ + private static final RolesMapping ANONYMOUS_USER_CUSTOM_ROLE_MAPPING = new RolesMapping(ANONYMOUS_USER_CUSTOM_ROLE) + .backendRoles(DEFAULT_ANONYMOUS_USER_BACKEND_ROLE_NAME); + + /** + * User who is stored in the internal user database and can authenticate + */ + private static final TestSecurityConfig.User EXISTING_USER = new TestSecurityConfig.User("existing_user") + .roles(new TestSecurityConfig.Role("existing_user")); + + /** + * User who is not stored in the internal user database and can not authenticate + */ + private static final TestSecurityConfig.User NOT_EXISTING_USER = new TestSecurityConfig.User("not_existing_user") + .roles(new TestSecurityConfig.Role("not_existing_user")); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(true) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(EXISTING_USER) + .roles(ANONYMOUS_USER_CUSTOM_ROLE) + .rolesMapping(ANONYMOUS_USER_CUSTOM_ROLE_MAPPING) + .build(); + + private static final String USER_NAME_POINTER = "/user_name"; + private static final String BACKEND_ROLES_POINTER = "/backend_roles"; + private static final String ROLES_POINTER = "/roles"; + + + @Test + public void shouldAuthenticate_positive_anonymousUser() { + try(TestRestClient client = cluster.getRestClient()){ + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + + String username = response.getTextFromJsonBody(USER_NAME_POINTER); + assertThat(username, equalTo(DEFAULT_ANONYMOUS_USER_NAME)); + + List backendRoles = response.getTextArrayFromJsonBody(BACKEND_ROLES_POINTER); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, contains(DEFAULT_ANONYMOUS_USER_BACKEND_ROLE_NAME)); + + List roles = response.getTextArrayFromJsonBody(ROLES_POINTER); + assertThat(roles, hasSize(1)); + assertThat(roles, contains(ANONYMOUS_USER_CUSTOM_ROLE.getName())); + } + } + + @Test + public void shouldAuthenticate_positive_existingUser() { + try(TestRestClient client = cluster.getRestClient(EXISTING_USER)){ + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + + String username = response.getTextFromJsonBody(USER_NAME_POINTER); + assertThat(username, equalTo(EXISTING_USER.getName())); + + List backendRoles = response.getTextArrayFromJsonBody(BACKEND_ROLES_POINTER); + assertThat(backendRoles, hasSize(0)); + + List roles = response.getTextArrayFromJsonBody(ROLES_POINTER); + assertThat(roles, hasSize(EXISTING_USER.getRoleNames().size())); + assertThat(roles, containsInAnyOrder(EXISTING_USER.getRoleNames().toArray())); + } + } + + @Test + public void shouldAuthenticate_negative_notExistingUser() { + try(TestRestClient client = cluster.getRestClient(NOT_EXISTING_USER)){ + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + } + } +} From 1ec875c7f69b74d3bba7df68ca70f79db5217f72 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 5 Jan 2023 14:34:03 -0600 Subject: [PATCH 114/369] When excluding fields also exclude the term + `.keyword` (#2375) Signed-off-by: Peter Nied --- .../configuration/DlsFlsFilterLeafReader.java | 1 + .../security/dlic/dlsfls/FlsKeywordTests.java | 86 +++++++++++++++++++ src/test/resources/dlsfls/roles_keyword.yml | 18 ++++ .../dlsfls/roles_mappings_keyword.yml | 22 +++++ 4 files changed, 127 insertions(+) create mode 100644 src/test/java/org/opensearch/security/dlic/dlsfls/FlsKeywordTests.java create mode 100644 src/test/resources/dlsfls/roles_keyword.yml create mode 100644 src/test/resources/dlsfls/roles_mappings_keyword.yml diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java index e94bdcb593..06a89ac099 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java @@ -142,6 +142,7 @@ class DlsFlsFilterLeafReader extends SequentialStoredFieldsLeafReader { if (firstChar == '!' || firstChar == '~') { excludesSet.add(incExc.substring(1)); + excludesSet.add(incExc.substring(1) + KEYWORD); } else { includesSet.add(incExc); } diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsKeywordTests.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsKeywordTests.java new file mode 100644 index 0000000000..25069e48ef --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsKeywordTests.java @@ -0,0 +1,86 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.dlsfls; + +import java.util.Arrays; + +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.junit.Test; + +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest.RefreshPolicy; +import org.opensearch.client.Client; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.security.test.DynamicSecurityConfig; +import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.IsNot.not; +import static org.hamcrest.core.StringContains.containsString; + +public class FlsKeywordTests extends AbstractDlsFlsTest { + + protected void populateData(Client tc) { + tc.index(new IndexRequest("movies").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"year\": 2013, \"title\": \"Rush\", \"actors\": [\"Daniel Br\u00FChl\", \"Chris Hemsworth\", \"Olivia Wilde\"]}", XContentType.JSON)).actionGet(); + } + + private Header movieUser = encodeBasicHeader("user_aaa", "password"); + private Header movieNoActorUser = encodeBasicHeader("user_bbb", "password"); + + private String[] actors = new String[] {"Daniel Br\u00FChl", "Chris Hemsworth", "Olivia Wilde"}; + + @Test + public void testKeywordsAreAutomaticallyFiltered() throws Exception { + setup(new DynamicSecurityConfig() + .setSecurityRoles("roles_keyword.yml") + .setSecurityRolesMapping("roles_mappings_keyword.yml")); + + final String searchQuery = "/movies/_search?filter_path=hits.hits._source"; + final String aggQuery = "/movies/_search?filter_path=aggregations.actors.buckets.key"; + final String aggByActorKeyword = "{\"aggs\":{\"actors\":{\"terms\":{\"field\":\"actors.keyword\",\"size\":10}}}}"; + + // At document level, the user should see actors + final HttpResponse searchMovieUser = rh.executeGetRequest(searchQuery, movieUser); + assertThat(searchMovieUser.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertActorsPresent(searchMovieUser); + + // In aggregate search, the user should see actors + final HttpResponse searchAggregateMovieUser = rh.executePostRequest(aggQuery, aggByActorKeyword, movieUser); + assertThat(searchAggregateMovieUser.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertActorsPresent(searchAggregateMovieUser); + + // At document level, the user should see no actors + final HttpResponse searchMovieNoActorUser = rh.executeGetRequest(searchQuery, movieNoActorUser); + assertThat(searchMovieNoActorUser.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertActorsNotPresent(searchMovieNoActorUser); + + // In aggregate search, the user should see no actors + final HttpResponse searchAggregateMovieNoActorUser = rh.executePostRequest(aggQuery, aggByActorKeyword, movieNoActorUser); + assertThat(searchAggregateMovieNoActorUser.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertActorsNotPresent(searchAggregateMovieNoActorUser); + } + + private void assertActorsPresent(final HttpResponse response) { + Arrays.stream(actors).forEach(actor -> { + assertThat(response.getBody(), containsString(actor)); + }); + } + + private void assertActorsNotPresent(final HttpResponse response) { + Arrays.stream(actors).forEach(actor -> { + assertThat(response.getBody(), not(containsString(actor))); + }); + } +} diff --git a/src/test/resources/dlsfls/roles_keyword.yml b/src/test/resources/dlsfls/roles_keyword.yml new file mode 100644 index 0000000000..c93209562a --- /dev/null +++ b/src/test/resources/dlsfls/roles_keyword.yml @@ -0,0 +1,18 @@ +--- +_meta: + type: "roles" + config_version: 2 +movies: + index_permissions: + - index_patterns: + - "movies*" + allowed_actions: + - "read" +movies_no_actors: + index_permissions: + - index_patterns: + - "movies*" + fls: + - "~actors" + allowed_actions: + - "read" diff --git a/src/test/resources/dlsfls/roles_mappings_keyword.yml b/src/test/resources/dlsfls/roles_mappings_keyword.yml new file mode 100644 index 0000000000..c3ec6710d6 --- /dev/null +++ b/src/test/resources/dlsfls/roles_mappings_keyword.yml @@ -0,0 +1,22 @@ +--- +_meta: + type: "rolesmapping" + config_version: 2 +movies: + reserved: false + hidden: false + backend_roles: [] + hosts: [] + users: + - "user_aaa" + and_backend_roles: [] + description: "Movies with all fields" +movies_no_actors: + reserved: false + hidden: false + backend_roles: [] + hosts: [] + users: + - "user_bbb" + and_backend_roles: [] + description: "Movies without actors" From 563df8f94106a411f143d37007fa1b19eb2e9035 Mon Sep 17 00:00:00 2001 From: "Daniel (dB.) Doubrovkine" Date: Thu, 5 Jan 2023 14:45:27 -0800 Subject: [PATCH 115/369] Updated MAINTAINERS.md format. (#2376) * Updated MAINTAINERS.md to match recommended opensearch-project format. Signed-off-by: dblock --- MAINTAINERS.md | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index ef0bbe9dfd..881ab6b8a9 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -1,14 +1,17 @@ -- [OpenSearch Security Maintainers](#opensearch-security-maintainers) - - [Maintainers](#maintainers) - - [Updating Practices](#updating-practices) +- [Overview](#overview) +- [Current Maintainers](#current-maintainers) - [Practices](#practices) + - [Updating Practices](#updating-practices) - [Reverting Commits](#reverting-commits) - - [Performing Revert](#performing-revert) - - [Squashing a pull request](#squashing-a-pull-request) + - [Performing Revert](#performing-revert) + - [Squashing a Pull Request](#squashing-a-pull-request) -# OpenSearch Security Maintainers +## Overview + +This document contains a list of maintainers in this repo. See [opensearch-project/.github/RESPONSIBILITIES.md](https://github.com/opensearch-project/.github/blob/main/RESPONSIBILITIES.md#maintainer-responsibilities) that explains what the role of maintainer means, what maintainers do in this and other repos, and how they should be doing it. If you're interested in contributing, and becoming a maintainer, see [CONTRIBUTING](CONTRIBUTING.md). + +## Current Maintainers -## Maintainers | Maintainer | GitHub ID | Affiliation | | ---------------- | ----------------------------------------------------- | ----------- | | Chang Liu | [cliu123](https://github.com/cliu123) | Amazon | @@ -18,22 +21,26 @@ | Craig Perkins | [cwperks](https://github.com/cwperks) | Amazon | | Ryan Liang | [RyanL1997](https://github.com/RyanL1997) | Amazon | +## Practices + ### Updating Practices -To ensure common practices as maintainers, all practices are expected to be documented here or enforced through github actions. There should be no expectations beyond what is documented in the repo [CONTRIBUTING.md](./CONTRIBUTING.md) and OpenSearch-Project [CONTRIBUTING.md](https://github.com/opensearch-project/.github/blob/main/CONTRIBUTING.md). To modify an existing processes or create a new one, make a pull request on this MAINTAINERS.md for review and merge it after all maintainers approve of it. -# Practices +To ensure common practices as maintainers, all practices are expected to be documented here or enforced through github actions. There should be no expectations beyond what is documented in the repo [CONTRIBUTING.md](./CONTRIBUTING.md) and OpenSearch-Project [CONTRIBUTING.md](https://github.com/opensearch-project/.github/blob/main/CONTRIBUTING.md). To modify an existing processes or create a new one, make a pull request on this MAINTAINERS.md for review and merge it after all maintainers approve of it. + +### Reverting Commits -## Reverting Commits There will be changes that destabilize or block contributions. The impact of these changes will be localized on the repository or even the entire OpenSearch project. We should bias towards keeping contributions unblocked by immediately reverting impacting changes, these reverts will be done by a maintainer. After the change has been reverted, an issue will be openned to re-merge the change and callout the elements of the contribution that need extra examination such as additional tests or even pull request workflows. Exceptional, instead of immediately reverting, if a contributor knows how and will resolve the issue in an hour or less we should fix-forward to reduce overhead. ### Performing Revert + Go to the pull request of the change that was an issue, there is a `Revert` button at the bottom. If there are no conflicts to resolve, this can be done immediately bypassing standard approval. Reverts can also be done via the command line using `git revert ` and creating a new pull request. If done in this way they should have references to the pull request that was reverted. -## Squashing a pull request +### Squashing a Pull Request + When a PR is going to be merged, our repositories are set to automatically squash the commits into a single commit. This process needs human intervention to produce high quality commit messages, with the following steps to be followed as much as possible: - The commit subject is clean and conveys what is being merged From 8ccad5c7d2653fda12acabc1b94afe2b3d9a99dd Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 5 Jan 2023 22:08:56 -0600 Subject: [PATCH 116/369] Update tool scripts to run in windows (#2371) --- tools/audit_config_migrater.bat | 13 ++++++++----- tools/hash.bat | 13 ++++++++----- tools/securityadmin.bat | 13 ++++++++----- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/tools/audit_config_migrater.bat b/tools/audit_config_migrater.bat index d33cd8c794..52ccd84915 100644 --- a/tools/audit_config_migrater.bat +++ b/tools/audit_config_migrater.bat @@ -1,16 +1,19 @@ @echo off -set SCRIPT_DIR=%~dp0 +set DIR=%~dp0 echo "**************************************************************************" echo "** This tool will be deprecated in the next major release of OpenSearch **" echo "** https://github.com/opensearch-project/security/issues/1755 **" echo "**************************************************************************" -rem comparing to empty string makes this equivalent to bash -v check on env var -if not "%OPENSEARCH_JAVA_HOME%" == "" ( +if defined OPENSEARCH_JAVA_HOME ( set BIN_PATH="%OPENSEARCH_JAVA_HOME%\bin\java.exe" -) else ( +) else if defined JAVA_HOME ( set BIN_PATH="%JAVA_HOME%\bin\java.exe" +) else ( + echo Unable to find java runtime + echo OPENSEARCH_JAVA_HOME or JAVA_HOME must be defined + exit /b 1 ) -%BIN_PATH% -cp "%SCRIPT_DIR%\..\..\opendistro_security_ssl\*;%SCRIPT_DIR%\..\deps\*;%SCRIPT_DIR%\..\*;%SCRIPT_DIR%\..\..\..\lib\*" org.opensearch.security.tools.AuditConfigMigrater %* +%BIN_PATH% -cp "%DIR%\..\*;%DIR%\..\..\..\lib\*;%DIR%\..\deps\*" org.opensearch.security.tools.AuditConfigMigrater %* diff --git a/tools/hash.bat b/tools/hash.bat index 182596fa8d..fe5f57b823 100644 --- a/tools/hash.bat +++ b/tools/hash.bat @@ -1,17 +1,20 @@ @echo off -set SCRIPT_DIR=%~dp0 +set DIR=%~dp0 echo "**************************************************************************" echo "** This tool will be deprecated in the next major release of OpenSearch **" echo "** https://github.com/opensearch-project/security/issues/1755 **" echo "**************************************************************************" -rem comparing to empty string makes this equivalent to bash -v check on env var -if not "%OPENSEARCH_JAVA_HOME%" == "" ( +if defined OPENSEARCH_JAVA_HOME ( set BIN_PATH="%OPENSEARCH_JAVA_HOME%\bin\java.exe" -) else ( +) else if defined JAVA_HOME ( set BIN_PATH="%JAVA_HOME%\bin\java.exe" +) else ( + echo Unable to find java runtime + echo OPENSEARCH_JAVA_HOME or JAVA_HOME must be defined + exit /b 1 ) -%BIN_PATH% -cp "%SCRIPT_DIR%\..\..\opendistro_security_ssl\*;%SCRIPT_DIR%\..\deps\*;%SCRIPT_DIR%\..\*;%SCRIPT_DIR%\..\..\..\lib\*" org.opensearch.security.tools.Hasher %* +%BIN_PATH% -cp "%DIR%\..\*;%DIR%\..\..\..\lib\*;%DIR%\..\deps\*" org.opensearch.security.tools.Hasher %* diff --git a/tools/securityadmin.bat b/tools/securityadmin.bat index 359727c20a..d798f78bf0 100644 --- a/tools/securityadmin.bat +++ b/tools/securityadmin.bat @@ -1,16 +1,19 @@ @echo off -set SCRIPT_DIR=%~dp0 +set DIR=%~dp0 echo "**************************************************************************" echo "** This tool will be deprecated in the next major release of OpenSearch **" echo "** https://github.com/opensearch-project/security/issues/1755 **" echo "**************************************************************************" -rem comparing to empty string makes this equivalent to bash -v check on env var -if not "%OPENSEARCH_JAVA_HOME%" == "" ( +if defined OPENSEARCH_JAVA_HOME ( set BIN_PATH="%OPENSEARCH_JAVA_HOME%\bin\java.exe" -) else ( +) else if defined JAVA_HOME ( set BIN_PATH="%JAVA_HOME%\bin\java.exe" +) else ( + echo Unable to find java runtime + echo OPENSEARCH_JAVA_HOME or JAVA_HOME must be defined + exit /b 1 ) -%BIN_PATH% -Dorg.apache.logging.log4j.simplelog.StatusLogger.level=OFF -cp "%SCRIPT_DIR%\..\..\opendistro_security-ssl\*;%SCRIPT_DIR%\..\deps\*;%SCRIPT_DIR%\..\*;%SCRIPT_DIR%\..\..\..\lib\*" org.opensearch.security.tools.SecurityAdmin %* 2> nul +%BIN_PATH% -Dorg.apache.logging.log4j.simplelog.StatusLogger.level=OFF -cp "%DIR%\..\*;%DIR%\..\..\..\lib\*;%DIR%\..\deps\*" org.opensearch.security.tools.SecurityAdmin %* 2> nul \ No newline at end of file From 8ee28d18bb7f61b73c9540457ecd96442f3d29d4 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 6 Jan 2023 13:54:40 -0500 Subject: [PATCH 117/369] Remove trimming of whitespace when extracting SAML backend roles (#2381) Signed-off-by: Craig Perkins --- .../jwt/AbstractHTTPJwtAuthenticator.java | 4 -- .../http/saml/HTTPSamlAuthenticatorTest.java | 39 +++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java index 02919c186f..08fa0a0100 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java @@ -216,10 +216,6 @@ public String[] extractRoles(JwtClaims claims) { roles = ((Collection) rolesObject).toArray(new String[0]); } - for (int i = 0; i < roles.length; i++) { - roles[i] = roles[i].trim(); - } - return roles; } diff --git a/src/test/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java index b9f9952256..186539521b 100644 --- a/src/test/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java @@ -314,6 +314,45 @@ public void shouldNotEscapeSamlEntities() throws Exception { Assert.assertEquals("ABC/Admin", samlAuthenticator.httpJwtAuthenticator.extractRoles(jwt.getClaims())[0]); } + @Test + public void shouldNotTrimWhitespaceInJwtRoles() throws Exception { + mockSamlIdpServer.setAuthenticateUser("ABC/User1"); + mockSamlIdpServer.setEndpointQueryString(null); + mockSamlIdpServer.setSpSignatureCertificate(spSigningCertificate); + mockSamlIdpServer.setEncryptAssertion(true); + mockSamlIdpServer.setAuthenticateUserRoles(Arrays.asList(" ABC/Admin ")); + + Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put("sp.signature_private_key", "-BEGIN PRIVATE KEY-\n" + + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-") + .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + + HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); + + AuthenticateHeaders authenticateHeaders = getAutenticateHeaders(samlAuthenticator); + + String encodedSamlResponse = mockSamlIdpServer.handleSsoGetRequestURI(authenticateHeaders.location); + + RestRequest tokenRestRequest = buildTokenExchangeRestRequest(encodedSamlResponse, authenticateHeaders); + TestRestChannel tokenRestChannel = new TestRestChannel(tokenRestRequest); + + samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); + + String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); + HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, + new TypeReference>() { + }); + String authorization = (String) response.get("authorization"); + + Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); + + JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(authorization.replaceAll("\\s*bearer\\s*", "")); + JwtToken jwt = jwtConsumer.getJwtToken(); + + Assert.assertEquals("ABC/Admin", samlAuthenticator.httpJwtAuthenticator.extractRoles(jwt.getClaims())[0]); + } + @Test public void testMetadataBody() throws Exception { mockSamlIdpServer.setSignResponses(true); From be7beb9ecf19f43b378f453fef5750d4b4aa939b Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Fri, 6 Jan 2023 12:26:05 -0800 Subject: [PATCH 118/369] Add script for workflow version increment (#2374) * Add script for workflow version increment Signed-off-by: Ryan Liang --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index cd172d7ba3..31ca010a20 100644 --- a/build.gradle +++ b/build.gradle @@ -644,6 +644,7 @@ task updateVersion { fileset(dir: projectDir) { // Include the required files that needs to be updated with new Version include(name: "bwc-test/build.gradle") + include(name: ".github/workflows/plugin_install.yml") } } ant.replaceregexp(file:'build.gradle', match: '"opensearch.version", "\\d.*"', replace: '"opensearch.version", "' + newVersion.tokenize('-')[0] + '-SNAPSHOT"', flags:'g', byline:true) From e720446d0a96e02082de6f70e601dfd67962c743 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Mon, 9 Jan 2023 15:02:26 -0500 Subject: [PATCH 119/369] Add a test in order to catch incorrect handling of index parsing during Snapshot Restoration (#2384) * Add a test in order to catch incorrect handling of index parsing during Snapshot Restoration Signed-off-by: Stephen Crawford --- .../security/SnapshotRestoreTests.java | 242 +++++++++++------- 1 file changed, 145 insertions(+), 97 deletions(-) diff --git a/src/test/java/org/opensearch/security/SnapshotRestoreTests.java b/src/test/java/org/opensearch/security/SnapshotRestoreTests.java index ef7189d4b4..8e869e250d 100644 --- a/src/test/java/org/opensearch/security/SnapshotRestoreTests.java +++ b/src/test/java/org/opensearch/security/SnapshotRestoreTests.java @@ -26,13 +26,18 @@ package org.opensearch.security; +import java.util.Arrays; +import java.util.List; + import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; import org.opensearch.action.admin.cluster.repositories.put.PutRepositoryRequest; import org.opensearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; +import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest; import org.opensearch.action.support.WriteRequest.RefreshPolicy; import org.opensearch.client.Client; import org.opensearch.common.settings.Settings; @@ -45,33 +50,38 @@ import org.opensearch.security.test.helper.cluster.ClusterConfiguration; import org.opensearch.security.test.helper.rest.RestHelper; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; + + public class SnapshotRestoreTests extends SingleClusterTest { private ClusterConfiguration currentClusterConfig = ClusterConfiguration.DEFAULT; @Test public void testSnapshotEnableSecurityIndexRestore() throws Exception { - + final Settings settings = Settings.builder() .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) .put("plugins.security.check_snapshot_restore_write_privileges", false) .put("plugins.security.unsupported.restore.securityindex.enabled", true) .build(); - + setup(settings, currentClusterConfig); - + try (Client tc = getClient()) { tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.admin().cluster().putRepository(new PutRepositoryRequest("vulcangov").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/vulcangov"))).actionGet(); tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("vulcangov", "vulcangov_1").indices("vulcangov").includeGlobalState(true).waitForCompletion(true)).actionGet(); - + tc.admin().cluster().putRepository(new PutRepositoryRequest(".opendistro_security").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/.opendistro_security"))).actionGet(); tc.admin().cluster().createSnapshot(new CreateSnapshotRequest(".opendistro_security", "opendistro_security_1").indices(".opendistro_security").includeGlobalState(false).waitForCompletion(true)).actionGet(); - + tc.admin().cluster().putRepository(new PutRepositoryRequest("all").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/all"))).actionGet(); tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("all", "all_1").indices("*").includeGlobalState(false).waitForCompletion(true)).actionGet(); } - + RestHelper rh = nonSslRestHelper(); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/vulcangov", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/vulcangov/vulcangov_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); @@ -81,7 +91,7 @@ public void testSnapshotEnableSecurityIndexRestore() throws Exception { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","", encodeBasicHeader("worf", "worf")).getStatusCode()); // Try to restore vulcangov index as .opendistro_security index, not possible since Security index is open Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - + // Try to restore .opendistro_security index. Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/.opendistro_security", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/.opendistro_security/opendistro_security_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); @@ -89,7 +99,7 @@ public void testSnapshotEnableSecurityIndexRestore() throws Exception { Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); // Try to restore .opendistro_security index as .opendistro_security_copy index Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - + // Try to restore all indices. Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/all", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/all/all_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); @@ -100,39 +110,39 @@ public void testSnapshotEnableSecurityIndexRestore() throws Exception { // Try to restore .opendistro_security index as .opendistro_security_copy index. Delete opendistro_security_copy first, was created in test above Assert.assertEquals(HttpStatus.SC_OK, rh.executeDeleteRequest("opendistro_security_copy", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - - // Try to restore a unknown snapshot + + // Try to restore an unknown snapshot Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, rh.executePostRequest("_snapshot/all/unknown-snapshot/_restore?wait_for_completion=true", "", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - + // close and restore Security index Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest(".opendistro_security/_close", "", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest(".opendistro_security/_open", "", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); } - + @Test public void testSnapshot() throws Exception { - + final Settings settings = Settings.builder() .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) .put("plugins.security.check_snapshot_restore_write_privileges", false) .build(); - + setup(settings, currentClusterConfig); - + try (Client tc = getClient()) { tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.admin().cluster().putRepository(new PutRepositoryRequest("vulcangov").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/vulcangov"))).actionGet(); tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("vulcangov", "vulcangov_1").indices("vulcangov").includeGlobalState(true).waitForCompletion(true)).actionGet(); - + tc.admin().cluster().putRepository(new PutRepositoryRequest(".opendistro_security").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/.opendistro_security"))).actionGet(); tc.admin().cluster().createSnapshot(new CreateSnapshotRequest(".opendistro_security", "opendistro_security_1").indices(".opendistro_security").includeGlobalState(false).waitForCompletion(true)).actionGet(); - + tc.admin().cluster().putRepository(new PutRepositoryRequest("all").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/all"))).actionGet(); tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("all", "all_1").indices("*").includeGlobalState(false).waitForCompletion(true)).actionGet(); } - + RestHelper rh = nonSslRestHelper(); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/vulcangov", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/vulcangov/vulcangov_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); @@ -141,14 +151,14 @@ public void testSnapshot() throws Exception { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","", encodeBasicHeader("worf", "worf")).getStatusCode()); // Try to restore vulcangov index as .opendistro_security index Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - + // Try to restore .opendistro_security index. Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/.opendistro_security", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/.opendistro_security/opendistro_security_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); // Try to restore .opendistro_security index as .opendistro_security_copy index Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - + // Try to restore all indices. Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/all", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/all/all_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); @@ -157,39 +167,38 @@ public void testSnapshot() throws Exception { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); // Try to restore .opendistro_security index as .opendistro_security_copy index Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - - // Try to restore a unknown snapshot + + // Try to restore an unknown snapshot Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/unknown-snapshot/_restore?wait_for_completion=true", "", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); // Assert.assertEquals(HttpStatus.SC_FORBIDDEN, executePostRequest("_snapshot/all/unknown-snapshot/_restore?wait_for_completion=true","{ \"indices\": \"the-unknown-index\" }", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); } @Test public void testSnapshotCheckWritePrivileges() throws Exception { - + final Settings settings = Settings.builder() .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) .build(); - + setup(settings, currentClusterConfig); - + try (Client tc = getClient()) { tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.admin().cluster().putRepository(new PutRepositoryRequest("vulcangov").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/vulcangov"))).actionGet(); tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("vulcangov", "vulcangov_1").indices("vulcangov").includeGlobalState(true).waitForCompletion(true)).actionGet(); - + tc.admin().cluster().putRepository(new PutRepositoryRequest(".opendistro_security").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/.opendistro_security"))).actionGet(); tc.admin().cluster().createSnapshot(new CreateSnapshotRequest(".opendistro_security", "opendistro_security_1").indices(".opendistro_security").includeGlobalState(false).waitForCompletion(true)).actionGet(); - + tc.admin().cluster().putRepository(new PutRepositoryRequest("all").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/all"))).actionGet(); tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("all", "all_1").indices("*").includeGlobalState(false).waitForCompletion(true)).actionGet(); - + ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config","roles","rolesmapping","internalusers","actiongroups"})).actionGet(); Assert.assertFalse(cur.hasFailures()); Assert.assertEquals(currentClusterConfig.getNodes(), cur.getNodes().size()); - System.out.println(cur.getNodesMap()); } - + RestHelper rh = nonSslRestHelper(); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/vulcangov", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/vulcangov/vulcangov_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); @@ -198,14 +207,14 @@ public void testSnapshotCheckWritePrivileges() throws Exception { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","", encodeBasicHeader("worf", "worf")).getStatusCode()); // Try to restore vulcangov index as .opendistro_security index Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - + // Try to restore .opendistro_security index. Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/.opendistro_security", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/.opendistro_security/opendistro_security_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); // Try to restore .opendistro_security index as .opendistro_security_copy index Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - + // Try to restore all indices. Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/all", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/all/all_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); @@ -214,14 +223,14 @@ public void testSnapshotCheckWritePrivileges() throws Exception { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); // Try to restore .opendistro_security index as .opendistro_security_copy index Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - - // Try to restore a unknown snapshot + + // Try to restore an unknown snapshot Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/unknown-snapshot/_restore?wait_for_completion=true", "", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - + // Tests snapshot with write permissions (OK) Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_restore_1\" }", encodeBasicHeader("restoreuser", "restoreuser")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_restore_2a\" }", encodeBasicHeader("restoreuser", "restoreuser")).getStatusCode()); - + // Test snapshot with write permissions (OK) Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_no_restore_1\" }", encodeBasicHeader("restoreuser", "restoreuser")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_no_restore_2\" }", encodeBasicHeader("restoreuser", "restoreuser")).getStatusCode()); @@ -231,13 +240,13 @@ public void testSnapshotCheckWritePrivileges() throws Exception { @Test public void testSnapshotRestore() throws Exception { - + final Settings settings = Settings.builder() .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) .build(); - + setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityActionGroups("action_groups_packaged.yml"), settings, true, currentClusterConfig); - + try (Client tc = getClient()) { tc.index(new IndexRequest("testsnap1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("testsnap2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); @@ -245,52 +254,91 @@ public void testSnapshotRestore() throws Exception { tc.index(new IndexRequest("testsnap4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("testsnap5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("testsnap6").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.admin().cluster().putRepository(new PutRepositoryRequest("bckrepo").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/bckrepo"))).actionGet(); } - - RestHelper rh = nonSslRestHelper(); + + RestHelper rh = nonSslRestHelper(); String putSnapshot = - "{"+ - "\"indices\": \"testsnap1\","+ - "\"ignore_unavailable\": false,"+ - "\"include_global_state\": false"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); + "{"+ + "\"indices\": \"testsnap1\","+ + "\"ignore_unavailable\": false,"+ + "\"include_global_state\": false"+ + "}"; + + Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"/_restore?wait_for_completion=true&pretty","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - + putSnapshot = - "{"+ - "\"indices\": \".opendistro_security\","+ - "\"ignore_unavailable\": false,"+ - "\"include_global_state\": false"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); + "{"+ + "\"indices\": \".opendistro_security\","+ + "\"ignore_unavailable\": false,"+ + "\"include_global_state\": false"+ + "}"; + + Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"/_restore?wait_for_completion=true&pretty","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - + + putSnapshot = - "{"+ - "\"indices\": \"testsnap2\","+ - "\"ignore_unavailable\": false,"+ - "\"include_global_state\": true"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); + "{"+ + "\"indices\": \"testsnap2\","+ + "\"ignore_unavailable\": false,"+ + "\"include_global_state\": true"+ + "}"; + + Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"/_restore?wait_for_completion=true&pretty","{ \"include_global_state\": true, \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); } + @Test + public void testSnapshotRestoreSpecialIndicesPatterns() throws Exception { + // Run with ./gradlew test --tests org.opensearch.security.SnapshotRestoreTests.testSnapshotRestoreSpecialIndicesPatterns + + final List listOfIndexesToTest = Arrays.asList("foo", "bar", "baz"); + + final Settings settings = Settings.builder() + .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) + .build(); + + setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityActionGroups("action_groups_packaged.yml"), settings, true, currentClusterConfig); + + try (Client tc = getClient()) { + for (String index : listOfIndexesToTest) { + tc.admin().indices().create(new CreateIndexRequest(index)).actionGet(); + tc.index(new IndexRequest(index).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).id("document1").source("{ \"foo\": \"bar\" }", XContentType.JSON)).actionGet(); + } + } + + try (Client tc = getClient()) { + tc.admin().cluster().putRepository(new PutRepositoryRequest("all").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/all"))).actionGet(); + tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("all", "all_1").indices(listOfIndexesToTest).includeGlobalState(false).waitForCompletion(true)).actionGet(); + } + + RestHelper rh = nonSslRestHelper(); + + Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{\"indices\": \"b*,-bar\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"wild_first_restored_index_$1\"}", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{\"indices\": \"-bar,b*\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"neg_first_restored_index_$1\"}", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + String wild_first_body = rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{\"indices\": \"b*,-bar\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"wild_first_restored_index_$1\"}", encodeBasicHeader("nagilum", "nagilum")).getBody(); + assertThat(wild_first_body, not(containsString("wild_first_restored_index_foo"))); + assertThat(wild_first_body, not(containsString("wild_first_restored_index_bar"))); + assertThat(wild_first_body, containsString("wild_first_restored_index_baz")); + String neg_first_body = rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{\"indices\": \"-bar,b*\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"negate_first_restored_index_$1\"}", encodeBasicHeader("nagilum", "nagilum")).getBody(); + assertThat(neg_first_body, not(containsString("negate_first_restored_index_foo"))); + assertThat(neg_first_body, not(containsString("negate_first_restored_index_bar"))); + assertThat(neg_first_body, containsString("negate_first_restored_index_baz")); + } + @Test public void testNoSnapshotRestore() throws Exception { - + final Settings settings = Settings.builder() .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) .put("plugins.security.enable_snapshot_restore_privilege", false) .build(); - + setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityActionGroups("action_groups_packaged.yml"), settings, true, currentClusterConfig); - + try (Client tc = getClient()) { tc.index(new IndexRequest("testsnap1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("testsnap2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); @@ -298,39 +346,39 @@ public void testNoSnapshotRestore() throws Exception { tc.index(new IndexRequest("testsnap4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("testsnap5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("testsnap6").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.admin().cluster().putRepository(new PutRepositoryRequest("bckrepo").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/bckrepo"))).actionGet(); } - - RestHelper rh = nonSslRestHelper(); + + RestHelper rh = nonSslRestHelper(); String putSnapshot = - "{"+ - "\"indices\": \"testsnap1\","+ - "\"ignore_unavailable\": false,"+ - "\"include_global_state\": false"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); + "{"+ + "\"indices\": \"testsnap1\","+ + "\"ignore_unavailable\": false,"+ + "\"include_global_state\": false"+ + "}"; + + Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"/_restore?wait_for_completion=true&pretty","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - + putSnapshot = - "{"+ - "\"indices\": \".opendistro_security\","+ - "\"ignore_unavailable\": false,"+ - "\"include_global_state\": false"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); + "{"+ + "\"indices\": \".opendistro_security\","+ + "\"ignore_unavailable\": false,"+ + "\"include_global_state\": false"+ + "}"; + + Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"/_restore?wait_for_completion=true&pretty","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - + putSnapshot = - "{"+ - "\"indices\": \"testsnap2\","+ - "\"ignore_unavailable\": false,"+ - "\"include_global_state\": true"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); + "{"+ + "\"indices\": \"testsnap2\","+ + "\"ignore_unavailable\": false,"+ + "\"include_global_state\": true"+ + "}"; + + Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"/_restore?wait_for_completion=true&pretty","{ \"include_global_state\": true, \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); } } From 3ea4702a270fed70f9846a98ae4ba273716fd83a Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 10 Jan 2023 19:45:52 -0500 Subject: [PATCH 120/369] Re-add jackson-annotations and jackson-databind (#2391) Signed-off-by: Craig Perkins --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 31ca010a20..77076a00b4 100644 --- a/build.gradle +++ b/build.gradle @@ -458,6 +458,9 @@ dependencies { testRuntimeOnly "org.apache.kafka:kafka-metadata:${kafka_version}" testRuntimeOnly "org.apache.kafka:kafka-storage:${kafka_version}" + implementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}" + implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" + compileOnly "org.opensearch:opensearch:${opensearch_version}" //integration test framework: From b0116b711678517b3a179841dd0d8147a6fed077 Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Tue, 10 Jan 2023 19:11:15 -0800 Subject: [PATCH 121/369] Add release note for 2.5.0 (#2394) Signed-off-by: Ryan Liang --- .../opensearch-security.release-notes-2.5.0.0.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-2.5.0.0.md diff --git a/release-notes/opensearch-security.release-notes-2.5.0.0.md b/release-notes/opensearch-security.release-notes-2.5.0.0.md new file mode 100644 index 0000000000..0f04803442 --- /dev/null +++ b/release-notes/opensearch-security.release-notes-2.5.0.0.md @@ -0,0 +1,15 @@ +## 2023-01-17 Version 2.5.0.0 + +Compatible with OpenSearch 2.5.0 + +### Enhancements +* When excluding fields also exclude the term + .keyword ([#2377](https://github.com/opensearch-project/security/pull/2377)) +* Update tool scripts to run in windows ([#2371](https://github.com/opensearch-project/security/pull/2371), [#2379](https://github.com/opensearch-project/security/pull/2379)) +* Remove trimming of whitespace when extracting SAML backend roles ([#2381](https://github.com/opensearch-project/security/pull/2381), [#2383](https://github.com/opensearch-project/security/pull/2383)) +* Add script for workflow version increment ([#2374](https://github.com/opensearch-project/security/pull/2374), [#2386](https://github.com/opensearch-project/security/pull/2386)) + +### Bug Fixes +* Changing logging type to give warning for basic auth with no creds ([#2347](https://github.com/opensearch-project/security/pull/2347), [#2364](https://github.com/opensearch-project/security/pull/2364)) + +### Maintenance +* Upgrade CXF to 3.5.5 to address CVE-2022-46363 ([#2350](https://github.com/opensearch-project/security/pull/2350), [#2357](https://github.com/opensearch-project/security/pull/2357)) From 9cdaeca7e97ca4a4351a2d7bbc729db0f4a1704e Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 13 Jan 2023 12:19:50 -0500 Subject: [PATCH 122/369] Revert "Windows Install Action Fix" (#2401) * Reverts #2308 and uses -i -c as arguments to the windows program to make it equivalent to -y Signed-off-by: Craig Perkins --- .github/workflows/plugin_install.yml | 4 ++-- tools/install_demo_configuration.bat | 5 ----- tools/install_demo_configuration.sh | 5 ----- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/plugin_install.yml b/.github/workflows/plugin_install.yml index d3900381f3..75289d560a 100644 --- a/.github/workflows/plugin_install.yml +++ b/.github/workflows/plugin_install.yml @@ -39,14 +39,14 @@ jobs: run: | cat > setup.sh <<'EOF' chmod +x ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/plugins/${{ env.PLUGIN_NAME }}/tools/install_demo_configuration.sh - /bin/bash -c "./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/plugins/${{ env.PLUGIN_NAME }}/tools/install_demo_configuration.sh -y" + /bin/bash -c "yes | ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/plugins/${{ env.PLUGIN_NAME }}/tools/install_demo_configuration.sh" EOF - name: Create Setup Script if: ${{ runner.os == 'Windows' }} run: | New-Item .\setup.bat -type file - Set-Content .\setup.bat -Value "powershell.exe .\opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT\plugins\${{ env.PLUGIN_NAME }}\tools\install_demo_configuration.bat -y" + Set-Content .\setup.bat -Value "powershell.exe .\opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT\plugins\${{ env.PLUGIN_NAME }}\tools\install_demo_configuration.bat -i -c -y" Get-Content .\setup.bat - name: Run Opensearch with A Single Plugin diff --git a/tools/install_demo_configuration.bat b/tools/install_demo_configuration.bat index 88e3c185c0..06fbf5ec51 100755 --- a/tools/install_demo_configuration.bat +++ b/tools/install_demo_configuration.bat @@ -62,11 +62,6 @@ if %cluster_mode% == 0 ( ) ) -if %assumeyes% == 1 ( - set "initsecurity=1" - set "cluster_mode=1" -) - set BASE_DIR=%SCRIPT_DIR%\..\..\..\ if not exist %BASE_DIR% ( echo "basedir does not exist" diff --git a/tools/install_demo_configuration.sh b/tools/install_demo_configuration.sh index fc0d7de095..fad75c5cdf 100755 --- a/tools/install_demo_configuration.sh +++ b/tools/install_demo_configuration.sh @@ -97,11 +97,6 @@ if [ "$cluster_mode" == 0 ] && [ "$assumeyes" == 0 ]; then esac fi -if [ "$assumeyes" == 1 ]; then - cluster_mode=1 - initsecurity=1 -fi - set -e BASE_DIR="$DIR/../../.." if [ -d "$BASE_DIR" ]; then From e4b7361a4687e03fdb990b32dad205dcd16914e0 Mon Sep 17 00:00:00 2001 From: Stefan Reuter Date: Fri, 20 Jan 2023 13:35:19 +0100 Subject: [PATCH 123/369] =?UTF-8?q?Add=20action=20indices:admin/index=5Fte?= =?UTF-8?q?mplate/*=20to=20cluster=5Fmanage=5Findex=5Ftem=E2=80=A6=20(#240?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add actions cluster:admin/component_template/* to cluster_manage_index_templates Signed-off-by: Stefan Reuter --- src/main/resources/static_config/static_action_groups.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/static_config/static_action_groups.yml b/src/main/resources/static_config/static_action_groups.yml index c7c351d171..e1d3e0aece 100644 --- a/src/main/resources/static_config/static_action_groups.yml +++ b/src/main/resources/static_config/static_action_groups.yml @@ -208,6 +208,8 @@ cluster_manage_index_templates: static: true allowed_actions: - "indices:admin/template/*" + - "indices:admin/index_template/*" + - "cluster:admin/component_template/*" type: "cluster" description: "Manage index templates" cluster_manage_pipelines: From b86eba2ae57dc99a82d8b45cfbcefda9aecafb2e Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 20 Jan 2023 15:24:32 -0600 Subject: [PATCH 124/369] Adding `@scrawfor99` to the maintainers (#2413) Stephen Crawford (@scrawfor99) has been an avid contributor to Security plugin & Security Dashboard plugin since October '22, clocking in with 137 contributions in the OpenSearch project. He has earned his place as a maintainer with the following valuable contributions. - Created [41](https://github.com/search?q=repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin+author%3Ascrawfor99&type=pullrequests&ref=advsearch) pull requests. - Involved in [55 & 31](https://github.com/search?q=repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin+-author%3Ascrawfor99+involves%3Ascrawfor99+&type=pullrequests&ref=advsearch) issues and pull requests respectively. - Mentioned in [52](https://github.com/search?q=repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin+mentions%3Ascrawfor99&type=pullrequests&ref=advsearch) issues and pull requests. - Pioneered security's open triage [#2164](https://github.com/opensearch-project/security/pull/2164) Signed-off-by: Peter Nied --- MAINTAINERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 881ab6b8a9..c72f5f3e3a 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -20,6 +20,7 @@ This document contains a list of maintainers in this repo. See [opensearch-proje | Peter Nied | [peternied](https://github.com/peternied) | Amazon | | Craig Perkins | [cwperks](https://github.com/cwperks) | Amazon | | Ryan Liang | [RyanL1997](https://github.com/RyanL1997) | Amazon | +| Stephen Crawford | [scrawfor99](https://github.com/scrawfor99) | Amazon | ## Practices From 4b51ea7ad47d10a93594943b952132d862ef5947 Mon Sep 17 00:00:00 2001 From: Dave Lago Date: Tue, 24 Jan 2023 14:58:21 -0500 Subject: [PATCH 125/369] Updated triaging meeting agenda (#2415) * Updated triaging meeting agenda Signed-off-by: Dave Lago --- TRIAGING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TRIAGING.md b/TRIAGING.md index c9e129c982..4b8eebb819 100644 --- a/TRIAGING.md +++ b/TRIAGING.md @@ -22,10 +22,10 @@ If you have an issue you'd like to bring forth please consider getting a link to Meetings are lightly structured as follows: -1. Announcements: If there are any announcements to be made they will happen at the start of the meeting. -2. Review of new issues: The meetings always start with reviewing all untriaged issues for the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Auntriaged) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aissue+is%3Aopen+-label%3Atriaged) repo. We will also provide an opportunity for any issues introduced since the last meeting but already triaged to be discussed. -3. Sprint backlog review: Next, we will review persisting issues in the sprint backlog for the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A%22sprint+backlog%22) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aopen+is%3Aissue+label%3A%22sprint+backlog%22) repo, checking to see that issues have not gone stale or been addressed. -4. Backlog review: Finally, any remaining time will be spent reviewing the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Atriaged+-label%3A%22sprint+backlog%22) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aopen+is%3Aissue+label%3Atriaged+-label%3A%22sprint+backlog%22+) repo. +1. Announcements: If there are any announcements to be made they will happen at the start of the meeting. +2. Review of new issues: The meetings always start with reviewing all untriaged issues for the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Auntriaged) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aissue+is%3Aopen+-label%3Atriaged) repo. We will also provide an opportunity for any issues introduced since the last meeting but already triaged to be discussed. +3. Backlog discussion: Next, we open the floor in case anyone from the audience wants to highlight an issue from the backlogs ([security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Atriaged+-label%3A%22sprint+backlog%22) and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aopen+is%3Aissue+label%3Atriaged+-label%3A%22sprint+backlog%22+)). +4. Sprint backlog review: Finally, we will review persisting issues in the sprint backlog for the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A%22sprint+backlog%22) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aopen+is%3Aissue+label%3A%22sprint+backlog%22) repo, checking to see that issues have not gone stale or been addressed. There is no specific ordering within each category. From f808b2ae207442b5f18dc47bfb67c5a71e75e8b8 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Date: Mon, 30 Jan 2023 15:06:34 -0500 Subject: [PATCH 126/369] Updates toString calls affected by change in method signature (#2418) Signed-off-by: Darshit Chanpura --- .../framework/cluster/LocalOpenSearchCluster.java | 3 ++- .../test/framework/cluster/TestRestClient.java | 5 +++-- .../security/action/whoami/WhoAmIResponse.java | 3 ++- .../configuration/ConfigurationRepository.java | 3 ++- .../security/configuration/DlsFlsValveImpl.java | 7 ++++--- .../opensearch/security/tools/SecurityAdmin.java | 2 +- .../java/org/opensearch/security/ConfigTests.java | 15 ++++++++------- .../opensearch/security/dlic/dlsfls/DlsTest.java | 2 +- 8 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java index 9a8602fb2e..1b7befbcfc 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java @@ -60,6 +60,7 @@ import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; +import org.opensearch.common.xcontent.XContentType; import org.opensearch.http.BindHttpException; import org.opensearch.node.PluginAwareNode; import org.opensearch.plugins.Plugin; @@ -292,7 +293,7 @@ public void waitForCluster(ClusterHealthStatus status, TimeValue timeout, int ex .setClusterManagerNodeTimeout(timeout).setWaitForNodes("" + expectedNodeCount).execute().actionGet(); if (log.isDebugEnabled()) { - log.debug("Current ClusterState:\n{}", Strings.toString(healthResponse)); + log.debug("Current ClusterState:\n{}", Strings.toString(XContentType.JSON,healthResponse)); } if (healthResponse.isTimedOut()) { diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java index b7be0a5548..8df1b6ed25 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -71,6 +71,7 @@ import org.opensearch.common.Strings; import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.DefaultObjectMapper; import static java.lang.String.format; @@ -162,7 +163,7 @@ private StringEntity toStringEntity(String body) { } public HttpResponse putJson(String path, ToXContentObject body) { - return putJson(path, Strings.toString(body)); + return putJson(path, Strings.toString(XContentType.JSON, body)); } public HttpResponse put(String path) { @@ -181,7 +182,7 @@ public HttpResponse postJson(String path, String body, Header... headers) { } public HttpResponse postJson(String path, ToXContentObject body) { - return postJson(path, Strings.toString(body)); + return postJson(path, Strings.toString(XContentType.JSON, body)); } public HttpResponse post(String path) { diff --git a/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java b/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java index 69ed3e52ba..635cba8945 100644 --- a/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java +++ b/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java @@ -34,6 +34,7 @@ import org.opensearch.common.io.stream.StreamOutput; import org.opensearch.common.xcontent.ToXContent; import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.common.xcontent.XContentType; public class WhoAmIResponse extends ActionResponse implements ToXContent { @@ -105,6 +106,6 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws @Override public String toString() { - return Strings.toString(this, true, true); + return Strings.toString(XContentType.JSON,this, true, true); } } diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java index 5a8c9a069c..19e036b5e9 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java @@ -63,6 +63,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.common.util.concurrent.ThreadContext.StoredContext; +import org.opensearch.common.xcontent.XContentType; import org.opensearch.env.Environment; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.auditlog.config.AuditConfig; @@ -379,7 +380,7 @@ public Map> getConfigurationsFromIndex(Co if (logComplianceEvent && auditLog.getComplianceConfig().isEnabled()) { CType configurationType = configTypes.iterator().next(); Map fields = new HashMap(); - fields.put(configurationType.toLCString(), Strings.toString(retVal.get(configurationType))); + fields.put(configurationType.toLCString(), Strings.toString(XContentType.JSON, retVal.get(configurationType))); auditLog.logDocumentRead(this.securityIndex, configurationType.toLCString(), null, fields); } diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java index f69e736b8f..327b260b32 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java @@ -52,6 +52,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.common.xcontent.XContentType; import org.opensearch.index.query.ParsedQuery; import org.opensearch.rest.RestStatus; import org.opensearch.search.DocValueFormat; @@ -211,10 +212,10 @@ public boolean invoke(String action, ActionRequest request, final ActionListener StringBuilder sb = new StringBuilder(); if (searchRequest.source() != null) { - sb.append(Strings.toString(searchRequest.source()) + System.lineSeparator()); + sb.append(Strings.toString(XContentType.JSON, searchRequest.source()) + System.lineSeparator()); } - sb.append(Strings.toString(af) + System.lineSeparator()); + sb.append(Strings.toString(XContentType.JSON, af) + System.lineSeparator()); LogManager.getLogger("debuglogger").error(sb.toString()); @@ -224,7 +225,7 @@ public boolean invoke(String action, ActionRequest request, final ActionListener searchRequest.requestCache(Boolean.FALSE); } else { LogManager.getLogger("debuglogger").error("Shard requestcache enabled for " - + (searchRequest.source() == null ? "" : Strings.toString(searchRequest.source()))); + + (searchRequest.source() == null ? "" : Strings.toString(XContentType.JSON, searchRequest.source()))); } } else { diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index ee4d8e72d7..c9717e20be 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -1007,7 +1007,7 @@ protected static void generateDiagnoseTrace(final RestHighLevelClient restHighLe try { sb.append("ClusterHealthRequest:"+System.lineSeparator()); ClusterHealthResponse nir = restHighLevelClient.cluster().health(new ClusterHealthRequest(), RequestOptions.DEFAULT); - sb.append(Strings.toString(nir, true, true)); + sb.append(Strings.toString(XContentType.JSON, nir, true, true)); } catch (Exception e1) { sb.append(ExceptionsHelper.stackTrace(e1)); } diff --git a/src/test/java/org/opensearch/security/ConfigTests.java b/src/test/java/org/opensearch/security/ConfigTests.java index 8d7ebf8003..c5b8c089e4 100644 --- a/src/test/java/org/opensearch/security/ConfigTests.java +++ b/src/test/java/org/opensearch/security/ConfigTests.java @@ -28,6 +28,7 @@ import org.opensearch.common.Strings; import org.opensearch.common.collect.Tuple; +import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.securityconf.Migration; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; @@ -59,18 +60,18 @@ public void testMigrate() throws Exception { Tuple, SecurityDynamicConfiguration> rolesResult = Migration.migrateRoles((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/roles.yml", CType.ROLES), (SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/roles_mapping.yml", CType.ROLESMAPPING)); - System.out.println(Strings.toString(rolesResult.v2(), true, false)); - System.out.println(Strings.toString(rolesResult.v1(), true, false)); + System.out.println(Strings.toString(XContentType.JSON, rolesResult.v2(), true, false)); + System.out.println(Strings.toString(XContentType.JSON, rolesResult.v1(), true, false)); SecurityDynamicConfiguration actionGroupsResult = Migration.migrateActionGroups((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/action_groups.yml", CType.ACTIONGROUPS)); - System.out.println(Strings.toString(actionGroupsResult, true, false)); + System.out.println(Strings.toString(XContentType.JSON, actionGroupsResult, true, false)); SecurityDynamicConfiguration configResult =Migration.migrateConfig((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/config.yml", CType.CONFIG)); - System.out.println(Strings.toString(configResult, true, false)); + System.out.println(Strings.toString(XContentType.JSON, configResult, true, false)); SecurityDynamicConfiguration internalUsersResult = Migration.migrateInternalUsers((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/internal_users.yml", CType.INTERNALUSERS)); - System.out.println(Strings.toString(internalUsersResult, true, false)); + System.out.println(Strings.toString(XContentType.JSON, internalUsersResult, true, false)); SecurityDynamicConfiguration rolemappingsResult = Migration.migrateRoleMappings((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/roles_mapping.yml", CType.ROLESMAPPING)); - System.out.println(Strings.toString(rolemappingsResult, true, false)); + System.out.println(Strings.toString(XContentType.JSON, rolemappingsResult, true, false)); } @Test @@ -113,7 +114,7 @@ private void check(String file, CType cType) throws Exception { //Assert.assertTrue(dc.getCEntries().size() > 0); String jsonSerialize = DefaultObjectMapper.objectMapper.writeValueAsString(dc); SecurityDynamicConfiguration conf = SecurityDynamicConfiguration.fromJson(jsonSerialize, cType, configVersion, 0, 0); - SecurityDynamicConfiguration.fromJson(Strings.toString(conf), cType, configVersion, 0, 0); + SecurityDynamicConfiguration.fromJson(Strings.toString(XContentType.JSON, conf), cType, configVersion, 0, 0); } 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 554500257b..45a1e7b53f 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java @@ -42,7 +42,7 @@ protected void populateData(Client tc) { e.printStackTrace(); } System.out.println("q"); - System.out.println(Strings.toString(tc.search(new SearchRequest().indices(".opendistro_security")).actionGet())); + System.out.println(Strings.toString(XContentType.JSON, tc.search(new SearchRequest().indices(".opendistro_security")).actionGet())); tc.search(new SearchRequest().indices("deals")).actionGet(); } From 3ba3fc876b72cd6cf6578c049ef5d32078b83acd Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 3 Feb 2023 14:14:27 -0600 Subject: [PATCH 127/369] Fix test case that was broken with changes in calling patterns (#2428) Signed-off-by: Peter Nied --- .../org/opensearch/security/setting/DeprecatedSettingsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opensearch/security/setting/DeprecatedSettingsTest.java b/src/test/java/org/opensearch/security/setting/DeprecatedSettingsTest.java index d8227c53d7..72c1bc3741 100644 --- a/src/test/java/org/opensearch/security/setting/DeprecatedSettingsTest.java +++ b/src/test/java/org/opensearch/security/setting/DeprecatedSettingsTest.java @@ -58,6 +58,6 @@ public void testCheckForDeprecatedSettingFoundLegacy() { checkForDeprecatedSetting(settings, "legacyKey", "properKey"); - verify(logger).deprecate(eq("legacyKey"), anyString(), any()); + verify(logger).deprecate(eq("legacyKey"), anyString(), any(), any()); } } From 75b6c3055f99ed8df0fdbeb10756a865cf3e6c76 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 3 Feb 2023 15:25:34 -0600 Subject: [PATCH 128/369] Unify triage meeting queries and expand meeting agenda (#2421) * Unify triage meeting queries and expand meeting agenda (#2421) Signed-off-by: Peter Nied --- TRIAGING.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/TRIAGING.md b/TRIAGING.md index 4b8eebb819..88ed2412ff 100644 --- a/TRIAGING.md +++ b/TRIAGING.md @@ -23,9 +23,11 @@ If you have an issue you'd like to bring forth please consider getting a link to Meetings are lightly structured as follows: 1. Announcements: If there are any announcements to be made they will happen at the start of the meeting. -2. Review of new issues: The meetings always start with reviewing all untriaged issues for the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Auntriaged) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aissue+is%3Aopen+-label%3Atriaged) repo. We will also provide an opportunity for any issues introduced since the last meeting but already triaged to be discussed. -3. Backlog discussion: Next, we open the floor in case anyone from the audience wants to highlight an issue from the backlogs ([security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Atriaged+-label%3A%22sprint+backlog%22) and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aopen+is%3Aissue+label%3Atriaged+-label%3A%22sprint+backlog%22+)). -4. Sprint backlog review: Finally, we will review persisting issues in the sprint backlog for the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A%22sprint+backlog%22) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aopen+is%3Aissue+label%3A%22sprint+backlog%22) repo, checking to see that issues have not gone stale or been addressed. +2. Review of new issues: The meetings always start with reviewing all untriaged [issues](https://github.com/search?q=label%3Auntriaged+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=created&o=desc) for the security and security-dashboards repositories. +3. Untriaged items: Review any [issues](https://github.com/search?q=-label%3Atriaged+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=created&o=desc) that might have had the 'untriaged' label removed but require additional triage discussion. +4. Open discussion: Next, we open the floor in case anyone wants to highlight an issue. +5. Backlog discussion: Then, we review issues from the [backlogs](https://github.com/search?q=label%3A%22sprint+backlog%22+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=created&o=desc) of the security and security-dashboards repositories. +6. Least recent discussed issue: Finally, to close out the meeting we will [review the oldest](https://github.com/search?q=+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=updated&o=asc) issues from both repositories, security and security-dashboards, to help identify issues that have languished. There is no specific ordering within each category. From d676716e83d1ab387e9e6a0c0f3284e39ed967f5 Mon Sep 17 00:00:00 2001 From: Andrey Pleskach Date: Thu, 9 Feb 2023 12:21:47 -0500 Subject: [PATCH 129/369] Rest admin permissions (#2411) Permissions for REST admin user Added granular permissions for all REST API actions in OpenSearch to be individually assigned. Permissions are: - 'restapi:admin/actiongroups' - allow full access to actiongroups - 'restapi:admin/allowlist' - allow full access to allowlist - 'restapi:admin/internalusers'- allow full access to internalusers - 'restapi:admin/nodesdn'- allow full access to nodesdn - 'restapi:admin/roles' - allow full access to roles - 'restapi:admin/rolesmapping' - allow full access to roles mappings - 'restapi:admin/ssl/certs/info' - allow full access to certs info - 'restapi:admin/ssl/certs/reload' - allow full access to certs reload - 'restapi:admin/tenants' - allow full access to tenants Adds tests for these permissions. Signed-off-by: Andrey Pleskach --- config/roles.yml | 15 +- .../security/OpenSearchSecurityPlugin.java | 29 +- .../dlic/rest/api/AbstractApiAction.java | 22 +- .../dlic/rest/api/AccountApiAction.java | 7 + .../dlic/rest/api/ActionGroupsApiAction.java | 27 +- .../dlic/rest/api/AllowlistApiAction.java | 7 + .../dlic/rest/api/AuditApiAction.java | 7 + .../rest/api/AuthTokenProcessorAction.java | 8 + .../security/dlic/rest/api/Endpoint.java | 3 +- .../dlic/rest/api/FlushCacheApiAction.java | 8 + .../dlic/rest/api/InternalUsersApiAction.java | 7 + .../dlic/rest/api/MigrateApiAction.java | 7 + .../dlic/rest/api/NodesDnApiAction.java | 7 + .../rest/api/PatchableResourceApiAction.java | 21 +- .../api/RestApiAdminPrivilegesEvaluator.java | 164 +++++++ .../rest/api/RestApiPrivilegesEvaluator.java | 16 +- .../dlic/rest/api/RolesApiAction.java | 20 + .../dlic/rest/api/RolesMappingApiAction.java | 27 ++ .../dlic/rest/api/SecurityConfigAction.java | 7 + .../dlic/rest/api/SecurityRestApiActions.java | 20 +- .../dlic/rest/api/SecuritySSLCertsAction.java | 346 ++++++++++++++ .../dlic/rest/api/TenantsApiAction.java | 8 + .../dlic/rest/api/ValidateApiAction.java | 7 + .../security/dlic/rest/support/Utils.java | 12 + .../privileges/PrivilegesEvaluator.java | 12 + .../security/securityconf/ConfigModelV6.java | 10 + .../security/securityconf/ConfigModelV7.java | 8 + .../security/securityconf/SecurityRoles.java | 2 + .../ssl/rest/SecuritySSLCertsInfoAction.java | 190 -------- .../rest/SecuritySSLReloadCertsAction.java | 155 ------ .../rest/api/AbstractRestApiUnitTest.java | 65 ++- .../dlic/rest/api/ActionGroupsApiTest.java | 273 +++++++---- .../dlic/rest/api/AllowlistApiTest.java | 25 +- .../dlic/rest/api/NodesDnApiTest.java | 79 +++ .../security/dlic/rest/api/RolesApiTest.java | 448 ++++++++++++++---- .../dlic/rest/api/RolesMappingApiTest.java | 385 ++++++++++----- .../dlic/rest/api/SslCertsApiTest.java | 174 +++++++ .../security/dlic/rest/api/UserApiTest.java | 199 +++++--- .../api/legacy/LegacySslCertsApiTest.java | 29 ++ .../SecurityRolesPermissionsTest.java | 274 +++++++++++ .../ssl/SecuritySSLCertsInfoActionTests.java | 110 ----- .../SecuritySSLReloadCertsActionTests.java | 44 -- src/test/resources/restapi/internal_users.yml | 66 +++ src/test/resources/restapi/roles.yml | 48 ++ src/test/resources/restapi/roles_mapping.yml | 54 +++ src/test/resources/restapi/roles_tenants.yml | 10 + 46 files changed, 2521 insertions(+), 941 deletions(-) create mode 100644 src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java create mode 100644 src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java delete mode 100644 src/main/java/org/opensearch/security/ssl/rest/SecuritySSLCertsInfoAction.java delete mode 100644 src/main/java/org/opensearch/security/ssl/rest/SecuritySSLReloadCertsAction.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySslCertsApiTest.java create mode 100644 src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java delete mode 100644 src/test/java/org/opensearch/security/ssl/SecuritySSLCertsInfoActionTests.java diff --git a/config/roles.yml b/config/roles.yml index 29f6fcbe5d..fd485423fe 100644 --- a/config/roles.yml +++ b/config/roles.yml @@ -9,7 +9,20 @@ kibana_read_only: # The security REST API access role is used to assign specific users access to change the security settings through the REST API. security_rest_api_access: reserved: true - + +security_rest_api_full_access: + reserved: true + cluster_permissions: + - 'restapi:admin/actiongroups' + - 'restapi:admin/allowlist' + - 'restapi:admin/internalusers' + - 'restapi:admin/nodesdn' + - 'restapi:admin/roles' + - 'restapi:admin/rolesmapping' + - 'restapi:admin/ssl/certs/info' + - 'restapi:admin/ssl/certs/reload' + - 'restapi:admin/tenants' + # Allows users to view monitors, destinations and alerts alerting_read_access: reserved: true diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index e70aa01912..3b77212fa7 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -156,8 +156,6 @@ import org.opensearch.security.ssl.OpenSearchSecuritySSLPlugin; import org.opensearch.security.ssl.SslExceptionHandler; import org.opensearch.security.ssl.http.netty.ValidatingDispatcher; -import org.opensearch.security.ssl.rest.SecuritySSLCertsInfoAction; -import org.opensearch.security.ssl.rest.SecuritySSLReloadCertsAction; import org.opensearch.security.ssl.transport.DefaultPrincipalExtractor; import org.opensearch.security.ssl.transport.SecuritySSLNettyTransport; import org.opensearch.security.ssl.util.SSLConfigConstants; @@ -466,18 +464,25 @@ public List getRestHandlers(Settings settings, RestController restC if(!SSLConfig.isSslOnlyMode()) { handlers.add(new SecurityInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool))); handlers.add(new SecurityHealthAction(settings, restController, Objects.requireNonNull(backendRegistry))); - handlers.add(new SecuritySSLCertsInfoAction(settings, restController, sks, Objects.requireNonNull(threadPool), Objects.requireNonNull(adminDns))); handlers.add(new DashboardsInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool))); handlers.add(new TenantInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool), - Objects.requireNonNull(cs), Objects.requireNonNull(adminDns), Objects.requireNonNull(cr))); - handlers.add(new SecurityConfigUpdateAction(settings, restController,Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); - handlers.add(new SecurityWhoAmIAction(settings ,restController,Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); - if (sslCertReloadEnabled) { - handlers.add(new SecuritySSLReloadCertsAction(settings, restController, sks, Objects.requireNonNull(threadPool), Objects.requireNonNull(adminDns))); - } - final Collection apiHandlers = SecurityRestApiActions.getHandler(settings, configPath, restController, localClient, adminDns, cr, cs, principalExtractor, evaluator, threadPool, Objects.requireNonNull(auditLog)); - handlers.addAll(apiHandlers); - log.debug("Added {} management rest handler(s)", apiHandlers.size()); + Objects.requireNonNull(cs), Objects.requireNonNull(adminDns), Objects.requireNonNull(cr))); + handlers.add(new SecurityConfigUpdateAction(settings, restController, Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); + handlers.add(new SecurityWhoAmIAction(settings, restController, Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); + handlers.addAll( + SecurityRestApiActions.getHandler( + settings, + configPath, + restController, + localClient, + adminDns, + cr, cs, principalExtractor, + evaluator, + threadPool, + Objects.requireNonNull(auditLog), sks, + sslCertReloadEnabled) + ); + log.debug("Added {} rest handler(s)", handlers.size()); } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java index 8ceb7eaaae..873656f927 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java @@ -75,9 +75,9 @@ public abstract class AbstractApiAction extends BaseRestHandler { final ThreadPool threadPool; protected String opendistroIndex; private final RestApiPrivilegesEvaluator restApiPrivilegesEvaluator; + protected final RestApiAdminPrivilegesEvaluator restApiAdminPrivilegesEvaluator; protected final AuditLog auditLog; protected final Settings settings; - private AdminDNs adminDNs; protected AbstractApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, final AdminDNs adminDNs, final ConfigurationRepository cl, @@ -88,12 +88,13 @@ protected AbstractApiAction(final Settings settings, final Path configPath, fina this.opendistroIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); - this.adminDNs = adminDNs; this.cl = cl; this.cs = cs; this.threadPool = threadPool; this.restApiPrivilegesEvaluator = new RestApiPrivilegesEvaluator(settings, adminDNs, evaluator, principalExtractor, configPath, threadPool); + this.restApiAdminPrivilegesEvaluator = + new RestApiAdminPrivilegesEvaluator(threadPool.getThreadContext(), evaluator, adminDNs); this.auditLog = auditLog; } @@ -195,7 +196,12 @@ protected void handlePut(final RestChannel channel, final RestRequest request, f } boolean existed = existingConfiguration.exists(name); - existingConfiguration.putCObject(name, DefaultObjectMapper.readTree(content, existingConfiguration.getImplementingClass())); + final Object newContent = DefaultObjectMapper.readTree(content, existingConfiguration.getImplementingClass()); + if (!hasPermissionsToCreate(existingConfiguration, newContent, getResourceName())) { + forbidden(channel, "No permissions"); + return; + } + existingConfiguration.putCObject(name, newContent); saveAnUpdateConfigs(client, request, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { @@ -216,6 +222,12 @@ protected void handlePost(final RestChannel channel, final RestRequest request, notImplemented(channel, Method.POST); } + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) throws IOException { + return false; + } + protected void handleGet(final RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException{ @@ -448,7 +460,6 @@ protected static XContentBuilder convertToJson(RestChannel channel, ToXContent t } protected void response(RestChannel channel, RestStatus status, String message) { - try { final XContentBuilder builder = channel.newBuilder(); builder.startObject(); @@ -558,8 +569,7 @@ public String getName() { protected abstract Endpoint getEndpoint(); protected boolean isSuperAdmin() { - User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - return adminDNs.isAdmin(user); + return restApiAdminPrivilegesEvaluator.isCurrentUserRestApiAdminFor(getEndpoint()); } /** diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java index 07a0424055..20de3500dd 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java @@ -83,6 +83,13 @@ public AccountApiAction(Settings settings, this.threadContext = threadPool.getThreadContext(); } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) { + return true; + } + @Override public List routes() { return routes; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java index 83d1a993ff..23a3a451b9 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java @@ -118,12 +118,35 @@ protected void handlePut(RestChannel channel, RestRequest request, Client client // Prevent the case where action group references to itself in the allowed_actions. final SecurityDynamicConfiguration existingActionGroupsConfig = load(getConfigName(), false); - existingActionGroupsConfig.putCObject(name, DefaultObjectMapper.readTree(content, existingActionGroupsConfig.getImplementingClass())); + final Object actionGroup = DefaultObjectMapper.readTree(content, existingActionGroupsConfig.getImplementingClass()); + existingActionGroupsConfig.putCObject(name, actionGroup); if (hasActionGroupSelfReference(existingActionGroupsConfig, name)) { badRequestResponse(channel, name + " cannot be an allowed_action of itself"); return; } - + // prevent creation of groups for REST admin api + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(actionGroup)) { + forbidden(channel, "Not allowed"); + return; + } super.handlePut(channel, request, client, content); } + + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfiguration, + final Object content, + final String resourceName) throws IOException { + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(content)) { + return false; + } + return true; + } + + @Override + protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(existingConfiguration.getCEntry(name))) { + return true; + } + return super.isReadOnly(existingConfiguration, name); + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java index afe08bc486..b37375a461 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java @@ -98,6 +98,13 @@ public AllowlistApiAction(final Settings settings, final Path configPath, final super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) { + return true; + } + @Override protected void handleApiRequest(final RestChannel channel, final RestRequest request, final Client client) throws IOException { if (!isSuperAdmin()) { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java index ce11b74509..e19f04d437 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java @@ -165,6 +165,13 @@ public AuditApiAction(final Settings settings, } } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) { + return true; + } + @Override public List routes() { return routes; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java index 2338fe15e9..497efcdf76 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java @@ -34,6 +34,7 @@ import org.opensearch.security.dlic.rest.validation.NoOpValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.ssl.transport.PrincipalExtractor; import org.opensearch.threadpool.ThreadPool; @@ -53,6 +54,13 @@ public AuthTokenProcessorAction(final Settings settings, final Path configPath, auditLog); } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) { + return true; + } + @Override public List routes() { return routes; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/Endpoint.java b/src/main/java/org/opensearch/security/dlic/rest/api/Endpoint.java index ce57070825..84a447bcac 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/Endpoint.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/Endpoint.java @@ -28,5 +28,6 @@ public enum Endpoint { VALIDATE, WHITELIST, ALLOWLIST, - NODESDN; + NODESDN, + SSL; } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java index 7e6d7989a9..406b81679c 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java @@ -38,6 +38,7 @@ import org.opensearch.security.dlic.rest.validation.NoOpValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.ssl.transport.PrincipalExtractor; import org.opensearch.threadpool.ThreadPool; @@ -58,6 +59,13 @@ public FlushCacheApiAction(final Settings settings, final Path configPath, final super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) { + return true; + } + @Override public List routes() { return routes; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java index 2394488041..3f0ff75f8f 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java @@ -78,6 +78,13 @@ public InternalUsersApiAction(final Settings settings, final Path configPath, fi auditLog); } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) { + return true; + } + @Override public List routes() { return routes; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java index 02bb9f61b2..1a1092e2fb 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java @@ -92,6 +92,13 @@ protected Endpoint getEndpoint() { return Endpoint.MIGRATE; } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) { + return true; + } + @SuppressWarnings("unchecked") @Override protected void handlePost(RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java index 5498c2a50b..22897b8305 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java @@ -77,6 +77,13 @@ public NodesDnApiAction(final Settings settings, final Path configPath, final Re this.staticNodesDnFromEsYml = settings.getAsList(ConfigConstants.SECURITY_NODES_DN, Collections.emptyList()); } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) { + return true; + } + @Override public List routes() { if (settings.getAsBoolean(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, false)) { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java index 74abb1d10a..6d644c1eae 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java @@ -55,9 +55,9 @@ public abstract class PatchableResourceApiAction extends AbstractApiAction { protected final Logger log = LogManager.getLogger(this.getClass()); public PatchableResourceApiAction(Settings settings, Path configPath, RestController controller, Client client, - AdminDNs adminDNs, ConfigurationRepository cl, ClusterService cs, - PrincipalExtractor principalExtractor, PrivilegesEvaluator evaluator, ThreadPool threadPool, - AuditLog auditLog) { + AdminDNs adminDNs, ConfigurationRepository cl, ClusterService cs, + PrincipalExtractor principalExtractor, PrivilegesEvaluator evaluator, ThreadPool threadPool, + AuditLog auditLog) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } @@ -146,7 +146,7 @@ private void handleSinglePatch(RestChannel channel, RestRequest request, Client if (!validator.validate()) { request.params().clear(); - badRequestResponse(channel, validator); + badRequestResponse(channel, validator); return; } @@ -156,7 +156,7 @@ private void handleSinglePatch(RestChannel channel, RestRequest request, Client , existingConfiguration.getVersion(), existingConfiguration.getSeqNo(), existingConfiguration.getPrimaryTerm()); if (existingConfiguration.getCType().equals(CType.ACTIONGROUPS)) { - if(hasActionGroupSelfReference(mdc, name)) { + if (hasActionGroupSelfReference(mdc, name)) { badRequestResponse(channel, name + " cannot be an allowed_action of itself"); return; } @@ -188,7 +188,6 @@ private void handleBulkPatch(RestChannel channel, RestRequest request, Client cl for (String resourceName : existingConfiguration.getCEntries().keySet()) { JsonNode oldResource = existingAsObjectNode.get(resourceName); JsonNode patchedResource = patchedAsJsonNode.get(resourceName); - if (oldResource != null && !oldResource.equals(patchedResource) && !isWriteable(channel, existingConfiguration, resourceName)) { return; } @@ -206,7 +205,7 @@ private void handleBulkPatch(RestChannel channel, RestRequest request, Client cl if(originalValidator != null) { if (!originalValidator.validate()) { request.params().clear(); - badRequestResponse(channel, originalValidator); + badRequestResponse(channel, originalValidator); return; } } @@ -222,7 +221,13 @@ private void handleBulkPatch(RestChannel channel, RestRequest request, Client cl if (!validator.validate()) { request.params().clear(); - badRequestResponse(channel, validator); + badRequestResponse(channel, validator); + return; + } + final Object newContent = DefaultObjectMapper.readTree(patchedResource, existingConfiguration.getImplementingClass()); + if (!hasPermissionsToCreate(existingConfiguration, newContent, resourceName)) { + request.params().clear(); + forbidden(channel, "No permissions"); return; } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java new file mode 100644 index 0000000000..c3449e99bb --- /dev/null +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java @@ -0,0 +1,164 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api; + +import java.util.Locale; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.common.transport.TransportAddress; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.security.configuration.AdminDNs; +import org.opensearch.security.dlic.rest.support.Utils; +import org.opensearch.security.privileges.PrivilegesEvaluator; +import org.opensearch.security.securityconf.impl.v7.ActionGroupsV7; +import org.opensearch.security.securityconf.impl.v7.RoleV7; +import org.opensearch.security.support.WildcardMatcher; +import org.opensearch.security.user.User; + +public class RestApiAdminPrivilegesEvaluator { + + protected final Logger logger = LogManager.getLogger(RestApiAdminPrivilegesEvaluator.class); + + public final static String CERTS_INFO_ACTION = "certs"; + + public final static String RELOAD_CERTS_ACTION = "reloadcerts"; + + private final static String REST_API_PERMISSION_PREFIX = "restapi:admin"; + + private final static String REST_ENDPOINT_PERMISSION_PATTERN = + REST_API_PERMISSION_PREFIX + "/%s"; + + private final static String REST_ENDPOINT_ACTION_PERMISSION_PATTERN = + REST_API_PERMISSION_PREFIX + "/%s/%s"; + + private final static WildcardMatcher REST_API_PERMISSION_PREFIX_MATCHER = + WildcardMatcher.from(REST_API_PERMISSION_PREFIX + "/*"); + + @FunctionalInterface + public interface PermissionBuilder { + + default String build() { + return build(null); + } + + String build(final String action); + + } + + public final static Map ENDPOINTS_WITH_PERMISSIONS = + ImmutableMap.builder() + .put(Endpoint.ACTIONGROUPS, action -> buildEndpointPermission(Endpoint.ACTIONGROUPS)) + .put(Endpoint.ALLOWLIST, action -> buildEndpointPermission(Endpoint.ALLOWLIST)) + .put(Endpoint.INTERNALUSERS, action -> buildEndpointPermission(Endpoint.INTERNALUSERS)) + .put(Endpoint.NODESDN, action -> buildEndpointPermission(Endpoint.NODESDN)) + .put(Endpoint.ROLES, action -> buildEndpointPermission(Endpoint.ROLES)) + .put(Endpoint.ROLESMAPPING, action -> buildEndpointPermission(Endpoint.ROLESMAPPING)) + .put(Endpoint.TENANTS, action -> buildEndpointPermission(Endpoint.TENANTS)) + .put(Endpoint.SSL, action -> { + switch (action) { + case CERTS_INFO_ACTION: + return buildEndpointActionPermission(Endpoint.SSL, "certs/info"); + case RELOAD_CERTS_ACTION: + return buildEndpointActionPermission(Endpoint.SSL, "certs/reload"); + default: + return null; + } + }).build(); + + private final ThreadContext threadContext; + + private final PrivilegesEvaluator privilegesEvaluator; + + private final AdminDNs adminDNs; + + public RestApiAdminPrivilegesEvaluator( + final ThreadContext threadContext, + final PrivilegesEvaluator privilegesEvaluator, + final AdminDNs adminDNs) { + this.threadContext = threadContext; + this.privilegesEvaluator = privilegesEvaluator; + this.adminDNs = adminDNs; + } + + public boolean isCurrentUserRestApiAdminFor(final Endpoint endpoint, final String action) { + final Pair userAndRemoteAddress = Utils.userAndRemoteAddressFrom(threadContext); + if (userAndRemoteAddress.getLeft() == null) { + return false; + } + if (adminDNs.isAdmin(userAndRemoteAddress.getLeft())) { + if (logger.isDebugEnabled()) { + logger.debug( + "Security admin permissions required for endpoint {} but {} is not an admin", + endpoint, userAndRemoteAddress.getLeft().getName()); + } + return true; + } + if (!ENDPOINTS_WITH_PERMISSIONS.containsKey(endpoint)) { + if (logger.isDebugEnabled()) { + logger.debug("No permission found for {} endpoint", endpoint); + } + return false; + } + final String permission = ENDPOINTS_WITH_PERMISSIONS.get(endpoint).build(action); + if (logger.isDebugEnabled()) { + logger.debug("Checking permission {} for endpoint {}", permission, endpoint); + } + return privilegesEvaluator.hasRestAdminPermissions( + userAndRemoteAddress.getLeft(), + userAndRemoteAddress.getRight(), + permission + ); + } + + public boolean containsRestApiAdminPermissions(final Object configObject) { + if (configObject == null) { + return false; + } + if (configObject instanceof RoleV7) { + return ((RoleV7) configObject) + .getCluster_permissions() + .stream() + .anyMatch(REST_API_PERMISSION_PREFIX_MATCHER); + } else if (configObject instanceof ActionGroupsV7) { + return ((ActionGroupsV7) configObject) + .getAllowed_actions() + .stream() + .anyMatch(REST_API_PERMISSION_PREFIX_MATCHER); + } else { + return false; + } + } + + public boolean isCurrentUserRestApiAdminFor(final Endpoint endpoint) { + return isCurrentUserRestApiAdminFor(endpoint, null); + } + + private static String buildEndpointActionPermission(final Endpoint endpoint, final String action) { + return String.format( + REST_ENDPOINT_ACTION_PERMISSION_PATTERN, + endpoint.name().toLowerCase(Locale.ROOT), + action); + } + + private static String buildEndpointPermission(final Endpoint endpoint) { + return String.format( + REST_ENDPOINT_PERMISSION_PATTERN, + endpoint.name().toLowerCase(Locale.ROOT) + ); + } + +} diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluator.java b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluator.java index 21316876cb..04b4b31c77 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluator.java @@ -25,6 +25,7 @@ import java.util.Map.Entry; import java.util.Set; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -71,7 +72,11 @@ public class RestApiPrivilegesEvaluator { private final Boolean roleBasedAccessEnabled; - public RestApiPrivilegesEvaluator(Settings settings, AdminDNs adminDNs, PrivilegesEvaluator privilegesEvaluator, PrincipalExtractor principalExtractor, Path configPath, + public RestApiPrivilegesEvaluator(final Settings settings, + final AdminDNs adminDNs, + final PrivilegesEvaluator privilegesEvaluator, + final PrincipalExtractor principalExtractor, + final Path configPath, ThreadPool threadPool) { this.adminDNs = adminDNs; @@ -80,9 +85,7 @@ public RestApiPrivilegesEvaluator(Settings settings, AdminDNs adminDNs, Privileg this.configPath = configPath; this.threadPool = threadPool; this.settings = settings; - // set up - // all endpoints and methods Map> allEndpoints = new HashMap<>(); for(Endpoint endpoint : Endpoint.values()) { @@ -344,8 +347,10 @@ private String checkRoleBasedAccessPermissions(RestRequest request, Endpoint end if (this.roleBasedAccessEnabled) { // get current user and roles - final User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - final TransportAddress remoteAddress = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); + final Pair userAndRemoteAddress = + Utils.userAndRemoteAddressFrom(threadPool.getThreadContext()); + final User user = userAndRemoteAddress.getLeft(); + final TransportAddress remoteAddress = userAndRemoteAddress.getRight(); // map the users Security roles Set userRoles = privilegesEvaluator.mapRoles(user, remoteAddress); @@ -355,7 +360,6 @@ private String checkRoleBasedAccessPermissions(RestRequest request, Endpoint end // yes, calculate disabled end points. Since a user can have // multiple roles, the endpoint // needs to be disabled in all roles. - Map> disabledEndpointsForUser = getDisabledEndpointsForCurrentUser(user.getName(), userRoles); if (isDebugEnabled) { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java index 7fb6dfa915..7b2676c246 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java @@ -11,6 +11,7 @@ package org.opensearch.security.dlic.rest.api; +import java.io.IOException; import java.nio.file.Path; import java.util.List; @@ -31,6 +32,7 @@ import org.opensearch.security.dlic.rest.validation.RolesValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.ssl.transport.PrincipalExtractor; import org.opensearch.threadpool.ThreadPool; @@ -77,4 +79,22 @@ protected CType getConfigName() { return CType.ROLES; } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfiguration, final Object content, final String resourceName) throws IOException { + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(content)) { + return isSuperAdmin(); + } else { + return true; + } + } + + @Override + protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(existingConfiguration.getCEntry(name))) { + return !isSuperAdmin(); + } else { + return super.isReadOnly(existingConfiguration, name); + } + } + } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java index c7e7f3d7ec..b056a25ad2 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java @@ -68,11 +68,18 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C return; } + final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); final SecurityDynamicConfiguration rolesMappingConfiguration = load(getConfigName(), false); final boolean rolesMappingExists = rolesMappingConfiguration.exists(name); if (!isValidRolesMapping(channel, name)) return; + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(name))) { + if (!isSuperAdmin()) { + forbidden(channel, "No permissions"); + return; + } + } rolesMappingConfiguration.putCObject(name, DefaultObjectMapper.readTree(content, rolesMappingConfiguration.getImplementingClass())); saveAnUpdateConfigs(client, request, getConfigName(), rolesMappingConfiguration, new OnSucessActionListener(channel) { @@ -89,6 +96,26 @@ public void onResponse(IndexResponse response) { }); } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, final Object content, final String resourceName) throws IOException { + final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(resourceName))) { + return isSuperAdmin(); + } else { + return true; + } + } + + @Override + protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { + final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(name))) { + return !isSuperAdmin(); + } else { + return super.isReadOnly(existingConfiguration, name); + } + } + @Override public List routes() { return routes; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java index 7545f47113..66888bc126 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java @@ -74,6 +74,13 @@ public List routes() { return allowPutOrPatch ? allRoutes : getRoutes; } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) { + return true; + } + @Override protected void handleGet(RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException{ final SecurityDynamicConfiguration configuration = load(getConfigName(), true); diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java index 54ea46efd1..9655ba67ea 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java @@ -26,15 +26,26 @@ import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; import org.opensearch.security.privileges.PrivilegesEvaluator; +import org.opensearch.security.ssl.SecurityKeyStore; import org.opensearch.security.ssl.transport.PrincipalExtractor; import org.opensearch.threadpool.ThreadPool; public class SecurityRestApiActions { - public static Collection getHandler(Settings settings, Path configPath, RestController controller, Client client, - AdminDNs adminDns, ConfigurationRepository cr, ClusterService cs, PrincipalExtractor principalExtractor, - final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { - final List handlers = new ArrayList(15); + public static Collection getHandler(final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDns, + final ConfigurationRepository cr, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + final ThreadPool threadPool, + final AuditLog auditLog, + final SecurityKeyStore securityKeyStore, + final boolean certificatesReloadEnabled) { + final List handlers = new ArrayList(16); handlers.add(new InternalUsersApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); handlers.add(new RolesMappingApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); handlers.add(new RolesApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); @@ -51,6 +62,7 @@ public static Collection getHandler(Settings settings, Path configP handlers.add(new WhitelistApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); handlers.add(new AllowlistApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); handlers.add(new AuditApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); + handlers.add(new SecuritySSLCertsAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog, securityKeyStore, certificatesReloadEnabled)); return Collections.unmodifiableCollection(handlers); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java new file mode 100644 index 0000000000..8e936b167a --- /dev/null +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java @@ -0,0 +1,346 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api; + +import java.io.IOException; +import java.nio.file.Path; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.client.Client; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.bytes.BytesReference; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestController; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.RestRequest.Method; +import org.opensearch.rest.RestStatus; +import org.opensearch.security.auditlog.AuditLog; +import org.opensearch.security.configuration.AdminDNs; +import org.opensearch.security.configuration.ConfigurationRepository; +import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; +import org.opensearch.security.privileges.PrivilegesEvaluator; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; +import org.opensearch.security.ssl.SecurityKeyStore; +import org.opensearch.security.ssl.transport.PrincipalExtractor; +import org.opensearch.security.ssl.util.SSLConfigConstants; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.threadpool.ThreadPool; + +import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; + + +/** + * Rest API action to get SSL certificate information related to http and transport encryption. + * Only super admin users are allowed to access this API. + * This action serves GET request for _plugins/_security/api/ssl/certs endpoint and + * PUT _plugins/_security/api/ssl/{certType}/reloadcerts + */ +public class SecuritySSLCertsAction extends AbstractApiAction { + private static final List ROUTES = addRoutesPrefix( + ImmutableList.of( + new Route(Method.GET, "/ssl/certs"), + new Route(Method.PUT, "/ssl/{certType}/reloadcerts") + ) + ); + + private final Logger log = LogManager.getLogger(this.getClass()); + + private final SecurityKeyStore securityKeyStore; + + private final boolean certificatesReloadEnabled; + + private final RestApiAdminPrivilegesEvaluator restApiAdminPrivilegesEvaluator; + + private final boolean httpsEnabled; + + public SecuritySSLCertsAction(final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator privilegesEvaluator, + final ThreadPool threadPool, + final AuditLog auditLog, + final SecurityKeyStore securityKeyStore, + final boolean certificatesReloadEnabled) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, privilegesEvaluator, threadPool, auditLog); + this.securityKeyStore = securityKeyStore; + this.restApiAdminPrivilegesEvaluator = + new RestApiAdminPrivilegesEvaluator(threadPool.getThreadContext(), privilegesEvaluator, adminDNs); + this.certificatesReloadEnabled = certificatesReloadEnabled; + this.httpsEnabled = settings.getAsBoolean(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true); + } + + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) { + return true; + } + + @Override + public List routes() { + return ROUTES; + } + + @Override + protected void handleApiRequest(final RestChannel channel, final RestRequest request, final Client client) throws IOException { + switch (request.method()) { + case GET: + if (!restApiAdminPrivilegesEvaluator.isCurrentUserRestApiAdminFor(getEndpoint(), "certs")) { + forbidden(channel, ""); + return; + } + handleGet(channel, request, client, null); + break; + case PUT: + if (!restApiAdminPrivilegesEvaluator.isCurrentUserRestApiAdminFor(getEndpoint(), "reloadcerts")) { + forbidden(channel, ""); + return; + } + if (!certificatesReloadEnabled) { + badRequestResponse( + channel, + String.format( + "no handler found for uri [%s] and method [%s]. In order to use SSL reload functionality set %s to true", + request.path(), + request.method(), + ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED + ) + ); + return; + } + handlePut(channel, request, client, null); + break; + default: + notImplemented(channel, request.method()); + break; + } + } + + /** + * GET request to fetch transport certificate details + * + * Sample request: + * GET _plugins/_security/api/ssl/certs + * + * Sample response: + * { + * "http_certificates_list" : [ + * { + * "issuer_dn" : "CN=Example Com Inc. Signing CA, OU=Example Com Inc. Signing CA, O=Example Com Inc., DC=example, DC=com", + * "subject_dn" : "CN=transport-0.example.com, OU=SSL, O=Test, L=Test, C=DE", + * "san", "[[2, node-0.example.com], [2, localhost], [7, 127.0.0.1], [8, 1.2.3.4.5.5]]", + * "not_before" : "2018-05-05T14:37:09.000Z", + * "not_after" : "2028-05-02T14:37:09.000Z" + * } + * "transport_certificates_list" : [ + * { + * "issuer_dn" : "CN=Example Com Inc. Signing CA, OU=Example Com Inc. Signing CA, O=Example Com Inc., DC=example, DC=com", + * "subject_dn" : "CN=transport-0.example.com, OU=SSL, O=Test, L=Test, C=DE", + * "san", "[[2, node-0.example.com], [2, localhost], [7, 127.0.0.1], [8, 1.2.3.4.5.5]]", + * "not_before" : "2018-05-05T14:37:09.000Z", + * "not_after" : "2028-05-02T14:37:09.000Z" + * } + * ] + * } + * + * @param request request to be served + * @param client client + * @throws IOException + */ + @Override + protected void handleGet(final RestChannel channel, + final RestRequest request, + final Client client, + final JsonNode content) throws IOException { + if (securityKeyStore == null) { + noKeyStoreResponse(channel); + return; + } + try (final XContentBuilder contentBuilder = channel.newBuilder()) { + channel.sendResponse( + new BytesRestResponse( + RestStatus.OK, + contentBuilder + .startObject() + .field( + "http_certificates_list", + httpsEnabled ? generateCertDetailList(securityKeyStore.getHttpCerts()) : null + ).field( + "transport_certificates_list", + generateCertDetailList(securityKeyStore.getTransportCerts()) + ).endObject() + ) + ); + } catch (final Exception e) { + internalErrorResponse(channel, e.getMessage()); + log.error("Error handle request ", e); + } + } + + /** + * PUT request to reload SSL Certificates. + * + * Sample request: + * PUT _opendistro/_security/api/ssl/transport/reloadcerts + * PUT _opendistro/_security/api/ssl/http/reloadcerts + * + * NOTE: No request body is required. We will assume new certificates are loaded in the paths specified in your opensearch.yml file + * (https://docs-beta.opensearch.org/docs/security/configuration/tls/) + * + * Sample response: + * { "message": "updated http certs" } + * + * @param request request to be served + * @param client client + * @throws IOException + */ + @Override + protected void handlePut(final RestChannel channel, + final RestRequest request, + final Client client, + final JsonNode content) throws IOException { + if (securityKeyStore == null) { + noKeyStoreResponse(channel); + return; + } + final String certType = request.param("certType").toLowerCase().trim(); + try (final XContentBuilder contentBuilder = channel.newBuilder()) { + switch (certType) { + case "http": + if (!httpsEnabled) { + badRequestResponse(channel, "SSL for HTTP is disabled"); + return; + } + securityKeyStore.initHttpSSLConfig(); + channel.sendResponse( + new BytesRestResponse( + RestStatus.OK, + contentBuilder + .startObject() + .field("message", "updated http certs") + .endObject() + ) + ); + break; + case "transport": + securityKeyStore.initTransportSSLConfig(); + channel.sendResponse( + new BytesRestResponse( + RestStatus.OK, + contentBuilder + .startObject() + .field("message", "updated transport certs") + .endObject() + ) + ); + break; + default: + forbidden(channel, + "invalid uri path, please use /_plugins/_security/api/ssl/http/reload or " + + "/_plugins/_security/api/ssl/transport/reload" + ); + break; + } + } catch (final Exception e) { + log.error("Reload of certificates for {} failed", certType, e); + try (final XContentBuilder contentBuilder = channel.newBuilder()) { + channel.sendResponse(new BytesRestResponse( + RestStatus.INTERNAL_SERVER_ERROR, + contentBuilder + .startObject() + .field("error", e.toString()) + .endObject() + ) + ); + } + } + } + + private List> generateCertDetailList(final X509Certificate[] certs) { + if (certs == null) { + return null; + } + return Arrays + .stream(certs) + .map(cert -> { + final String issuerDn = cert != null && cert.getIssuerX500Principal() != null ? cert.getIssuerX500Principal().getName() : ""; + final String subjectDn = cert != null && cert.getSubjectX500Principal() != null ? cert.getSubjectX500Principal().getName() : ""; + + final String san = securityKeyStore.getSubjectAlternativeNames(cert); + + final String notBefore = cert != null && cert.getNotBefore() != null ? cert.getNotBefore().toInstant().toString() : ""; + final String notAfter = cert != null && cert.getNotAfter() != null ? cert.getNotAfter().toInstant().toString() : ""; + return ImmutableMap.of( + "issuer_dn", issuerDn, + "subject_dn", subjectDn, + "san", san, + "not_before", notBefore, + "not_after", notAfter + ); + }) + .collect(Collectors.toList()); + } + + private void noKeyStoreResponse(final RestChannel channel) throws IOException { + response(channel, RestStatus.OK, "keystore is not initialized"); + } + + @Override + protected Endpoint getEndpoint() { + return Endpoint.SSL; + } + + @Override + public String getName() { + return "SSL Certificates Action"; + } + + @Override + protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... params) { + return null; + } + + @Override + protected void consumeParameters(RestRequest request) { + request.param("certType"); + } + + @Override + protected String getResourceName() { + return null; + } + + @Override + protected CType getConfigName() { + return null; + } + +} diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java index 26d39767d5..ef8d1d3b9f 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java @@ -47,6 +47,7 @@ import org.opensearch.security.dlic.rest.validation.TenantValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.ssl.transport.PrincipalExtractor; import org.opensearch.threadpool.ThreadPool; @@ -69,6 +70,13 @@ public TenantsApiAction(final Settings settings, final Path configPath, final Re super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) { + return true; + } + @Override public List routes() { return routes; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java index a3f2b202ee..8e2222cab0 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java @@ -67,6 +67,13 @@ public ValidateApiAction(final Settings settings, final Path configPath, final R super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } + @Override + protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName) { + return true; + } + @Override public List routes() { return routes; diff --git a/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java b/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java index 047a649718..099087523b 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java +++ b/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java @@ -29,12 +29,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import org.apache.commons.lang3.tuple.Pair; import org.bouncycastle.crypto.generators.OpenBSDBCrypt; import org.opensearch.ExceptionsHelper; import org.opensearch.OpenSearchParseException; import org.opensearch.SpecialPermission; import org.opensearch.common.bytes.BytesReference; +import org.opensearch.common.transport.TransportAddress; +import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.common.xcontent.NamedXContentRegistry; import org.opensearch.common.xcontent.ToXContent; import org.opensearch.common.xcontent.XContentHelper; @@ -44,6 +47,8 @@ import org.opensearch.rest.RestHandler.DeprecatedRoute; import org.opensearch.rest.RestHandler.Route; import org.opensearch.security.DefaultObjectMapper; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; import static org.opensearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; @@ -265,4 +270,11 @@ public static List addDeprecatedRoutesPrefix(List new DeprecatedRoute(r.getMethod(), p + r.getPath(), r.getDeprecationMessage()))) .collect(ImmutableList.toImmutableList()); } + + public static Pair userAndRemoteAddressFrom(final ThreadContext threadContext) { + final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final TransportAddress remoteAddress = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); + return Pair.of(user, remoteAddress); + } + } diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index df9c432827..9967859d9f 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -179,6 +179,18 @@ private SecurityRoles getSecurityRoles(Set roles) { return configModel.getSecurityRoles().filter(roles); } + public boolean hasRestAdminPermissions(final User user, + final TransportAddress remoteAddress, + final String permissions) { + final Set userRoles = mapRoles(user, remoteAddress); + return hasRestAdminPermissions(userRoles, permissions); + } + + private boolean hasRestAdminPermissions(final Set roles, String permission) { + final SecurityRoles securityRoles = getSecurityRoles(roles); + return securityRoles.hasExplicitClusterPermissionPermission(permission); + } + public boolean isInitialized() { return configModel !=null && configModel.getSecurityRoles() != null && dcm != null; } diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java index a9ba2d7142..9de3cb9417 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java @@ -482,10 +482,20 @@ public boolean get(Resolved resolved, User user, String[] actions, IndexNameExpr return false; } + @Override public boolean impliesClusterPermissionPermission(String action) { return roles.stream().filter(r -> r.impliesClusterPermission(action)).count() > 0; } + @Override + public boolean hasExplicitClusterPermissionPermission(String action) { + return roles.stream() + .map(r -> { + final WildcardMatcher m = WildcardMatcher.from(r.clusterPerms); + return m == WildcardMatcher.ANY ? WildcardMatcher.NONE : m; + }).filter(m -> m.test(action)).count() > 0; + } + //rolespan public boolean impliesTypePermGlobal(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index cbd7cb8301..ab3c47d911 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -479,10 +479,18 @@ public boolean get(Resolved resolved, User user, String[] actions, IndexNameExpr return false; } + @Override public boolean impliesClusterPermissionPermission(String action) { return roles.stream().filter(r -> r.impliesClusterPermission(action)).count() > 0; } + @Override + public boolean hasExplicitClusterPermissionPermission(String action) { + return roles.stream() + .map(r -> r.clusterPerms == WildcardMatcher.ANY ? WildcardMatcher.NONE : r.clusterPerms) + .filter(m -> m.test(action)).count() > 0; + } + //rolespan public boolean impliesTypePermGlobal(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { diff --git a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java index d71b279afb..478e0f03dd 100644 --- a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java +++ b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java @@ -39,6 +39,8 @@ public interface SecurityRoles { boolean impliesClusterPermissionPermission(String action0); + boolean hasExplicitClusterPermissionPermission(String action); + Set getRoleNames(); Set reduce(Resolved requestedResolved, User user, String[] strings, IndexNameExpressionResolver resolver, ClusterService clusterService); diff --git a/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLCertsInfoAction.java b/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLCertsInfoAction.java deleted file mode 100644 index 6c1ec5296c..0000000000 --- a/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLCertsInfoAction.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.ssl.rest; - -import java.io.IOException; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.client.node.NodeClient; -import org.opensearch.common.settings.Settings; -import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.BytesRestResponse; -import org.opensearch.rest.RestChannel; -import org.opensearch.rest.RestController; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.RestRequest.Method; -import org.opensearch.rest.RestStatus; -import org.opensearch.security.configuration.AdminDNs; -import org.opensearch.security.ssl.SecurityKeyStore; -import org.opensearch.security.support.ConfigConstants; -import org.opensearch.security.user.User; -import org.opensearch.threadpool.ThreadPool; - -import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; - - -/** - * Rest API action to get SSL certificate information related to http and transport encryption. - * Only super admin users are allowed to access this API. - * Currently this action serves GET request for _plugins/_security/api/ssl/certs endpoint - */ -public class SecuritySSLCertsInfoAction extends BaseRestHandler { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(Method.GET, "/ssl/certs") - )); - - private final Logger log = LogManager.getLogger(this.getClass()); - private Settings settings; - private SecurityKeyStore odsks; - private AdminDNs adminDns; - private ThreadContext threadContext; - - public SecuritySSLCertsInfoAction(final Settings settings, - final RestController restController, - final SecurityKeyStore odsks, - final ThreadPool threadPool, - final AdminDNs adminDns) { - super(); - this.settings = settings; - this.odsks = odsks; - this.adminDns = adminDns; - this.threadContext = threadPool.getThreadContext(); - } - - @Override - public List routes() { - return routes; - } - - /** - * GET request to fetch transport certificate details - * - * Sample request: - * GET _plugins/_security/api/ssl/certs - * - * Sample response: - * { - * "http_certificates_list" : [ - * { - * "issuer_dn" : "CN=Example Com Inc. Signing CA, OU=Example Com Inc. Signing CA, O=Example Com Inc., DC=example, DC=com", - * "subject_dn" : "CN=transport-0.example.com, OU=SSL, O=Test, L=Test, C=DE", - * "san", "[[2, node-0.example.com], [2, localhost], [7, 127.0.0.1], [8, 1.2.3.4.5.5]]", - * "not_before" : "2018-05-05T14:37:09.000Z", - * "not_after" : "2028-05-02T14:37:09.000Z" - * } - * "transport_certificates_list" : [ - * { - * "issuer_dn" : "CN=Example Com Inc. Signing CA, OU=Example Com Inc. Signing CA, O=Example Com Inc., DC=example, DC=com", - * "subject_dn" : "CN=transport-0.example.com, OU=SSL, O=Test, L=Test, C=DE", - * "san", "[[2, node-0.example.com], [2, localhost], [7, 127.0.0.1], [8, 1.2.3.4.5.5]]", - * "not_before" : "2018-05-05T14:37:09.000Z", - * "not_after" : "2028-05-02T14:37:09.000Z" - * } - * ] - * } - * - * @param request request to be served - * @param client client - * @throws IOException - */ - @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - - return new RestChannelConsumer() { - - @Override - public void accept(RestChannel channel) throws Exception { - XContentBuilder builder = channel.newBuilder(); - BytesRestResponse response = null; - - // Check for Super admin user - final User user = (User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - if(user == null || !adminDns.isAdmin(user)) { - response = new BytesRestResponse(RestStatus.FORBIDDEN, builder); - } else { - try { - // Check if keystore initialised - if (odsks != null) { - builder.startObject(); - builder.field("http_certificates_list", generateCertDetailList(odsks.getHttpCerts())); - builder.field("transport_certificates_list", generateCertDetailList(odsks.getTransportCerts())); - builder.endObject(); - response = new BytesRestResponse(RestStatus.OK, builder); - } else { - builder.startObject(); - builder.field("message", "keystore is not initialized"); - builder.endObject(); - response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); - } - } catch (final Exception e1) { - log.error("Error handle request ", e1); - builder = channel.newBuilder(); - builder.startObject(); - builder.field("error", e1.toString()); - builder.endObject(); - response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); - } finally { - if (builder != null) { - builder.close(); - } - } - } - channel.sendResponse(response); - } - - /** - * Helper that construct list of certificate details. - * @param certs list of certificates. - * @return Array containing certificate details. - */ - private List> generateCertDetailList(final X509Certificate[] certs) { - if (certs == null) { - return null; - } - return Arrays.stream(certs) - .map(cert -> { - final String issuerDn = cert != null && cert.getIssuerX500Principal() != null ? cert.getIssuerX500Principal().getName(): ""; - final String subjectDn = cert != null && cert.getSubjectX500Principal() != null ? cert.getSubjectX500Principal().getName(): ""; - - final String san = odsks.getSubjectAlternativeNames(cert); - - final String notBefore = cert != null && cert.getNotBefore() != null ? cert.getNotBefore().toInstant().toString(): ""; - final String notAfter = cert != null && cert.getNotAfter() != null ? cert.getNotAfter().toInstant().toString(): ""; - return ImmutableMap.builder() - .put("issuer_dn", issuerDn) - .put("subject_dn", subjectDn) - .put("san", san) - .put("not_before", notBefore) - .put("not_after", notAfter) - .build(); - }) - .collect(Collectors.toList()); - } - }; - } - - @Override - public String getName() { - return "SSL Certificate Information Action"; - } -} diff --git a/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLReloadCertsAction.java b/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLReloadCertsAction.java deleted file mode 100644 index ef4c89c625..0000000000 --- a/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLReloadCertsAction.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.ssl.rest; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; - -import org.opensearch.client.node.NodeClient; -import org.opensearch.common.settings.Settings; -import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.BytesRestResponse; -import org.opensearch.rest.RestChannel; -import org.opensearch.rest.RestController; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.RestStatus; -import org.opensearch.security.configuration.AdminDNs; -import org.opensearch.security.ssl.SecurityKeyStore; -import org.opensearch.security.support.ConfigConstants; -import org.opensearch.security.user.User; -import org.opensearch.threadpool.ThreadPool; - -import static org.opensearch.rest.RestRequest.Method.PUT; - - -/** - * Rest API action to reload SSL certificates. - * Can be used to reload SSL certificates that are about to expire without restarting OpenSearch node. - * This API assumes that new certificates are in the same location specified by the security configurations in opensearch.yml - * (https://docs-beta.opensearch.org/docs/security-configuration/tls/) - * To keep sensitive certificate reload secure, this API will only allow hot reload - * with certificates issued by the same Issuer and Subject DN and SAN with expiry dates after the current one. - * Currently this action serves PUT request for /_opendistro/_security/ssl/http/reloadcerts or /_opendistro/_security/ssl/transport/reloadcerts endpoint - */ -public class SecuritySSLReloadCertsAction extends BaseRestHandler { - private static final List routes = Collections.singletonList( - new Route(PUT, "_opendistro/_security/api/ssl/{certType}/reloadcerts/") - ); - - private final Settings settings; - private final SecurityKeyStore sks; - private final ThreadContext threadContext; - private final AdminDNs adminDns; - - public SecuritySSLReloadCertsAction(final Settings settings, - final RestController restController, - final SecurityKeyStore sks, - final ThreadPool threadPool, - final AdminDNs adminDns) { - super(); - this.settings = settings; - this.sks = sks; - this.adminDns = adminDns; - this.threadContext = threadPool.getThreadContext(); - } - - @Override - public List routes() { - return routes; - } - - /** - * PUT request to reload SSL Certificates. - * - * Sample request: - * PUT _opendistro/_security/api/ssl/transport/reloadcerts - * PUT _opendistro/_security/api/ssl/http/reloadcerts - * - * NOTE: No request body is required. We will assume new certificates are loaded in the paths specified in your opensearch.yml file - * (https://docs-beta.opensearch.org/docs/security/configuration/tls/) - * - * Sample response: - * { "message": "updated http certs" } - * - * @param request request to be served - * @param client client - * @throws IOException - */ - @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - return new RestChannelConsumer() { - - final String certType = request.param("certType").toLowerCase().trim(); - - @Override - public void accept(RestChannel channel) throws Exception { - XContentBuilder builder = channel.newBuilder(); - BytesRestResponse response = null; - - // Check for Super admin user - final User user = (User) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - if(user ==null||!adminDns.isAdmin(user)) { - response = new BytesRestResponse(RestStatus.FORBIDDEN, ""); - } else { - try { - builder.startObject(); - if (sks != null) { - switch (certType) { - case "http": - sks.initHttpSSLConfig(); - builder.field("message", "updated http certs"); - builder.endObject(); - response = new BytesRestResponse(RestStatus.OK, builder); - break; - case "transport": - sks.initTransportSSLConfig(); - builder.field("message", "updated transport certs"); - builder.endObject(); - response = new BytesRestResponse(RestStatus.OK, builder); - break; - default: - builder.field("message", "invalid uri path, please use /_opendistro/_security/api/ssl/http/reload or " + - "/_opendistro/_security/api/ssl/transport/reload"); - builder.endObject(); - response = new BytesRestResponse(RestStatus.FORBIDDEN, builder); - break; - } - } else { - builder.field("message", "keystore is not initialized"); - builder.endObject(); - response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); - } - } catch (final Exception e1) { - builder = channel.newBuilder(); - builder.startObject(); - builder.field("error", e1.toString()); - builder.endObject(); - response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); - } finally { - if (builder != null) { - builder.close(); - } - } - } - channel.sendResponse(response); - } - }; - } - - @Override - public String getName() { - return "SSL Cert Reload Action"; - } -} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java index 6e775bbc62..a1cb20d99c 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java @@ -16,10 +16,14 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.stream.Stream; import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; @@ -90,18 +94,7 @@ protected final void setupWithRestRoles(Settings nodeOverride) throws Exception .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("restapi/truststore.jks")); - builder.put("plugins.security.restapi.roles_enabled.0", "opendistro_security_role_klingons"); - builder.put("plugins.security.restapi.roles_enabled.1", "opendistro_security_role_vulcans"); - builder.put("plugins.security.restapi.roles_enabled.2", "opendistro_security_test"); - - builder.put("plugins.security.restapi.endpoints_disabled.global.CACHE.0", "*"); - - builder.put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.conFiGuration.0", "*"); - builder.put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.wRongType.0", "WRONGType"); - builder.put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.ROLESMAPPING.0", "PUT"); - builder.put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.ROLESMAPPING.1", "DELETE"); - - builder.put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_vulcans.CONFIG.0", "*"); + builder.put(rolesSettings()); if (null != nodeOverride) { builder.put(nodeOverride); @@ -114,6 +107,20 @@ protected final void setupWithRestRoles(Settings nodeOverride) throws Exception AuditTestUtils.updateAuditConfig(rh, nodeOverride != null ? nodeOverride : Settings.EMPTY); } + protected Settings rolesSettings() { + return Settings.builder() + .put("plugins.security.restapi.roles_enabled.0", "opendistro_security_role_klingons") + .put("plugins.security.restapi.roles_enabled.1", "opendistro_security_role_vulcans") + .put("plugins.security.restapi.roles_enabled.2", "opendistro_security_test") + .put("plugins.security.restapi.endpoints_disabled.global.CACHE.0", "*") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.conFiGuration.0", "*") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.wRongType.0", "WRONGType") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.ROLESMAPPING.0", "PUT") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.ROLESMAPPING.1", "DELETE") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_vulcans.CONFIG.0", "*") + .build(); + } + protected void deleteUser(String username) throws Exception { boolean sendAdminCertificate = rh.sendAdminCertificate; rh.sendAdminCertificate = true; @@ -197,30 +204,23 @@ protected void checkGeneralAccess(int status, String username, String password) protected String checkReadAccess(int status, String username, String password, String indexName, String actionType, int id) throws Exception { - boolean sendAdminCertificate = rh.sendAdminCertificate; rh.sendAdminCertificate = false; String action = indexName + "/" + actionType + "/" + id; - HttpResponse response = rh.executeGetRequest(action, - encodeBasicHeader(username, password)); + HttpResponse response = rh.executeGetRequest(action, encodeBasicHeader(username, password)); int returnedStatus = response.getStatusCode(); Assert.assertEquals(status, returnedStatus); - rh.sendAdminCertificate = sendAdminCertificate; return response.getBody(); } protected String checkWriteAccess(int status, String username, String password, String indexName, String actionType, int id) throws Exception { - - boolean sendAdminCertificate = rh.sendAdminCertificate; rh.sendAdminCertificate = false; String action = indexName + "/" + actionType + "/" + id; String payload = "{\"value\" : \"true\"}"; - HttpResponse response = rh.executePutRequest(action, payload, - encodeBasicHeader(username, password)); + HttpResponse response = rh.executePutRequest(action, payload, encodeBasicHeader(username, password)); int returnedStatus = response.getStatusCode(); Assert.assertEquals(status, returnedStatus); - rh.sendAdminCertificate = sendAdminCertificate; return response.getBody(); } @@ -260,4 +260,27 @@ protected Map jsonStringToMap(String json) throws JsonParseExcep protected static Collection> asCollection(Class... plugins) { return Arrays.asList(plugins); } + + String createRestAdminPermissionsPayload(String... additionPerms) throws JsonProcessingException { + final ObjectNode rootNode = (ObjectNode) DefaultObjectMapper.objectMapper.createObjectNode(); + rootNode.set("cluster_permissions", clusterPermissionsForRestAdmin(additionPerms)); + return DefaultObjectMapper.objectMapper.writeValueAsString(rootNode); + } + + ArrayNode clusterPermissionsForRestAdmin(String... additionPerms) { + final ArrayNode permissionsArray = (ArrayNode) DefaultObjectMapper.objectMapper.createArrayNode(); + for (final Map.Entry entry : RestApiAdminPrivilegesEvaluator.ENDPOINTS_WITH_PERMISSIONS.entrySet()) { + if (entry.getKey() == Endpoint.SSL) { + permissionsArray + .add(entry.getValue().build("certs")) + .add(entry.getValue().build("reloadcerts")); + } else { + permissionsArray.add(entry.getValue().build()); + } + } + if (additionPerms.length != 0) { + Stream.of(additionPerms).forEach(permissionsArray::add); + } + return permissionsArray; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java index 6323746a7f..a0d1ece77a 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java @@ -13,6 +13,9 @@ import java.util.List; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; @@ -20,6 +23,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; @@ -44,9 +48,27 @@ public void testActionGroupsApi() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; + // create index + setupStarfleetIndex(); + + // add user picard, role starfleet, maps to opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + rh.sendAdminCertificate = true; + verifyGetForSuperAdmin(new Header[0]); + rh.sendAdminCertificate = true; + verifyDeleteForSuperAdmin(new Header[0], true); + rh.sendAdminCertificate = true; + verifyPutForSuperAdmin(new Header[0], true); + rh.sendAdminCertificate = true; + verifyPatchForSuperAdmin(new Header[0], true); + } + + void verifyGetForSuperAdmin(final Header[] header) throws Exception { // --- GET_UT // GET_UT, actiongroup exists - HttpResponse response = rh.executeGetRequest(ENDPOINT+"/CRUD_UT", new Header[0]); + HttpResponse response = rh.executeGetRequest(ENDPOINT+"/CRUD_UT", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); List permissions = settings.getAsList("CRUD_UT.allowed_actions"); @@ -56,61 +78,50 @@ public void testActionGroupsApi() throws Exception { Assert.assertTrue(permissions.contains("OPENDISTRO_SECURITY_WRITE")); // GET_UT, actiongroup does not exist - response = rh.executeGetRequest(ENDPOINT+"/nothinghthere", new Header[0]); + response = rh.executeGetRequest(ENDPOINT+"/nothinghthere", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // GET_UT, old endpoint - response = rh.executeGetRequest(ENDPOINT, new Header[0]); + response = rh.executeGetRequest(ENDPOINT, header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET_UT, old endpoint - response = rh.executeGetRequest(ENDPOINT, new Header[0]); + response = rh.executeGetRequest(ENDPOINT, header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET_UT, new endpoint which replaces configuration endpoint - response = rh.executeGetRequest(ENDPOINT, new Header[0]); + response = rh.executeGetRequest(ENDPOINT, header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET_UT, old endpoint - response = rh.executeGetRequest(ENDPOINT, new Header[0]); + response = rh.executeGetRequest(ENDPOINT, header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET_UT, new endpoint which replaces configuration endpoint - response = rh.executeGetRequest(ENDPOINT, new Header[0]); + response = rh.executeGetRequest(ENDPOINT, header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET_UT, new endpoint which replaces configuration endpoint - response = rh.executeGetRequest(ENDPOINT, new Header[0]); + response = rh.executeGetRequest(ENDPOINT, header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + } - // create index - setupStarfleetIndex(); - - // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - // TODO: only one doctype allowed for ES6 - // checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "public", 0); - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); - // TODO: only one doctype allowed for ES6 - //checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "public", 0); - + void verifyDeleteForSuperAdmin(final Header[] header, final boolean userAdminCert) throws Exception { // -- DELETE // Non-existing role - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = userAdminCert; - response = rh.executeDeleteRequest(ENDPOINT+"/idonotexist", new Header[0]); + HttpResponse response = rh.executeDeleteRequest(ENDPOINT+"/idonotexist", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // remove action group READ_UT, read access not possible since // opendistro_security_role_starfleet // uses this action group. - response = rh.executeDeleteRequest(ENDPOINT+"/READ_UT", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT+"/READ_UT", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); rh.sendAdminCertificate = false; checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); - // put picard in captains role. Role opendistro_security_role_captains uses the CRUD_UT // action group // which uses READ_UT and WRITE action groups. We removed READ_UT, so only @@ -125,24 +136,25 @@ public void testActionGroupsApi() throws Exception { rh.sendAdminCertificate = false; checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + } + void verifyPutForSuperAdmin(final Header[] header, final boolean userAdminCert) throws Exception { // -- PUT - // put with empty payload, must fail - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/SOMEGROUP", "", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + HttpResponse response = rh.executePutRequest(ENDPOINT+"/SOMEGROUP", "", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason")); // put new configuration with invalid payload, must fail response = rh.executePutRequest(ENDPOINT+"/SOMEGROUP", FileHelper.loadFile("restapi/actiongroup_not_parseable.json"), - new Header[0]); + header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason")); - response = rh.executePutRequest(ENDPOINT+"/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_crud.json"), new Header[0]); + response = rh.executePutRequest(ENDPOINT+"/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_crud.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; @@ -152,8 +164,8 @@ public void testActionGroupsApi() throws Exception { checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); // restore READ_UT action groups - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/READ_UT", FileHelper.loadFile("restapi/actiongroup_read.json"), new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePutRequest(ENDPOINT+"/READ_UT", FileHelper.loadFile("restapi/actiongroup_read.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; @@ -162,99 +174,106 @@ public void testActionGroupsApi() throws Exception { checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); // -- PUT, new JSON format including readonly flag, disallowed in REST API - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_readonly.json"), new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePutRequest(ENDPOINT+"/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_readonly.json"), header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // -- DELETE read only resource, must be forbidden // superAdmin can delete read only resource - rh.sendAdminCertificate = true; - response = rh.executeDeleteRequest(ENDPOINT+"/GET_UT", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executeDeleteRequest(ENDPOINT+"/GET_UT", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // -- PUT read only resource, must be forbidden // superAdmin can add/update read only resource - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/GET_UT", FileHelper.loadFile("restapi/actiongroup_read.json"), new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePutRequest(ENDPOINT+"/GET_UT", FileHelper.loadFile("restapi/actiongroup_read.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); Assert.assertFalse(response.getBody().contains("Resource 'GET_UT' is read-only.")); // PUT with role name - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/kibana_user", FileHelper.loadFile("restapi/actiongroup_read.json"), new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePutRequest(ENDPOINT+"/kibana_user", FileHelper.loadFile("restapi/actiongroup_read.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("kibana_user is an existing role. A action group cannot be named with an existing role name.")); // PUT with self-referencing action groups - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/reference_itself", "{\"allowed_actions\": [\"reference_itself\"]}", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePutRequest(ENDPOINT+"/reference_itself", "{\"allowed_actions\": [\"reference_itself\"]}", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("reference_itself cannot be an allowed_action of itself")); // -- GET_UT hidden resource, must be 404 but super admin can find it - rh.sendAdminCertificate = true; - response = rh.executeGetRequest(ENDPOINT+"/INTERNAL", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executeGetRequest(ENDPOINT+"/INTERNAL", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"hidden\":true")); // -- DELETE hidden resource, must be 404 - rh.sendAdminCertificate = true; - response = rh.executeDeleteRequest(ENDPOINT+"/INTERNAL", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executeDeleteRequest(ENDPOINT+"/INTERNAL", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("'INTERNAL' deleted.")); // -- PUT hidden resource, must be forbidden - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/INTERNAL", FileHelper.loadFile("restapi/actiongroup_read.json"), new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePutRequest(ENDPOINT+"/INTERNAL", FileHelper.loadFile("restapi/actiongroup_read.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + } + void verifyPatchForSuperAdmin(final Header[] header, final boolean userAdminCert) throws Exception { // -- PATCH // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + HttpResponse response = rh.executePatchRequest(ENDPOINT+"/imnothere", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // PATCH read only resource, must be forbidden // SuperAdmin can patch read only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/GET_UT", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT+"/GET_UT", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH with self-referencing action groups - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/GET_UT", "[{ \"op\": \"add\", \"path\": \"/allowed_actions/-\", \"value\": \"GET_UT\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT+"/GET_UT", "[{ \"op\": \"add\", \"path\": \"/allowed_actions/-\", \"value\": \"GET_UT\" }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("GET_UT cannot be an allowed_action of itself")); // bulk PATCH with self-referencing action groups - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/BULKNEW1\", \"value\": {\"allowed_actions\": [\"BULKNEW1\"] } }," + "{ \"op\": \"add\", \"path\": \"/BULKNEW2\", \"value\": {\"allowed_actions\": [\"READ_UT\"] } }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/BULKNEW1\", \"value\": {\"allowed_actions\": [\"BULKNEW1\"] } }," + + "{ \"op\": \"add\", \"path\": \"/BULKNEW2\", \"value\": {\"allowed_actions\": [\"READ_UT\"] } }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("BULKNEW1 cannot be an allowed_action of itself")); // PATCH hidden resource, must be not found, can be found by superadmin, but fails with no path exist error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/INTERNAL", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT+"/INTERNAL", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody(), response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH with relative JSON pointer, must fail - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"1/INTERNAL/allowed_actions/-\", \"value\": \"OPENDISTRO_SECURITY_DELETE\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"1/INTERNAL/allowed_actions/-\", " + + "\"value\": \"OPENDISTRO_SECURITY_DELETE\" }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH new format - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"/allowed_actions/-\", \"value\": \"OPENDISTRO_SECURITY_DELETE\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"/allowed_actions/-\", " + + "\"value\": \"OPENDISTRO_SECURITY_DELETE\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT+"/CRUD_UT", new Header[0]); + response = rh.executeGetRequest(ENDPOINT+"/CRUD_UT", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - permissions = settings.getAsList("CRUD_UT.allowed_actions"); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + List permissions = settings.getAsList("CRUD_UT.allowed_actions"); Assert.assertNotNull(permissions); Assert.assertEquals(3, permissions.size()); Assert.assertTrue(permissions.contains("READ_UT")); @@ -265,49 +284,50 @@ public void testActionGroupsApi() throws Exception { // -- PATCH on whole config resource // PATCH read only resource, must be forbidden // SuperAdmin can patch read only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/GET_UT/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/GET_UT/a\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/GET_UT/description\", \"value\": \"foo\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/GET_UT/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be bad request - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/INTERNAL/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/INTERNAL/a\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH delete read only resource, must be forbidden // SuperAdmin can delete read only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/GET_UT\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/GET_UT\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH delete hidden resource, must be bad request - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/INTERNAL\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/INTERNAL\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"message\":\"Resource updated.")); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/CRUD_UT/hidden\", \"value\": true }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/CRUD_UT/hidden\", \"value\": true }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // add new resource with hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/NEWNEWNEW\", \"value\": {\"allowed_actions\": [\"indices:data/write*\"], \"hidden\":true }}]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, + "[{ \"op\": \"add\", \"path\": \"/NEWNEWNEW\", \"value\": {\"allowed_actions\": [\"indices:data/write*\"], \"hidden\":true }}]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // add new valid resources - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/BULKNEW1\", \"value\": {\"allowed_actions\": [\"indices:data/*\", \"cluster:monitor/*\"] } }," + "{ \"op\": \"add\", \"path\": \"/BULKNEW2\", \"value\": {\"allowed_actions\": [\"READ_UT\"] } }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/BULKNEW1\", \"value\": {\"allowed_actions\": [\"indices:data/*\", \"cluster:monitor/*\"] } }," + "{ \"op\": \"add\", \"path\": \"/BULKNEW2\", \"value\": {\"allowed_actions\": [\"READ_UT\"] } }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT+"/BULKNEW1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT+"/BULKNEW1", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); permissions = settings.getAsList("BULKNEW1.allowed_actions"); @@ -316,7 +336,7 @@ public void testActionGroupsApi() throws Exception { Assert.assertTrue(permissions.contains("indices:data/*")); Assert.assertTrue(permissions.contains("cluster:monitor/*")); - response = rh.executeGetRequest(ENDPOINT+"/BULKNEW2", new Header[0]); + response = rh.executeGetRequest(ENDPOINT+"/BULKNEW2", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); permissions = settings.getAsList("BULKNEW2.allowed_actions"); @@ -325,13 +345,13 @@ public void testActionGroupsApi() throws Exception { Assert.assertTrue(permissions.contains("READ_UT")); // delete resource - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/BULKNEW1\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/BULKNEW1\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT+"/BULKNEW1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT+"/BULKNEW1", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // assert other resource is still there - response = rh.executeGetRequest(ENDPOINT+"/BULKNEW2", new Header[0]); + response = rh.executeGetRequest(ENDPOINT+"/BULKNEW2", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); permissions = settings.getAsList("BULKNEW2.allowed_actions"); @@ -340,6 +360,85 @@ public void testActionGroupsApi() throws Exception { Assert.assertTrue(permissions.contains("READ_UT")); } + @Test + public void testActionGroupsApiForRestAdmin() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + // create index + setupStarfleetIndex(); + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + + // add user picard, role starfleet, maps to opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + verifyGetForSuperAdmin(new Header[] {restApiAdminHeader}); + verifyDeleteForSuperAdmin(new Header[]{restApiAdminHeader}, false); + verifyPutForSuperAdmin(new Header[]{restApiAdminHeader}, false); + verifyPatchForSuperAdmin(new Header[]{restApiAdminHeader}, false); + } + + @Test + public void testActionGroupsApiForActionGroupsRestApiAdmin() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + // create index + setupStarfleetIndex(); + final Header restApiAdminActionGroupsHeader = encodeBasicHeader("rest_api_admin_actiongroups", "rest_api_admin_actiongroups"); + + // add user picard, role starfleet, maps to opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + verifyGetForSuperAdmin(new Header[] {restApiAdminActionGroupsHeader}); + verifyDeleteForSuperAdmin(new Header[]{restApiAdminActionGroupsHeader}, false); + verifyPutForSuperAdmin(new Header[]{restApiAdminActionGroupsHeader}, false); + verifyPatchForSuperAdmin(new Header[]{restApiAdminActionGroupsHeader}, false); + } + + @Test + public void testCreateActionGroupWithRestAdminPermissionsForbidden() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + final Header restApiAdminActionGroupsHeader = encodeBasicHeader("rest_api_admin_actiongroups", "rest_api_admin_actiongroups"); + final Header restApiHeader = encodeBasicHeader("test", "test"); + + HttpResponse response = rh.executePutRequest(ENDPOINT + "/rest_api_admin_group", restAdminAllowedActions(), + restApiAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePutRequest(ENDPOINT + "/rest_api_admin_group", restAdminAllowedActions(), restApiAdminActionGroupsHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePutRequest(ENDPOINT + "/rest_api_admin_group", restAdminAllowedActions(), restApiHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePatchRequest(ENDPOINT, restAdminPatchBody(), restApiAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePatchRequest(ENDPOINT, restAdminPatchBody(), restApiAdminActionGroupsHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePatchRequest(ENDPOINT, restAdminPatchBody(), restApiHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + String restAdminAllowedActions() throws JsonProcessingException { + final ObjectNode rootNode = DefaultObjectMapper.objectMapper.createObjectNode(); + rootNode.set("allowed_actions", clusterPermissionsForRestAdmin("cluster/*")); + return DefaultObjectMapper.objectMapper.writeValueAsString(rootNode); + } + + String restAdminPatchBody() throws JsonProcessingException { + final ArrayNode rootNode = DefaultObjectMapper.objectMapper.createArrayNode(); + final ObjectNode opAddRootNode = DefaultObjectMapper.objectMapper.createObjectNode(); + final ObjectNode allowedActionsNode = DefaultObjectMapper.objectMapper.createObjectNode(); + allowedActionsNode.set("allowed_actions", clusterPermissionsForRestAdmin("cluster/*")); + opAddRootNode + .put("op", "add") + .put("path", "/rest_api_admin_group") + .set("value", allowedActionsNode); + rootNode.add(opAddRootNode); + return DefaultObjectMapper.objectMapper.writeValueAsString(rootNode); + } + @Test public void testActionGroupsApiForNonSuperAdmin() throws Exception { diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java index 3d9e2dfc66..05739cc4ab 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java @@ -142,7 +142,7 @@ public void testPayloadMandatory() throws Exception { */ @Test public void testAllowlistApi() throws Exception { - setupWithRestRoles(null); + setupWithRestRoles(); // No creds, no admin certificate - UNAUTHORIZED checkGetAndPutAllowlistPermissions(HttpStatus.SC_UNAUTHORIZED, false); @@ -156,6 +156,29 @@ public void testAllowlistApi() throws Exception { checkGetAndPutAllowlistPermissions(HttpStatus.SC_OK, true, nonAdminCredsHeader); } + @Test + public void testAllowlistApiWithPermissions() throws Exception { + setupWithRestRoles(); + + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + final Header restApiAllowlistHeader = encodeBasicHeader("rest_api_admin_allowlist", "rest_api_admin_allowlist"); + final Header restApiUserHeader = encodeBasicHeader("test", "test"); + + checkGetAndPutAllowlistPermissions(HttpStatus.SC_FORBIDDEN, false, restApiUserHeader); + checkGetAndPutAllowlistPermissions(HttpStatus.SC_OK, false, restApiAdminHeader); + } + + @Test + public void testAllowlistApiWithAllowListPermissions() throws Exception { + setupWithRestRoles(); + + final Header restApiAllowlistHeader = encodeBasicHeader("rest_api_admin_allowlist", "rest_api_admin_allowlist"); + final Header restApiUserHeader = encodeBasicHeader("test", "test"); + + checkGetAndPutAllowlistPermissions(HttpStatus.SC_FORBIDDEN, false, restApiUserHeader); + checkGetAndPutAllowlistPermissions(HttpStatus.SC_OK, false, restApiAllowlistHeader); + } + @Test public void testAllowlistAuditComplianceLogging() throws Exception { Settings settings = Settings.builder() diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java index ba46781e7e..03d16ee78d 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java @@ -181,6 +181,85 @@ public void testNodesDnApi() throws Exception { } } + + @Test + public void testNodesDnApiWithPermissions() throws Exception { + Settings settings = Settings.builder().put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) + .build(); + setupWithRestRoles(settings); + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + final Header restApiNodesDnHeader = encodeBasicHeader("rest_api_admin_nodesdn", "rest_api_admin_nodesdn"); + final Header restApiUserHeader = encodeBasicHeader("test", "test"); + //full access admin + { + rh.sendAdminCertificate = false; + response = rh.executeGetRequest( + ENDPOINT + "/nodesdn", restApiAdminHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + response = rh.executePutRequest( + ENDPOINT + "/nodesdn/c1", "{\"nodes_dn\": [\"cn=popeye\"]}", + restApiAdminHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); + + response = rh.executePatchRequest( + ENDPOINT + "/nodesdn/c1", + "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", + restApiAdminHeader + ); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + response = rh.executeDeleteRequest(ENDPOINT + "/nodesdn/c1", restApiAdminHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + } + //NodesDN only + { + rh.sendAdminCertificate = false; + response = rh.executeGetRequest(ENDPOINT + "/nodesdn", restApiNodesDnHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + response = rh.executePutRequest( + ENDPOINT + "/nodesdn/c1", "{\"nodes_dn\": [\"cn=popeye\"]}", + restApiNodesDnHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); + + response = rh.executePatchRequest( + ENDPOINT + "/nodesdn/c1", + "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", + restApiNodesDnHeader + ); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + response = rh.executeDeleteRequest(ENDPOINT + "/nodesdn/c1", restApiNodesDnHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + response = rh.executeGetRequest(ENDPOINT + "/actiongroups", restApiNodesDnHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + } + //rest api user + { + rh.sendAdminCertificate = false; + response = rh.executeGetRequest(ENDPOINT + "/nodesdn", restApiUserHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + + response = rh.executePutRequest( + ENDPOINT + "/nodesdn/c1", "{\"nodes_dn\": [\"cn=popeye\"]}", + restApiUserHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + + response = rh.executePatchRequest( + ENDPOINT + "/nodesdn/c1", + "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", + restApiUserHeader + ); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + + response = rh.executeDeleteRequest(ENDPOINT + "/nodesdn/c1", restApiUserHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + } + + } + @Test public void testNodesDnApiAuditComplianceLogging() throws Exception { Settings settings = Settings.builder().put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java index 01fa5b4baf..529658940a 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java @@ -13,7 +13,10 @@ import java.util.List; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; @@ -30,12 +33,13 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class RolesApiTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public RolesApiTest(){ + public RolesApiTest() { ENDPOINT = getEndpointPrefix() + "/api"; } @@ -67,7 +71,26 @@ public void testAllRolesForSuperAdmin() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - HttpResponse response = rh.executeGetRequest(ENDPOINT + "/roles"); + + checkSuperAdminRoles(new Header[0]); + } + + @Test + public void testAllRolesForRestAdmin() throws Exception { + setupWithRestRoles(); + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + checkSuperAdminRoles(new Header[]{restApiAdminHeader}); + } + + @Test + public void testAllRolesForRolesRestAdmin() throws Exception { + setupWithRestRoles(); + final Header restApiAdminRolesHeader = encodeBasicHeader("rest_api_admin_roles", "rest_api_admin_roles"); + checkSuperAdminRoles(new Header[]{restApiAdminRolesHeader}); + } + + void checkSuperAdminRoles(final Header[] header) { + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/roles", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertFalse(response.getBody().contains("_meta")); @@ -121,103 +144,110 @@ public void testRolesApi() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; + // create index + setupStarfleetIndex(); + + // add user picard, role starfleet, maps to opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + + rh.sendAdminCertificate = true; + verifyGetForSuperAdmin(new Header[0]); + rh.sendAdminCertificate = true; + verifyDeleteForSuperAdmin(new Header[0], true); + rh.sendAdminCertificate = true; + verifyPutForSuperAdmin(new Header[0], true); + rh.sendAdminCertificate = true; + verifyPatchForSuperAdmin(new Header[0], true); + } + + void verifyGetForSuperAdmin(final Header[] header) throws Exception { // check roles exists - HttpResponse response = rh.executeGetRequest(ENDPOINT + "/roles"); + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/roles", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // -- GET - // GET opendistro_security_role_starfleet - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); JsonNode settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(1, settings.size()); // GET, role does not exist - response = rh.executeGetRequest(ENDPOINT + "/roles/nothinghthere", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/nothinghthere", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/roles/", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/roles", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"cluster_permissions\":[\"*\"]")); Assert.assertFalse(response.getBody().contains("\"cluster_permissions\" : [")); - response = rh.executeGetRequest(ENDPOINT + "/roles?pretty", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles?pretty", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertFalse(response.getBody().contains("\"cluster_permissions\":[\"*\"]")); Assert.assertTrue(response.getBody().contains("\"cluster_permissions\" : [")); // Super admin should be able to describe hidden role - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_hidden", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_hidden", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"hidden\":true")); + } - // create index - setupStarfleetIndex(); - - // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[] { "starfleet", "captains" }, HttpStatus.SC_CREATED); - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - - + void verifyDeleteForSuperAdmin(final Header[] header, final boolean sendAdminCert) throws Exception { // -- DELETE - - rh.sendAdminCertificate = true; - // Non-existing role - response = rh.executeDeleteRequest(ENDPOINT + "/roles/idonotexist", new Header[0]); + HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/roles/idonotexist", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // read only role, SuperAdmin can delete the read-only role - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_transport_client", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_transport_client", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // hidden role allowed for superadmin - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_internal", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("'opendistro_security_internal' deleted.")); // remove complete role mapping for opendistro_security_role_starfleet_captains - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - rh.sendAdminCertificate = false; - // user has only role starfleet left, role has READ access only checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); - // ES7 only supports one doc type, but OpenSearch permission checks run first // So we also get a 403 FORBIDDEN when tring to add new document type checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; // remove also starfleet role, nothing is allowed anymore - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + } + void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) throws Exception { // -- PUT // put with empty roles, must fail - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", "", new Header[0]); + HttpResponse response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", "", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - settings = DefaultObjectMapper.readTree(response.getBody()); + JsonNode settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason").asText()); // put new configuration with invalid payload, must fail response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", - FileHelper.loadFile("restapi/roles_not_parseable.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_not_parseable.json"), header); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason").asText()); // put new configuration with invalid keys, must fail response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", - FileHelper.loadFile("restapi/roles_invalid_keys.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_invalid_keys.json"), header); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage(), settings.get("reason").asText()); @@ -227,7 +257,7 @@ public void testRolesApi() throws Exception { // put new configuration with wrong datatypes, must fail response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", - FileHelper.loadFile("restapi/roles_wrong_datatype.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_wrong_datatype.json"), header); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason").asText()); @@ -236,17 +266,17 @@ public void testRolesApi() throws Exception { // put read only role, must be forbidden // But SuperAdmin can still create it response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_transport_client", - FileHelper.loadFile("restapi/roles_captains.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // put hidden role, must be forbidden, but allowed for super admin response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_internal", - FileHelper.loadFile("restapi/roles_captains.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // restore starfleet role response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", - FileHelper.loadFile("restapi/roles_starfleet.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_starfleet.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); @@ -258,41 +288,41 @@ public void testRolesApi() throws Exception { // - 'indices:*' checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; // restore captains role response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_complete_invalid.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_complete_invalid.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); -// rh.sendAdminCertificate = true; +// rh.sendAdminCertificate = sendAdminCert; // response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", -// FileHelper.loadFile("restapi/roles_multiple.json"), new Header[0]); +// FileHelper.loadFile("restapi/roles_multiple.json"), header); // Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_multiple_2.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_multiple_2.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // check tenants - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_tenants.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains_tenants.json"), header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(2, settings.size()); Assert.assertEquals(settings.get("status").asText(), "OK"); - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); System.out.println(response.getBody()); settings = DefaultObjectMapper.readTree(response.getBody()); @@ -305,13 +335,13 @@ public void testRolesApi() throws Exception { response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_tenants2.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains_tenants2.json"), header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(2, settings.size()); Assert.assertEquals(settings.get("status").asText(), "OK"); - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(1, settings.size()); @@ -327,13 +357,13 @@ public void testRolesApi() throws Exception { // remove tenants from role response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_no_tenants.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains_no_tenants.json"), header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(2, settings.size()); Assert.assertEquals(settings.get("status").asText(), "OK"); - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(1, settings.size()); @@ -341,32 +371,46 @@ public void testRolesApi() throws Exception { Assert.assertTrue(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(0).isNull()); response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_tenants_malformed.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains_tenants_malformed.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(settings.get("status").asText(), "error"); Assert.assertEquals(settings.get("reason").asText(), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage()); + } + void verifyPatchForSuperAdmin(final Header[] header, final boolean sendAdminCert) throws Exception { // -- PATCH // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + HttpResponse response = rh.executePatchRequest( + ENDPOINT + "/roles/imnothere", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // PATCH read only resource, must be forbidden // SuperAdmin can patch it - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_transport_client", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles/opendistro_security_transport_client", + "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", + header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be not found, can be found for superadmin, but will fail with no path present exception - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles/opendistro_security_internal", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet", + "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody(), response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); @@ -389,122 +433,328 @@ public void testRolesApi() throws Exception { // -- PATCH on whole config resource // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"add\", \"path\": \"/imnothere/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/imnothere/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH read only resource, must be forbidden - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_transport_client/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_transport_client/a\", \"value\": [ \"foo\", \"bar\" ] }]", + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH hidden resource, must be bad request - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH delete read only resource, must be forbidden // SuperAdmin can delete read only user - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"remove\", \"path\": \"/opendistro_security_transport_client\" }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", "[{ \"op\": \"remove\", \"path\": \"/opendistro_security_transport_client\" }]", + header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be bad request, but allowed for superadmin - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"remove\", \"path\": \"/opendistro_security_internal\"}]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", + "[{ \"op\": \"remove\", \"path\": \"/opendistro_security_internal\"}]", + header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"message\":\"Resource updated.")); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"add\", \"path\": \"/newnewnew\", \"value\": { \"hidden\": true, \"index_permissions\" : [ {\"index_patterns\" : [ \"sf\" ],\"allowed_actions\" : [ \"OPENDISTRO_SECURITY_READ\" ]}] }}]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/newnewnew\", \"value\": { \"hidden\": true, \"index_permissions\" : " + + "[ {\"index_patterns\" : [ \"sf\" ],\"allowed_actions\" : [ \"OPENDISTRO_SECURITY_READ\" ]}] }}]", + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"index_permissions\" : [ {\"index_patterns\" : [ \"sf\" ],\"allowed_actions\" : [ \"OPENDISTRO_SECURITY_READ\" ]}] }}]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"index_permissions\" : " + + "[ {\"index_patterns\" : [ \"sf\" ],\"allowed_actions\" : [ \"OPENDISTRO_SECURITY_READ\" ]}] }}]", + header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/roles/bulknew1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/bulknew1", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = DefaultObjectMapper.readTree(response.getBody()); - permissions = new SecurityJsonNode(settings).get("bulknew1").get("index_permissions").get(0).get("allowed_actions").asList(); + JsonNode settings = DefaultObjectMapper.readTree(response.getBody()); + permissions = new SecurityJsonNode(settings).get("bulknew1").get("index_permissions").get(0).get("allowed_actions").asList(); Assert.assertNotNull(permissions); Assert.assertEquals(1, permissions.size()); Assert.assertTrue(permissions.contains("OPENDISTRO_SECURITY_READ")); // delete resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"remove\", \"path\": \"/bulknew1\"}]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"remove\", \"path\": \"/bulknew1\"}]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/roles/bulknew1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/bulknew1", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // put valid field masks response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_field_mask_valid", - FileHelper.loadFile("restapi/roles_field_masks_valid.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_field_masks_valid.json"), header); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); // put invalid field masks response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_field_mask_invalid", - FileHelper.loadFile("restapi/roles_field_masks_invalid.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_field_masks_invalid.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + } + + @Test + public void testRolesApiWithAllRestApiPermissions() throws Exception { + setupWithRestRoles(); + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + + rh.sendAdminCertificate = false; + setupStarfleetIndex(); + + // add user picard, role starfleet, maps to opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + + verifyGetForSuperAdmin(new Header[]{restApiAdminHeader}); + verifyDeleteForSuperAdmin(new Header[]{restApiAdminHeader}, false); + verifyPutForSuperAdmin(new Header[]{restApiAdminHeader}, false); + verifyPatchForSuperAdmin(new Header[]{restApiAdminHeader}, false); } @Test - public void testRolesApiForNonSuperAdmin() throws Exception { + public void testRolesApiWithRestApiRolePermission() throws Exception { + setupWithRestRoles(); + + final Header restApiRolesHeader = encodeBasicHeader("rest_api_admin_roles", "rest_api_admin_roles"); + + rh.sendAdminCertificate = false; + setupStarfleetIndex(); + + // add user picard, role starfleet, maps to opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + + verifyGetForSuperAdmin(new Header[]{restApiRolesHeader}); + verifyDeleteForSuperAdmin(new Header[]{restApiRolesHeader}, false); + verifyPutForSuperAdmin(new Header[]{restApiRolesHeader}, false); + verifyPatchForSuperAdmin(new Header[]{restApiRolesHeader}, false); + } + + @Test + public void testCreateOrUpdateRestApiAdminRoleForbiddenForNonSuperAdmin() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + final Header adminHeader = encodeBasicHeader("admin", "admin"); + final Header restApiHeader = encodeBasicHeader("test", "test"); + + final String restAdminPermissionsPayload = createRestAdminPermissionsPayload("cluster/*"); + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/roles/new_rest_admin_role", restAdminPermissionsPayload, restApiAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + response = rh.executePutRequest( + ENDPOINT + "/roles/rest_admin_role_to_delete", restAdminPermissionsPayload, restApiAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + // attempt to create a new rest admin role by admin + response = rh.executePutRequest( + ENDPOINT + "/roles/some_rest_admin_role", + restAdminPermissionsPayload, + adminHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // attempt to update exiting admin role + response = rh.executePutRequest( + ENDPOINT + "/roles/new_rest_admin_role", + restAdminPermissionsPayload, + adminHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // attempt to patch exiting admin role + response = rh.executePatchRequest( + ENDPOINT + "/roles/new_rest_admin_role", + createPatchRestAdminPermissionsPayload("replace"), + adminHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // attempt to update exiting admin role + response = rh.executePutRequest( + ENDPOINT + "/roles/new_rest_admin_role", + restAdminPermissionsPayload, + restApiHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // attempt to create a new rest admin role by admin + response = rh.executePutRequest( + ENDPOINT + "/roles/some_rest_admin_role", + restAdminPermissionsPayload, + restApiHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // attempt to patch exiting admin role and crate a new one + response = rh.executePatchRequest( + ENDPOINT + "/roles", + createPatchRestAdminPermissionsPayload("replace"), + restApiHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePatchRequest( + ENDPOINT + "/roles", + createPatchRestAdminPermissionsPayload("add"), + restApiHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePatchRequest( + ENDPOINT + "/roles", + createPatchRestAdminPermissionsPayload("remove"), + restApiHeader); + System.out.println("RESPONSE: " + response.getBody()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + @Test + public void testDeleteRestApiAdminRoleForbiddenForNonSuperAdmin() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + final Header adminHeader = encodeBasicHeader("admin", "admin"); + final Header restApiHeader = encodeBasicHeader("test", "test"); + + final String allRestAdminPermissionsPayload = createRestAdminPermissionsPayload("cluster/*"); + + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/roles/new_rest_admin_role", allRestAdminPermissionsPayload, restApiAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + // attempt to update exiting admin role + response = rh.executeDeleteRequest( + ENDPOINT + "/roles/new_rest_admin_role", + adminHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + //true to change + response = rh.executeDeleteRequest( + ENDPOINT + "/roles/new_rest_admin_role", + allRestAdminPermissionsPayload, + restApiHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + + private String createPatchRestAdminPermissionsPayload(final String op) throws JsonProcessingException { + final ArrayNode rootNode = (ArrayNode) DefaultObjectMapper.objectMapper.createArrayNode(); + final ObjectNode opAddObjectNode = DefaultObjectMapper.objectMapper.createObjectNode(); + final ObjectNode clusterPermissionsNode = DefaultObjectMapper.objectMapper.createObjectNode(); + clusterPermissionsNode.set("cluster_permissions", clusterPermissionsForRestAdmin("cluster/*")); + if ("add".equals(op)) { + opAddObjectNode + .put("op", "add") + .put("path", "/some_rest_admin_role") + .set("value", clusterPermissionsNode); + rootNode.add(opAddObjectNode); + } + + if ("remove".equals(op)) { + final ObjectNode opRemoveObjectNode = DefaultObjectMapper.objectMapper.createObjectNode(); + opRemoveObjectNode + .put("op", "remove") + .put("path", "/rest_admin_role_to_delete"); + rootNode.add(opRemoveObjectNode); + } + + if ("replace".equals(op)) { + final ObjectNode replaceRemoveObjectNode = DefaultObjectMapper.objectMapper.createObjectNode(); + replaceRemoveObjectNode + .put("op", "replace") + .put("path", "/new_rest_admin_role/cluster_permissions") + .set("value", clusterPermissionsForRestAdmin("*")); + + rootNode.add(replaceRemoveObjectNode); + } + return DefaultObjectMapper.objectMapper.writeValueAsString(rootNode); + } + + @Test + public void testRolesApiForNonSuperAdmin() throws Exception { setupWithRestRoles(); rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = false; rh.sendHTTPClientCredentials = true; + checkNonSuperAdminRoles(new Header[0]); + } + void checkNonSuperAdminRoles(final Header[] header) throws Exception { HttpResponse response; // Delete read only roles - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_transport_client" , new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_transport_client", header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Put read only roles - response = rh.executePutRequest( ENDPOINT + "/roles/opendistro_security_transport_client", - FileHelper.loadFile("restapi/roles_captains.json"), new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_transport_client", + FileHelper.loadFile("restapi/roles_captains.json"), header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch single read only roles - response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_transport_client", "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT + "/roles/opendistro_security_transport_client", + "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", + header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch multiple read only roles - response = rh.executePatchRequest(ENDPOINT + "/roles/", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_transport_client/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/roles/", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_transport_client/description\", \"value\": \"foo\" }]", + header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // get hidden role - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_internal"); + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // delete hidden role - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_internal" , new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // put hidden role String body = FileHelper.loadFile("restapi/roles_captains.json"); - response = rh.executePutRequest( ENDPOINT+ "/roles/opendistro_security_internal", body, new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_internal", body, header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch single hidden roles - response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_internal", "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_internal", + "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch multiple hidden roles - response = rh.executePatchRequest(ENDPOINT + "/roles/", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/roles/", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", + header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - } @Test - public void checkNullElementsInArray() throws Exception{ + public void checkNullElementsInArray() throws Exception { setup(); rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; @@ -516,7 +766,7 @@ public void checkNullElementsInArray() throws Exception{ Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); body = FileHelper.loadFile("restapi/roles_null_array_element_index_permissions.json"); - response = rh.executePutRequest(ENDPOINT+ "/roles/opendistro_security_role_starfleet", body, new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", body, new Header[0]); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java index 168f15dc43..f6405e984a 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java @@ -13,6 +13,9 @@ import java.util.List; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; @@ -20,6 +23,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; @@ -44,12 +48,114 @@ public void testRolesMappingApi() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; + // create index + setupStarfleetIndex(); + // add user picard, role captains initially maps to + // opendistro_security_role_starfleet_captains and opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[] { "captains" }, HttpStatus.SC_CREATED); + checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + // TODO: only one doctype allowed for ES6 + //checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + rh.sendAdminCertificate = true; + verifyGetForSuperAdmin(new Header[0]); + rh.sendAdminCertificate = true; + verifyDeleteForSuperAdmin(new Header[0], true); + rh.sendAdminCertificate = true; + verifyPutForSuperAdmin(new Header[0]); + verifyPatchForSuperAdmin(new Header[0]); + // mapping with several backend roles, one of the is captain + deleteAndputNewMapping(new Header[0],"rolesmapping_backendroles_captains_list.json", true); + checkAllSfAllowed(); + + // mapping with one backend role, captain + deleteAndputNewMapping(new Header[0],"rolesmapping_backendroles_captains_single.json", true); + checkAllSfAllowed(); + + // mapping with several users, one is picard + deleteAndputNewMapping(new Header[0],"rolesmapping_users_picard_list.json", true); + checkAllSfAllowed(); + + // just user picard + deleteAndputNewMapping(new Header[0],"rolesmapping_users_picard_single.json", true); + checkAllSfAllowed(); + + // hosts + deleteAndputNewMapping(new Header[0],"rolesmapping_hosts_list.json", true); + checkAllSfAllowed(); + + // hosts + deleteAndputNewMapping(new Header[0],"rolesmapping_hosts_single.json", true); + checkAllSfAllowed(); + + // full settings, access + deleteAndputNewMapping(new Header[0],"rolesmapping_all_access.json", true); + checkAllSfAllowed(); + + // full settings, no access + deleteAndputNewMapping(new Header[0],"rolesmapping_all_noaccess.json", true); + checkAllSfForbidden(); + } + + @Test + public void testRolesMappingApiWithFullPermissions() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + // create index + setupStarfleetIndex(); + // add user picard, role captains initially maps to + // opendistro_security_role_starfleet_captains and opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[] { "captains" }, HttpStatus.SC_CREATED); + checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + // TODO: only one doctype allowed for ES6 + //checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + + verifyGetForSuperAdmin(new Header[]{restApiAdminHeader}); + verifyDeleteForSuperAdmin(new Header[]{restApiAdminHeader}, false); + verifyPutForSuperAdmin(new Header[]{restApiAdminHeader}); + verifyPatchForSuperAdmin(new Header[]{restApiAdminHeader}); + // mapping with several backend roles, one of the is captain + deleteAndputNewMapping(new Header[]{restApiAdminHeader}, "rolesmapping_backendroles_captains_list.json", false); + checkAllSfAllowed(); + + // mapping with one backend role, captain + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_backendroles_captains_single.json", true); + checkAllSfAllowed(); + + // mapping with several users, one is picard + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_users_picard_list.json", true); + checkAllSfAllowed(); + + // just user picard + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_users_picard_single.json", true); + checkAllSfAllowed(); + + // hosts + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_hosts_list.json", true); + checkAllSfAllowed(); + + // hosts + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_hosts_single.json", true); + checkAllSfAllowed(); + + // full settings, access + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_all_access.json", true); + checkAllSfAllowed(); + + // full settings, no access + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_all_noaccess.json", true); + checkAllSfForbidden(); + + } + + void verifyGetForSuperAdmin(final Header[] header) throws Exception { // check rolesmapping exists, old config api - HttpResponse response = rh.executeGetRequest(ENDPOINT + "/rolesmapping"); + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // check rolesmapping exists, new API - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping"); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getContentType(), response.isJsonContentType()); @@ -61,9 +167,8 @@ public void testRolesMappingApi() throws Exception { // -- GET - // GET opendistro_security_role_starfleet, exists - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getContentType(), response.isJsonContentType()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); @@ -73,55 +178,42 @@ public void testRolesMappingApi() throws Exception { Assert.assertEquals("nagilum", settings.getAsList("opendistro_security_role_starfleet.users").get(0)); // GET, rolesmapping does not exist - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/nothinghthere", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/nothinghthere", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // GET, new URL endpoint in security - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getContentType(), response.isJsonContentType()); // GET, new URL endpoint in security - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getContentType(), response.isJsonContentType()); // Super admin should be able to describe particular hidden rolemapping - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"hidden\":true")); + } - // create index - setupStarfleetIndex(); - - // add user picard, role captains initially maps to - // opendistro_security_role_starfleet_captains and opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[] { "captains" }, HttpStatus.SC_CREATED); - checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); - - // TODO: only one doctype allowed for ES6 - //checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); - - // --- DELETE - - rh.sendAdminCertificate = true; - + void verifyDeleteForSuperAdmin(final Header[] header, final boolean useAdminCert) throws Exception { // Non-existing role - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/idonotexist", new Header[0]); + HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/idonotexist", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // read only role // SuperAdmin can delete read only role - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // hidden role - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("'opendistro_security_internal' deleted.")); // remove complete role mapping for opendistro_security_role_starfleet_captains - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/configuration/rolesmapping"); rh.sendAdminCertificate = false; @@ -134,32 +226,30 @@ public void testRolesMappingApi() throws Exception { // checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 1); // remove also opendistro_security_role_starfleet, poor picard has no mapping left - rh.sendAdminCertificate = true; - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", new Header[0]); + rh.sendAdminCertificate = useAdminCert; + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); rh.sendAdminCertificate = false; checkAllSfForbidden(); + } - rh.sendAdminCertificate = true; - - // --- PUT - + void verifyPutForSuperAdmin(final Header[] header) throws Exception { // put with empty mapping, must fail - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", "", new Header[0]); + HttpResponse response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", "", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason")); // put new configuration with invalid payload, must fail response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_not_parseable.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_not_parseable.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason")); // put new configuration with invalid keys, must fail response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_invalid_keys.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_invalid_keys.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage(), settings.get("reason")); @@ -170,7 +260,7 @@ public void testRolesMappingApi() throws Exception { // wrong datatypes response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_backendroles_captains_single_wrong_datatype.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_backendroles_captains_single_wrong_datatype.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -179,7 +269,7 @@ public void testRolesMappingApi() throws Exception { Assert.assertTrue(settings.get("users") == null); response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_hosts_single_wrong_datatype.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_hosts_single_wrong_datatype.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -188,7 +278,7 @@ public void testRolesMappingApi() throws Exception { Assert.assertTrue(settings.get("users") == null); response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_users_picard_single_wrong_datatype.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_users_picard_single_wrong_datatype.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -199,81 +289,83 @@ public void testRolesMappingApi() throws Exception { // Read only role mapping // SuperAdmin can add read only roles - mappings response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // hidden role, allowed for super admin response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + } - // -- PATCH + void verifyPatchForSuperAdmin(final Header[] header) throws Exception { // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + HttpResponse response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // PATCH read only resource, must be forbidden // SuperAdmin can patch read-only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\"] }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", + "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\"] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH hidden resource, must be not found, can be found by super admin - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ " + - "\"foo\", \"bar\" ] }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ " + + "\"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", + "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", "[{ \"op\": \"add\", \"path\": \"/backend_roles/-\", \"value\": \"spring\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", + "[{ \"op\": \"add\", \"path\": \"/backend_roles/-\", \"value\": \"spring\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); List permissions = settings.getAsList("opendistro_security_role_vulcans.backend_roles"); Assert.assertNotNull(permissions); Assert.assertTrue(permissions.contains("spring")); // -- PATCH on whole config resource // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH read only resource, must be forbidden // SuperAdmin can patch read only resource rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be bad request rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_vulcans/hidden\", \"value\": true }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_vulcans/hidden\", \"value\": true }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"backend_roles\":[\"vulcanadmin\"]} }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"backend_roles\":[\"vulcanadmin\"]} }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); permissions = settings.getAsList("bulknew1.backend_roles"); @@ -281,45 +373,10 @@ public void testRolesMappingApi() throws Exception { Assert.assertTrue(permissions.contains("vulcanadmin")); // PATCH delete - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"remove\", \"path\": \"/bulknew1\"}]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"remove\", \"path\": \"/bulknew1\"}]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - - // mapping with several backend roles, one of the is captain - deleteAndputNewMapping("rolesmapping_backendroles_captains_list.json"); - checkAllSfAllowed(); - - // mapping with one backend role, captain - deleteAndputNewMapping("rolesmapping_backendroles_captains_single.json"); - checkAllSfAllowed(); - - // mapping with several users, one is picard - deleteAndputNewMapping("rolesmapping_users_picard_list.json"); - checkAllSfAllowed(); - - // just user picard - deleteAndputNewMapping("rolesmapping_users_picard_single.json"); - checkAllSfAllowed(); - - // hosts - deleteAndputNewMapping("rolesmapping_hosts_list.json"); - checkAllSfAllowed(); - - // hosts - deleteAndputNewMapping("rolesmapping_hosts_single.json"); - checkAllSfAllowed(); - - // full settings, access - deleteAndputNewMapping("rolesmapping_all_access.json"); - checkAllSfAllowed(); - - // full settings, no access - deleteAndputNewMapping("rolesmapping_all_noaccess.json"); - checkAllSfForbidden(); - } private void checkAllSfAllowed() throws Exception { @@ -334,13 +391,13 @@ private void checkAllSfForbidden() throws Exception { checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); } - private HttpResponse deleteAndputNewMapping(String fileName) throws Exception { - rh.sendAdminCertificate = true; + private HttpResponse deleteAndputNewMapping(final Header[] header, final String fileName, final boolean useAdminCert) throws Exception { + rh.sendAdminCertificate = useAdminCert; HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - new Header[0]); + header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/"+fileName), new Header[0]); + FileHelper.loadFile("restapi/"+fileName), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; return response; @@ -355,46 +412,142 @@ public void testRolesMappingApiForNonSuperAdmin() throws Exception { rh.sendAdminCertificate = false; rh.sendHTTPClientCredentials = true; + verifyNonSuperAdminUser(new Header[0]); + } + + @Test + public void testRolesMappingApiForNonSuperAdminRestApiUser() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + final Header restApiHeader = encodeBasicHeader("test", "test"); + verifyNonSuperAdminUser(new Header[] {restApiHeader}); + } + + void verifyNonSuperAdminUser(final Header[] header) throws Exception { HttpResponse response; // Delete read only roles mapping - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library" , new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library" , header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Put read only roles mapping response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch single read only roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch multiple read only roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // GET, rolesmapping is hidden, allowed for super admin - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Delete hidden roles mapping - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal" , new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal" , header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Put hidden roles mapping response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch hidden roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch multiple hidden roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", header); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); } + + @Test + public void testChangeRestApiAdminRoleMappingForbiddenForNonSuperAdmin() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + final Header adminHeader = encodeBasicHeader("admin", "admin"); + final Header restApiHeader = encodeBasicHeader("test", "test"); + + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/roles/new_rest_api_role", + createRestAdminPermissionsPayload(), restApiAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + response = rh.executePutRequest( + ENDPOINT + "/roles/new_rest_api_role_without_mapping", + createRestAdminPermissionsPayload(), restApiAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/new_rest_api_role", + createUsersPayload("a", "b", "c"), restApiAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + verifyRestApiPutAndDeleteForNonRestApiAdmin(adminHeader); + verifyRestApiPutAndDeleteForNonRestApiAdmin(restApiHeader); + verifyRestApiPatchForNonRestApiAdmin(adminHeader, false); + verifyRestApiPatchForNonRestApiAdmin(restApiHeader, false); + verifyRestApiPatchForNonRestApiAdmin(adminHeader, true); + verifyRestApiPatchForNonRestApiAdmin(restApiHeader, true); + } + + private void verifyRestApiPutAndDeleteForNonRestApiAdmin(final Header header) throws Exception { + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/new_rest_api_role", createUsersPayload("a", "b", "c"), header); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executeDeleteRequest( + ENDPOINT + "/rolesmapping/new_rest_api_role", "", header); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + private void verifyRestApiPatchForNonRestApiAdmin(final Header header, boolean bulk) throws Exception { + String path = ENDPOINT + "/rolesmapping"; + if (!bulk) { + path += "/new_rest_api_role"; + } + HttpResponse response = rh.executePatchRequest(path, createPathPayload("add"), header); + System.err.println(response.getBody()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePatchRequest(path, createPathPayload("replace"), header); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePatchRequest(path, createPathPayload("remove"), header); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + private ObjectNode createUsersObjectNode(final String... users) { + final ArrayNode usersArray = DefaultObjectMapper.objectMapper.createArrayNode(); + for (final String user : users) { + usersArray.add(user); + } + return DefaultObjectMapper.objectMapper.createObjectNode().set("users", usersArray); + } + + private String createUsersPayload(final String... users) throws JsonProcessingException { + return DefaultObjectMapper.objectMapper.writeValueAsString(createUsersObjectNode(users)); + } + private String createPathPayload(final String op) throws JsonProcessingException { + final ArrayNode arrayNode = DefaultObjectMapper.objectMapper.createArrayNode(); + final ObjectNode opNode = DefaultObjectMapper.objectMapper.createObjectNode(); + opNode.put("op", op); + if ("add".equals(op)) { + opNode.put("path", "/new_rest_api_role_without_mapping"); + opNode.set("value", createUsersObjectNode("d", "e", "f")); + } + if ("replace".equals(op)) { + opNode.put("path", "/new_rest_api_role"); + opNode.set("value", createUsersObjectNode("g", "h", "i")); + } + if ("remove".equals(op)) { + opNode.put("path", "/new_rest_api_role"); + } + return DefaultObjectMapper.objectMapper.writeValueAsString(arrayNode.add(opNode)); } @Test diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java new file mode 100644 index 0000000000..2df1fdfaf7 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java @@ -0,0 +1,174 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.junit.Assert; +import org.junit.Test; + +import org.opensearch.common.settings.Settings; +import org.opensearch.security.DefaultObjectMapper; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; + +import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; + +public class SslCertsApiTest extends AbstractRestApiUnitTest { + + static final String HTTP_CERTS = "http"; + + static final String TRANSPORT_CERTS = "transport"; + + private final static List> EXPECTED_CERTIFICATES = + ImmutableList.of( + ImmutableMap.of( + "issuer_dn", "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", + "subject_dn", "CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE", + "san", "[[2, node-0.example.com], [2, localhost], [7, 127.0.0.1], [8, 1.2.3.4.5.5]]", + "not_before", "2018-05-05T14:37:09Z", + "not_after", "2028-05-02T14:37:09Z" + ), + ImmutableMap.of( + "issuer_dn", "CN=Example Com Inc. Root CA,OU=Example Com Inc. Root CA,O=Example Com Inc.,DC=example,DC=com", + "subject_dn", "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", + "san", "", + "not_before", "2018-05-05T14:37:08Z", + "not_after", "2028-05-04T14:37:08Z" + ) + ); + + private final static String EXPECTED_CERTIFICATES_BY_TYPE; + static { + try { + EXPECTED_CERTIFICATES_BY_TYPE = DefaultObjectMapper.objectMapper.writeValueAsString( + ImmutableMap.of( + "http_certificates_list", EXPECTED_CERTIFICATES, + "transport_certificates_list", EXPECTED_CERTIFICATES + ) + ); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + private final Header restApiCertsInfoAdminHeader = encodeBasicHeader("rest_api_admin_ssl_info", "rest_api_admin_ssl_info"); + + private final Header restApiReloadCertsAdminHeader = encodeBasicHeader("rest_api_admin_ssl_reloadcerts", "rest_api_admin_ssl_reloadcerts"); + + private final Header restApiHeader = encodeBasicHeader("test", "test"); + + + public String certsInfoEndpoint() { + return PLUGINS_PREFIX + "/api/ssl/certs"; + } + + public String certsReloadEndpoint(final String certType) { + return String.format("%s/api/ssl/%s/reloadcerts", PLUGINS_PREFIX, certType); + } + + @Test + public void testCertsInfo() throws Exception { + setupWithRestRoles(); + final Header adminCredsHeader = encodeBasicHeader("admin", "admin"); + // No creds, no admin certificate - UNAUTHORIZED + rh.sendAdminCertificate = false; + HttpResponse response = rh.executeGetRequest(certsInfoEndpoint()); + Assert.assertEquals(response.getBody(), HttpStatus.SC_UNAUTHORIZED, response.getStatusCode()); + + rh.sendAdminCertificate = false; + response = rh.executeGetRequest(certsInfoEndpoint(), adminCredsHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + sendAdminCert(); + response = rh.executeGetRequest(certsInfoEndpoint()); + Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertEquals(EXPECTED_CERTIFICATES_BY_TYPE, response.getBody()); + + rh.sendAdminCertificate = false; + Assert.assertEquals(EXPECTED_CERTIFICATES_BY_TYPE, loadCerts(restApiAdminHeader)); + Assert.assertEquals(EXPECTED_CERTIFICATES_BY_TYPE, loadCerts(restApiCertsInfoAdminHeader)); + + response = rh.executeGetRequest(certsInfoEndpoint(), restApiHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + private String loadCerts(final Header... header) throws Exception { + HttpResponse response = rh.executeGetRequest(certsInfoEndpoint(), restApiAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); + return response.getBody(); + } + + @Test + public void testReloadCertsNotAvailableByDefault() throws Exception { + setupWithRestRoles(); + + sendAdminCert(); + verifyReloadCertsNotAvailable(); + + rh.sendAdminCertificate = false; + verifyReloadCertsNotAvailable(restApiAdminHeader); + verifyReloadCertsNotAvailable(restApiReloadCertsAdminHeader); + + HttpResponse response = rh.executePutRequest(certsReloadEndpoint(HTTP_CERTS), "{}", restApiHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePutRequest(certsReloadEndpoint(TRANSPORT_CERTS), "{}", restApiHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + private void verifyReloadCertsNotAvailable(final Header... header) { + HttpResponse response = rh.executePutRequest(certsReloadEndpoint(HTTP_CERTS), "{}", header); + Assert.assertEquals(response.getBody(), HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + response = rh.executePutRequest(certsReloadEndpoint(TRANSPORT_CERTS), "{}", header); + Assert.assertEquals(response.getBody(), HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + } + + @Test + public void testReloadCertsWrongCertsType() throws Exception { + setupWithRestRoles(reloadEnabled()); + sendAdminCert(); + HttpResponse response = rh.executePutRequest(certsReloadEndpoint("aaaaa"), "{}"); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + rh.sendAdminCertificate = false; + response = rh.executePutRequest(certsReloadEndpoint("bbbb"), "{}", restApiAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePutRequest(certsReloadEndpoint("cccc"), "{}", restApiReloadCertsAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + } + + @Test + public void testReloadCerts() throws Exception { + setupWithRestRoles(reloadEnabled()); + } + + + private void sendAdminCert() { + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendAdminCertificate = true; + } + + Settings reloadEnabled() { + return Settings.builder() + .put(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, true) + .build(); + } + +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index f100b4ba7f..88eab72766 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -58,7 +58,7 @@ public void testSecurityRoles() throws Exception { .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(56, settings.size()); + Assert.assertEquals(133, settings.size()); response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/newuser\", \"value\": {\"password\": \"newuser\", \"opendistro_security_roles\": [\"opendistro_security_all_access\"] } }]", new Header[0]); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); @@ -104,50 +104,57 @@ public void testUserApi() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - // initial configuration, 6 users - HttpResponse response = rh - .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); + // initial configuration + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(56, settings.size()); - // --- GET + Assert.assertEquals(133, settings.size()); + verifyGet(); + verifyPut(); + verifyPatch(true); + // create index first + setupStarfleetIndex(); + verifyRoles(true); + } + private void verifyGet(final Header... header) throws Exception { + // --- GET // GET, user admin, exists - response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", new Header[0]); + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", header); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(7, settings.size()); // hash must be filtered Assert.assertEquals("", settings.get("admin.hash")); // GET, user does not exist - response = rh.executeGetRequest(ENDPOINT + "/internalusers/nothinghthere", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/nothinghthere", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // GET, new URL endpoint in security - response = rh.executeGetRequest(ENDPOINT + "/user/", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/user/", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET, new URL endpoint in security - response = rh.executeGetRequest(ENDPOINT + "/user", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/user", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + } + private void verifyPut(final Header... header) throws Exception { // -- PUT - // no username given - response = rh.executePutRequest(ENDPOINT + "/internalusers/", "{\"hash\": \"123\"}", new Header[0]); + HttpResponse response = rh.executePutRequest(ENDPOINT + "/internalusers/", "{\"hash\": \"123\"}", header); Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode()); // Faulty JSON payload response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{some: \"thing\" asd other: \"thing\"}", - new Header[0]); + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage()); // Missing quotes in JSON - parseable in 6.x, but wrong config keys - response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{some: \"thing\", other: \"thing\"}", - new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{some: \"thing\", other: \"thing\"}", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); //JK: this should be "Could not parse content of request." because JSON is truly invalid @@ -156,102 +163,105 @@ public void testUserApi() throws Exception { //Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("other")); // Get hidden role - response = rh.executeGetRequest(ENDPOINT + "/internalusers/hide" , new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/hide" , header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"hidden\":true")); // Associating with hidden role is allowed (for superadmin) response = rh.executePutRequest(ENDPOINT + "/internalusers/test", "{ \"opendistro_security_roles\": " + - "[\"opendistro_security_hidden\"]}", new Header[0]); + "[\"opendistro_security_hidden\"]}", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // Associating with reserved role is allowed (for superadmin) response = rh.executePutRequest(ENDPOINT + "/internalusers/test", "{ \"opendistro_security_roles\": [\"opendistro_security_reserved\"], " + - "\"hash\": \"123\"}", - new Header[0]); + "\"hash\": \"123\"}", + header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // Associating with non-existent role is not allowed response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{ \"opendistro_security_roles\": [\"non_existent\"]}", - new Header[0]); + header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(settings.get("message"), "Role 'non_existent' is not available for role-mapping."); // Wrong config keys response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{\"some\": \"thing\", \"other\": \"thing\"}", - new Header[0]); + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage()); Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("some")); Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("other")); + } + private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) throws Exception { // -- PATCH // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + HttpResponse response = rh.executePatchRequest(ENDPOINT + "/internalusers/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // PATCH read only resource, must be forbidden, // but SuperAdmin can PATCH read-only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/sarek", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers/sarek", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be not found, can be found for super admin - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/q", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers/q", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/test", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers/test", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH password - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/test", "[{ \"op\": \"add\", \"path\": \"/password\", \"value\": \"neu\" }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers/test", "[{ \"op\": \"add\", \"path\": \"/password\", \"value\": \"neu\" }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/internalusers/test", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/test", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertFalse(settings.hasValue("test.password")); Assert.assertTrue(settings.hasValue("test.hash")); // -- PATCH on whole config resource // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH read only resource, must be forbidden, // but SuperAdmin can PATCH read only resouce - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/sarek/description\", \"value\": \"foo\" }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/sarek/description\", \"value\": \"foo\" }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); rh.sendAdminCertificate = false; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/sarek/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/sarek/a\", \"value\": [ \"foo\", \"bar\" ] }]"); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusCode()); // PATCH hidden resource, must be bad request - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/q/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/q/a\", \"value\": [ \"foo\", \"bar\" ] }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/test/hidden\", \"value\": true }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/test/hidden\", \"value\": true }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": {\"password\": \"bla\", \"backend_roles\": [\"vulcan\"] } }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers", + "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": {\"password\": \"bla\", \"backend_roles\": [\"vulcan\"] } }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/internalusers/bulknew1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/bulknew1", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertFalse(settings.hasValue("bulknew1.password")); @@ -267,17 +277,17 @@ public void testUserApi() throws Exception { // add/update user, user is read only, forbidden // SuperAdmin can add read only users - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; addUserWithHash("sarek", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_OK); // add/update user, user is hidden, forbidden, allowed for super admin - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; addUserWithHash("q", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_OK); // add users - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; addUserWithHash("nagilum", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); @@ -285,20 +295,20 @@ public void testUserApi() throws Exception { checkGeneralAccess(HttpStatus.SC_OK, "nagilum", "nagilum"); // try remove user, no username - rh.sendAdminCertificate = true; - response = rh.executeDeleteRequest(ENDPOINT + "/internalusers", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executeDeleteRequest(ENDPOINT + "/internalusers", restAdminHeader); Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode()); // try remove user, nonexisting user - response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/picard", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/picard", restAdminHeader); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // try remove readonly user - response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/sarek", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/sarek", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // try remove hidden user, allowed for super admin - response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/q", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/q", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("'q' deleted.")); // now really remove user @@ -309,7 +319,7 @@ public void testUserApi() throws Exception { checkGeneralAccess(HttpStatus.SC_UNAUTHORIZED, "nagilum", "nagilum"); // use password instead of hash - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; addUserWithPassword("nagilum", "correctpassword", HttpStatus.SC_CREATED); rh.sendAdminCertificate = false; @@ -319,7 +329,7 @@ public void testUserApi() throws Exception { deleteUser("nagilum"); // Check unchanged password functionality - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; // new user, password or hash is mandatory addUserWithoutPasswordOrHash("nagilum", new String[]{"starfleet"}, HttpStatus.SC_BAD_REQUEST); @@ -329,35 +339,32 @@ public void testUserApi() throws Exception { // update user, do not specify hash or password, hash must remain the same addUserWithoutPasswordOrHash("nagilum", new String[]{"starfleet"}, HttpStatus.SC_OK); // get user, check hash, must be untouched - response = rh.executeGetRequest(ENDPOINT + "/internalusers/nagilum", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/nagilum", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertTrue(settings.get("nagilum.hash").equals("")); + } - - // ROLES - // create index first - setupStarfleetIndex(); - + private void verifyRoles(final boolean sendAdminCert, Header... header) throws Exception { // wrong datatypes in roles file - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes.json"), new Header[0]); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + rh.sendAdminCertificate = sendAdminCert; + HttpResponse response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes.json"), header); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); rh.sendAdminCertificate = false; - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes.json"), new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); rh.sendAdminCertificate = false; - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes2.json"), new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes2.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -365,8 +372,8 @@ public void testUserApi() throws Exception { Assert.assertTrue(settings.get("backend_roles") == null); rh.sendAdminCertificate = false; - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes3.json"), new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes3.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -395,12 +402,12 @@ public void testUserApi() throws Exception { checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); - rh.sendAdminCertificate = true; - response = rh.executeGetRequest(ENDPOINT + "/internalusers/picard", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executeGetRequest(ENDPOINT + "/internalusers/picard", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals("", settings.get("picard.hash")); - roles = settings.getAsList("picard.backend_roles"); + List roles = settings.getAsList("picard.backend_roles"); Assert.assertNotNull(roles); Assert.assertEquals(2, roles.size()); Assert.assertTrue(roles.contains("starfleet")); @@ -411,10 +418,46 @@ public void testUserApi() throws Exception { // check tabs in json - response = rh.executePutRequest(ENDPOINT + "/internalusers/userwithtabs", "\t{\"hash\": \t \"123\"\t} ", new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/internalusers/userwithtabs", "\t{\"hash\": \t \"123\"\t} ", header); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); } + @Test + public void testUserApiWithRestAdminPermissions() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + // initial configuration + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString(), restApiAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(133, settings.size()); + verifyGet(restApiAdminHeader); + verifyPut(restApiAdminHeader); + verifyPatch(false, restApiAdminHeader); + // create index first + setupStarfleetIndex(); + verifyRoles(false, restApiAdminHeader); + } + + @Test + public void testUserApiWithRestInternalUsersAdminPermissions() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + final Header restApiInternalUsersAdminHeader = encodeBasicHeader("rest_api_admin_internalusers", "rest_api_admin_internalusers"); + // initial configuration + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString(), restApiInternalUsersAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(133, settings.size()); + verifyGet(restApiInternalUsersAdminHeader); + verifyPut(restApiInternalUsersAdminHeader); + verifyPatch(false, restApiInternalUsersAdminHeader); + // create index first + setupStarfleetIndex(); + verifyRoles(false, restApiInternalUsersAdminHeader); + } + @Test public void testPasswordRules() throws Exception { @@ -436,7 +479,7 @@ public void testPasswordRules() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); System.out.println(response.getBody()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(56, settings.size()); + Assert.assertEquals(133, settings.size()); addUserWithPassword("tooshoort", "", HttpStatus.SC_BAD_REQUEST); addUserWithPassword("tooshoort", "123", HttpStatus.SC_BAD_REQUEST); @@ -516,7 +559,7 @@ public void testUserApiWithDots() throws Exception { .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(56, settings.size()); + Assert.assertEquals(133, settings.size()); addUserWithPassword(".my.dotuser0", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySslCertsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySslCertsApiTest.java new file mode 100644 index 0000000000..5d1c3ae538 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySslCertsApiTest.java @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api.legacy; + +import org.opensearch.security.dlic.rest.api.SslCertsApiTest; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacySslCertsApiTest extends SslCertsApiTest { + + @Override + public String certsInfoEndpoint() { + return LEGACY_OPENDISTRO_PREFIX + "/api/ssl/certs"; + } + + @Override + public String certsReloadEndpoint(String certType) { + return String.format("%s/api/ssl/%s/reloadcerts", LEGACY_OPENDISTRO_PREFIX, certType); + } +} diff --git a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java new file mode 100644 index 0000000000..010b453b85 --- /dev/null +++ b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java @@ -0,0 +1,274 @@ +/* + * Copyright 2015-2018 _floragunn_ GmbH + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + */ + +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.securityconf; + +import java.io.IOException; +import java.util.AbstractMap.SimpleEntry; +import java.util.Arrays; +import java.util.Collection; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import org.opensearch.common.settings.Settings; +import org.opensearch.security.DefaultObjectMapper; +import org.opensearch.security.dlic.rest.api.Endpoint; +import org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.PermissionBuilder; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; + +import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.CERTS_INFO_ACTION; +import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.ENDPOINTS_WITH_PERMISSIONS; +import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.RELOAD_CERTS_ACTION; + +public class SecurityRolesPermissionsTest { + + static final Map NO_REST_ADMIN_PERMISSIONS_ROLES = + ImmutableMap.builder() + .put( + "all_access", + role("*")) + .put( + "all_cluster_and_indices", + role("custer:*", "indices:*") + ).build(); + + static final Map REST_ADMIN_PERMISSIONS_FULL_ACCESS_ROLES = + ImmutableMap.builder() + .put( + "security_rest_api_full_access", + role(allRestApiPermissions())) + .put( + "security_rest_api_full_access_with_star", + role("restapi:admin/*")) + .build(); + + + static String restAdminApiRoleName(final String endpoint) { + return String.format("security_rest_api_%s_only", endpoint); + } + + static final Map REST_ADMIN_PERMISSIONS_ROLES = + ENDPOINTS_WITH_PERMISSIONS + .entrySet() + .stream() + .flatMap(e -> { + final String endpoint = e.getKey().name().toLowerCase(Locale.ROOT); + final PermissionBuilder pb = e.getValue(); + if (e.getKey() == Endpoint.SSL) { + return Stream.of( + new SimpleEntry<>( + restAdminApiRoleName(CERTS_INFO_ACTION), + role(pb.build(CERTS_INFO_ACTION)) + ), + new SimpleEntry<>( + restAdminApiRoleName(RELOAD_CERTS_ACTION), + role(pb.build(RELOAD_CERTS_ACTION)) + ) + ); + } else { + return Stream.of( + new SimpleEntry<>(restAdminApiRoleName(endpoint), role(pb.build())) + ); + } + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + static ObjectNode role(final String... clusterPermissions) { + final ArrayNode clusterPermissionsArrayNode = DefaultObjectMapper.objectMapper.createArrayNode(); + Arrays.stream(clusterPermissions).forEach(clusterPermissionsArrayNode::add); + return DefaultObjectMapper.objectMapper + .createObjectNode() + .put("reserved", true) + .set("cluster_permissions", clusterPermissionsArrayNode); + } + + static String[] allRestApiPermissions() { + return ENDPOINTS_WITH_PERMISSIONS + .entrySet() + .stream() + .flatMap(entry -> { + if (entry.getKey() == Endpoint.SSL) { + return Stream.of(entry.getValue().build(CERTS_INFO_ACTION), entry.getValue().build(RELOAD_CERTS_ACTION)); + } else { + return Stream.of(entry.getValue().build()); + } + }).toArray(String[]::new); + } + + final ConfigModel configModel; + + public SecurityRolesPermissionsTest() throws IOException { + this.configModel = + new ConfigModelV7( + createRolesConfig(), + createRoleMappingsConfig(), + createActionGroupsConfig(), + createTenantsConfig(), + Mockito.mock(DynamicConfigModel.class), + Settings.EMPTY + ); + } + + @Test + public void hasNoExplicitClusterPermissionPermissionForRestAdmin() { + for (final String role : NO_REST_ADMIN_PERMISSIONS_ROLES.keySet()) { + final SecurityRoles securityRolesForRole = configModel.getSecurityRoles().filter(ImmutableSet.of(role)); + for (final Map.Entry entry : ENDPOINTS_WITH_PERMISSIONS.entrySet()) { + final Endpoint endpoint = entry.getKey(); + final PermissionBuilder permissionBuilder = entry.getValue(); + if (endpoint == Endpoint.SSL) { + Assert.assertFalse( + endpoint.name(), + securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(CERTS_INFO_ACTION)) + ); + Assert.assertFalse( + endpoint.name(), + securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(RELOAD_CERTS_ACTION)) + ); + } else { + Assert.assertFalse( + endpoint.name(), + securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build()) + ); + } + } + } + } + + @Test + public void hasExplicitClusterPermissionPermissionForRestAdminWitFullAccess() { + for (final String role : REST_ADMIN_PERMISSIONS_FULL_ACCESS_ROLES.keySet()) { + final SecurityRoles securityRolesForRole = configModel.getSecurityRoles().filter(ImmutableSet.of(role)); + for (final Map.Entry entry : ENDPOINTS_WITH_PERMISSIONS.entrySet()) { + final Endpoint endpoint = entry.getKey(); + final PermissionBuilder permissionBuilder = entry.getValue(); + if (endpoint == Endpoint.SSL) { + Assert.assertTrue(endpoint.name() + "/" + CERTS_INFO_ACTION, securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(CERTS_INFO_ACTION))); + Assert.assertTrue(endpoint.name() + "/" + CERTS_INFO_ACTION, securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(RELOAD_CERTS_ACTION))); + } else { + Assert.assertTrue(endpoint.name(), securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build())); + } + } + } + } + + @Test + public void hasExplicitClusterPermissionPermissionForRestAdmin() { + // verify all endpoint except SSL + final Collection noSslEndpoints = + ENDPOINTS_WITH_PERMISSIONS.keySet().stream() + .filter(e -> e != Endpoint.SSL).collect(Collectors.toList()); + for (final Endpoint endpoint : noSslEndpoints) { + final String permission = ENDPOINTS_WITH_PERMISSIONS.get(endpoint).build(); + final SecurityRoles allowOnePermissionRole = + configModel.getSecurityRoles().filter( + ImmutableSet.of(restAdminApiRoleName(endpoint.name().toLowerCase(Locale.ROOT)))); + Assert.assertTrue(endpoint.name(), allowOnePermissionRole.hasExplicitClusterPermissionPermission(permission)); + assertHasNoPermissionsForRestApiAdminOnePermissionRole( + endpoint, + allowOnePermissionRole + ); + } + // verify SSL endpoint with 2 actions + for (final String sslAction : ImmutableSet.of(CERTS_INFO_ACTION, RELOAD_CERTS_ACTION)) { + final SecurityRoles sslAllowRole = + configModel.getSecurityRoles().filter(ImmutableSet.of(restAdminApiRoleName(sslAction))); + final PermissionBuilder permissionBuilder = ENDPOINTS_WITH_PERMISSIONS.get(Endpoint.SSL); + Assert.assertTrue( + Endpoint.SSL + "/" + sslAction, + sslAllowRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(sslAction)) + ); + assertHasNoPermissionsForRestApiAdminOnePermissionRole(Endpoint.SSL, sslAllowRole); + } + } + + void assertHasNoPermissionsForRestApiAdminOnePermissionRole(final Endpoint allowEndpoint, final SecurityRoles allowOnlyRoleForRole) { + final Collection noPermissionEndpoints = + ENDPOINTS_WITH_PERMISSIONS.keySet().stream() + .filter(e -> e != allowEndpoint) + .collect(Collectors.toList()); + for (final Endpoint endpoint : noPermissionEndpoints) { + final PermissionBuilder permissionBuilder = ENDPOINTS_WITH_PERMISSIONS.get(endpoint); + if (endpoint == Endpoint.SSL) { + Assert.assertFalse( + endpoint.name(), + allowOnlyRoleForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(CERTS_INFO_ACTION))); + Assert.assertFalse( + endpoint.name(), + allowOnlyRoleForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(RELOAD_CERTS_ACTION))); + } else { + Assert.assertFalse( + endpoint.name(), + allowOnlyRoleForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build())); + } + } + } + + static ObjectNode meta(final String type) { + return DefaultObjectMapper.objectMapper + .createObjectNode() + .put("type", type) + .put("config_version", 2); + } + + static SecurityDynamicConfiguration createRolesConfig() throws IOException { + final ObjectNode rolesNode = DefaultObjectMapper.objectMapper.createObjectNode(); + rolesNode.set("_meta", meta("roles")); + NO_REST_ADMIN_PERMISSIONS_ROLES.forEach(rolesNode::set); + REST_ADMIN_PERMISSIONS_FULL_ACCESS_ROLES.forEach(rolesNode::set); + REST_ADMIN_PERMISSIONS_ROLES.forEach(rolesNode::set); + return SecurityDynamicConfiguration.fromNode(rolesNode, CType.ROLES, 2, 0, 0); + } + + static SecurityDynamicConfiguration createRoleMappingsConfig() throws IOException { + final ObjectNode metaNode = DefaultObjectMapper.objectMapper.createObjectNode(); + metaNode.set("_meta", meta("rolesmapping")); + return SecurityDynamicConfiguration.fromNode(metaNode, CType.ROLESMAPPING, 2, 0, 0); + } + + static SecurityDynamicConfiguration createActionGroupsConfig() throws IOException { + final ObjectNode metaNode = DefaultObjectMapper.objectMapper.createObjectNode(); + metaNode.set("_meta", meta("actiongroups")); + return SecurityDynamicConfiguration.fromNode(metaNode, CType.ACTIONGROUPS, 2, 0, 0); + } + + static SecurityDynamicConfiguration createTenantsConfig() throws IOException { + final ObjectNode metaNode = DefaultObjectMapper.objectMapper.createObjectNode(); + metaNode.set("_meta", meta("tenants")); + return SecurityDynamicConfiguration.fromNode(metaNode, CType.TENANTS, 2, 0, 0); + } + +} diff --git a/src/test/java/org/opensearch/security/ssl/SecuritySSLCertsInfoActionTests.java b/src/test/java/org/opensearch/security/ssl/SecuritySSLCertsInfoActionTests.java deleted file mode 100644 index c9618e6463..0000000000 --- a/src/test/java/org/opensearch/security/ssl/SecuritySSLCertsInfoActionTests.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.ssl; - -import java.util.List; -import java.util.Map; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import net.minidev.json.JSONObject; -import org.junit.Assert; -import org.junit.Test; - -import org.opensearch.common.settings.Settings; -import org.opensearch.security.ssl.util.SSLConfigConstants; -import org.opensearch.security.support.ConfigConstants; -import org.opensearch.security.test.SingleClusterTest; -import org.opensearch.security.test.helper.file.FileHelper; -import org.opensearch.security.test.helper.rest.RestHelper; - -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; -import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; - -public class SecuritySSLCertsInfoActionTests extends SingleClusterTest { - private final List> NODE_CERT_DETAILS = ImmutableList.of( - ImmutableMap.of( - "issuer_dn", "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", - "subject_dn", "CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE", - "san", "[[2, node-0.example.com], [2, localhost], [7, 127.0.0.1], [8, 1.2.3.4.5.5]]", - "not_before","2018-05-05T14:37:09Z", - "not_after","2028-05-02T14:37:09Z" - )); - - @Test - public void testCertInfo_Legacy_Pass() throws Exception { - certInfo_Pass(LEGACY_OPENDISTRO_PREFIX + "/api/ssl/certs"); - } - - @Test - public void testCertInfo_Pass() throws Exception { - certInfo_Pass(PLUGINS_PREFIX + "/api/ssl/certs"); - } - - public void certInfo_Pass(final String endpoint) throws Exception { - initTestCluster(); - final RestHelper rh = restHelper(); - rh.enableHTTPClientSSL = true; - rh.trustHTTPServerCertificate = true; - rh.sendAdminCertificate = true; - rh.keystore = "kirk-keystore.jks"; - - final RestHelper.HttpResponse transportInfoRestResponse = rh.executeGetRequest(endpoint); - JSONObject expectedJsonResponse = new JSONObject(); - expectedJsonResponse.appendField("http_certificates_list", NODE_CERT_DETAILS); - expectedJsonResponse.appendField("transport_certificates_list", NODE_CERT_DETAILS); - Assert.assertEquals(expectedJsonResponse.toString(), transportInfoRestResponse.getBody()); - } - - @Test - public void testCertInfoFail_Legacy_NonAdmin() throws Exception { - certInfoFail_NonAdmin(LEGACY_OPENDISTRO_PREFIX + "/api/ssl/certs"); - } - - @Test - public void testCertInfoFail_NonAdmin() throws Exception { - certInfoFail_NonAdmin(PLUGINS_PREFIX + "/api/ssl/certs"); - } - - public void certInfoFail_NonAdmin(final String endpoint) throws Exception { - initTestCluster(); - final RestHelper rh = restHelper(); - rh.enableHTTPClientSSL = true; - rh.trustHTTPServerCertificate = true; - rh.sendAdminCertificate = true; - rh.keystore = "spock-keystore.jks"; - - final RestHelper.HttpResponse transportInfoRestResponse = rh.executeGetRequest(endpoint); - Assert.assertEquals(401, transportInfoRestResponse.getStatusCode()); // Forbidden for non-admin - Assert.assertEquals("Unauthorized", transportInfoRestResponse.getStatusReason()); - } - - /** - * Helper method to initialize test cluster for CertInfoAction Tests - */ - private void initTestCluster() throws Exception { - final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper. getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .put(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, true) - .build(); - setup(settings); - } -} diff --git a/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java b/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java index ea78ee043c..f7684a9c51 100644 --- a/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java +++ b/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java @@ -127,32 +127,6 @@ public void testReloadHttpSSLCertsPass() throws Exception { assertReloadCertificateSuccess(rh, "http", getUpdatedCertDetailsExpectedResponse("http")); } - @Test - public void testReloadHttpSSLCerts_FailWrongUri() throws Exception { - initClusterWithTestCerts(); - RestHelper rh = getRestHelperAdminUser(); - - RestHelper.HttpResponse reloadCertsResponse = rh.executePutRequest("_opendistro/_security/api/ssl/wrong/reloadcerts", null); - JSONObject expectedResponse = new JSONObject(); - // Note: toString and toJSONString replace / with \/. This helps get rid of the additional \ character. - expectedResponse.put("message", "invalid uri path, please use /_opendistro/_security/api/ssl/http/reload or /_opendistro/_security/api/ssl/transport/reload"); - final String expectedResponseString = expectedResponse.toString().replace("\\", ""); - Assert.assertEquals(expectedResponseString, reloadCertsResponse.getBody()); - } - - - @Test - public void testSSLReloadFail_UnAuthorizedUser() throws Exception { - initClusterWithTestCerts(); - // Test endpoint for non-admin user - RestHelper rh = getRestHelperNonAdminUser(); - - final RestHelper.HttpResponse reloadCertsResponse = rh.executePutRequest(RELOAD_TRANSPORT_CERTS_ENDPOINT, null); - Assert.assertEquals(401, reloadCertsResponse.getStatusCode()); - Assert.assertEquals("Unauthorized", reloadCertsResponse.getStatusReason()); - } - - @Test public void testSSLReloadFail_InvalidDNAndDate() throws Exception { initClusterWithTestCerts(); @@ -169,24 +143,6 @@ public void testSSLReloadFail_InvalidDNAndDate() throws Exception { Assert.assertEquals(expectedResponse.toString(), reloadCertsResponse.getBody()); } - @Test - public void testSSLReloadFail_NoReloadSet() throws Exception { - updateFiles(defaultCertFilePath, pemCertFilePath); - updateFiles(defaultKeyFilePath, pemKeyFilePath); - // This is when SSLCertReload property is set to false - initTestCluster(pemCertFilePath, pemKeyFilePath, pemCertFilePath, pemKeyFilePath, false); - - RestHelper rh = getRestHelperAdminUser(); - - final RestHelper.HttpResponse reloadCertsResponse = rh.executePutRequest(RELOAD_TRANSPORT_CERTS_ENDPOINT, null); - Assert.assertEquals(400, reloadCertsResponse.getStatusCode()); - JSONObject expectedResponse = new JSONObject(); - expectedResponse.appendField("error", "no handler found for uri [/_opendistro/_security/api/ssl/transport/reloadcerts] and method [PUT]"); - // Note: toString and toJSONString replace / with \/. This helps get rid of the additional \ character. - final String expectedResponseString = expectedResponse.toString().replace("\\", ""); - Assert.assertEquals(expectedResponseString, reloadCertsResponse.getBody()); - } - @Test public void testReloadTransportSSLSameCertsPass() throws Exception { initClusterWithTestCerts(); diff --git a/src/test/resources/restapi/internal_users.yml b/src/test/resources/restapi/internal_users.yml index 0049ab8c86..658d3f3aa1 100644 --- a/src/test/resources/restapi/internal_users.yml +++ b/src/test/resources/restapi/internal_users.yml @@ -61,3 +61,69 @@ admin_all_access: - "vulcan" attributes: {} description: "sample user with all_access, used to test whitelisting" +rest_api_admin_user: + hash: "$2y$12$X5ZamIheHYc2bihGTbK66Oe1.1vJ19akH0OFGF7TvI2BhbbED.KcO" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Full Access admin user" +rest_api_admin_actiongroups: + hash: "$2y$12$XE9zXOgyYBBxnzoWMowIsO1w6o6oIc5w3vgwYjdEE44m9MxFZEuR." + reserved: false + hidden: false + backend_roles: [] + description: "REST API Action groups admin user" +rest_api_admin_allowlist: + hash: "$2y$12$W5AdCO/j08KiDu7EF/1Zf.nkcQM/7s.TtAdN2pRpbDM31xXcIIJUq" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Allow list admin user" +rest_api_admin_audit: + hash: "$2y$12$UEbBqz9S6xuEefbK3LDvge5MwX4V1GvJYzUP8M24ItkfXMXg/NSh6" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Audit admin user" +rest_api_admin_internalusers: + hash: "$2y$12$pUn1a6jdIeR.stkvEqNe5uK3rOY7Dj3uQfE8Cvd2bjNjTQ2HbsBMK" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Internal users admin user" +rest_api_admin_nodesdn: + hash: "$2y$12$xFUIepz0vILRMzMkZMGY1Ow1P1eJo8TJ2oGiaFXaenGrOMsmDnKZS" + reserved: false + hidden: false + backend_roles: [] + description: "REST API NodesDN admin user" +rest_api_admin_roles: + hash: "$2y$12$BR.CBsElNLj8v2dzpHJ7bOKVLwWKWjKDhlEvBIvAe9b6/m0xWy2Bq" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Roles admin user" +rest_api_admin_rolesmapping: + hash: "$2y$12$WQb7PsnRRr04zxjuZsDwU.F7QEr7W0f/rJLjUNLf50hpoJuTqqnaS" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Roles Mapping admin user" +rest_api_admin_ssl_info: + hash: "$2y$12$irI4k0eKE8z9OXEd1jO4eeQfPV8WRMfttzutAhEeRBWy5XNXOlpr." + reserved: false + hidden: false + backend_roles: [] + description: "REST API SSL Certs admin user" +rest_api_admin_ssl_reloadcerts: + hash: "$2y$12$DxNdaBBMvTq5wO5XlnwlTeGSaC7yNoFoJt2N5TVtraopxPnGjMol2" + reserved: false + hidden: false + backend_roles: [] + description: "REST API SSL Reload Certs admin user" +rest_api_admin_tenants: + hash: "$2y$12$q05T7m7DFtkLLj.MVJ6jjuZkAywG4ZwaNi9fiYn6XCJelN2TUXCy2" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Tenats admin user" diff --git a/src/test/resources/restapi/roles.yml b/src/test/resources/restapi/roles.yml index d82382e4f6..6deb194e9b 100644 --- a/src/test/resources/restapi/roles.yml +++ b/src/test/resources/restapi/roles.yml @@ -393,3 +393,51 @@ opendistro_security_role_starfleet_captains: allowed_actions: - "CRUD_UT" tenant_permissions: [] +rest_api_admin_full_access: + reserved: true + cluster_permissions: + - 'restapi:admin/actiongroups' + - 'restapi:admin/allowlist' + - 'restapi:admin/internalusers' + - 'restapi:admin/nodesdn' + - 'restapi:admin/roles' + - 'restapi:admin/rolesmapping' + - 'restapi:admin/ssl/certs/info' + - 'restapi:admin/ssl/certs/reload' + - 'restapi:admin/tenants' +rest_api_admin_actiongroups_only: + reserved: true + cluster_permissions: + - 'restapi:admin/actiongroups' +rest_api_admin_allowlist_only: + reserved: true + cluster_permissions: + - 'restapi:admin/allowlist' +rest_api_admin_internalusers_only: + reserved: true + cluster_permissions: + - 'restapi:admin/internalusers' +rest_api_admin_nodesdn_only: + reserved: true + cluster_permissions: + - 'restapi:admin/nodesdn' +rest_api_admin_roles_only: + reserved: true + cluster_permissions: + - 'restapi:admin/roles' +rest_api_admin_rolesmapping_only: + reserved: true + cluster_permissions: + - 'restapi:admin/rolesmapping' +rest_api_admin_ssl_info_only: + reserved: true + cluster_permissions: + - 'restapi:admin/ssl/certs/info' +rest_api_admin_ssl_reloadcerts_only: + reserved: true + cluster_permissions: + - 'restapi:admin/ssl/certs/reload' +rest_api_admin_tenants_only: + reserved: true + cluster_permissions: + - 'restapi:admin/tenants' diff --git a/src/test/resources/restapi/roles_mapping.yml b/src/test/resources/restapi/roles_mapping.yml index 8c46942854..a87287d5ff 100644 --- a/src/test/resources/restapi/roles_mapping.yml +++ b/src/test/resources/restapi/roles_mapping.yml @@ -185,6 +185,16 @@ opendistro_security_test: hosts: [] users: - "test" + - "rest_api_admin_user" + - "rest_api_admin_nodesdn" + - "rest_api_admin_allowlist" + - "rest_api_admin_roles" + - "rest_api_admin_rolesmapping" + - "rest_api_admin_actiongroups" + - "rest_api_admin_internalusers" + - "rest_api_admin_tenants" + - "rest_api_admin_ssl_info" + - "rest_api_admin_ssl_reloadcerts" and_backend_roles: [] description: "Migrated from v6" opendistro_security_role_starfleet_captains: @@ -206,3 +216,47 @@ opendistro_security_role_host2: - "opendistro_security_host_localhost" and_backend_roles: [] description: "Migrated from v6" +rest_api_admin_full_access: + reserved: false + hidden: true + users: [rest_api_admin_user] +rest_api_admin_actiongroups_only: + reserved: false + hidden: true + users: [rest_api_admin_actiongroups] +rest_api_admin_allowlist_only: + reserved: false + hidden: true + users: [rest_api_admin_allowlist] +rest_api_admin_audit_only: + reserved: false + hidden: true + users: [rest_api_admin_audit] +rest_api_admin_internalusers_only: + reserved: false + hidden: true + users: [rest_api_admin_internalusers] +rest_api_admin_nodesdn_only: + reserved: false + hidden: true + users: [rest_api_admin_nodesdn] +rest_api_admin_roles_only: + reserved: false + hidden: true + users: [rest_api_admin_roles] +rest_api_admin_rolesmapping_only: + reserved: false + hidden: true + users: [rest_api_admin_rolesmapping] +rest_api_admin_ssl_info_only: + reserved: false + hidden: true + users: [rest_api_admin_ssl_info] +rest_api_admin_ssl_reloadcerts_only: + reserved: false + hidden: true + users: [rest_api_admin_ssl_reloadcerts] +rest_api_admin_tenants_only: + reserved: false + hidden: true + users: [rest_api_admin_tenants] diff --git a/src/test/resources/restapi/roles_tenants.yml b/src/test/resources/restapi/roles_tenants.yml index 93b510dd16..e9b724e342 100644 --- a/src/test/resources/restapi/roles_tenants.yml +++ b/src/test/resources/restapi/roles_tenants.yml @@ -2,3 +2,13 @@ _meta: type: "tenants" config_version: 2 +some_admin_tenant: + reserved: false + description: "Demo tenant for admin user" +hidden_tenant: + reserved: true + hidden: true + description: "Hidden tenant" +reserved_tenant: + reserved: true + description: "Reserved tenant" From bae002b0041e43efdcbd56eef24db142d3f6e832 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 14 Feb 2023 09:09:39 -0500 Subject: [PATCH 130/369] Update usage of XContentFactory.xContent (#2433) * Switch from XContentType to MediaType Signed-off-by: Craig Perkins Signed-off-by: Peter Nied Co-authored-by: Peter Nied --- .../java/org/opensearch/security/support/ConfigHelper.java | 5 +++-- .../java/org/opensearch/security/tools/SecurityAdmin.java | 5 +++-- .../org/opensearch/security/auditlog/sink/KafkaSinkTest.java | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opensearch/security/support/ConfigHelper.java b/src/main/java/org/opensearch/security/support/ConfigHelper.java index 23f93e3672..5d9b36192e 100644 --- a/src/main/java/org/opensearch/security/support/ConfigHelper.java +++ b/src/main/java/org/opensearch/security/support/ConfigHelper.java @@ -41,6 +41,7 @@ import org.opensearch.action.support.WriteRequest.RefreshPolicy; import org.opensearch.client.Client; import org.opensearch.common.bytes.BytesReference; +import org.opensearch.common.xcontent.MediaType; import org.opensearch.common.xcontent.NamedXContentRegistry; import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; @@ -116,11 +117,11 @@ public static String createEmptySdcYaml(CType cType, int configVersion) throws E return DefaultObjectMapper.YAML_MAPPER.writeValueAsString(createEmptySdc(cType, configVersion)); } - public static BytesReference readXContent(final Reader reader, final XContentType xContentType) throws IOException { + public static BytesReference readXContent(final Reader reader, final MediaType mediaType) throws IOException { BytesReference retVal; XContentParser parser = null; try { - parser = XContentFactory.xContent(xContentType).createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, reader); + parser = XContentFactory.xContent(mediaType).createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, reader); parser.nextToken(); final XContentBuilder builder = XContentFactory.jsonBuilder(); builder.copyCurrentStructure(parser); diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index c9717e20be..a24e16a6e9 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -114,6 +114,7 @@ import org.opensearch.common.collect.Tuple; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; +import org.opensearch.common.xcontent.MediaType; import org.opensearch.common.xcontent.NamedXContentRegistry; import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; @@ -945,11 +946,11 @@ private static boolean retrieveFile(final RestHighLevelClient restHighLevelClien return false; } - private static BytesReference readXContent(final String content, final XContentType xContentType) throws IOException { + private static BytesReference readXContent(final String content, final MediaType mediaType) throws IOException { BytesReference retVal; XContentParser parser = null; try { - parser = XContentFactory.xContent(xContentType).createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, content); + parser = XContentFactory.xContent(mediaType).createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, content); parser.nextToken(); final XContentBuilder builder = XContentFactory.jsonBuilder(); builder.copyCurrentStructure(parser); diff --git a/src/test/java/org/opensearch/security/auditlog/sink/KafkaSinkTest.java b/src/test/java/org/opensearch/security/auditlog/sink/KafkaSinkTest.java index ea9ce18e3d..b074dd2b62 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/KafkaSinkTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/KafkaSinkTest.java @@ -54,7 +54,7 @@ public void after() { public void testKafka() throws Exception { String configYml = FileHelper.loadFile("auditlog/endpoints/sink/configuration_kafka.yml"); configYml = configYml.replace("_RPLC_BOOTSTRAP_SERVERS_",embeddedKafka.getEmbeddedKafka().getBrokersAsString()); - Settings.Builder settingsBuilder = Settings.builder().loadFromSource(configYml, YamlXContent.yamlXContent.type()); + Settings.Builder settingsBuilder = Settings.builder().loadFromSource(configYml, YamlXContent.yamlXContent.mediaType()); try(KafkaConsumer consumer = createConsumer()) { consumer.subscribe(Arrays.asList("compliance")); From 5bdcb29f02fe3a5e1bbd7367d173ee495ceebfa9 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 16 Feb 2023 08:54:04 -0600 Subject: [PATCH 131/369] Publish snapshots to maven (#2438) * Publish snapshots to maven Adds a workflow to publish snapshots to the OpenSearch-Project maven repository when updates are pushed to main, 1.* branches, or 2.* branches. Following the example from https://github.com/opensearch-project/opensearch-sdk-java Signed-off-by: Peter Nied --- .github/workflows/maven-publish.yml | 42 +++++++++++++++++++++++++++++ build.gradle | 10 +++++++ 2 files changed, 52 insertions(+) create mode 100644 .github/workflows/maven-publish.yml diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml new file mode 100644 index 0000000000..78ee2a62a7 --- /dev/null +++ b/.github/workflows/maven-publish.yml @@ -0,0 +1,42 @@ +name: Publish snapshots to maven + +on: + workflow_dispatch: + push: + branches: [ + main + 1.* + 2.* + ] + +jobs: + build-and-publish-snapshots: + strategy: + fail-fast: false + matrix: + jdk: [11, 17] + platform: ["ubuntu-latest", "windows-latest", "macos-latest"] + if: github.repository == 'opensearch-project/security' + runs-on: ${{ matrix.platform }} + + permissions: + id-token: write + contents: write + + steps: + - uses: actions/setup-java@v3 + with: + distribution: temurin # Temurin is a distribution of adoptium + java-version: ${{ matrix.jdk }} + - uses: actions/checkout@v3 + - uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: ${{ secrets.PUBLISH_SNAPSHOTS_ROLE }} + aws-region: us-east-1 + - name: publish snapshots to maven + run: | + export SONATYPE_USERNAME=$(aws secretsmanager get-secret-value --secret-id maven-snapshots-username --query SecretString --output text) + export SONATYPE_PASSWORD=$(aws secretsmanager get-secret-value --secret-id maven-snapshots-password --query SecretString --output text) + echo "::add-mask::$SONATYPE_USERNAME" + echo "::add-mask::$SONATYPE_PASSWORD" + ./gradlew publishPluginZipPublicationToSnapshotsRepository diff --git a/build.gradle b/build.gradle index 77076a00b4..d7bfdb6f89 100644 --- a/build.gradle +++ b/build.gradle @@ -230,6 +230,16 @@ publishing { } } } + repositories { + maven { + name = "Snapshots" // optional target repository name + url = "https://aws.oss.sonatype.org/content/repositories/snapshots" + credentials { + username "$System.env.SONATYPE_USERNAME" + password "$System.env.SONATYPE_PASSWORD" + } + } + } } repositories { From aaae74d9c56e97a6018ee210021ab950b437c38e Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 17 Feb 2023 11:06:22 -0600 Subject: [PATCH 132/369] Remove unneeded jdk and platform from publishing workflow (#2442) Maven doesn't have a way to filter published artifacts by platform build for and since our plugin zip works in both platforms switching to a single publishing leg. Signed-off-by: Peter Nied --- .github/workflows/maven-publish.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 78ee2a62a7..f9c7617314 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -11,13 +11,7 @@ on: jobs: build-and-publish-snapshots: - strategy: - fail-fast: false - matrix: - jdk: [11, 17] - platform: ["ubuntu-latest", "windows-latest", "macos-latest"] - if: github.repository == 'opensearch-project/security' - runs-on: ${{ matrix.platform }} + runs-on: ubuntu-latest permissions: id-token: write @@ -27,7 +21,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: temurin # Temurin is a distribution of adoptium - java-version: ${{ matrix.jdk }} + java-version: 11 - uses: actions/checkout@v3 - uses: aws-actions/configure-aws-credentials@v1 with: From 43d09d73a84888425ef0508b89a45e08c8fac0ed Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Fri, 17 Feb 2023 13:34:32 -0800 Subject: [PATCH 133/369] Add release note for 1.3.8 (#2425) * Add release note for 1.3.8 Signed-off-by: Ryan Liang * Rebase and fix lint Signed-off-by: Ryan Liang --------- Signed-off-by: Ryan Liang --- .../opensearch-security.release-notes-1.3.8.0.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-1.3.8.0.md diff --git a/release-notes/opensearch-security.release-notes-1.3.8.0.md b/release-notes/opensearch-security.release-notes-1.3.8.0.md new file mode 100644 index 0000000000..09089c9344 --- /dev/null +++ b/release-notes/opensearch-security.release-notes-1.3.8.0.md @@ -0,0 +1,13 @@ +## 2023-02-02 Version 1.3.8.0 + +Compatible with OpenSearch 1.3.8 + +### Enhancement + +* [Backport 1.3] Username validation for special characters ([#2277](https://github.com/opensearch-project/security/pull/2277)) +* [Backport 1.X] Update tool scripts to run in windows ([#2371](https://github.com/opensearch-project/security/pull/2371)) +* [Backport 1.X] When excluding fields also exclude the term + `.keyword` ([2375](https://github.com/opensearch-project/security/pull/2375)) + +### Maintenance + +* Update `cxf-core` to 3.5.5 ([#2349](https://github.com/opensearch-project/security/pull/2349)) From a447b50221a6fee5889ff84ef7904c08d88dc646 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Mon, 20 Feb 2023 21:09:18 -0500 Subject: [PATCH 134/369] Update maintainers (#2445) * Update codeowners Signed-off-by: Stephen Crawford --- .github/CODEOWNERS | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index eb72d15b5e..9cd5577ad8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,8 @@ -* @opensearch-project/security +# Should now match maintainers list +* @cliu123 +* @cwperks +* @DarshitChanpura +* @davidlago +* @peternied +* @RyanL1997 +* @scrawfor99 From 0a9229939be5ed03237d1cec553ae2b9910b853f Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Tue, 21 Feb 2023 13:44:59 -0500 Subject: [PATCH 135/369] Update documentation for new developers (#2448) * Update documentation for new developers Signed-off-by: Stephen Crawford --- DEVELOPER_GUIDE.md | 34 ++++++------ DEVELOPING_WITH_DOCKER.md | 107 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 DEVELOPING_WITH_DOCKER.md diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index ba036b2022..4322a18d52 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -1,5 +1,6 @@ # Developer Guide -So you want to contribute code to this project? Excellent! We're glad you're here. Here's what you need to do. + +So you want to contribute code to OpenSearch Security? Excellent! We're glad you're here. Here's what you need to do. - [Developer Guide](#developer-guide) - [Prerequisites](#prerequisites) @@ -16,16 +17,17 @@ So you want to contribute code to this project? Excellent! We're glad you're her > Please make sure to follow the OpenSearch [Install Prerequisites](https://github.com/opensearch-project/OpenSearch/blob/main/DEVELOPER_GUIDE.md#install-prerequisites) before starting for the first time. -This project runs as a plugin of OpenSearch. You can [download a minimal release of OpenSearch](https://opensearch.org/downloads.html#minimal) and then install this plugin there. However, we will compile it using source code so that we are pulling in changes from the latest commit. +OpenSearch Security runs as a plugin of OpenSearch. You can [download a minimal release of OpenSearch](https://opensearch.org/downloads.html#minimal) and then install the Security plugin there. However, we will compile OpenSearch Security using source code so that we are pulling in changes from the latest commit. ### Native platforms -Not all platforms natively support OpenSearch, to check distribution avaliability please check these [issues](https://github.com/opensearch-project/opensearch-build/labels/distributions). -On MacOS / PC the OpenSearch distribution can be run with docker. This distribution contains the released version of OpenSearch including the security plugin. For development we do not recommend using this docker image. +Not all platforms natively support OpenSearch, to view distribution availability please check these [issues](https://github.com/opensearch-project/opensearch-build/issues?q=label%3Adistributions). + +On MacOS / PC the OpenSearch distribution can be run with Docker. This distribution contains the released version of OpenSearch including the security plugin. If you wish to use the Docker image for development, you will need to follow the steps found on the [Developing with Docker](DEVELOPING_WITH_DOCKER.md) guide. -To get started, follow the [getting started section](https://github.com/opensearch-project/OpenSearch/blob/main/DEVELOPER_GUIDE.md#getting-started) of OpenSearch's developer guide. This will get OpenSearch up and running built from source code. You can skip the `./gradlew check` step to save some time. Reach to the point where you can run a successful `curl localhost:9200` call. Great! now kill the server with `Ctrl+C`. +To get started, follow the [getting started section](https://github.com/opensearch-project/OpenSearch/blob/main/DEVELOPER_GUIDE.md#getting-started) of OpenSearch's developer guide. This will get OpenSearch up and running built from source code. You can skip the `./gradlew check` step to save some time. You should follow the steps until you reach the point where you can run a successful `curl localhost:9200` call. Great! now kill the server with `Ctrl+C`. -Next, run the following commands to copy the built code (snapshot) to a new folder in a different location. (This where you'll be running OpenSearch service). Run this from the base directory of the OpenSearch fork you cloned above: +Next, run the following commands to copy the built code (snapshot) to a new folder in a different location. (This where you'll be running the OpenSearch service). Run this from the base directory of the OpenSearch fork you cloned above: ```bash export OPENSEARCH_HOME=~//opensearch-* export OPENSEARCH_BUILD=distribution/archives/darwin-tar/build/install/opensearch-* @@ -41,20 +43,20 @@ cd $OPENSEARCH_HOME ./bin/opensearch ``` -The `curl localhost:9200` call should succeed again. Kill the server with `Ctrl+c`. We are ready to install the security plugin. +The `curl localhost:9200` call should succeed again. Kill the server with `Ctrl+c`. We are now ready to install the security plugin. >Worth noting:\ > The version of OpenSearch and the security plugin must match as there is an explicit version check at startup. This can be a bit confusing as, for example, at the time of writing this guide, the `main` branch of this security plugin builds version `1.3.0.0-SNAPSHOT` compatible with OpenSearch `1.3.0-SNAPSHOT` that gets built from branch `1.x`. Check the expected compatible version [here](https://github.com/opensearch-project/security/blob/main/plugin-descriptor.properties#L27) and make sure you get the correct branch from OpenSearch when building that project. ## Building -First create a fork of this repo and clone it locally. Changing to directory containing this clone and run this to build the project: +First create a fork of this repo and clone it locally. You should then change to the directory containing the clone and run this to build the project: ```bash ./gradlew clean assemble ``` -Install the built plugin into the OpenSearch server: +To install the built plugin into the OpenSearch server run: ```bash export OPENSEARCH_SECURITY_HOME=$OPENSEARCH_HOME/plugins/opensearch-security @@ -68,7 +70,7 @@ mv config/* $OPENSEARCH_HOME/config/opensearch-security/ rm -rf config/ ``` -Install the demo certificates and default configuration, answer `y` to the first two questions and `n` to the last one. The log should look like below: +To install the demo certificates and default configuration, answer `y` to the first two questions and `n` to the last one. The log should look like below: ```bash ./tools/install_demo_configuration.sh @@ -103,7 +105,7 @@ Detected OpenSearch Security Version: * ``` Now if we start our server again and try the original `curl localhost:9200`, it will fail. -Try this one instead: `curl -XGET https://localhost:9200 -u 'admin:admin' --insecure`. It should succeed. +Try this command instead: `curl -XGET https://localhost:9200 -u 'admin:admin' --insecure`. It should succeed. You can also make this call to return the authenticated user details: @@ -140,15 +142,17 @@ Launch IntelliJ IDEA, choose **Project from Existing Sources**, and select direc ## Running integration tests -Locally these can be run with `./gradlew test` with detailed results being avaliable at `${project-root}/build/reports/tests/test/index.html`, or run through an IDEs JUnit test runner. +Locally these can be run with `./gradlew test` with detailed results being available at `${project-root}/build/reports/tests/test/index.html`. You can also run tests through an IDEs JUnit test runner. -Integration tests are automatically run on all pull requests for all supported versions of the JDK. These must pass for change(s) to be merged. Detailed logs of these test results are avaliable by going to the GitHub action workflow's summary view and downloading the associated jdk version run of the tests, after extracting this file onto your local machine integration tests results are at `./tests/tests/index.html`. +Integration tests are automatically run on all pull requests for all supported versions of the JDK. These must pass for change(s) to be merged. Detailed logs of these test results are available by going to the GitHub Actions workflow summary view and downloading the workflow run of the tests. If you see multiple tests listed with different JDK versions, you can download the version with whichever JDK you are interested in. After extracting the test file on your local machine, integration tests results can be found at `./tests/tests/index.html`. ### Bulk test runs -To collect reliability data on test runs there is a manual GitHub action workflow called `Bulk Integration Test`. The workflow is started for a branch on this project or in a fork by going to [GitHub action workflows](https://github.com/opensearch-project/security/actions/workflows/integration-tests.yml) and selecting `Run Workflow`. + +To collect reliability data on test runs, there is a manual GitHub action workflow called `Bulk Integration Test`. The workflow is started for a branch on this project or in a fork by going to [GitHub action workflows](https://github.com/opensearch-project/security/actions/workflows/integration-tests.yml) and selecting `Run Workflow`. ### Checkstyle Violations -Checkstyle enforced several rules within this codebase. Sometimes exceptions will be necessary for components that are set for deprecation but the new version is unavailable. There are two formats of suppression that can be used when dealing with violations of this nature, one for disabling a single rule, or another for disabling all rules - its best to be as specific as possible. + +Checkstyle enforces several rules within this codebase. Sometimes it will be necessary for exceptions to be made when dealing with components that are set for deprecation. This can happen when the new version of a deprecation-path component is unavailable. There are two formats of suppression that can be used when dealing with violations of this nature, one for disabling a single rule, or another for disabling all rules. It is best to only disable specific rules when possible. *Execute Checkstyle* ``` diff --git a/DEVELOPING_WITH_DOCKER.md b/DEVELOPING_WITH_DOCKER.md new file mode 100644 index 0000000000..a0ba045846 --- /dev/null +++ b/DEVELOPING_WITH_DOCKER.md @@ -0,0 +1,107 @@ +# Developing with Docker + +Docker is a powerful tool that can be used to quickly spin up an OpenSearch cluster. When you follow the steps to run [OpenSearch with Docker](https://opensearch.org/docs/latest/install-and-configure/install-opensearch/docker/), you will find the Security Plugin already included in the basic distribution. + +- [Developing with Docker](#developing-with-docker) + - [Configuring Security](#configuring-security) + - [Mounting Local Volumes](#mounting-local-volumes) + - [Example docker-compose](#example-docker-compose) + + +## Configuring Security + +By default, the Docker installation of OpenSearch does not enable the Security plugin. In order to enable Security development, you will need set `DISABLE_SECURITY_PLUGIN=false`, as well as change `DISABLE_INSTALL_DEMO_CONFIG` and `DISABLE_SECURITY_DASHBOARDS_PLUGIN`. This will install the demo certificates, and allow you to develop with realistic Security configurations. An example of a completely configured docker-compose file is shown below. + +> Warning: You should never use the demo certificates for a production environment. Instead, you will need to follow the steps on [configuring security](https://opensearch.org/docs/latest/security/configuration/index/) before using the cluster for production. + +### Mounting Local Volumes + +In order to test development changes with an OpenSearch Docker-installation, you will need to mount the volumes in your docker-compose file. + +To update your cluster to have local changes, follow these steps: + +1. First you will need to make changes in your local `opensearch-project/security` repository. For this example, assume your fork is cloned into a directory called `security`. +2. After you make changes to your cloned repository, you will need to run `./gradlew assemble`. This will create a `.jar` file you can mount into the Docker container. The file will be located at `./security/build/distributions/opensearch-security-.0-SNAPSHOT.jar`, where the `` field is simply the OpenSearch distribution. +3. You will then need to navigate to your `docker-compose.yml` file where you are running you OpenSearch cluster from. For this example, let us assume this is in another directory called `opensearch-docker`. +4. Modify the compose file, so that in the `volumes:` section of each node configuration (the default configuration will have `opensearch-node1` and `opensearch-node2`), you have a new line which reads `~/security/build/distributions/opensearch-security-.0-SNAPSHOT.jar:/usr/share/opensearch/plugins/opensearch-security/opensearch-security-.0.jar`. This line should be added to the volumes section of all nodes in the compose file. You will not need to add it to the `opensearch-dashboards` section. +5. You can now restart the Docker container by running `docker-compose down -v` and `docker-compose up`. Your changes will now be live in the OpenSearch cluster instance. + +### Example docker-compose + +This is an example of a completely configured docker-compose file for a local installation of the 2.5.0 version of OpenSearch. + +``` +version: '3' +services: + opensearch-node1: + image: opensearchstaging/opensearch:2.5.0 # This is a image of the 2.5.0 distribution + environment: + - cluster.name=opensearch-cluster + - node.name=opensearch-node1 + - discovery.seed_hosts=opensearch-node1,opensearch-node2 + - cluster.initial_master_nodes=opensearch-node1,opensearch-node2 + - bootstrap.memory_lock=true # along with the memlock settings below, disables swapping + - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 # maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems + hard: 65536 + ports: + - 9200:9200 + - 9600:9600 # required for Performance Analyzer + networks: + - opensearch-net + # volumes: + # - ./config/opensearch.yml:/usr/share/opensearch/config/opensearch.yml # These paths are relative to the location of the docker-compose file + # - ./config/esnode.pem:/usr/share/opensearch/config/esnode.pem + # - ./config/esnode-key.pem:/usr/share/opensearch/config/esnode-key.pem + # - ./config/root-ca.pem:/usr/share/opensearch/config/root-ca.pem + # - ./config/opensearch-security/audit.yml:/usr/share/opensearch/config/opensearch-security/audit.yml + # - ./config/opensearch-security/tenants.yml:/usr/share/opensearch/config/opensearch-security/tenants.yml + # - /OpenSearch-Snapshots:/mnt/snapshots # This is where your snapshots would be stored + # - /security/build/distributions/opensearch-security-2.5.0.0-SNAPSHOT.jar:/usr/share/opensearch/plugins/opensearch-security/opensearch-security-2.5.0.0.jar + opensearch-node2: # This is the same settings as the opensearch-node1 + image: opensearchstaging/opensearch:2.5.0 + environment: + - cluster.name=opensearch-cluster + - node.name=opensearch-node2 + - discovery.seed_hosts=opensearch-node1,opensearch-node2 + - cluster.initial_master_nodes=opensearch-node1,opensearch-node2 + - bootstrap.memory_lock=true + - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 + hard: 65536 + #volumes: + # - ./config/opensearch.yml:/usr/share/opensearch/config/opensearch.yml + # - ./config/esnode.pem:/usr/share/opensearch/config/esnode.pem + # - ./config/esnode-key.pem:/usr/share/opensearch/config/esnode-key.pem + # - ./config/root-ca.pem:/usr/share/opensearch/config/root-ca.pem + # - ./config/opensearch-security/audit.yml:/usr/share/opensearch/config/opensearch-security/audit.yml + # - ./config/opensearch-security/tenants.yml:/usr/share/opensearch/config/opensearch-security/tenants.yml + # - /OpenSearch-Snapshots:/mnt/snapshots + # - /security/build/distributions/opensearch-security-2.5.0.0-SNAPSHOT.jar:/usr/share/opensearch/plugins/opensearch-security/opensearch-security-2.5.0.0.jar + networks: + - opensearch-net + opensearch-dashboards: + image: opensearchstaging/opensearch-dashboards:2.5.0 + ports: + - 5601:5601 + expose: + - "5601" + environment: + OPENSEARCH_HOSTS: '["https://opensearch-node1:9200","https://opensearch-node2:9200"]' + networks: + - opensearch-net + # volumes: + # - ./opensearch_dashboards.yml:/usr/share/opensearch-dashboards/config/opensearch_dashboard.yml # this would mount a local dashboards configuration file +networks: + opensearch-net: +``` From 44e7b477382fd7b17fd6ffda28b30e0b3aaa98c9 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Wed, 22 Feb 2023 09:10:55 -0500 Subject: [PATCH 136/369] Add auto github release workflow (#2450) Signed-off-by: Andriy Redko --- .github/workflows/auto-release.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/auto-release.yml diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml new file mode 100644 index 0000000000..ce71aed419 --- /dev/null +++ b/.github/workflows/auto-release.yml @@ -0,0 +1,28 @@ +name: Releases + +on: + push: + tags: + - '*' + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: GitHub App token + id: github_app_token + uses: tibdex/github-app-token@v1.5.0 + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + installation_id: 22958780 + - name: Get tag + id: tag + uses: dawidd6/action-get-tag@v1 + - uses: actions/checkout@v2 + - uses: ncipollo/release-action@v1 + with: + github_token: ${{ steps.github_app_token.outputs.token }} + bodyFile: release-notes/opensearch-security.release-notes-${{steps.tag.outputs.tag}}.md From 2ce736a84d424d01f9110a57591c26b840e283c4 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 22 Feb 2023 13:27:41 -0500 Subject: [PATCH 137/369] Update imports from org.opensearch.common.xcontent to org.opensearch.core.xcontent (#2457) * Update imports from org.opensearch.common.xcontent to org.opensearch.core.xcontent Signed-off-by: Craig Perkins Signed-off-by: Peter Nied Co-authored-by: Peter Nied --- .../org/opensearch/test/framework/AuditCompliance.java | 4 ++-- .../opensearch/test/framework/AuditConfiguration.java | 4 ++-- .../org/opensearch/test/framework/AuditFilters.java | 4 ++-- .../test/framework/AuthFailureListeners.java | 4 ++-- .../test/framework/AuthorizationBackend.java | 4 ++-- .../org/opensearch/test/framework/AuthzDomain.java | 4 ++-- .../org/opensearch/test/framework/RateLimiting.java | 4 ++-- .../org/opensearch/test/framework/RolesMapping.java | 4 ++-- .../opensearch/test/framework/TestSecurityConfig.java | 4 ++-- .../java/org/opensearch/test/framework/XffConfig.java | 4 ++-- .../test/framework/cluster/TestRestClient.java | 2 +- .../auth/http/kerberos/HTTPSpnegoAuthenticator.java | 2 +- .../opensearch/security/OpenSearchSecurityPlugin.java | 2 +- .../action/configupdate/ConfigUpdateNodeResponse.java | 4 ++-- .../action/configupdate/ConfigUpdateResponse.java | 4 ++-- .../security/action/whoami/WhoAmIResponse.java | 4 ++-- .../security/auditlog/impl/AbstractAuditLog.java | 8 ++++---- .../security/auditlog/impl/RequestResolver.java | 2 +- .../configuration/ConfigurationLoaderSecurity7.java | 6 +++--- .../security/configuration/DlsFlsFilterLeafReader.java | 2 +- .../security/configuration/DlsFlsRequestValve.java | 2 +- .../security/configuration/DlsFlsValveImpl.java | 2 +- .../security/configuration/DlsQueryParser.java | 6 +++--- .../security/dlic/rest/api/AbstractApiAction.java | 4 ++-- .../security/dlic/rest/api/AccountApiAction.java | 2 +- .../security/dlic/rest/api/InternalUsersApiAction.java | 2 +- .../security/dlic/rest/api/PermissionsInfoAction.java | 2 +- .../security/dlic/rest/api/SecuritySSLCertsAction.java | 2 +- .../opensearch/security/dlic/rest/support/Utils.java | 8 ++++---- .../validation/AbstractConfigurationValidator.java | 2 +- .../security/http/SecurityHttpServerTransport.java | 2 +- .../http/SecurityNonSslHttpServerTransport.java | 2 +- .../security/privileges/PrivilegesEvaluator.java | 2 +- .../security/queries/QueryBuilderTraverser.java | 6 +++--- .../opensearch/security/rest/DashboardsInfoAction.java | 2 +- .../opensearch/security/rest/SecurityHealthAction.java | 2 +- .../opensearch/security/rest/SecurityInfoAction.java | 2 +- .../opensearch/security/rest/SecurityWhoAmIAction.java | 2 +- .../org/opensearch/security/rest/TenantInfoAction.java | 2 +- .../security/securityconf/ConfigModelV6.java | 2 +- .../security/securityconf/ConfigModelV7.java | 2 +- .../security/securityconf/SecurityRoles.java | 2 +- .../impl/SecurityDynamicConfiguration.java | 4 ++-- .../security/ssl/OpenSearchSecuritySSLPlugin.java | 2 +- .../netty/SecuritySSLNettyHttpServerTransport.java | 2 +- .../security/ssl/rest/SecuritySSLInfoAction.java | 2 +- .../org/opensearch/security/support/ConfigHelper.java | 10 +++++----- .../opensearch/security/tools/AuditConfigMigrater.java | 4 ++-- .../org/opensearch/security/tools/SecurityAdmin.java | 10 +++++----- .../dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java | 2 +- .../opensearch/security/RolesInjectorIntegTest.java | 2 +- .../opensearch/security/RolesValidationIntegTest.java | 2 +- .../security/TransportUserInjectorIntegTest.java | 2 +- .../auditlog/config/AuditConfigSerializeTest.java | 2 +- .../security/auditlog/helper/MockRestRequest.java | 2 +- .../security/dlic/dlsfls/AbstractDlsFlsTest.java | 4 ++-- .../security/dlic/dlsfls/CCReplicationTest.java | 2 +- .../security/dlic/dlsfls/DlsTermLookupQueryTest.java | 8 ++++---- .../opensearch/security/dlic/dlsfls/FlsPerfTest.java | 2 +- .../http/proxy/HTTPExtendedProxyAuthenticatorTest.java | 2 +- .../protected_indices/ProtectedIndicesTests.java | 4 ++-- .../security/system_indices/SystemIndicesTests.java | 4 ++-- .../security/test/helper/file/FileHelper.java | 8 ++++---- .../security/test/plugin/UserInjectorPlugin.java | 2 +- 64 files changed, 109 insertions(+), 109 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuditCompliance.java b/src/integrationTest/java/org/opensearch/test/framework/AuditCompliance.java index fdf3cdd6f1..c9d5f8b40a 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/AuditCompliance.java +++ b/src/integrationTest/java/org/opensearch/test/framework/AuditCompliance.java @@ -13,8 +13,8 @@ import java.util.Collections; import java.util.List; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; public class AuditCompliance implements ToXContentObject { diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuditConfiguration.java b/src/integrationTest/java/org/opensearch/test/framework/AuditConfiguration.java index 762b27085a..783fa19af0 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/AuditConfiguration.java +++ b/src/integrationTest/java/org/opensearch/test/framework/AuditConfiguration.java @@ -11,8 +11,8 @@ import java.io.IOException; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; public class AuditConfiguration implements ToXContentObject { private final boolean enabled; diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuditFilters.java b/src/integrationTest/java/org/opensearch/test/framework/AuditFilters.java index 24dc3449b9..bc9f972906 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/AuditFilters.java +++ b/src/integrationTest/java/org/opensearch/test/framework/AuditFilters.java @@ -13,8 +13,8 @@ import java.util.Collections; import java.util.List; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; public class AuditFilters implements ToXContentObject { diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java b/src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java index 1f506cba71..5d467bd754 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java +++ b/src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java @@ -14,8 +14,8 @@ import java.util.Map; import java.util.Objects; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; public class AuthFailureListeners implements ToXContentObject { diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuthorizationBackend.java b/src/integrationTest/java/org/opensearch/test/framework/AuthorizationBackend.java index 56f94e9673..2d9d1c1b66 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/AuthorizationBackend.java +++ b/src/integrationTest/java/org/opensearch/test/framework/AuthorizationBackend.java @@ -14,8 +14,8 @@ import java.util.Objects; import java.util.function.Supplier; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; public class AuthorizationBackend implements ToXContentObject { private final String type; diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuthzDomain.java b/src/integrationTest/java/org/opensearch/test/framework/AuthzDomain.java index d42caa9f0c..7d611881e0 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/AuthzDomain.java +++ b/src/integrationTest/java/org/opensearch/test/framework/AuthzDomain.java @@ -11,8 +11,8 @@ import java.io.IOException; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; /** * The class represents authorization domain diff --git a/src/integrationTest/java/org/opensearch/test/framework/RateLimiting.java b/src/integrationTest/java/org/opensearch/test/framework/RateLimiting.java index 4b43194572..b41c6f8f1d 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/RateLimiting.java +++ b/src/integrationTest/java/org/opensearch/test/framework/RateLimiting.java @@ -12,8 +12,8 @@ import java.io.IOException; import java.util.Objects; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; public class RateLimiting implements ToXContentObject { diff --git a/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java b/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java index d5f6dbee07..a0f048f953 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java +++ b/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java @@ -14,8 +14,8 @@ import java.util.Arrays; import java.util.List; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.test.framework.TestSecurityConfig.Role; import static java.util.Objects.requireNonNull; diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index 15e9e48b19..43b98b02ce 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -54,9 +54,9 @@ import org.opensearch.client.Client; import org.opensearch.common.Strings; import org.opensearch.common.bytes.BytesReference; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.test.framework.cluster.OpenSearchClientProvider.UserCredentialsHolder; diff --git a/src/integrationTest/java/org/opensearch/test/framework/XffConfig.java b/src/integrationTest/java/org/opensearch/test/framework/XffConfig.java index 7214104597..fa4ec5d849 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/XffConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/XffConfig.java @@ -13,8 +13,8 @@ import org.apache.commons.lang3.StringUtils; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; /** *

diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java index 8df1b6ed25..864b8db3f1 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -70,8 +70,8 @@ import org.apache.logging.log4j.Logger; import org.opensearch.common.Strings; -import org.opensearch.common.xcontent.ToXContentObject; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.ToXContentObject; import org.opensearch.security.DefaultObjectMapper; import static java.lang.String.format; diff --git a/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java index 3603aeb94e..15aff90f1a 100644 --- a/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java @@ -45,8 +45,8 @@ import org.opensearch.SpecialPermission; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.env.Environment; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 3b77212fa7..ce64299f13 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -90,7 +90,7 @@ import org.opensearch.common.util.BigArrays; import org.opensearch.common.util.PageCacheRecycler; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; import org.opensearch.http.HttpServerTransport; diff --git a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java index 420d23913b..29c5b6c9a1 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java @@ -33,8 +33,8 @@ import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; public class ConfigUpdateNodeResponse extends BaseNodeResponse implements ToXContentObject { diff --git a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java index a907e4464c..3a57ca4144 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java @@ -34,8 +34,8 @@ import org.opensearch.cluster.ClusterName; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; public class ConfigUpdateResponse extends BaseNodesResponse implements ToXContentObject { diff --git a/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java b/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java index 635cba8945..876079dced 100644 --- a/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java +++ b/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java @@ -32,9 +32,9 @@ import org.opensearch.common.Strings; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; public class WhoAmIResponse extends ActionResponse implements ToXContent { 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 3ebabb14bb..e4aa062641 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java @@ -47,12 +47,12 @@ import org.opensearch.common.collect.Tuple; import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentHelper; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.env.Environment; import org.opensearch.index.engine.Engine.Delete; import org.opensearch.index.engine.Engine.DeleteResult; @@ -73,7 +73,7 @@ import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.TransportRequest; -import static org.opensearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; +import static org.opensearch.core.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; public abstract class AbstractAuditLog implements AuditLog { diff --git a/src/main/java/org/opensearch/security/auditlog/impl/RequestResolver.java b/src/main/java/org/opensearch/security/auditlog/impl/RequestResolver.java index 294e336a05..5b4881d1eb 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/RequestResolver.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/RequestResolver.java @@ -46,9 +46,9 @@ import org.opensearch.common.collect.Tuple; import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.index.Index; import org.opensearch.index.reindex.DeleteByQueryRequest; import org.opensearch.index.reindex.ReindexRequest; diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java b/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java index 785a923bf8..3019c76462 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java @@ -50,10 +50,10 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.NamedXContentRegistry; import org.opensearch.common.xcontent.XContentHelper; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auditlog.config.AuditConfig; import org.opensearch.security.securityconf.impl.CType; @@ -63,7 +63,7 @@ import org.opensearch.security.support.SecurityUtils; import org.opensearch.threadpool.ThreadPool; -import static org.opensearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; +import static org.opensearch.core.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; public class ConfigurationLoaderSecurity7 { diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java index 06a89ac099..d849e6d999 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java @@ -69,10 +69,10 @@ import org.opensearch.common.collect.Tuple; import org.opensearch.common.lucene.index.SequentialStoredFieldsLeafReader; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.common.xcontent.XContentType; import org.opensearch.common.xcontent.support.XContentMapValues; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.index.IndexService; import org.opensearch.index.shard.ShardId; import org.opensearch.security.auditlog.AuditLog; diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java b/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java index c639fe6b85..4aa9fadcae 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java @@ -28,7 +28,7 @@ import org.opensearch.action.ActionListener; import org.opensearch.action.ActionRequest; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.search.internal.SearchContext; import org.opensearch.search.query.QuerySearchResult; import org.opensearch.security.resolver.IndexResolverReplacer.Resolved; diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java index 327b260b32..532f820210 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java @@ -51,8 +51,8 @@ import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.NamedXContentRegistry; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.index.query.ParsedQuery; import org.opensearch.rest.RestStatus; import org.opensearch.search.DocValueFormat; diff --git a/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java b/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java index b0a3aa14af..fd3b3aee98 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java +++ b/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java @@ -30,10 +30,10 @@ import org.apache.lucene.search.join.BitSetProducer; import org.apache.lucene.search.join.ToChildBlockJoinQuery; -import org.opensearch.common.xcontent.DeprecationHandler; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.xcontent.DeprecationHandler; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.query.AbstractQueryBuilder; import org.opensearch.index.query.ParsedQuery; import org.opensearch.index.query.QueryBuilder; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java index 873656f927..0e98124b6f 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java @@ -33,10 +33,10 @@ import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext.StoredContext; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.index.engine.VersionConflictEngineException; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java index 20de3500dd..885a5476af 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java @@ -28,7 +28,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestController; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java index 3f0ff75f8f..417465e353 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java @@ -27,7 +27,7 @@ import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestController; import org.opensearch.rest.RestRequest; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/PermissionsInfoAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/PermissionsInfoAction.java index 3b84c498d0..d07be301bd 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/PermissionsInfoAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/PermissionsInfoAction.java @@ -26,7 +26,7 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java index 8e936b167a..4168bf4109 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java @@ -29,7 +29,7 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestController; diff --git a/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java b/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java index 099087523b..aba2807846 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java +++ b/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java @@ -38,19 +38,19 @@ import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.ToXContent; import org.opensearch.common.xcontent.XContentHelper; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.rest.RestHandler.DeprecatedRoute; import org.opensearch.rest.RestHandler.Route; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.user.User; -import static org.opensearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; +import static org.opensearch.core.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; public class Utils { diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java index 54709c8a15..81942d9c11 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java @@ -30,9 +30,9 @@ import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; import org.opensearch.rest.RestRequest.Method; diff --git a/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java b/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java index e3e3604c42..3d977dcc7e 100644 --- a/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java +++ b/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java @@ -30,7 +30,7 @@ import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.BigArrays; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.security.ssl.SecurityKeyStore; import org.opensearch.security.ssl.SslExceptionHandler; import org.opensearch.security.ssl.http.netty.SecuritySSLNettyHttpServerTransport; diff --git a/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java b/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java index 446d07653a..b05153db4c 100644 --- a/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java +++ b/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java @@ -33,7 +33,7 @@ import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.BigArrays; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.http.HttpHandlingSettings; import org.opensearch.http.netty4.Netty4HttpServerTransport; import org.opensearch.threadpool.ThreadPool; diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 9967859d9f..cceaeb4cb0 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -82,7 +82,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.index.reindex.ReindexAction; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.ClusterInfoHolder; diff --git a/src/main/java/org/opensearch/security/queries/QueryBuilderTraverser.java b/src/main/java/org/opensearch/security/queries/QueryBuilderTraverser.java index dba7a6fef1..7bde8523a5 100644 --- a/src/main/java/org/opensearch/security/queries/QueryBuilderTraverser.java +++ b/src/main/java/org/opensearch/security/queries/QueryBuilderTraverser.java @@ -17,10 +17,10 @@ import java.util.Set; import java.util.function.Predicate; -import org.opensearch.common.xcontent.DeprecationHandler; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.xcontent.DeprecationHandler; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.query.AbstractQueryBuilder; import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.BoostingQueryBuilder; diff --git a/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java b/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java index 56bd9112c8..aa714ebcbb 100644 --- a/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java @@ -36,7 +36,7 @@ import org.opensearch.client.node.NodeClient; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; diff --git a/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java b/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java index 4ad722841e..b88d2700c9 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java @@ -33,7 +33,7 @@ import org.opensearch.client.node.NodeClient; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; diff --git a/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java b/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java index 9455c19168..f8e03da5d2 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java @@ -42,7 +42,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; diff --git a/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java b/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java index ddb656694e..982448a53f 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java @@ -22,7 +22,7 @@ import org.opensearch.client.node.NodeClient; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; diff --git a/src/main/java/org/opensearch/security/rest/TenantInfoAction.java b/src/main/java/org/opensearch/security/rest/TenantInfoAction.java index 4f39625d89..266d2edf49 100644 --- a/src/main/java/org/opensearch/security/rest/TenantInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/TenantInfoAction.java @@ -41,7 +41,7 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java index 9de3cb9417..987b8fac64 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java @@ -53,7 +53,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.set.Sets; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.security.resolver.IndexResolverReplacer.Resolved; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.securityconf.impl.v6.ActionGroupsV6; diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index ab3c47d911..1e2adee1db 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -56,7 +56,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.set.Sets; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.security.resolver.IndexResolverReplacer.Resolved; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.securityconf.impl.v7.ActionGroupsV7; diff --git a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java index 478e0f03dd..de7afbc27b 100644 --- a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java +++ b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java @@ -31,7 +31,7 @@ import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.security.resolver.IndexResolverReplacer.Resolved; import org.opensearch.security.user.User; diff --git a/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java b/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java index 14cfab0040..09eeee41e3 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java @@ -41,10 +41,10 @@ import org.opensearch.ExceptionsHelper; import org.opensearch.common.bytes.BytesReference; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.NonValidatingObjectMapper; import org.opensearch.security.securityconf.Hashed; diff --git a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java index fa1539596d..755a2b188f 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java @@ -56,7 +56,7 @@ import org.opensearch.common.util.BigArrays; import org.opensearch.common.util.PageCacheRecycler; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; import org.opensearch.http.HttpServerTransport; diff --git a/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java b/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java index 0738973483..7c53b6268a 100644 --- a/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java +++ b/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java @@ -31,7 +31,7 @@ import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.BigArrays; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.http.HttpChannel; import org.opensearch.http.HttpHandlingSettings; import org.opensearch.http.netty4.Netty4HttpChannel; diff --git a/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLInfoAction.java b/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLInfoAction.java index 8127960efc..f5050d3242 100644 --- a/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLInfoAction.java +++ b/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLInfoAction.java @@ -31,7 +31,7 @@ import org.opensearch.client.node.NodeClient; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; diff --git a/src/main/java/org/opensearch/security/support/ConfigHelper.java b/src/main/java/org/opensearch/security/support/ConfigHelper.java index 5d9b36192e..f451b8794d 100644 --- a/src/main/java/org/opensearch/security/support/ConfigHelper.java +++ b/src/main/java/org/opensearch/security/support/ConfigHelper.java @@ -41,19 +41,19 @@ import org.opensearch.action.support.WriteRequest.RefreshPolicy; import org.opensearch.client.Client; import org.opensearch.common.bytes.BytesReference; -import org.opensearch.common.xcontent.MediaType; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.MediaType; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.engine.VersionConflictEngineException; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.Meta; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; -import static org.opensearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; +import static org.opensearch.core.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; public class ConfigHelper { diff --git a/src/main/java/org/opensearch/security/tools/AuditConfigMigrater.java b/src/main/java/org/opensearch/security/tools/AuditConfigMigrater.java index c4af584d4d..824aee3d5b 100644 --- a/src/main/java/org/opensearch/security/tools/AuditConfigMigrater.java +++ b/src/main/java/org/opensearch/security/tools/AuditConfigMigrater.java @@ -26,9 +26,9 @@ import org.apache.commons.cli.Options; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auditlog.config.AuditConfig; diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index a24e16a6e9..161ad72528 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -114,13 +114,13 @@ import org.opensearch.common.collect.Tuple; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; -import org.opensearch.common.xcontent.MediaType; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.xcontent.MediaType; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.IndexNotFoundException; import org.opensearch.rest.RestStatus; import org.opensearch.security.DefaultObjectMapper; @@ -145,7 +145,7 @@ import org.opensearch.security.support.PemKeyReader; import org.opensearch.security.support.SecurityJsonNode; -import static org.opensearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; +import static org.opensearch.core.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; import static org.opensearch.security.support.SecurityUtils.replaceEnvVars; // CS-ENFORCE-SINGLE diff --git a/src/test/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java index 186539521b..bfaf33049d 100644 --- a/src/test/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java @@ -45,8 +45,8 @@ import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.io.stream.BytesStreamOutput; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; import org.opensearch.rest.RestRequest.Method; diff --git a/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java b/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java index 06f7d31507..d14f8d6600 100644 --- a/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java @@ -35,7 +35,7 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.io.stream.NamedWriteableRegistry; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; import org.opensearch.node.Node; diff --git a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java index 36626b3428..1b4aee3e51 100644 --- a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java @@ -29,7 +29,7 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.io.stream.NamedWriteableRegistry; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; import org.opensearch.node.Node; diff --git a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java index 8ad576be53..8fa665e8ba 100644 --- a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java @@ -27,7 +27,7 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.io.stream.NamedWriteableRegistry; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; import org.opensearch.node.Node; diff --git a/src/test/java/org/opensearch/security/auditlog/config/AuditConfigSerializeTest.java b/src/test/java/org/opensearch/security/auditlog/config/AuditConfigSerializeTest.java index 3fe9808a15..d7ba321ea9 100644 --- a/src/test/java/org/opensearch/security/auditlog/config/AuditConfigSerializeTest.java +++ b/src/test/java/org/opensearch/security/auditlog/config/AuditConfigSerializeTest.java @@ -26,8 +26,8 @@ import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.security.auditlog.impl.AuditCategory; import org.opensearch.security.compliance.ComplianceConfig; import org.opensearch.security.support.ConfigConstants; diff --git a/src/test/java/org/opensearch/security/auditlog/helper/MockRestRequest.java b/src/test/java/org/opensearch/security/auditlog/helper/MockRestRequest.java index 77f79b084b..6328c94352 100644 --- a/src/test/java/org/opensearch/security/auditlog/helper/MockRestRequest.java +++ b/src/test/java/org/opensearch/security/auditlog/helper/MockRestRequest.java @@ -14,7 +14,7 @@ import java.util.Collections; import org.opensearch.common.bytes.BytesReference; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.rest.RestRequest; public class MockRestRequest extends RestRequest { diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/AbstractDlsFlsTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/AbstractDlsFlsTest.java index 7cec79aa05..06d428a483 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/AbstractDlsFlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/AbstractDlsFlsTest.java @@ -24,9 +24,9 @@ import org.opensearch.client.Client; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.LoggingDeprecationHandler; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.SingleClusterTest; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java index fb557f038b..4cd6987b5a 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java @@ -47,8 +47,8 @@ import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.NamedXContentRegistry; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; import org.opensearch.node.Node; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTermLookupQueryTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTermLookupQueryTest.java index 875a3599c4..b4a0d1f129 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTermLookupQueryTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTermLookupQueryTest.java @@ -32,12 +32,12 @@ import org.opensearch.action.search.SearchResponse; import org.opensearch.action.support.WriteRequest.RefreshPolicy; import org.opensearch.client.Client; -import org.opensearch.common.ParseField; -import org.opensearch.common.xcontent.ContextParser; import org.opensearch.common.xcontent.LoggingDeprecationHandler; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.ParseField; +import org.opensearch.core.xcontent.ContextParser; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.search.SearchHit; import org.opensearch.search.aggregations.Aggregation; import org.opensearch.search.aggregations.Aggregations; diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsPerfTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsPerfTest.java index ca1e297f6c..565dbdde9c 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsPerfTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsPerfTest.java @@ -25,8 +25,8 @@ import org.opensearch.action.index.IndexRequest; import org.opensearch.client.Client; import org.opensearch.common.StopWatch; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; @Ignore diff --git a/src/test/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticatorTest.java b/src/test/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticatorTest.java index c11d39d4e8..111a07bf40 100644 --- a/src/test/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticatorTest.java +++ b/src/test/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticatorTest.java @@ -40,7 +40,7 @@ import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.http.HttpChannel; import org.opensearch.http.HttpRequest; import org.opensearch.http.HttpResponse; diff --git a/src/test/java/org/opensearch/security/protected_indices/ProtectedIndicesTests.java b/src/test/java/org/opensearch/security/protected_indices/ProtectedIndicesTests.java index 60a19d4210..52f66f3462 100644 --- a/src/test/java/org/opensearch/security/protected_indices/ProtectedIndicesTests.java +++ b/src/test/java/org/opensearch/security/protected_indices/ProtectedIndicesTests.java @@ -45,9 +45,9 @@ import org.opensearch.client.Client; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.LoggingDeprecationHandler; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.rest.RestStatus; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.DynamicSecurityConfig; diff --git a/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java b/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java index 11bd4738e6..273214b0fe 100644 --- a/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java +++ b/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java @@ -29,9 +29,9 @@ import org.opensearch.client.Client; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.LoggingDeprecationHandler; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.rest.RestStatus; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.DynamicSecurityConfig; diff --git a/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java b/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java index 93cc31ffee..6aadd2acbb 100644 --- a/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java +++ b/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java @@ -47,13 +47,13 @@ import org.apache.logging.log4j.Logger; import org.opensearch.common.bytes.BytesReference; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; -import static org.opensearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; +import static org.opensearch.core.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; public class FileHelper { diff --git a/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java b/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java index 0bb034c602..ae6f5116a1 100644 --- a/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java +++ b/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java @@ -38,7 +38,7 @@ import org.opensearch.common.util.BigArrays; import org.opensearch.common.util.PageCacheRecycler; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.http.HttpServerTransport; import org.opensearch.http.HttpServerTransport.Dispatcher; import org.opensearch.http.netty4.Netty4HttpServerTransport; From a58af2fa9ae6b813668bc4310e9682bb5c263d4b Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Wed, 22 Feb 2023 13:57:06 -0600 Subject: [PATCH 138/369] Remove MacOS platform from CI checks (#2468) Remove MacOS platform from CI checks Issues - Resolves https://github.com/opensearch-project/security/issues/2467 Signed-off-by: Peter Nied --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c859d3615..c6465c5d4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: jdk: [11, 17] - platform: ["ubuntu-latest", "windows-latest", "macos-latest"] + platform: ["ubuntu-latest", "windows-latest"] runs-on: ${{ matrix.platform }} steps: @@ -59,7 +59,7 @@ jobs: fail-fast: false matrix: jdk: [17] - platform: ["ubuntu-latest", "windows-latest", "macos-latest"] + platform: ["ubuntu-latest", "windows-latest"] runs-on: ${{ matrix.platform }} steps: From 8c506fa675036851d360563bec56e144597ebf67 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Wed, 22 Feb 2023 13:57:34 -0600 Subject: [PATCH 139/369] Fix the format of the codeowners file (#2469) Fix the format of the codeowners file All maintainers are considered code-owners instead of only @scrawfor99 :D Signed-off-by: Peter Nied --- .github/CODEOWNERS | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9cd5577ad8..5478135783 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,8 +1 @@ -# Should now match maintainers list -* @cliu123 -* @cwperks -* @DarshitChanpura -* @davidlago -* @peternied -* @RyanL1997 -* @scrawfor99 +* @cliu123 @cwperks @DarshitChanpura @davidlago @peternied @RyanL1997 @scrawfor99 From 787f37434e288ce4d2f5322b9c332596106068fc Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 22 Feb 2023 15:30:46 -0500 Subject: [PATCH 140/369] Add release notes for 2.6.0.0 (#2456) Signed-off-by: Craig Perkins --- .../opensearch-security.release-notes-2.6.0.0.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-2.6.0.0.md diff --git a/release-notes/opensearch-security.release-notes-2.6.0.0.md b/release-notes/opensearch-security.release-notes-2.6.0.0.md new file mode 100644 index 0000000000..748454a95d --- /dev/null +++ b/release-notes/opensearch-security.release-notes-2.6.0.0.md @@ -0,0 +1,15 @@ +## 2023-02-28 Version 2.6.0.0 + +Compatible with OpenSearch 2.6.0 + +### Enhancements + +* Add actions cluster:admin/component_template/* to cluster_manage_index_templates ([#2409](https://github.com/opensearch-project/security/pull/2409)) +* Publish snapshots to maven ([#2438](https://github.com/opensearch-project/security/pull/2438)) +* Integrate k-NN functionality with security plugin ([#2274](https://github.com/opensearch-project/security/pull/2274)) + +### Maintenance + +* Updates toString calls affected by change in method signature ([#2418](https://github.com/opensearch-project/security/pull/2418)) +* Updates DlsFlsFilterLeafReader with Lucene change and fix broken deprecation logger test ([#2429](https://github.com/opensearch-project/security/pull/2429)) +* Add CODEOWNERS ([#2445](https://github.com/opensearch-project/security/pull/2445)) From bba5c4d91f9304590ad735607a03f440f35e736d Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Wed, 22 Feb 2023 17:45:35 -0600 Subject: [PATCH 141/369] Use correct format for push trigger (#2474) Builds were not being automatically published, after reviewing the documentation https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#running-your-workflow-only-when-a-push-to-specific-branches-occurs branches is an object, not an array. Signed-off-by: Peter Nied --- .github/workflows/maven-publish.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index f9c7617314..1490e1d7f6 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -3,11 +3,10 @@ name: Publish snapshots to maven on: workflow_dispatch: push: - branches: [ - main - 1.* - 2.* - ] + branches: + - 'main' + - '1.*' + - '2.*' jobs: build-and-publish-snapshots: From 2df8acd6789910ced83d6a198b3a06fa221fb58e Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Wed, 22 Feb 2023 19:58:20 -0500 Subject: [PATCH 142/369] Flatten response times (#2471) --- .../InternalAuthenticationBackend.java | 38 +++-- .../auth/InternalAuthBackendTests.java | 145 ++++++++++++++++++ 2 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 src/test/java/org/opensearch/security/auth/InternalAuthBackendTests.java diff --git a/src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java b/src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java index 2546cbae46..adc49c8735 100644 --- a/src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java +++ b/src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java @@ -75,7 +75,7 @@ public boolean exists(User user) { if(securityRoles != null) { user.addSecurityRoles(securityRoles); } - + user.addAttributes(attributeMap); return true; } @@ -83,20 +83,38 @@ public boolean exists(User user) { return false; } + /** + * A helper function used to verify that both invalid and valid usernames have a hashing check during testing. + * @param hash A string hash of the stored user's password. + * @param array A char array of the provided password + * @return Whether the hash matches the provided password + */ + public boolean passwordMatchesHash(String hash, char[] array) { + return OpenBSDBCrypt.checkPassword(hash, array); + } + @Override public User authenticate(final AuthCredentials credentials) { + boolean userExists; + if (internalUsersModel == null) { throw new OpenSearchSecurityException("Internal authentication backend not configured. May be OpenSearch is not initialized."); } + final byte[] password; + String hash; if(!internalUsersModel.exists(credentials.getUsername())) { - throw new OpenSearchSecurityException(credentials.getUsername() + " not found"); + userExists = false; + password = credentials.getPassword(); + hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2"; // Ensure the same cryptographic complexity for users not found and invalid password + } else { + userExists = true; + password = credentials.getPassword(); + hash = internalUsersModel.getHash(credentials.getUsername()); } - final byte[] password = credentials.getPassword(); - - if(password == null || password.length == 0) { + if (password == null || password.length == 0) { throw new OpenSearchSecurityException("empty passwords not supported"); } @@ -108,7 +126,7 @@ public User authenticate(final AuthCredentials credentials) { Arrays.fill(password, (byte)0); try { - if (OpenBSDBCrypt.checkPassword(internalUsersModel.getHash(credentials.getUsername()), array)) { + if (passwordMatchesHash(hash, array) && userExists) { final List roles = internalUsersModel.getBackenRoles(credentials.getUsername()); final Map customAttributes = internalUsersModel.getAttributes(credentials.getUsername()); if(customAttributes != null) { @@ -116,16 +134,18 @@ public User authenticate(final AuthCredentials credentials) { credentials.addAttribute("attr.internal."+attributeName.getKey(), attributeName.getValue()); } } - + final User user = new User(credentials.getUsername(), roles, credentials); - + final List securityRoles = internalUsersModel.getSecurityRoles(credentials.getUsername()); if(securityRoles != null) { user.addSecurityRoles(securityRoles); } - return user; } else { + if (!userExists) { + throw new OpenSearchSecurityException(credentials.getUsername() + " not found"); + } throw new OpenSearchSecurityException("password does not match"); } } finally { diff --git a/src/test/java/org/opensearch/security/auth/InternalAuthBackendTests.java b/src/test/java/org/opensearch/security/auth/InternalAuthBackendTests.java new file mode 100644 index 0000000000..3821be7038 --- /dev/null +++ b/src/test/java/org/opensearch/security/auth/InternalAuthBackendTests.java @@ -0,0 +1,145 @@ +/* + * 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.auth; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import org.opensearch.OpenSearchSecurityException; +import org.opensearch.security.auth.internal.InternalAuthenticationBackend; +import org.opensearch.security.securityconf.InternalUsersModel; +import org.opensearch.security.user.AuthCredentials; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +public class InternalAuthBackendTests { + + private InternalUsersModel internalUsersModel; + + private InternalAuthenticationBackend internalAuthenticationBackend; + + @Before + public void internalAuthBackendTestsSetup() { + internalAuthenticationBackend = spy(new InternalAuthenticationBackend()); + internalUsersModel = mock(InternalUsersModel.class); + internalAuthenticationBackend.onInternalUsersModelChanged(internalUsersModel); + } + + private char[] createArrayFromPasswordBytes(byte[] password) { + ByteBuffer wrap = ByteBuffer.wrap(password); + CharBuffer buf = StandardCharsets.UTF_8.decode(wrap); + char[] array = new char[buf.limit()]; + buf.get(array); + Arrays.fill(password, (byte)0); + return array; + } + + @Test + public void testHashActionWithValidUserValidPassword() { + + // Make authentication info for valid username with valid password + final String validPassword = "admin"; + final byte[] validPasswordBytes = validPassword.getBytes(); + + final AuthCredentials validUsernameAuth = new AuthCredentials("admin", validPasswordBytes); + + final String hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2"; + + char[] array = createArrayFromPasswordBytes(validPasswordBytes); + + + when(internalUsersModel.getHash(validUsernameAuth.getUsername())).thenReturn(hash); + when(internalUsersModel.exists(validUsernameAuth.getUsername())).thenReturn(true); + doReturn(true).when(internalAuthenticationBackend).passwordMatchesHash(Mockito.any(String.class), Mockito.any(char[].class)); + + //Act + internalAuthenticationBackend.authenticate(validUsernameAuth); + + verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array); + verify(internalUsersModel, times(1)).getBackenRoles(validUsernameAuth.getUsername()); + } + + @Test + public void testHashActionWithValidUserInvalidPassword() { + + // Make authentication info for valid with bad password + final String gibberishPassword = "ajdhflkasdjfaklsdf"; + final byte[] gibberishPasswordBytes = gibberishPassword.getBytes(); + final AuthCredentials validUsernameAuth = new AuthCredentials("admin", gibberishPasswordBytes); + + final String hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2"; + + char[] array = createArrayFromPasswordBytes(gibberishPasswordBytes); + + when(internalUsersModel.getHash("admin")).thenReturn(hash); + when(internalUsersModel.exists("admin")).thenReturn(true); + + OpenSearchSecurityException ex = Assert.assertThrows(OpenSearchSecurityException.class, + () -> internalAuthenticationBackend.authenticate(validUsernameAuth)); + assert(ex.getMessage().contains("password does not match")); + verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array); + } + + @Test + public void testHashActionWithInvalidUserValidPassword() { + + // Make authentication info for valid and invalid usernames both with bad passwords + final String validPassword = "admin"; + final byte[] validPasswordBytes = validPassword.getBytes(); + final AuthCredentials invalidUsernameAuth = new AuthCredentials("ertyuiykgjjfguyifdghc", validPasswordBytes); + + final String hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2"; + + char[] array = createArrayFromPasswordBytes(validPasswordBytes); + + when(internalUsersModel.exists("ertyuiykgjjfguyifdghc")).thenReturn(false); + when(internalAuthenticationBackend.passwordMatchesHash(hash, array)).thenReturn(true); //Say that the password is correct + + OpenSearchSecurityException ex = Assert.assertThrows(OpenSearchSecurityException.class, + () -> internalAuthenticationBackend.authenticate(invalidUsernameAuth)); + assert(ex.getMessage().contains("not found")); + verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array); + } + + @Test + public void testHashActionWithInvalidUserInvalidPassword() { + + // Make authentication info for valid and invalid usernames both with bad passwords + final String gibberishPassword = "ajdhflkasdjfaklsdf"; + final byte[] gibberishPasswordBytes = gibberishPassword.getBytes(); + final AuthCredentials invalidUsernameAuth = new AuthCredentials("ertyuiykgjjfguyifdghc", gibberishPasswordBytes); + + final String hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2"; + + char[] array = createArrayFromPasswordBytes(gibberishPasswordBytes); + + when(internalUsersModel.exists("ertyuiykgjjfguyifdghc")).thenReturn(false); + + + OpenSearchSecurityException ex = Assert.assertThrows(OpenSearchSecurityException.class, + () -> internalAuthenticationBackend.authenticate(invalidUsernameAuth)); + verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array); + assert(ex.getMessage().contains("not found")); + } +} From 7547d035ff11a00257f6c7e8316499e2138790e8 Mon Sep 17 00:00:00 2001 From: xieshujian Date: Mon, 27 Feb 2023 10:50:04 +0800 Subject: [PATCH 143/369] fix kafka CVE-2023-25194, update kafka client to 3.4.0 (#2484) Signed-off-by: Shujian Xie Signed-off-by: Peter Nied --- build.gradle | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index d7bfdb6f89..a786a41002 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ buildscript { opensearch_build = version_tokens[0] + '.0' common_utils_version = System.getProperty("common_utils.version", '3.0.0.0-SNAPSHOT') - kafka_version = '3.0.2' + kafka_version = '3.4.0' if (buildVersionQualifier) { opensearch_build += "-${buildVersionQualifier}" @@ -411,16 +411,16 @@ dependencies { runtimeOnly 'com.google.j2objc:j2objc-annotations:1.3' runtimeOnly 'com.google.code.findbugs:jsr305:3.0.2' runtimeOnly 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' - runtimeOnly 'org.lz4:lz4-java:1.7.1' + runtimeOnly 'org.lz4:lz4-java:1.8.0' runtimeOnly 'io.dropwizard.metrics:metrics-core:3.1.2' runtimeOnly 'org.slf4j:slf4j-api:1.7.30' - runtimeOnly 'org.xerial.snappy:snappy-java:1.1.8.1' + runtimeOnly 'org.xerial.snappy:snappy-java:1.1.8.4' runtimeOnly 'org.codehaus.woodstox:stax2-api:4.2.1' runtimeOnly 'org.glassfish.jaxb:txw2:2.3.4' runtimeOnly 'com.fasterxml.woodstox:woodstox-core:6.4.0' runtimeOnly 'org.apache.ws.xmlschema:xmlschema-core:2.2.5' runtimeOnly 'org.apache.santuario:xmlsec:2.2.3' - runtimeOnly 'com.github.luben:zstd-jni:1.5.0-2' + runtimeOnly 'com.github.luben:zstd-jni:1.5.2-1' runtimeOnly 'org.checkerframework:checker-qual:3.5.0' runtimeOnly "org.bouncycastle:bcpkix-jdk15on:${versions.bouncycastle}" @@ -443,9 +443,10 @@ dependencies { testImplementation 'org.apache.httpcomponents:fluent-hc:4.5.13' testImplementation "org.apache.httpcomponents.client5:httpclient5-fluent:${versions.httpclient5}" testImplementation "org.apache.kafka:kafka_2.13:${kafka_version}" + testImplementation "org.apache.kafka:kafka-group-coordinator:${kafka_version}" testImplementation "org.apache.kafka:kafka_2.13:${kafka_version}:test" testImplementation "org.apache.kafka:kafka-clients:${kafka_version}:test" - testImplementation 'org.springframework.kafka:spring-kafka-test:2.8.6' + testImplementation 'org.springframework.kafka:spring-kafka-test:2.9.6' testImplementation 'org.springframework:spring-beans:5.3.20' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' From 08e21dd0e04c9b92c47131bc60a3de3e4043e22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santiago=20Due=C3=B1as?= Date: Mon, 27 Feb 2023 17:37:17 +0100 Subject: [PATCH 144/369] Support multitenancy for the anonymous user (#2459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current version of the plugin supports multitenancy for all the users but not for the anonymous user. The header 'securitytenant' is ignored when this user is authorized. Therefore, all the operations run with it, use the default tenant - normally the 'Global' tenant -. This patch fixes this issue assigning the selected tenant - defined by the 'securitytenant' header - to the anonymous user. Co-authored-by: Eva Millán --- .../security/auth/BackendRegistry.java | 8 +++- .../multitenancy/test/MultitenancyTests.java | 31 +++++++++++++++ .../multitenancy/config_anonymous.yml | 39 +++++++++++++++++++ src/test/resources/multitenancy/roles.yml | 20 ++++++++++ .../resources/multitenancy/roles_mapping.yml | 10 +++++ .../resources/multitenancy/roles_tenants.yml | 4 ++ 6 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/multitenancy/config_anonymous.yml diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index 79c63bca33..635811a7ae 100644 --- a/src/main/java/org/opensearch/security/auth/BackendRegistry.java +++ b/src/main/java/org/opensearch/security/auth/BackendRegistry.java @@ -338,8 +338,12 @@ public boolean authenticate(final RestRequest request, final RestChannel channel } if(authCredenetials == null && anonymousAuthEnabled) { - threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, User.ANONYMOUS); - auditLog.logSucceededLogin(User.ANONYMOUS.getName(), false, null, request); + final String tenant = Utils.coalesce(request.header("securitytenant"), request.header("security_tenant")); + User anonymousUser = new User(User.ANONYMOUS.getName(), new HashSet(User.ANONYMOUS.getRoles()), null); + anonymousUser.setRequestedTenant(tenant); + + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, anonymousUser); + auditLog.logSucceededLogin(anonymousUser.getName(), false, null, request); if (isDebugEnabled) { log.debug("Anonymous User is authenticated"); } diff --git a/src/test/java/org/opensearch/security/multitenancy/test/MultitenancyTests.java b/src/test/java/org/opensearch/security/multitenancy/test/MultitenancyTests.java index 7bfb5f9e87..5177dbae10 100644 --- a/src/test/java/org/opensearch/security/multitenancy/test/MultitenancyTests.java +++ b/src/test/java/org/opensearch/security/multitenancy/test/MultitenancyTests.java @@ -437,4 +437,35 @@ public void testTenantParametersSubstitution() throws Exception { assertThat(res.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(res.findValueInJson("_source.tenant"), equalTo(tenantNameAppended)); } + + @Test + public void testMultitenancyAnonymousUser() throws Exception { + final Settings settings = Settings.builder() + .build(); + setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_anonymous.yml"), settings); + final RestHelper rh = nonSslRestHelper(); + + HttpResponse res; + + /* Create the tenant for the anonymous user to run the tests */ + final String url = ".kibana/_doc/5.6.0?pretty"; + final String anonymousTenant = "anonymous_tenant"; + final String createTenantBody = "{\"buildNum\": 15460, \"defaultIndex\": \"anon\", \"tenant\": \"" + anonymousTenant + "\"}"; + + res = rh.executePutRequest( + url, + createTenantBody, + encodeBasicHeader("admin", "admin"), + new BasicHeader("securitytenant", anonymousTenant) + ); + + /* The anonymous user has access to its tenant */ + res = rh.executeGetRequest(url, new BasicHeader("securitytenant", anonymousTenant)); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(anonymousTenant, res.findValueInJson("_source.tenant")); + + /* No access to other tenants */ + res = rh.executeGetRequest(url, new BasicHeader("securitytenant", "human_resources")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); + } } diff --git a/src/test/resources/multitenancy/config_anonymous.yml b/src/test/resources/multitenancy/config_anonymous.yml new file mode 100644 index 0000000000..97a43b20a8 --- /dev/null +++ b/src/test/resources/multitenancy/config_anonymous.yml @@ -0,0 +1,39 @@ +--- +_meta: + type: "config" + config_version: 2 +config: + dynamic: + filtered_alias_mode: "warn" + disable_rest_auth: false + disable_intertransport_auth: false + respect_request_indices_options: false + license: null + kibana: + multitenancy_enabled: true + server_username: "kibanaserver" + index: ".kibana" + http: + anonymous_auth_enabled: true + xff: + enabled: true + internalProxies: ".*" + remoteIpHeader: "x-forwarded-for" + authc: + basic_internal_auth_domain: + http_enabled: true + transport_enabled: true + order: 0 + http_authenticator: + challenge: true + type: "basic" + config: {} + authentication_backend: + type: "intern" + config: {} + description: "Migrated from v6" + authz: {} + do_not_fail_on_forbidden: true + multi_rolespan_enabled: false + hosts_resolver_mode: "ip-only" + transport_userrname_attribute: null diff --git a/src/test/resources/multitenancy/roles.yml b/src/test/resources/multitenancy/roles.yml index e930156b70..efc17f7464 100644 --- a/src/test/resources/multitenancy/roles.yml +++ b/src/test/resources/multitenancy/roles.yml @@ -225,6 +225,7 @@ opendistro_security_all_access: - tenant_patterns: - "adm_tenant" - "test_tenant_ro" + - "anonymous_tenant" allowed_actions: - "kibana_all_write" opendistro_security_logstash: @@ -480,3 +481,22 @@ opendistro_security_role_tenant_parameters_substitution: - "${attr.internal.attribute1}_1" allowed_actions: - "kibana_all_write" +opendistro_security_anonymous_multitenancy: + reserved: false + hidden: false + description: "PR#2459" + cluster_permissions: + - "OPENDISTRO_SECURITY_CLUSTER_COMPOSITE_OPS_RO" + index_permissions: + - index_patterns: + - "*" + dls: null + fls: null + masked_fields: null + allowed_actions: + - "OPENDISTRO_SECURITY_READ" + tenant_permissions: + - tenant_patterns: + - "anonymous_tenant" + allowed_actions: + - "kibana_all_read" diff --git a/src/test/resources/multitenancy/roles_mapping.yml b/src/test/resources/multitenancy/roles_mapping.yml index 5d6ea857d2..a7d2867db1 100644 --- a/src/test/resources/multitenancy/roles_mapping.yml +++ b/src/test/resources/multitenancy/roles_mapping.yml @@ -169,3 +169,13 @@ opendistro_security_role_tenant_parameters_substitution: - "user_tenant_parameters_substitution" and_backend_roles: [] description: "PR#819 / Issue#817" +opendistro_security_anonymous_multitenancy: + reserved: false + hidden: false + backend_roles: + - opendistro_security_anonymous_backendrole + hosts: [] + users: + - "opendistro_security_anonymous" + and_backend_roles: [] + description: "PR#2459" diff --git a/src/test/resources/multitenancy/roles_tenants.yml b/src/test/resources/multitenancy/roles_tenants.yml index 26e082a42a..a4025e94da 100644 --- a/src/test/resources/multitenancy/roles_tenants.yml +++ b/src/test/resources/multitenancy/roles_tenants.yml @@ -62,3 +62,7 @@ tenant_parameters_substitution_1: reserved: false hidden: false description: "PR#819 / Issue#817" +anonymous_tenant: + reserved: false + hidden: false + description: "PR#2459" From 9cce399f980e3b8d77619ec24df8a2828207b60d Mon Sep 17 00:00:00 2001 From: Andrea Pasqualini Date: Thu, 2 Mar 2023 18:22:32 +0100 Subject: [PATCH 145/369] Clock skew tolerance for oidc token validation (#2482) --- config/config.yml | 1 + .../jwt/AbstractHTTPJwtAuthenticator.java | 6 +- .../auth/http/jwt/keybyoidc/JwtVerifier.java | 8 ++- ...wtKeyByOpenIdConnectAuthenticatorTest.java | 71 ++++++++++++++++++- .../auth/http/jwt/keybyoidc/TestJwts.java | 11 +++ 5 files changed, 91 insertions(+), 6 deletions(-) diff --git a/config/config.yml b/config/config.yml index 59f5f05352..0537ff8406 100644 --- a/config/config.yml +++ b/config/config.yml @@ -131,6 +131,7 @@ config: signing_key: "base64 encoded HMAC key or public RSA/ECDSA pem key" jwt_header: "Authorization" jwt_url_parameter: null + jwt_clock_skew_tolerance_seconds: 30 roles_key: null subject_key: null authentication_backend: diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java index 08fa0a0100..49b030a5ff 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java @@ -56,16 +56,20 @@ public abstract class AbstractHTTPJwtAuthenticator implements HTTPAuthenticator private final String subjectKey; private final String rolesKey; + public static final int DEFAULT_CLOCK_SKEW_TOLERANCE_SECONDS = 30; + private final int clockSkewToleranceSeconds ; + public AbstractHTTPJwtAuthenticator(Settings settings, Path configPath) { jwtUrlParameter = settings.get("jwt_url_parameter"); jwtHeaderName = settings.get("jwt_header", HttpHeaders.AUTHORIZATION); isDefaultAuthHeader = HttpHeaders.AUTHORIZATION.equalsIgnoreCase(jwtHeaderName); rolesKey = settings.get("roles_key"); subjectKey = settings.get("subject_key"); + clockSkewToleranceSeconds = settings.getAsInt("jwt_clock_skew_tolerance_seconds", DEFAULT_CLOCK_SKEW_TOLERANCE_SECONDS); try { this.keyProvider = this.initKeyProvider(settings, configPath); - jwtVerifier = new JwtVerifier(keyProvider); + jwtVerifier = new JwtVerifier(keyProvider, clockSkewToleranceSeconds ); } catch (Exception e) { log.error("Error creating JWT authenticator. JWT authentication will not work", e); diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java index cffb3cf5da..99074ab233 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java @@ -32,9 +32,11 @@ public class JwtVerifier { private final static Logger log = LogManager.getLogger(JwtVerifier.class); private final KeyProvider keyProvider; + private final int clockSkewToleranceSeconds; - public JwtVerifier(KeyProvider keyProvider) { + public JwtVerifier(KeyProvider keyProvider, int clockSkewToleranceSeconds ) { this.keyProvider = keyProvider; + this.clockSkewToleranceSeconds = clockSkewToleranceSeconds; } public JwtToken getVerifiedJwtToken(String encodedJwt) throws BadCredentialsException { @@ -108,8 +110,8 @@ private void validateClaims(JwtToken jwt) throws BadCredentialsException, JwtExc JwtClaims claims = jwt.getClaims(); if (claims != null) { - JwtUtils.validateJwtExpiry(claims, 0, false); - JwtUtils.validateJwtNotBefore(claims, 0, false); + JwtUtils.validateJwtExpiry(claims, clockSkewToleranceSeconds, false); + JwtUtils.validateJwtNotBefore(claims, clockSkewToleranceSeconds, false); } } } diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java index 03769ab585..a2df67632b 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java @@ -8,7 +8,6 @@ * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ - package com.amazon.dlic.auth.http.jwt.keybyoidc; import java.util.HashMap; @@ -113,15 +112,83 @@ public void testExp() throws Exception { Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - + AuthCredentials creds = jwtAuth.extractCredentials( new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_EXPIRED_SIGNED_OCT_1), new HashMap()), null); Assert.assertNull(creds); + } + + @Test + public void testExpInSkew() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("jwt_clock_skew_tolerance_seconds", "10") + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + long expiringDate = System.currentTimeMillis()/1000-5; + long notBeforeDate = System.currentTimeMillis()/1000-25; + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest( + ImmutableMap.of( + "Authorization", + "bearer "+TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), + new HashMap()), + null); + + Assert.assertNotNull(creds); } + @Test + public void testNbf() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("jwt_clock_skew_tolerance_seconds", "0") + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + long expiringDate = 20+System.currentTimeMillis()/1000; + long notBeforeDate = 5+System.currentTimeMillis()/1000; + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest( + ImmutableMap.of( + "Authorization", + "bearer "+TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), + new HashMap()), + null); + + Assert.assertNull(creds); + } + + @Test + public void testNbfInSkew() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("jwt_clock_skew_tolerance_seconds", "10") + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + long expiringDate = 20+System.currentTimeMillis()/1000; + long notBeforeDate = 5+System.currentTimeMillis()/1000;; + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "bearer "+TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), + new HashMap()), + null); + + Assert.assertNotNull(creds); + } + + @Test public void testRS256() throws Exception { diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java index 0fdfbb166d..217e64926c 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java @@ -105,4 +105,15 @@ static String createSignedWithPeculiarEscaping(JwtToken baseJwt, JsonWebKey jwk) return new JoseJwtProducer().processJwt(signedToken, null, signatureProvider); } + static String createMcCoySignedOct1(long nbf, long exp) + { + JwtToken jwt_token = create( + MCCOY_SUBJECT, TEST_AUDIENCE, + ROLES_CLAIM, TEST_ROLES_STRING, + JwtConstants.CLAIM_NOT_BEFORE, nbf, + JwtConstants.CLAIM_EXPIRY, exp); + + return createSigned(jwt_token, TestJwk.OCT_1); + } + } From 68feb5c4d9829ee5c668daea5c169edf32a5c806 Mon Sep 17 00:00:00 2001 From: Andrea Pasqualini Date: Mon, 6 Mar 2023 15:34:53 +0100 Subject: [PATCH 146/369] Fix testExp issue (#2501) The "testExp" test was missing the "Bearer" auth-scheme in the request, which caused the token expiry test to not execute properly. Signed-off-by: Andrea Pasqualini --- .../HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java index a2df67632b..6419e84891 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java @@ -114,7 +114,7 @@ public void testExp() throws Exception { HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_EXPIRED_SIGNED_OCT_1), + new FakeRestRequest(ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_EXPIRED_SIGNED_OCT_1), new HashMap()), null); @@ -137,7 +137,7 @@ public void testExpInSkew() throws Exception { new FakeRestRequest( ImmutableMap.of( "Authorization", - "bearer "+TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), + "Bearer "+TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), new HashMap()), null); @@ -160,7 +160,7 @@ public void testNbf() throws Exception { new FakeRestRequest( ImmutableMap.of( "Authorization", - "bearer "+TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), + "Bearer "+TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), new HashMap()), null); @@ -181,7 +181,7 @@ public void testNbfInSkew() throws Exception { AuthCredentials creds = jwtAuth.extractCredentials( new FakeRestRequest( - ImmutableMap.of("Authorization", "bearer "+TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), + ImmutableMap.of("Authorization", "Bearer "+TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), new HashMap()), null); From fb70a751312af9ab9683aaff878c8b145dda7ac6 Mon Sep 17 00:00:00 2001 From: Chang Liu Date: Mon, 6 Mar 2023 06:36:01 -0800 Subject: [PATCH 147/369] add index template put permission to kibana_server role (#2503) Signed-off-by: Chang Liu --- src/main/resources/static_config/static_roles.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/static_config/static_roles.yml b/src/main/resources/static_config/static_roles.yml index 0d7f66531a..417e4f0ab7 100644 --- a/src/main/resources/static_config/static_roles.yml +++ b/src/main/resources/static_config/static_roles.yml @@ -87,6 +87,7 @@ kibana_server: - "cluster_composite_ops" - "manage_point_in_time" - "indices:admin/template*" + - "indices:admin/index_template*" - "indices:data/read/scroll*" index_permissions: - index_patterns: From 8d7b9043f49027e6186f770dfc28e7b20cf21671 Mon Sep 17 00:00:00 2001 From: Andrey Pleskach Date: Tue, 7 Mar 2023 00:20:33 +0100 Subject: [PATCH 148/369] Fix lost privileges during auto initializing of the index (#2498) * Lost privileges fix During default initialization of the plugin configuration (plugins.security.allow_default_init_securityindex is set to true) it is possible that plugin could lose its privileges due to the thread context switching for the cluster with more than 3 nodes. Signed-off-by: Andrey Pleskach * Wait for cluster managed node Added a new check that waits while cluster is in the global lock state and do not initialize index util cluster will finish leader election. Signed-off-by: Andrey Pleskach --------- Signed-off-by: Andrey Pleskach --- .../ConfigurationRepository.java | 142 +++++++++--------- .../security/support/ConfigHelper.java | 45 +++--- 2 files changed, 97 insertions(+), 90 deletions(-) diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java index 19e036b5e9..31e2a7d16f 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java @@ -65,6 +65,7 @@ import org.opensearch.common.util.concurrent.ThreadContext.StoredContext; import org.opensearch.common.xcontent.XContentType; import org.opensearch.env.Environment; +import org.opensearch.rest.RestStatus; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.auditlog.config.AuditConfig; import org.opensearch.security.securityconf.DynamicConfigFactory; @@ -110,91 +111,92 @@ private ConfigurationRepository(Settings settings, final Path configPath, Thread .newBuilder() .build(); - bgThread = new Thread(new Runnable() { - @Override - public void run() { - try { - LOGGER.info("Background init thread started. Install default config?: "+installDefaultConfig.get()); - - - if(installDefaultConfig.get()) { + bgThread = new Thread(() -> { + try { + LOGGER.info("Background init thread started. Install default config?: "+installDefaultConfig.get()); + // wait for the cluster here until it will finish managed node election + while (clusterService.state().blocks().hasGlobalBlockWithStatus(RestStatus.SERVICE_UNAVAILABLE)) { + LOGGER.info("Wait for cluster to be available ..."); + TimeUnit.SECONDS.sleep(1); + } - try { - String lookupDir = System.getProperty("security.default_init.dir"); - final String cd = lookupDir != null? (lookupDir+"/") : new Environment(settings, configPath).configDir().toAbsolutePath().toString()+"/opensearch-security/"; - File confFile = new File(cd+"config.yml"); - if(confFile.exists()) { - final ThreadContext threadContext = threadPool.getThreadContext(); - try(StoredContext ctx = threadContext.stashContext()) { - threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); - - createSecurityIndexIfAbsent(); - waitForSecurityIndexToBeAtLeastYellow(); - - ConfigHelper.uploadFile(client, cd+"config.yml", securityIndex, CType.CONFIG, DEFAULT_CONFIG_VERSION); - ConfigHelper.uploadFile(client, cd+"roles.yml", securityIndex, CType.ROLES, DEFAULT_CONFIG_VERSION); - ConfigHelper.uploadFile(client, cd+"roles_mapping.yml", securityIndex, CType.ROLESMAPPING, DEFAULT_CONFIG_VERSION); - ConfigHelper.uploadFile(client, cd+"internal_users.yml", securityIndex, CType.INTERNALUSERS, DEFAULT_CONFIG_VERSION); - ConfigHelper.uploadFile(client, cd+"action_groups.yml", securityIndex, CType.ACTIONGROUPS, DEFAULT_CONFIG_VERSION); - if(DEFAULT_CONFIG_VERSION == 2) { - ConfigHelper.uploadFile(client, cd+"tenants.yml", securityIndex, CType.TENANTS, DEFAULT_CONFIG_VERSION); - } - final boolean populateEmptyIfFileMissing = true; - ConfigHelper.uploadFile(client, cd+"nodes_dn.yml", securityIndex, CType.NODESDN, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); - ConfigHelper.uploadFile(client, cd + "whitelist.yml", securityIndex, CType.WHITELIST, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); - ConfigHelper.uploadFile(client, cd + "allowlist.yml", securityIndex, CType.ALLOWLIST, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); - - // audit.yml is not packaged by default - final String auditConfigPath = cd + "audit.yml"; - if (new File(auditConfigPath).exists()) { - ConfigHelper.uploadFile(client, auditConfigPath, securityIndex, CType.AUDIT, DEFAULT_CONFIG_VERSION); - } + if(installDefaultConfig.get()) { + + try { + String lookupDir = System.getProperty("security.default_init.dir"); + final String cd = lookupDir != null? (lookupDir+"/") : new Environment(settings, configPath).configDir().toAbsolutePath().toString()+"/opensearch-security/"; + File confFile = new File(cd+"config.yml"); + if(confFile.exists()) { + final ThreadContext threadContext = threadPool.getThreadContext(); + try(StoredContext ctx = threadContext.stashContext()) { + threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); + + createSecurityIndexIfAbsent(); + waitForSecurityIndexToBeAtLeastYellow(); + + ConfigHelper.uploadFile(client, cd+"config.yml", securityIndex, CType.CONFIG, DEFAULT_CONFIG_VERSION); + ConfigHelper.uploadFile(client, cd+"roles.yml", securityIndex, CType.ROLES, DEFAULT_CONFIG_VERSION); + ConfigHelper.uploadFile(client, cd+"roles_mapping.yml", securityIndex, CType.ROLESMAPPING, DEFAULT_CONFIG_VERSION); + ConfigHelper.uploadFile(client, cd+"internal_users.yml", securityIndex, CType.INTERNALUSERS, DEFAULT_CONFIG_VERSION); + ConfigHelper.uploadFile(client, cd+"action_groups.yml", securityIndex, CType.ACTIONGROUPS, DEFAULT_CONFIG_VERSION); + if(DEFAULT_CONFIG_VERSION == 2) { + ConfigHelper.uploadFile(client, cd+"tenants.yml", securityIndex, CType.TENANTS, DEFAULT_CONFIG_VERSION); + } + final boolean populateEmptyIfFileMissing = true; + ConfigHelper.uploadFile(client, cd+"nodes_dn.yml", securityIndex, CType.NODESDN, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); + ConfigHelper.uploadFile(client, cd + "whitelist.yml", securityIndex, CType.WHITELIST, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); + ConfigHelper.uploadFile(client, cd + "allowlist.yml", securityIndex, CType.ALLOWLIST, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); + + // audit.yml is not packaged by default + final String auditConfigPath = cd + "audit.yml"; + if (new File(auditConfigPath).exists()) { + ConfigHelper.uploadFile(client, auditConfigPath, securityIndex, CType.AUDIT, DEFAULT_CONFIG_VERSION); } - } else { - LOGGER.error("{} does not exist", confFile.getAbsolutePath()); } - } catch (Exception e) { - LOGGER.error("Cannot apply default config (this is maybe not an error!)", e); + } else { + LOGGER.error("{} does not exist", confFile.getAbsolutePath()); } + } catch (Exception e) { + LOGGER.error("Cannot apply default config (this is maybe not an error!)", e); } + } - while(!dynamicConfigFactory.isInitialized()) { + while(!dynamicConfigFactory.isInitialized()) { + try { + LOGGER.debug("Try to load config ..."); + reloadConfiguration(Arrays.asList(CType.values())); + break; + } catch (Exception e) { + LOGGER.debug("Unable to load configuration due to {}", String.valueOf(ExceptionUtils.getRootCause(e))); try { - LOGGER.debug("Try to load config ..."); - reloadConfiguration(Arrays.asList(CType.values())); + Thread.sleep(3000); + } catch (InterruptedException e1) { + Thread.currentThread().interrupt(); + LOGGER.debug("Thread was interrupted so we cancel initialization"); break; - } catch (Exception e) { - LOGGER.debug("Unable to load configuration due to {}", String.valueOf(ExceptionUtils.getRootCause(e))); - try { - Thread.sleep(3000); - } catch (InterruptedException e1) { - Thread.currentThread().interrupt(); - LOGGER.debug("Thread was interrupted so we cancel initialization"); - break; - } } } + } - final Set deprecatedAuditKeysInSettings = AuditConfig.getDeprecatedKeys(settings); + final Set deprecatedAuditKeysInSettings = AuditConfig.getDeprecatedKeys(settings); + if (!deprecatedAuditKeysInSettings.isEmpty()) { + LOGGER.warn("Following keys {} are deprecated in opensearch settings. They will be removed in plugin v2.0.0.0", deprecatedAuditKeysInSettings); + } + final boolean isAuditConfigDocPresentInIndex = cl.isAuditConfigDocPresentInIndex(); + if (isAuditConfigDocPresentInIndex) { if (!deprecatedAuditKeysInSettings.isEmpty()) { - LOGGER.warn("Following keys {} are deprecated in opensearch settings. They will be removed in plugin v2.0.0.0", deprecatedAuditKeysInSettings); - } - final boolean isAuditConfigDocPresentInIndex = cl.isAuditConfigDocPresentInIndex(); - if (isAuditConfigDocPresentInIndex) { - if (!deprecatedAuditKeysInSettings.isEmpty()) { - LOGGER.warn("Audit configuration settings found in both index and opensearch settings (deprecated)"); - } - LOGGER.info("Hot-reloading of audit configuration is enabled"); - } else { - LOGGER.info("Hot-reloading of audit configuration is disabled. Using configuration with defaults from opensearch settings. Populate the configuration in index using audit.yml or securityadmin to enable it."); - auditLog.setConfig(AuditConfig.from(settings)); + LOGGER.warn("Audit configuration settings found in both index and opensearch settings (deprecated)"); } + LOGGER.info("Hot-reloading of audit configuration is enabled"); + } else { + LOGGER.info("Hot-reloading of audit configuration is disabled. Using configuration with defaults from opensearch settings. Populate the configuration in index using audit.yml or securityadmin to enable it."); + auditLog.setConfig(AuditConfig.from(settings)); + } - LOGGER.info("Node '{}' initialized", clusterService.localNode().getName()); + LOGGER.info("Node '{}' initialized", clusterService.localNode().getName()); - } catch (Exception e) { - LOGGER.error("Unexpected exception while initializing node "+e, e); - } + } catch (Exception e) { + LOGGER.error("Unexpected exception while initializing node "+e, e); } }); diff --git a/src/main/java/org/opensearch/security/support/ConfigHelper.java b/src/main/java/org/opensearch/security/support/ConfigHelper.java index f451b8794d..925962c816 100644 --- a/src/main/java/org/opensearch/security/support/ConfigHelper.java +++ b/src/main/java/org/opensearch/security/support/ConfigHelper.java @@ -32,6 +32,8 @@ import java.io.Reader; import java.io.StringReader; import java.nio.charset.StandardCharsets; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -56,7 +58,7 @@ import static org.opensearch.core.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; public class ConfigHelper { - + private static final Logger LOGGER = LogManager.getLogger(ConfigHelper.class); public static void uploadFile(Client tc, String filepath, String index, CType cType, int configVersion) throws Exception { @@ -67,27 +69,30 @@ public static void uploadFile(Client tc, String filepath, String index, CType cT final String configType = cType.toLCString(); LOGGER.info("Will update '" + configType + "' with " + filepath + " and populate it with empty doc if file missing and populateEmptyIfFileMissing=" + populateEmptyIfFileMissing); - if (!populateEmptyIfFileMissing) { - ConfigHelper.fromYamlFile(filepath, cType, configVersion, 0, 0); - } - - try (Reader reader = createFileOrStringReader(cType, configVersion, filepath, populateEmptyIfFileMissing)) { - - final IndexRequest indexRequest = new IndexRequest(index) - .id(configType) - .opType(OpType.CREATE) - .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(configType, readXContent(reader, XContentType.YAML)); - final String res = tc.index(indexRequest).actionGet().getId(); + AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + if (!populateEmptyIfFileMissing) { + ConfigHelper.fromYamlFile(filepath, cType, configVersion, 0, 0); + } - if (!configType.equals(res)) { - throw new Exception(" FAIL: Configuration for '" + configType - + "' failed for unknown reasons. Pls. consult logfile of opensearch"); + try (Reader reader = createFileOrStringReader(cType, configVersion, filepath, populateEmptyIfFileMissing)) { + + final IndexRequest indexRequest = new IndexRequest(index) + .id(configType) + .opType(OpType.CREATE) + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source(configType, readXContent(reader, XContentType.YAML)); + final String res = tc.index(indexRequest).actionGet().getId(); + + if (!configType.equals(res)) { + throw new Exception(" FAIL: Configuration for '" + configType + + "' failed for unknown reasons. Pls. consult logfile of opensearch"); + } + LOGGER.info("Doc with id '{}' and version {} is updated in {} index.", configType, configVersion, index); + } catch (VersionConflictEngineException versionConflictEngineException) { + LOGGER.info("Index {} already contains doc with id {}, skipping update.", index, configType); } - LOGGER.info("Doc with id '{}' and version {} is updated in {} index.", configType, configVersion, index); - } catch (VersionConflictEngineException versionConflictEngineException) { - LOGGER.info("Index {} already contains doc with id {}, skipping update.", index, configType); - } + return null; + }); } public static Reader createFileOrStringReader(CType cType, int configVersion, String filepath, boolean populateEmptyIfFileMissing) throws Exception { From 1daff1180ba2a0f02b9d4e38ecb20937572b5bad Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 9 Mar 2023 10:00:50 -0500 Subject: [PATCH 149/369] Update to gradle 8.0.2 (#2520) Signed-off-by: Craig Perkins --- build.gradle | 26 +++++++++--------- bwc-test/gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 61608 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 +- bwc-test/gradlew | 4 +-- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 4 +-- 7 files changed, 21 insertions(+), 19 deletions(-) diff --git a/build.gradle b/build.gradle index a786a41002..3996559755 100644 --- a/build.gradle +++ b/build.gradle @@ -52,13 +52,13 @@ plugins { id 'idea' id 'jacoco' id 'maven-publish' - id 'com.diffplug.spotless' version '5.11.0' + id 'com.diffplug.spotless' version '6.16.0' id 'checkstyle' - id 'nebula.ospackage' version "8.3.0" - id "org.gradle.test-retry" version "1.4.1" + id 'com.netflix.nebula.ospackage' version "11.0.0" + id "org.gradle.test-retry" version "1.5.2" id 'eclipse' id "com.github.spotbugs" version "5.0.13" - id "com.google.osdetector" version "1.7.1" + id "com.google.osdetector" version "1.7.3" } allprojects { @@ -206,7 +206,9 @@ opensearchplugin { loggerUsageCheck.enabled = false // No need to validate pom, as we do not upload to maven/sonatype -validateNebulaPom.enabled = false +tasks.matching {it.path in [":validateMavenPom", ":validateNebulaPom", ":validatePluginZipPom"]}.all { task -> + task.dependsOn ':generatePomFileForNebulaPublication', ':generatePomFileForPluginZipPublication', ':generatePomFileForMavenPublication' +} publishing { publications { @@ -550,12 +552,12 @@ task bundleSecurityAdminStandaloneTarGz(dependsOn: jar, type: Tar) { buildRpm { arch = 'NOARCH' addParentDirs = false - archiveName "${packageName}-${version}.rpm" + archiveFileName = "${packageName}-${version}.rpm" } buildDeb { arch = 'all' - archiveName "${packageName}-${version}.deb" + archiveFileName = "${packageName}-${version}.deb" } publishing { @@ -623,9 +625,8 @@ afterEvaluate { task renameRpm(type: Copy) { from("$buildDir/distributions") into("$buildDir/distributions") - include archiveName - rename archiveName, "${packageName}-${version}.rpm" - doLast { delete file("$buildDir/distributions/$archiveName") } + rename "$archiveFileName", "${packageName}-${version}.rpm" + doLast { delete file("$buildDir/distributions/$archiveFileName") } } } @@ -636,9 +637,8 @@ afterEvaluate { task renameDeb(type: Copy) { from("$buildDir/distributions") into("$buildDir/distributions") - include archiveName - rename archiveName, "${packageName}-${version}.deb" - doLast { delete file("$buildDir/distributions/$archiveName") } + rename "$archiveFileName", "${packageName}-${version}.deb" + doLast { delete file("$buildDir/distributions/$archiveFileName") } } } diff --git a/bwc-test/gradle/wrapper/gradle-wrapper.jar b/bwc-test/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..ccebba7710deaf9f98673a68957ea02138b60d0a 100644 GIT binary patch delta 5094 zcmZu#c|6qH|DG9RA4`noBZNWrC2N)tSqjO%%aX0^O4dPAB*iC6_9R<`apl^#h-_oY z)(k_0v8Fxp{fyi9-uwN%e)GpU&v~BrS>~KG^PF=MNmQjIDr&QHR7f-kM{%U_u*1=5 zGC}ae5(^Rrg9QY8$x^}oiJ0d2O9YW{J~$dD1ovlvh&0B4L)!4S=z;Hac>K{#9q9cKq;>>BtKo1!+gw`yqE zSK8x^jC|B!qmSW#uyb@T^CkB9qRd{N3V-rEi}AEgoU_J27lw_0X`}c0&m9JhxM;RK z54_gdZ(u?R5`B3}NeVal2NTHqlktM`2eTF28%6BZCWW$-shf0l-BOVSm)hU58MTPy zDcY-5777j;ccU!Yba8wH=X6OdPJ8O5Kp^3gUNo>!b=xb6T2F&LiC2eBJj8KuLPW!4 zw3V^NnAKZm^D?tmliCvzi>UtoDH%V#%SM0d*NS+m%4}qO<)M1E{OpQ(v&ZNc`vdi| zEGlVi$Dgxy1p6+k0qGLQt(JwxZxLCZ4>wJ=sb0v%Ki?*+!ic_2exumn{%Co|| z-axdK#RUC;P|vqbe?L`K!j;sUo=uuR_#ZkRvBf%Txo6{OL&I(?dz?47Z(DcX3KTw> zGY%A=kX;fBkq$F^sX|-)1Qkg##+n-Ci{qJVPj@P?l_1Y`nD^v>fZ3HMX%(4p-TlD(>yWwJij!6Jw}l7h>CIm@Ou5B@$Wy`Ky*814%Mdi1GfG1zDG9NogaoVHHr4gannv4?w6g&10!j=lKM zFW;@=Z0}vAPAxA=R4)|`J??*$|Fh`5=ks*V7TapX`+=4n*{aXxRhh-EGX_Xrzjb4r zn0vO7Cc~wtyeM_8{**~9y7>+}1JV8Buhg%*hy|PUc#!vw#W(HFTL|BpM)U0>JxG6S zLnqn1!0++RyyJ>5VU<4mDv8>Q#{EtgS3mj7Hx}Zkr0tz1}h8Kn6q`MiwC z{Y#;D!-ndlImST(C@(*i5f0U(jD29G7g#nkiPX zki6M$QYX_fNH=E4_eg9*FFZ3wF9YAKC}CP89Kl(GNS(Ag994)0$OL4-fj_1EdR}ARB#-vP_$bWF`Qk58+ z4Jq*-YkcmCuo9U%oxGeYe7Be=?n}pX+x>ob(8oPLDUPiIryT8v*N4@0{s_VYALi;lzj19ivLJKaXt7~UfU|mu9zjbhPnIhG2`uI34urWWA9IO{ z_1zJ)lwSs{qt3*UnD}3qB^kcRZ?``>IDn>qp8L96bRaZH)Zl`!neewt(wjSk1i#zf zb8_{x_{WRBm9+0CF4+nE)NRe6K8d|wOWN)&-3jCDiK5mj>77=s+TonlH5j`nb@rB5 z5NX?Z1dk`E#$BF{`(D>zISrMo4&}^wmUIyYL-$PWmEEfEn-U0tx_vy$H6|+ zi{ytv2@JXBsot|%I5s74>W1K{-cvj0BYdNiRJz*&jrV9>ZXYZhEMULcM=fCmxkN&l zEoi=)b)Vazc5TQC&Q$oEZETy@!`Gnj`qoXl7mcwdY@3a-!SpS2Mau|uK#++@>H8QC zr2ld8;<_8We%@E?S=E?=e9c$BL^9X?bj*4W;<+B&OOe+3{<`6~*fC(=`TO>o^A(Y! zA`Qc1ky?*6xjVfR?ugE~oY`Gtzhw^{Z@E6vZ`mMRAp>Odpa!m zzWmtjT|Lj^qiZMfj%%un-o$Eu>*v12qF{$kCKai^?DF=$^tfyV%m9;W@pm-BZn_6b z{jsXY3!U`%9hzk6n7YyHY%48NhjI6jjuUn?Xfxe0`ARD_Q+T_QBZ{ zUK@!63_Wr`%9q_rh`N4=J=m;v>T{Y=ZLKN^m?(KZQ2J%|3`hV0iogMHJ} zY6&-nXirq$Yhh*CHY&Qf*b@@>LPTMf z(cMorwW?M11RN{H#~ApKT)F!;R#fBHahZGhmy>Sox`rk>>q&Y)RG$-QwH$_TWk^hS zTq2TC+D-cB21|$g4D=@T`-ATtJ?C=aXS4Q}^`~XjiIRszCB^cvW0OHe5;e~9D%D10 zl4yP4O=s-~HbL7*4>#W52eiG7*^Hi)?@-#*7C^X5@kGwK+paI>_a2qxtW zU=xV7>QQROWQqVfPcJ$4GSx`Y23Z&qnS?N;%mjHL*EVg3pBT{V7bQUI60jtBTS?i~ zycZ4xqJ<*3FSC6_^*6f)N|sgB5Bep(^%)$=0cczl>j&n~KR!7WC|3;Zoh_^GuOzRP zo2Hxf50w9?_4Qe368fZ0=J|fR*jO_EwFB1I^g~i)roB|KWKf49-)!N%Ggb%w=kB8)(+_%kE~G!(73aF=yCmM3Cfb9lV$G!b zoDIxqY{dH>`SILGHEJwq%rwh46_i`wkZS-NY95qdNE)O*y^+k#JlTEij8NT(Y_J!W zFd+YFoZB|auOz~A@A{V*c)o7E(a=wHvb@8g5PnVJ&7D+Fp8ABV z5`&LD-<$jPy{-y*V^SqM)9!#_Pj2-x{m$z+9Z*o|JTBGgXYYVM;g|VbitDUfnVn$o zO)6?CZcDklDoODzj+ti@i#WcqPoZ!|IPB98LW!$-p+a4xBVM@%GEGZKmNjQMhh)zv z7D){Gpe-Dv=~>c9f|1vANF&boD=Nb1Dv>4~eD636Lldh?#zD5{6JlcR_b*C_Enw&~ z5l2(w(`{+01xb1FCRfD2ap$u(h1U1B6e&8tQrnC}Cy0GR=i^Uue26Rc6Dx}!4#K*0 zaxt`a+px7-Z!^(U1WN2#kdN#OeR|2z+C@b@w+L67VEi&ZpAdg+8`HJT=wIMJqibhT ztb3PFzsq&7jzQuod3xp7uL?h-7rYao&0MiT_Bux;U*N#ebGv92o(jM2?`1!N2W_M* zeo9$%hEtIy;=`8z1c|kL&ZPn0y`N)i$Y1R9>K!el{moiy)014448YC#9=K zwO3weN|8!`5bU_#f(+ZrVd*9`7Uw?!q?yo&7sk&DJ;#-^tcCtqt5*A(V;&LdHq7Hg zI6sC@!ly9p$^@v&XDsgIuv;9#w^!C1n5+10-tEw~ZdO1kqMDYyDl!5__o}f3hYe2M zCeO)~m&&=JZn%cVH3HzPlcE`9^@``2u+!Y}Remn)DLMHc-h5A9ATgs;7F7=u2=vBlDRbjeYvyNby=TvpI{5nb2@J_YTEEEj4q<@zaGSC_i&xxD!6)d zG{1??({Ma<=Wd4JL%bnEXoBOU_0bbNy3p%mFrMW>#c zzPEvryBevZVUvT^2P&Zobk#9j>vSIW_t?AHy>(^x-Bx~(mvNYb_%$ZFg(s5~oka+Kp(GU68I$h(Vq|fZ zC_u1FM|S)=ldt#5q>&p4r%%p)*7|Rf0}B#-FwHDTo*|P6HB_rz%R;{==hpl#xTt@VLdSrrf~g^ z`IA8ZV1b`UazYpnkn28h&U)$(gdZ*f{n`&kH%Oy54&Z;ebjlh4x?JmnjFAALu}EG} zfGmQ$5vEMJMH`a=+*src#dWK&N1^LFxK9Sa#q_rja$JWra09we<2oL9Q9Sx)?kZFW z$jhOFGE~VcihYlkaZv8?uA7v$*}?2h6i%Qmgc4n~3E(O_`YCRGy~}`NFaj@(?Wz;GS_?T+RqU{S)eD1j$1Gr;C^m z7zDK=xaJ^6``=#Y-2ssNfdRqh0ntJrutGV5Nv&WI%3k1wmD5n+0aRe{0k^!>LFReN zx1g*E>nbyx03KU~UT6->+rG%(owLF=beJxK&a0F;ie1GZ^eKg-VEZb&=s&ajKS#6w zjvC6J#?b|U_(%@uq$c#Q@V_me0S1%)pKz9--{EKwyM}_gOj*Og-NEWLDF_oFtPjG; zXCZ7%#=s}RKr&_5RFN@=H(015AGl4XRN9Bc51`;WWt%vzQvzexDI2BZ@xP~^2$I&7 zA(ndsgLsmA*su8p-~IS q+ZJUZM}`4#Zi@l2F-#HCw*??ha2ta#9s8?H3%YId(*zJG6aF78h1yF1 delta 5107 zcmY*d1zc0@|J{HQlai7V5+f#EN-H%&UP4MFm6QgFfuJK4DG4u#ARsbQL4i>MB1q|w zmWd#pqd~BR-yN@ieE-|$^W1aKIZtf&-p_fyw{(Uwc7_sWYDh^12cY!qXvcPQ!qF;q@b0nYU7 zP&ht}K7j%}P%%|ffm;4F0^i3P0R`a!2wm89L5P3Kfu;tTZJre<{N5}AzsH+E3DS`Q zJLIl`LRMf`JOTBLf(;IV(9(h{(}dXK!cPoSLm(o@fz8vRz}6fOw%3}3VYOsCczLF` za2RTsCWa2sS-uw(6|HLJg)Xf@S8#|+(Z5Y)ER+v+8;btfB3&9sWH6<=U}0)o-jIts zsi?Nko;No&JyZI%@1G&zsG5kKo^Zd7rk_9VIUao9;fC~nv(T0F&Af0&Rp`?x94EIS zUBPyBe5R5#okNiB1Xe--q4|hPyGzhJ?Lurt#Ci09BQ+}rlHpBhm;EmfLw{EbCz)sg zgseAE#f$met1jo;`Z6ihk?O1be3aa$IGV69{nzagziA!M*~E5lMc(Sp+NGm2IUjmn zql((DU9QP~Tn1pt6L`}|$Na-v(P+Zg&?6bAN@2u%KiB*Gmf}Z)R zMENRJgjKMqVbMpzPO{`!J~2Jyu7&xXnTDW?V?IJgy+-35q1)-J8T**?@_-2H`%X+6f5 zIRv`uLp&*?g7L~6+3O*saXT~gWsmhF*FNKw4X$29ePKi02G*)ysenhHv{u9-y?_do ztT(Cu04pk>51n}zu~=wgToY5Cx|MTlNw}GR>+`|6CAhQn=bh@S<7N)`w};;KTywDU z=QWO@RBj$WKOXSgCWg{BD`xl&DS!G}`Mm3$)=%3jzO_C+s+mfTFH5JL>}*(JKs@MqX|o2b#ZBX5P;p7;c)$F1y4HwvJ?KA938$rd)gn_U^CcUtmdaBW57 zlPph>Fz&L`cSScFjcj+7Jif3vxb20Ag~FPstm?9#OrD$e?Y~#1osDB0CFZ9Mu&%iE zSj~wZpFqu6!k%BT)}$F@Z%(d-Pqy07`N8ch2F7z^=S-!r-@j{#&{SM@a8O$P#SySx zZLD_z=I300OCA1YmKV0^lo@>^)THfZvW}s<$^w^#^Ce=kO5ymAnk>H7pK!+NJ-+F7 z1Bb6Y=r)0nZ+hRXUyD+BKAyecZxb+$JTHK5k(nWv*5%2a+u*GDt|rpReYQ}vft zXrIt#!kGO85o^~|9Oc-M5A!S@9Q)O$$&g8u>1=ew?T35h8B{-Z_S78oe=E(-YZhBPe@Y1sUt63A-Cdv>D1nIT~=Rub6$?8g>meFb7Ic@w^%@RN2z72oPZ#Ta%b(P1|&6I z61iO<8hT*)p19Bgd0JgXP{^c{P2~K@^DIXv=dF(u|DFfqD^dMIl8-x)xKIpJRZru@ zDxicyYJG}mh}=1Dfg%B$#H`CiAxPTj^;f4KRMZHUz-_x6)lEq!^mu%72*PI=t$6{Uql#dqm4 zClgaN63!&?v*enz4k1sbaM+yCqUf+i9rw$(YrY%ir1+%cWRB<;r}$8si!6QcNAk~J zk3?dejBaC`>=T<=y=>QVt*4kL>SwYwn$(4ES793qaH)>n(axyV3R5jdXDh#e-N0K- zuUgk|N^|3*D1!Wlz-!M*b}Zc5=;K6I+>1N$&Q%)&8LWUiTYi&aQIj(luA< zN5R<8Y8L#*i0xBio$jWcaiZ4S2w3#R@CGemesy~akKP)2GojQF6!$}!_RdUJPBevX zG#~uz%Yirb0@1wgQ;ayb=qD}6{=QXxjuZQ@@kxbN!QWhtEvuhS2yAZe8fZy6*4Inr zdSyR9Dec4HrE|I=z-U;IlH;_h#7e^Hq}gaJ<-z^}{*s!m^66wu2=(*EM0UaV*&u1q zJrq!K23TO8a(ecSQFdD$y+`xu)Xk36Z*;1i{hS=H2E<8<5yHuHG~22-S+Jq|3HMAw z%qBz3auT=M!=5F|Wqke|I^E8pmJ-}>_DwX5w%d3MSdC>xW%$ocm8w8HRdZ|^#cEt1 zM*I7S6sLQq;;Mecet(Q()+?s+&MeVLOvx}(MkvytkvLHl7h*N0AT1#AqC&(he(^%przH`KqA$z_dAvJJb409@F)fYwD$JW_{_Oie8!@VdJE zU>D$@B?LawAf5$;`AZ1E!krn=aAC%4+YQrzL!59yl1;|T2)u=RBYA8lk0Ek&gS!Rb zt0&hVuyhSa0}rpZGjTA>Gz}>Uv*4)F zf7S%D2nfA7x?gPEXZWk8DZimQs#xi0?So_k`2zb!UVQEAcbvjPLK9v>J~!awnxGpq zEh$EPOc4q&jywmglnC&D)1-P0DH!@)x;uJwMHdhPh>ZLWDw+p1pf52{X2dk{_|UOmakJa4MHu?CY`6Hhv!!d7=aNwiB5z zb*Wlq1zf^3iDlPf)b_SzI*{JCx2jN;*s~ra8NeB!PghqP!0po-ZL?0Jk;2~*~sCQ<%wU`mRImd)~!23RS?XJu|{u( ztFPy3*F=ZhJmBugTv48WX)4U*pNmm~4oD4}$*-92&<)n=R)5lT z-VpbEDk>(C1hoo#-H_u0`#%L6L$ zln(}h2*Cl(5(JtVM{YZ26@Fwmp;?Qt}9$_F%`?+-JHbC;bPZj8PLq9 zWo-KFw!i&r8WuA-!3F_m9!24Z(RhalAUR~_H#Ln=$%b5GY z)oB)zO%J5TY}&BXq^7#M>euVL%01Tzj4$6^ZOjT*7@zr~q@6GEjGi)nbwzSL`TiLN z{DVG~I$w@%^#tD{>1Ap@%=XogG_^Hvy_xiRn4yy?LKsC+ zU!S79X8orh&D%>1S`x2iyi&(iG&r#YT{}~iy(FIOo8?MZU#eo*c*(RjAGj@uDi zARJur)-*{n0PgW~&mFeg`MJ?(Kr;NUom)jh?ozZtyywN9bea6ikQlh}953Oul~N%4 z@Sx!@>?l1e7V*@HZMJx!gMo0TeXdU~#W6^n?YVQJ$)nuFRkvKbfwv_s*2g(!wPO|@ zvuXF=2MiPIX)A7x!|BthSa$GB%ECnuZe_Scx&AlnC z!~6C_SF24#@^VMIw)a-7{00}}Cr5NImPbW8OTIHoo6@NcxLVTna8<<;uy~YaaeMnd z;k_ynYc_8jQn9vW_W8QLkgaHtmwGC}wRcgZ^I^GPbz{lW)p#YYoinez1MjkY%6LBd z+Vr>j&^!?b-*Vk>8I!28o`r3w&^Lal8@=50zV4&9V9oXI{^r8;JmVeos&wf?O!;_o zk))^k*1fvYw9?WrS!sG2TcX`hH@Y3mF&@{i05;_AV{>Umi8{uZP_0W5_1V2yHU<)E z+qviK*7SJtnL;76{WK!?Pv$-!w$08<%8Qy|sB|P%GiV1<+dHw*sj!C~SjsB6+1L@so+Q~n# z+Uc5+Uz+mGmkR@>H7D*c?mm8WQz;3VOpktU_DeBi>3#@z zmLe;3gP<7KPy>~k47nEeT?G?7e2g6316Xdb_y+ja5C9Ayg6QTNr~&Kbs(1>7zp|f@le;9B z1e(+Ga%jPWR7oc}=XcB4$z?YD)l;%#U;}~gZzGViI=fwu9OAPCCK!0w>Ay^#$b49k zT&|M?JaIyRT<;@*t_jp1ifWPvL;{maf6o0T#X!#9YX;0Q;LTQ0}0tg^_Ru4pkSr4#P zmnW|D0`A#Ie6pEfBDv39=jN2;kiUoT6I&kChsbI!jMuY6zuZql5!&i%5!c zjsHlXtjT;NV?jAb`%vy)JOK_j1rponLqc>(2qgYlLPEs>|0QV<=Pw~C`fLFKJJitt zyC6003{rxCsmtGKjhB%W2W~*%vKH8l$pZoOFT*K@uL9%CD^3rh=ZtuTU1 zJpf4|%n^yjh#dKSSCJI8;YU*CD!8Wv20*e5`-fya^75@ADLU^RdHDg3Bk3k6)dGi7 z!!z;|O1h$8q!vO*w6 I6Xdi10eY*&F8}}l diff --git a/bwc-test/gradle/wrapper/gradle-wrapper.properties b/bwc-test/gradle/wrapper/gradle-wrapper.properties index f42e62f372..bdc9a83b1e 100644 --- a/bwc-test/gradle/wrapper/gradle-wrapper.properties +++ b/bwc-test/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/bwc-test/gradlew b/bwc-test/gradlew index 65dcd68d65..79a61d421c 100755 --- a/bwc-test/gradlew +++ b/bwc-test/gradlew @@ -144,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..ccebba7710deaf9f98673a68957ea02138b60d0a 100644 GIT binary patch delta 5094 zcmZu#c|6qH|DG9RA4`noBZNWrC2N)tSqjO%%aX0^O4dPAB*iC6_9R<`apl^#h-_oY z)(k_0v8Fxp{fyi9-uwN%e)GpU&v~BrS>~KG^PF=MNmQjIDr&QHR7f-kM{%U_u*1=5 zGC}ae5(^Rrg9QY8$x^}oiJ0d2O9YW{J~$dD1ovlvh&0B4L)!4S=z;Hac>K{#9q9cKq;>>BtKo1!+gw`yqE zSK8x^jC|B!qmSW#uyb@T^CkB9qRd{N3V-rEi}AEgoU_J27lw_0X`}c0&m9JhxM;RK z54_gdZ(u?R5`B3}NeVal2NTHqlktM`2eTF28%6BZCWW$-shf0l-BOVSm)hU58MTPy zDcY-5777j;ccU!Yba8wH=X6OdPJ8O5Kp^3gUNo>!b=xb6T2F&LiC2eBJj8KuLPW!4 zw3V^NnAKZm^D?tmliCvzi>UtoDH%V#%SM0d*NS+m%4}qO<)M1E{OpQ(v&ZNc`vdi| zEGlVi$Dgxy1p6+k0qGLQt(JwxZxLCZ4>wJ=sb0v%Ki?*+!ic_2exumn{%Co|| z-axdK#RUC;P|vqbe?L`K!j;sUo=uuR_#ZkRvBf%Txo6{OL&I(?dz?47Z(DcX3KTw> zGY%A=kX;fBkq$F^sX|-)1Qkg##+n-Ci{qJVPj@P?l_1Y`nD^v>fZ3HMX%(4p-TlD(>yWwJij!6Jw}l7h>CIm@Ou5B@$Wy`Ky*814%Mdi1GfG1zDG9NogaoVHHr4gannv4?w6g&10!j=lKM zFW;@=Z0}vAPAxA=R4)|`J??*$|Fh`5=ks*V7TapX`+=4n*{aXxRhh-EGX_Xrzjb4r zn0vO7Cc~wtyeM_8{**~9y7>+}1JV8Buhg%*hy|PUc#!vw#W(HFTL|BpM)U0>JxG6S zLnqn1!0++RyyJ>5VU<4mDv8>Q#{EtgS3mj7Hx}Zkr0tz1}h8Kn6q`MiwC z{Y#;D!-ndlImST(C@(*i5f0U(jD29G7g#nkiPX zki6M$QYX_fNH=E4_eg9*FFZ3wF9YAKC}CP89Kl(GNS(Ag994)0$OL4-fj_1EdR}ARB#-vP_$bWF`Qk58+ z4Jq*-YkcmCuo9U%oxGeYe7Be=?n}pX+x>ob(8oPLDUPiIryT8v*N4@0{s_VYALi;lzj19ivLJKaXt7~UfU|mu9zjbhPnIhG2`uI34urWWA9IO{ z_1zJ)lwSs{qt3*UnD}3qB^kcRZ?``>IDn>qp8L96bRaZH)Zl`!neewt(wjSk1i#zf zb8_{x_{WRBm9+0CF4+nE)NRe6K8d|wOWN)&-3jCDiK5mj>77=s+TonlH5j`nb@rB5 z5NX?Z1dk`E#$BF{`(D>zISrMo4&}^wmUIyYL-$PWmEEfEn-U0tx_vy$H6|+ zi{ytv2@JXBsot|%I5s74>W1K{-cvj0BYdNiRJz*&jrV9>ZXYZhEMULcM=fCmxkN&l zEoi=)b)Vazc5TQC&Q$oEZETy@!`Gnj`qoXl7mcwdY@3a-!SpS2Mau|uK#++@>H8QC zr2ld8;<_8We%@E?S=E?=e9c$BL^9X?bj*4W;<+B&OOe+3{<`6~*fC(=`TO>o^A(Y! zA`Qc1ky?*6xjVfR?ugE~oY`Gtzhw^{Z@E6vZ`mMRAp>Odpa!m zzWmtjT|Lj^qiZMfj%%un-o$Eu>*v12qF{$kCKai^?DF=$^tfyV%m9;W@pm-BZn_6b z{jsXY3!U`%9hzk6n7YyHY%48NhjI6jjuUn?Xfxe0`ARD_Q+T_QBZ{ zUK@!63_Wr`%9q_rh`N4=J=m;v>T{Y=ZLKN^m?(KZQ2J%|3`hV0iogMHJ} zY6&-nXirq$Yhh*CHY&Qf*b@@>LPTMf z(cMorwW?M11RN{H#~ApKT)F!;R#fBHahZGhmy>Sox`rk>>q&Y)RG$-QwH$_TWk^hS zTq2TC+D-cB21|$g4D=@T`-ATtJ?C=aXS4Q}^`~XjiIRszCB^cvW0OHe5;e~9D%D10 zl4yP4O=s-~HbL7*4>#W52eiG7*^Hi)?@-#*7C^X5@kGwK+paI>_a2qxtW zU=xV7>QQROWQqVfPcJ$4GSx`Y23Z&qnS?N;%mjHL*EVg3pBT{V7bQUI60jtBTS?i~ zycZ4xqJ<*3FSC6_^*6f)N|sgB5Bep(^%)$=0cczl>j&n~KR!7WC|3;Zoh_^GuOzRP zo2Hxf50w9?_4Qe368fZ0=J|fR*jO_EwFB1I^g~i)roB|KWKf49-)!N%Ggb%w=kB8)(+_%kE~G!(73aF=yCmM3Cfb9lV$G!b zoDIxqY{dH>`SILGHEJwq%rwh46_i`wkZS-NY95qdNE)O*y^+k#JlTEij8NT(Y_J!W zFd+YFoZB|auOz~A@A{V*c)o7E(a=wHvb@8g5PnVJ&7D+Fp8ABV z5`&LD-<$jPy{-y*V^SqM)9!#_Pj2-x{m$z+9Z*o|JTBGgXYYVM;g|VbitDUfnVn$o zO)6?CZcDklDoODzj+ti@i#WcqPoZ!|IPB98LW!$-p+a4xBVM@%GEGZKmNjQMhh)zv z7D){Gpe-Dv=~>c9f|1vANF&boD=Nb1Dv>4~eD636Lldh?#zD5{6JlcR_b*C_Enw&~ z5l2(w(`{+01xb1FCRfD2ap$u(h1U1B6e&8tQrnC}Cy0GR=i^Uue26Rc6Dx}!4#K*0 zaxt`a+px7-Z!^(U1WN2#kdN#OeR|2z+C@b@w+L67VEi&ZpAdg+8`HJT=wIMJqibhT ztb3PFzsq&7jzQuod3xp7uL?h-7rYao&0MiT_Bux;U*N#ebGv92o(jM2?`1!N2W_M* zeo9$%hEtIy;=`8z1c|kL&ZPn0y`N)i$Y1R9>K!el{moiy)014448YC#9=K zwO3weN|8!`5bU_#f(+ZrVd*9`7Uw?!q?yo&7sk&DJ;#-^tcCtqt5*A(V;&LdHq7Hg zI6sC@!ly9p$^@v&XDsgIuv;9#w^!C1n5+10-tEw~ZdO1kqMDYyDl!5__o}f3hYe2M zCeO)~m&&=JZn%cVH3HzPlcE`9^@``2u+!Y}Remn)DLMHc-h5A9ATgs;7F7=u2=vBlDRbjeYvyNby=TvpI{5nb2@J_YTEEEj4q<@zaGSC_i&xxD!6)d zG{1??({Ma<=Wd4JL%bnEXoBOU_0bbNy3p%mFrMW>#c zzPEvryBevZVUvT^2P&Zobk#9j>vSIW_t?AHy>(^x-Bx~(mvNYb_%$ZFg(s5~oka+Kp(GU68I$h(Vq|fZ zC_u1FM|S)=ldt#5q>&p4r%%p)*7|Rf0}B#-FwHDTo*|P6HB_rz%R;{==hpl#xTt@VLdSrrf~g^ z`IA8ZV1b`UazYpnkn28h&U)$(gdZ*f{n`&kH%Oy54&Z;ebjlh4x?JmnjFAALu}EG} zfGmQ$5vEMJMH`a=+*src#dWK&N1^LFxK9Sa#q_rja$JWra09we<2oL9Q9Sx)?kZFW z$jhOFGE~VcihYlkaZv8?uA7v$*}?2h6i%Qmgc4n~3E(O_`YCRGy~}`NFaj@(?Wz;GS_?T+RqU{S)eD1j$1Gr;C^m z7zDK=xaJ^6``=#Y-2ssNfdRqh0ntJrutGV5Nv&WI%3k1wmD5n+0aRe{0k^!>LFReN zx1g*E>nbyx03KU~UT6->+rG%(owLF=beJxK&a0F;ie1GZ^eKg-VEZb&=s&ajKS#6w zjvC6J#?b|U_(%@uq$c#Q@V_me0S1%)pKz9--{EKwyM}_gOj*Og-NEWLDF_oFtPjG; zXCZ7%#=s}RKr&_5RFN@=H(015AGl4XRN9Bc51`;WWt%vzQvzexDI2BZ@xP~^2$I&7 zA(ndsgLsmA*su8p-~IS q+ZJUZM}`4#Zi@l2F-#HCw*??ha2ta#9s8?H3%YId(*zJG6aF78h1yF1 delta 5107 zcmY*d1zc0@|J{HQlai7V5+f#EN-H%&UP4MFm6QgFfuJK4DG4u#ARsbQL4i>MB1q|w zmWd#pqd~BR-yN@ieE-|$^W1aKIZtf&-p_fyw{(Uwc7_sWYDh^12cY!qXvcPQ!qF;q@b0nYU7 zP&ht}K7j%}P%%|ffm;4F0^i3P0R`a!2wm89L5P3Kfu;tTZJre<{N5}AzsH+E3DS`Q zJLIl`LRMf`JOTBLf(;IV(9(h{(}dXK!cPoSLm(o@fz8vRz}6fOw%3}3VYOsCczLF` za2RTsCWa2sS-uw(6|HLJg)Xf@S8#|+(Z5Y)ER+v+8;btfB3&9sWH6<=U}0)o-jIts zsi?Nko;No&JyZI%@1G&zsG5kKo^Zd7rk_9VIUao9;fC~nv(T0F&Af0&Rp`?x94EIS zUBPyBe5R5#okNiB1Xe--q4|hPyGzhJ?Lurt#Ci09BQ+}rlHpBhm;EmfLw{EbCz)sg zgseAE#f$met1jo;`Z6ihk?O1be3aa$IGV69{nzagziA!M*~E5lMc(Sp+NGm2IUjmn zql((DU9QP~Tn1pt6L`}|$Na-v(P+Zg&?6bAN@2u%KiB*Gmf}Z)R zMENRJgjKMqVbMpzPO{`!J~2Jyu7&xXnTDW?V?IJgy+-35q1)-J8T**?@_-2H`%X+6f5 zIRv`uLp&*?g7L~6+3O*saXT~gWsmhF*FNKw4X$29ePKi02G*)ysenhHv{u9-y?_do ztT(Cu04pk>51n}zu~=wgToY5Cx|MTlNw}GR>+`|6CAhQn=bh@S<7N)`w};;KTywDU z=QWO@RBj$WKOXSgCWg{BD`xl&DS!G}`Mm3$)=%3jzO_C+s+mfTFH5JL>}*(JKs@MqX|o2b#ZBX5P;p7;c)$F1y4HwvJ?KA938$rd)gn_U^CcUtmdaBW57 zlPph>Fz&L`cSScFjcj+7Jif3vxb20Ag~FPstm?9#OrD$e?Y~#1osDB0CFZ9Mu&%iE zSj~wZpFqu6!k%BT)}$F@Z%(d-Pqy07`N8ch2F7z^=S-!r-@j{#&{SM@a8O$P#SySx zZLD_z=I300OCA1YmKV0^lo@>^)THfZvW}s<$^w^#^Ce=kO5ymAnk>H7pK!+NJ-+F7 z1Bb6Y=r)0nZ+hRXUyD+BKAyecZxb+$JTHK5k(nWv*5%2a+u*GDt|rpReYQ}vft zXrIt#!kGO85o^~|9Oc-M5A!S@9Q)O$$&g8u>1=ew?T35h8B{-Z_S78oe=E(-YZhBPe@Y1sUt63A-Cdv>D1nIT~=Rub6$?8g>meFb7Ic@w^%@RN2z72oPZ#Ta%b(P1|&6I z61iO<8hT*)p19Bgd0JgXP{^c{P2~K@^DIXv=dF(u|DFfqD^dMIl8-x)xKIpJRZru@ zDxicyYJG}mh}=1Dfg%B$#H`CiAxPTj^;f4KRMZHUz-_x6)lEq!^mu%72*PI=t$6{Uql#dqm4 zClgaN63!&?v*enz4k1sbaM+yCqUf+i9rw$(YrY%ir1+%cWRB<;r}$8si!6QcNAk~J zk3?dejBaC`>=T<=y=>QVt*4kL>SwYwn$(4ES793qaH)>n(axyV3R5jdXDh#e-N0K- zuUgk|N^|3*D1!Wlz-!M*b}Zc5=;K6I+>1N$&Q%)&8LWUiTYi&aQIj(luA< zN5R<8Y8L#*i0xBio$jWcaiZ4S2w3#R@CGemesy~akKP)2GojQF6!$}!_RdUJPBevX zG#~uz%Yirb0@1wgQ;ayb=qD}6{=QXxjuZQ@@kxbN!QWhtEvuhS2yAZe8fZy6*4Inr zdSyR9Dec4HrE|I=z-U;IlH;_h#7e^Hq}gaJ<-z^}{*s!m^66wu2=(*EM0UaV*&u1q zJrq!K23TO8a(ecSQFdD$y+`xu)Xk36Z*;1i{hS=H2E<8<5yHuHG~22-S+Jq|3HMAw z%qBz3auT=M!=5F|Wqke|I^E8pmJ-}>_DwX5w%d3MSdC>xW%$ocm8w8HRdZ|^#cEt1 zM*I7S6sLQq;;Mecet(Q()+?s+&MeVLOvx}(MkvytkvLHl7h*N0AT1#AqC&(he(^%przH`KqA$z_dAvJJb409@F)fYwD$JW_{_Oie8!@VdJE zU>D$@B?LawAf5$;`AZ1E!krn=aAC%4+YQrzL!59yl1;|T2)u=RBYA8lk0Ek&gS!Rb zt0&hVuyhSa0}rpZGjTA>Gz}>Uv*4)F zf7S%D2nfA7x?gPEXZWk8DZimQs#xi0?So_k`2zb!UVQEAcbvjPLK9v>J~!awnxGpq zEh$EPOc4q&jywmglnC&D)1-P0DH!@)x;uJwMHdhPh>ZLWDw+p1pf52{X2dk{_|UOmakJa4MHu?CY`6Hhv!!d7=aNwiB5z zb*Wlq1zf^3iDlPf)b_SzI*{JCx2jN;*s~ra8NeB!PghqP!0po-ZL?0Jk;2~*~sCQ<%wU`mRImd)~!23RS?XJu|{u( ztFPy3*F=ZhJmBugTv48WX)4U*pNmm~4oD4}$*-92&<)n=R)5lT z-VpbEDk>(C1hoo#-H_u0`#%L6L$ zln(}h2*Cl(5(JtVM{YZ26@Fwmp;?Qt}9$_F%`?+-JHbC;bPZj8PLq9 zWo-KFw!i&r8WuA-!3F_m9!24Z(RhalAUR~_H#Ln=$%b5GY z)oB)zO%J5TY}&BXq^7#M>euVL%01Tzj4$6^ZOjT*7@zr~q@6GEjGi)nbwzSL`TiLN z{DVG~I$w@%^#tD{>1Ap@%=XogG_^Hvy_xiRn4yy?LKsC+ zU!S79X8orh&D%>1S`x2iyi&(iG&r#YT{}~iy(FIOo8?MZU#eo*c*(RjAGj@uDi zARJur)-*{n0PgW~&mFeg`MJ?(Kr;NUom)jh?ozZtyywN9bea6ikQlh}953Oul~N%4 z@Sx!@>?l1e7V*@HZMJx!gMo0TeXdU~#W6^n?YVQJ$)nuFRkvKbfwv_s*2g(!wPO|@ zvuXF=2MiPIX)A7x!|BthSa$GB%ECnuZe_Scx&AlnC z!~6C_SF24#@^VMIw)a-7{00}}Cr5NImPbW8OTIHoo6@NcxLVTna8<<;uy~YaaeMnd z;k_ynYc_8jQn9vW_W8QLkgaHtmwGC}wRcgZ^I^GPbz{lW)p#YYoinez1MjkY%6LBd z+Vr>j&^!?b-*Vk>8I!28o`r3w&^Lal8@=50zV4&9V9oXI{^r8;JmVeos&wf?O!;_o zk))^k*1fvYw9?WrS!sG2TcX`hH@Y3mF&@{i05;_AV{>Umi8{uZP_0W5_1V2yHU<)E z+qviK*7SJtnL;76{WK!?Pv$-!w$08<%8Qy|sB|P%GiV1<+dHw*sj!C~SjsB6+1L@so+Q~n# z+Uc5+Uz+mGmkR@>H7D*c?mm8WQz;3VOpktU_DeBi>3#@z zmLe;3gP<7KPy>~k47nEeT?G?7e2g6316Xdb_y+ja5C9Ayg6QTNr~&Kbs(1>7zp|f@le;9B z1e(+Ga%jPWR7oc}=XcB4$z?YD)l;%#U;}~gZzGViI=fwu9OAPCCK!0w>Ay^#$b49k zT&|M?JaIyRT<;@*t_jp1ifWPvL;{maf6o0T#X!#9YX;0Q;LTQ0}0tg^_Ru4pkSr4#P zmnW|D0`A#Ie6pEfBDv39=jN2;kiUoT6I&kChsbI!jMuY6zuZql5!&i%5!c zjsHlXtjT;NV?jAb`%vy)JOK_j1rponLqc>(2qgYlLPEs>|0QV<=Pw~C`fLFKJJitt zyC6003{rxCsmtGKjhB%W2W~*%vKH8l$pZoOFT*K@uL9%CD^3rh=ZtuTU1 zJpf4|%n^yjh#dKSSCJI8;YU*CD!8Wv20*e5`-fya^75@ADLU^RdHDg3Bk3k6)dGi7 z!!z;|O1h$8q!vO*w6 I6Xdi10eY*&F8}}l diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f42e62f372..bdc9a83b1e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d65..79a61d421c 100755 --- a/gradlew +++ b/gradlew @@ -144,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac From ef6ffb58cf15f08babcfa30ed35f885afce5b1ca Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 10 Mar 2023 11:49:30 -0600 Subject: [PATCH 150/369] Fix error message when system index is blocked (#2525) There was a bug in the error message when interacting with a system index causing it to always return the security index instead of the index(es) that were blocked. Also includes unit test cases to verify the SecurityIndexAccessEvaluator https://github.com/opensearch-project/security/issues/2483 Signed-off-by: Peter Nied --- .../SecurityIndexAccessEvaluator.java | 18 ++- .../SecurityIndexAccessEvaluatorTest.java | 152 ++++++++++++++++++ 2 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java diff --git a/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java index 428c4efd8d..60456a4eb3 100644 --- a/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java @@ -30,6 +30,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -47,7 +48,7 @@ public class SecurityIndexAccessEvaluator { - protected final Logger log = LogManager.getLogger(this.getClass()); + Logger log = LogManager.getLogger(this.getClass()); private final String securityIndex; private final AuditLog auditLog; @@ -103,7 +104,7 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T presponse.allowed = false; return presponse.markComplete(); } - } else if (requestedResolved.getAllIndices().contains(securityIndex) || matchAnySystemIndices(requestedResolved)) { + } else if (matchAnySystemIndices(requestedResolved)) { if(filterSecurityIndex) { Set allWithoutSecurity = new HashSet<>(requestedResolved.getAllIndices()); allWithoutSecurity.remove(securityIndex); @@ -121,7 +122,8 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T return presponse; } else { auditLog.logSecurityIndexAttempt(request, action, task); - log.warn("{} for '{}' index is not allowed for a regular user", action, securityIndex); + final String foundSystemIndexes = getProtectedIndexes(requestedResolved).stream().collect(Collectors.joining(", ")); + log.warn("{} for '{}' index is not allowed for a regular user", action, foundSystemIndexes); presponse.allowed = false; return presponse.markComplete(); } @@ -149,6 +151,14 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T } private boolean matchAnySystemIndices(final Resolved requestedResolved){ - return systemIndexEnabled && systemIndexMatcher.matchAny(requestedResolved.getAllIndices()); + return !getProtectedIndexes(requestedResolved).isEmpty(); + } + + private List getProtectedIndexes(final Resolved requestedResolved) { + final List protectedIndexes = requestedResolved.getAllIndices().stream().filter(securityIndex::equals).collect(Collectors.toList()); + if (systemIndexEnabled) { + protectedIndexes.addAll(systemIndexMatcher.getMatchAny(requestedResolved.getAllIndices(), Collectors.toList())); + } + return protectedIndexes; } } diff --git a/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java b/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java new file mode 100644 index 0000000000..6d81c3b1da --- /dev/null +++ b/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java @@ -0,0 +1,152 @@ +/* + * 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.privileges; + +import com.google.common.collect.ImmutableSet; +import org.apache.logging.log4j.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.get.MultiGetRequest; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.support.IndicesOptions; +import org.opensearch.common.settings.Settings; +import org.opensearch.security.auditlog.AuditLog; +import org.opensearch.security.resolver.IndexResolverReplacer; +import org.opensearch.security.resolver.IndexResolverReplacer.Resolved; +import org.opensearch.tasks.Task; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class SecurityIndexAccessEvaluatorTest { + + @Mock + private AuditLog auditLog; + @Mock + private IndexResolverReplacer irr; + @Mock + private ActionRequest request; + @Mock + private Task task; + @Mock + private PrivilegesEvaluatorResponse presponse; + @Mock + private Logger log; + + + private SecurityIndexAccessEvaluator evaluator; + + private static final String UNPROTECTED_ACTION = "indices:data/read"; + private static final String PROTECTED_ACTION = "indices:data/write"; + + @Before + public void before() { + evaluator = new SecurityIndexAccessEvaluator( + Settings.EMPTY.builder() + .put("plugins.security.system_indices.indices", ".test") + .put("plugins.security.system_indices.enabled", true) + .build(), + auditLog, + irr); + evaluator.log = log; + + when(log.isDebugEnabled()).thenReturn(true); + } + + @After + public void after() { + verifyNoMoreInteractions(auditLog, irr, request, task, presponse, log); + } + + @Test + public void actionIsNotProtected_noSystemIndexInvolved() { + final Resolved resolved = createResolved(".test"); + + // Action + final PrivilegesEvaluatorResponse response = evaluator.evaluate(request, null, UNPROTECTED_ACTION, resolved, presponse); + + verifyNoInteractions(presponse); + assertThat(response, is(presponse)); + + verify(log).isDebugEnabled(); + } + + @Test + public void disableCacheOrRealtimeOnSystemIndex() { + final SearchRequest searchRequest = mock(SearchRequest.class); + final MultiGetRequest realtimeRequest = mock(MultiGetRequest.class); + final Resolved resolved = createResolved(".test"); + + // Action + evaluator.evaluate(request, null, UNPROTECTED_ACTION, resolved, presponse); + evaluator.evaluate(searchRequest, null, UNPROTECTED_ACTION, resolved, presponse); + evaluator.evaluate(realtimeRequest, null, UNPROTECTED_ACTION, resolved, presponse); + + verifyNoInteractions(presponse); + verify(searchRequest).requestCache(Boolean.FALSE); + verify(realtimeRequest).realtime(Boolean.FALSE); + + verify(log, times(3)).isDebugEnabled(); + verify(log).debug("Disable search request cache for this request"); + verify(log).debug("Disable realtime for this request"); + } + + @Test + public void protectedActionLocalAll() { + final Resolved resolved = Resolved._LOCAL_ALL; + + // Action + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse); + + verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); + assertThat(presponse.allowed, is(false)); + verify(presponse).markComplete(); + + verify(log).isDebugEnabled(); + verify(log).warn("{} for '_all' indices is not allowed for a regular user", "indices:data/write"); + } + + @Test + public void protectedActionSystemIndex() { + final Resolved resolved = createResolved(".test", ".opendistro_security"); + + // Action + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse); + + verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); + assertThat(presponse.allowed, is(false)); + verify(presponse).markComplete(); + + verify(log).isDebugEnabled(); + verify(log).warn( + "{} for '{}' index is not allowed for a regular user", + "indices:data/write", + ".opendistro_security, .test"); + } + + private Resolved createResolved(final String... indexes) { + return new Resolved(ImmutableSet.of(), ImmutableSet.copyOf(indexes), ImmutableSet.copyOf(indexes), ImmutableSet.of(), IndicesOptions.STRICT_EXPAND_OPEN); + } +} From a5c73f91107f4b2e042ac06a60dc436774300b8e Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Fri, 10 Mar 2023 12:41:52 -0800 Subject: [PATCH 151/369] Add release notes for 1.3.9 (#2539) * Add release notes for 1.3.9 Signed-off-by: Ryan Liang --- .../opensearch-security.release-notes-1.3.9.0.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-1.3.9.0.md diff --git a/release-notes/opensearch-security.release-notes-1.3.9.0.md b/release-notes/opensearch-security.release-notes-1.3.9.0.md new file mode 100644 index 0000000000..2e17d7e285 --- /dev/null +++ b/release-notes/opensearch-security.release-notes-1.3.9.0.md @@ -0,0 +1,15 @@ +## 2023-03-16 Version 1.3.9.0 + +Compatible with OpenSearch 1.3.9 + +### Enhancement + +* Flatten response times ([#2471](https://github.com/opensearch-project/security/pull/2471)) +* Add auto github release workflow ([#2450](https://github.com/opensearch-project/security/pull/2450)) +* Adding index template permissions to kibana_server role ([2503](https://github.com/opensearch-project/security/pull/2503)) +* Clock skew tolerance for oidc token validation ([2482](https://github.com/opensearch-project/security/pull/2482)) + +### Maintenance + +* Update kafka client to 3.4.0 ([#2484](https://github.com/opensearch-project/security/pull/2484)) +* Fix the format of the codeowners file ([#2469](https://github.com/opensearch-project/security/pull/2469)) From ca4d7528851ea94d936181f86428a25f0c59c8c1 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 14 Mar 2023 09:42:01 -0400 Subject: [PATCH 152/369] Add chmod to install_demo_configuration (#2550) Signed-off-by: Craig Perkins --- tools/install_demo_configuration.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/install_demo_configuration.sh b/tools/install_demo_configuration.sh index fad75c5cdf..ccd620b528 100755 --- a/tools/install_demo_configuration.sh +++ b/tools/install_demo_configuration.sh @@ -354,6 +354,12 @@ echo "$ROOT_CA" | $SUDO_CMD tee "$OPENSEARCH_CONF_DIR/root-ca.pem" > /dev/null echo "$NODE_KEY" | $SUDO_CMD tee "$OPENSEARCH_CONF_DIR/esnode-key.pem" > /dev/null echo "$ADMIN_CERT_KEY" | $SUDO_CMD tee "$OPENSEARCH_CONF_DIR/kirk-key.pem" > /dev/null +chmod 0600 "$OPENSEARCH_CONF_DIR/kirk.pem" +chmod 0600 "$OPENSEARCH_CONF_DIR/esnode.pem" +chmod 0600 "$OPENSEARCH_CONF_DIR/root-ca.pem" +chmod 0600 "$OPENSEARCH_CONF_DIR/esnode-key.pem" +chmod 0600 "$OPENSEARCH_CONF_DIR/kirk-key.pem" + echo "" | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" echo "######## Start OpenSearch Security Demo Configuration ########" | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null echo "# WARNING: revise all the lines below before you go into production" | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null From 575c2bc1e7283398ef448c404f480bd7b1eddef5 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Mon, 20 Mar 2023 06:23:02 -0400 Subject: [PATCH 153/369] [BUG] SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder" (#2564) Signed-off-by: Andriy Redko --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3996559755..4f4d5fcc61 100644 --- a/build.gradle +++ b/build.gradle @@ -416,6 +416,7 @@ dependencies { runtimeOnly 'org.lz4:lz4-java:1.8.0' runtimeOnly 'io.dropwizard.metrics:metrics-core:3.1.2' runtimeOnly 'org.slf4j:slf4j-api:1.7.30' + runtimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.1' runtimeOnly 'org.xerial.snappy:snappy-java:1.1.8.4' runtimeOnly 'org.codehaus.woodstox:stax2-api:4.2.1' runtimeOnly 'org.glassfish.jaxb:txw2:2.3.4' @@ -486,7 +487,6 @@ dependencies { integrationTestImplementation 'commons-io:commons-io:2.11.0' integrationTestImplementation 'org.apache.logging.log4j:log4j-core:2.17.1' integrationTestImplementation 'org.apache.logging.log4j:log4j-jul:2.17.1' - integrationTestImplementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.1' integrationTestImplementation 'org.hamcrest:hamcrest:2.2' integrationTestImplementation "org.bouncycastle:bcpkix-jdk15on:${versions.bouncycastle}" integrationTestImplementation "org.bouncycastle:bcutil-jdk15on:${versions.bouncycastle}" From 30c967e5d7c2d2b2f8889a0e4e5e1ddce274a2a1 Mon Sep 17 00:00:00 2001 From: Saadat Nursultan <39532643+nurSaadat@users.noreply.github.com> Date: Fri, 24 Mar 2023 14:10:04 +0100 Subject: [PATCH 154/369] Remove broken link from DEVELOPER_GUIDE.md (#2582) * Update DEVELOPER_GUIDE.md Signed-off-by: Saadat Nursultan * Update DEVELOPER_GUIDE.md Signed-off-by: Saadat Nursultan * Update DEVELOPER_GUIDE.md Signed-off-by: Saadat Nursultan * Update DEVELOPER_GUIDE.md Signed-off-by: Saadat Nursultan --------- Signed-off-by: Saadat Nursultan --- DEVELOPER_GUIDE.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 4322a18d52..5168d01a46 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -45,8 +45,16 @@ cd $OPENSEARCH_HOME The `curl localhost:9200` call should succeed again. Kill the server with `Ctrl+c`. We are now ready to install the security plugin. + >Worth noting:\ -> The version of OpenSearch and the security plugin must match as there is an explicit version check at startup. This can be a bit confusing as, for example, at the time of writing this guide, the `main` branch of this security plugin builds version `1.3.0.0-SNAPSHOT` compatible with OpenSearch `1.3.0-SNAPSHOT` that gets built from branch `1.x`. Check the expected compatible version [here](https://github.com/opensearch-project/security/blob/main/plugin-descriptor.properties#L27) and make sure you get the correct branch from OpenSearch when building that project. +> The version of OpenSearch and the security plugin must match as there is an explicit version check at startup. This can be a bit confusing as, for example, at the time of writing this guide, the `main` branch of this security plugin builds version `3.0.0.0-SNAPSHOT` compatible with OpenSearch `3.0.0`. Check the expected compatible version in `build.gradle` file [here](https://github.com/opensearch-project/security/blob/main/build.gradle) and make sure you get the correct branch from OpenSearch when building that project. +> +> The line to look for: `opensearch_version = System.getProperty("opensearch.version", "x")` +> +> Alternatively, you can find the compatible version of OpenSearch by running in project root folder +> ``` +> ./gradlew properties -q | grep -E '^version:' | awk '{print $2}' +> ``` ## Building From d47668756fda437dffb24a53f9b38f6579812d62 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 31 Mar 2023 08:13:47 -0500 Subject: [PATCH 155/369] Add Andriy Redko as a co-maintainer (#2610) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I nominated Andriy Redko (@reta) to be a co-maintainer for Security through our [nomination process](https://github.com/opensearch-project/.github/blob/main/MAINTAINERS.md#nomination). The maintainers have agreed, and Andriy has kindly accepted. Andriy’s involvement has taken many shapes, here are some highlights: * Code * Features work such as support for HTTP/2 * Manual Backports for CVEs * Build infrastructure such as Gradle changes * Test infrastructure such as fixes to OpenSSLTests * Particpation in issues * Adding contributors * @willyborankin * Active participate in triage meeting / process * Passion for quality Thank you Signed-off-by: Peter Nied --- .github/CODEOWNERS | 2 +- MAINTAINERS.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5478135783..5832378bff 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @cliu123 @cwperks @DarshitChanpura @davidlago @peternied @RyanL1997 @scrawfor99 +* @cliu123 @cwperks @DarshitChanpura @davidlago @peternied @RyanL1997 @scrawfor99 @reta diff --git a/MAINTAINERS.md b/MAINTAINERS.md index c72f5f3e3a..4605135a16 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -21,6 +21,7 @@ This document contains a list of maintainers in this repo. See [opensearch-proje | Craig Perkins | [cwperks](https://github.com/cwperks) | Amazon | | Ryan Liang | [RyanL1997](https://github.com/RyanL1997) | Amazon | | Stephen Crawford | [scrawfor99](https://github.com/scrawfor99) | Amazon | +| Andriy Redko | [reta](https://github.com/reta) | Aiven | ## Practices From 9bcf4a432c6a66a16972d62d2fc0fe40328feb65 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Fri, 31 Mar 2023 09:29:03 -0400 Subject: [PATCH 156/369] Update Spring and JSON-Smart (#2606) Signed-off-by: Stephen Crawford --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 4f4d5fcc61..1f32ff7e33 100644 --- a/build.gradle +++ b/build.gradle @@ -404,7 +404,7 @@ dependencies { implementation 'commons-lang:commons-lang:2.4' implementation 'commons-collections:commons-collections:3.2.2' implementation 'com.jayway.jsonpath:json-path:2.4.0' - implementation 'net.minidev:json-smart:2.4.7' + implementation 'net.minidev:json-smart:2.4.10' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.10.8' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.10.8' runtimeOnly 'com.google.guava:failureaccess:1.0.1' @@ -462,7 +462,7 @@ dependencies { testCompileOnly 'org.apiguardian:apiguardian-api:1.0.0' // Kafka test execution testRuntimeOnly 'org.springframework.retry:spring-retry:1.3.3' - testRuntimeOnly ('org.springframework:spring-core:5.3.21') { + testRuntimeOnly ('org.springframework:spring-core:5.3.26') { exclude(group:'org.springframework', module: 'spring-jcl' ) } testRuntimeOnly 'org.scala-lang:scala-library:2.13.9' From a9bad908d6692be439694288958bbb4b42f4b577 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Fri, 31 Mar 2023 09:40:41 -0400 Subject: [PATCH 157/369] Expand Dls Tests for easier verification of functionality (#2585) * Additional DlsTests Signed-off-by: Stephen Crawford * DLS Tests Signed-off-by: Stephen Crawford * Fix comments Signed-off-by: Stephen Crawford * Fix bad changes Signed-off-by: Stephen Crawford --------- Signed-off-by: Stephen Crawford --- .../security/DlsIntegrationTests.java | 479 ++++++++++++++++++ .../security/dlic/dlsfls/DlsTest.java | 92 ++-- 2 files changed, 525 insertions(+), 46 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/DlsIntegrationTests.java diff --git a/src/integrationTest/java/org/opensearch/security/DlsIntegrationTests.java b/src/integrationTest/java/org/opensearch/security/DlsIntegrationTests.java new file mode 100644 index 0000000000..a99d584a50 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/DlsIntegrationTests.java @@ -0,0 +1,479 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +* +*/ +package org.opensearch.security; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.BiFunction; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.client.Client; +import org.opensearch.client.RestHighLevelClient; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.search.aggregations.Aggregation; +import org.opensearch.search.aggregations.metrics.ParsedAvg; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions.Type.ADD; +import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.opensearch.client.RequestOptions.DEFAULT; +import static org.opensearch.security.Song.ARTIST_FIRST; +import static org.opensearch.security.Song.ARTIST_NO; +import static org.opensearch.security.Song.ARTIST_STRING; +import static org.opensearch.security.Song.ARTIST_TWINS; +import static org.opensearch.security.Song.ARTIST_UNKNOWN; +import static org.opensearch.security.Song.ARTIST_YES; +import static org.opensearch.security.Song.FIELD_ARTIST; +import static org.opensearch.security.Song.FIELD_STARS; +import static org.opensearch.security.Song.QUERY_TITLE_NEXT_SONG; +import static org.opensearch.security.Song.SONGS; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; +import static org.opensearch.test.framework.cluster.SearchRequestFactory.averageAggregationRequest; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.containAggregationWithNameAndType; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.isSuccessfulSearchResponse; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.numberOfTotalHitsIsEqualTo; +import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitContainsFieldWithValue; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class DlsIntegrationTests { + + static final String FIRST_INDEX_ID_SONG_1 = "INDEX_1_S1"; + static final String FIRST_INDEX_ID_SONG_2 = "INDEX_1_S2"; + static final String FIRST_INDEX_ID_SONG_3 = "INDEX_1_S3"; + static final String FIRST_INDEX_ID_SONG_4 = "INDEX_1_S4"; + static final String FIRST_INDEX_ID_SONG_5 = "INDEX_1_S5"; + static final String FIRST_INDEX_ID_SONG_6 = "INDEX_1_S6"; + static final String SECOND_INDEX_ID_SONG_1 = "INDEX_2_S1"; + static final String SECOND_INDEX_ID_SONG_2 = "INDEX_2_S2"; + static final String SECOND_INDEX_ID_SONG_3 = "INDEX_2_S3"; + static final String SECOND_INDEX_ID_SONG_4 = "INDEX_2_S4"; + + static final String INDEX_NAME_SUFFIX = "-test-index"; + static final String FIRST_INDEX_NAME = "first".concat(INDEX_NAME_SUFFIX); + static final String SECOND_INDEX_NAME = "second".concat(INDEX_NAME_SUFFIX); + static final String FIRST_INDEX_ALIAS = FIRST_INDEX_NAME.concat("-alias"); + static final String SECOND_INDEX_ALIAS = SECOND_INDEX_NAME.concat("-alias"); + static final String FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE = FIRST_INDEX_NAME.concat("-filtered-by-next-song-title"); + static final String FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST = FIRST_INDEX_NAME.concat("-filtered-by-twins-artist"); + static final String FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST = FIRST_INDEX_NAME.concat("-filtered-by-first-artist"); + static final String ALL_INDICES_ALIAS = "_all"; + + + static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + /** + * User who is allowed to read all indices. + */ + static final TestSecurityConfig.User READ_ALL_USER = new TestSecurityConfig.User("read_all_user") + .roles( + new TestSecurityConfig.Role("read_all_user") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .on("*") + ); + + /** + * User who is allowed to see all fields on indices {@link #FIRST_INDEX_NAME} and {@link #SECOND_INDEX_NAME}. + */ + static final TestSecurityConfig.User READ_FIRST_AND_SECOND_USER = new TestSecurityConfig.User("read_first_and_second_user") + .roles( + new TestSecurityConfig.Role("first_index_reader") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .on(FIRST_INDEX_NAME), + new TestSecurityConfig.Role("second_index_reader") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .on(SECOND_INDEX_NAME) + ); + + /** + * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_STRING}. + */ + static final TestSecurityConfig.User READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_STRING = new TestSecurityConfig.User("read_where_field_artist_matches_artist_string") + .roles( + new TestSecurityConfig.Role("read_where_field_artist_matches_artist_string") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_STRING)) + .on("*") + ); + + /** + * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_TWINS} OR {@link Song#FIELD_STARS} is greater than five. + */ + static final TestSecurityConfig.User READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_FIELD_STARS_GREATER_THAN_FIVE = new TestSecurityConfig.User("read_where_field_artist_matches_artist_twins_or_field_stars_greater_than_five") + .roles( + new TestSecurityConfig.Role("read_where_field_artist_matches_artist_twins") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_TWINS)) + .on("*"), + new TestSecurityConfig.Role("read_where_field_stars_greater_than_five") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"range\":{\"%s\":{\"gt\":%d}}}", FIELD_STARS, 5)) + .on("*") + ); + + + /** + * User who is allowed to see documents on indices where value of {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_TWINS} or {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_FIRST}: + */ + static final TestSecurityConfig.User READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_MATCHES_ARTIST_FIRST = new TestSecurityConfig.User("read_where_field_artist_matches_artist_twins_or_artist_first") + .roles( + new TestSecurityConfig.Role("read_where_field_artist_matches_artist_twins") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_TWINS)) + .on(FIRST_INDEX_NAME), + new TestSecurityConfig.Role("read_where_field_artist_matches_artist_first") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_FIRST)) + .on(SECOND_INDEX_NAME) + ); + + /** + * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_STARS} is less than three. + */ + static final TestSecurityConfig.User READ_WHERE_STARS_LESS_THAN_THREE = new TestSecurityConfig.User("read_where_stars_less_than_three") + .roles( + new TestSecurityConfig.Role("read_where_stars_less_than_three") + .clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"range\":{\"%s\":{\"lt\":%d}}}", FIELD_STARS, 3)) + .on("*") + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) + .nodeSettings(Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() +"__" + ALL_ACCESS.getName()))) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users( + ADMIN_USER, READ_ALL_USER, READ_FIRST_AND_SECOND_USER, READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_STRING, + READ_WHERE_STARS_LESS_THAN_THREE, READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_FIELD_STARS_GREATER_THAN_FIVE, + READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_MATCHES_ARTIST_FIRST + ) + .build(); + + /** + * Function that returns id assigned to song with title equal to given title or throws {@link RuntimeException} + * when no song matches. + */ + static final BiFunction, String, String> FIND_ID_OF_SONG_WITH_TITLE = (map, title) -> map.entrySet() + .stream().filter(entry -> title.equals(entry.getValue().getTitle())) + .findAny() + .map(Map.Entry::getKey) + .orElseThrow(() -> new RuntimeException("Cannot find id of song with title: " + title)); + + /** + * Function that returns id assigned to song with artist equal to given artist or throws {@link RuntimeException} + * when no song matches. + */ + static final BiFunction, String, String> FIND_ID_OF_SONG_WITH_ARTIST = (map, artist) -> map.entrySet() + .stream().filter(entry -> artist.equals(entry.getValue().getArtist())) + .findAny() + .map(Map.Entry::getKey) + .orElseThrow(() -> new RuntimeException("Cannot find id of song with artist: " + artist)); + + static final TreeMap FIRST_INDEX_SONGS_BY_ID = new TreeMap<>() {{ // SONG = (String artist, String title, String lyrics, Integer stars, String genre) + put(FIRST_INDEX_ID_SONG_1, SONGS[0]); // (ARTIST_FIRST, TITLE_MAGNUM_OPUS ,LYRICS_1, 1, GENRE_ROCK) + put(FIRST_INDEX_ID_SONG_2, SONGS[1]); // (ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES), + put(FIRST_INDEX_ID_SONG_3, SONGS[2]); // (ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ), + put(FIRST_INDEX_ID_SONG_4, SONGS[3]); // (ARTIST_NO, TITLE_POISON, LYRICS_4, 4, GENRE_ROCK), + put(FIRST_INDEX_ID_SONG_5, SONGS[4]); // (ARTIST_YES, TITLE_AFFIRMATIVE,LYRICS_5, 5, GENRE_BLUES), + put(FIRST_INDEX_ID_SONG_6, SONGS[5]); // (ARTIST_UNKNOWN, TITLE_CONFIDENTIAL, LYRICS_6, 6, GENRE_JAZZ) + }}; + + static final TreeMap SECOND_INDEX_SONGS_BY_ID = new TreeMap<>() {{ + put(SECOND_INDEX_ID_SONG_1, SONGS[3]); // (ARTIST_NO, TITLE_POISON, LYRICS_4, 4, GENRE_ROCK), + put(SECOND_INDEX_ID_SONG_2, SONGS[2]); // (ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ), + put(SECOND_INDEX_ID_SONG_3, SONGS[1]); // (ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES), + put(SECOND_INDEX_ID_SONG_4, SONGS[0]); // (ARTIST_FIRST, TITLE_MAGNUM_OPUS ,LYRICS_1, 1, GENRE_ROCK) + }}; + + @BeforeClass + public static void createTestData() { + try(Client client = cluster.getInternalNodeClient()){ + FIRST_INDEX_SONGS_BY_ID.forEach((id, song) -> { + client.prepareIndex(FIRST_INDEX_NAME).setId(id).setRefreshPolicy(IMMEDIATE).setSource(song.asMap()).get(); + }); + + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) + .indices(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS) + )).actionGet(); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) + .index(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE) + .filter(QueryBuilders.queryStringQuery(QUERY_TITLE_NEXT_SONG)) + )).actionGet(); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) + .index(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST) + .filter(QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, ARTIST_TWINS))) + )).actionGet(); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) + .index(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST) + .filter(QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, ARTIST_FIRST))) + )).actionGet(); + + SECOND_INDEX_SONGS_BY_ID.forEach((id, song) -> { + client.prepareIndex(SECOND_INDEX_NAME).setId(id).setRefreshPolicy(IMMEDIATE).setSource(song.asMap()).get(); + }); + client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) + .indices(SECOND_INDEX_NAME) + .alias(SECOND_INDEX_ALIAS) + )).actionGet(); + } + } + + @Test + public void testShouldSearchAll() throws IOException { + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_ALL_USER)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(6)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); + assertThat(searchResponse, searchHitContainsFieldWithValue(2, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(3, FIELD_ARTIST, ARTIST_NO)); + assertThat(searchResponse, searchHitContainsFieldWithValue(4, FIELD_ARTIST, ARTIST_YES)); + assertThat(searchResponse, searchHitContainsFieldWithValue(5, FIELD_ARTIST, ARTIST_UNKNOWN)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(4)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_NO)); + } + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_FIRST_AND_SECOND_USER)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(6)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); + assertThat(searchResponse, searchHitContainsFieldWithValue(2, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(3, FIELD_ARTIST, ARTIST_NO)); + assertThat(searchResponse, searchHitContainsFieldWithValue(4, FIELD_ARTIST, ARTIST_YES)); + assertThat(searchResponse, searchHitContainsFieldWithValue(5, FIELD_ARTIST, ARTIST_UNKNOWN)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(4)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_NO)); + } + } + + @Test + public void testShouldSearchI1_S2I2_S3() throws IOException { + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_STRING)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_STRING)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_STRING)); + } + } + + public void testShouldSearchI1_S3I1_S6I2_S2() throws IOException { + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_FIELD_STARS_GREATER_THAN_FIVE)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_UNKNOWN)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + } + } + + public void testShouldSearchI1_S1I1_S3I2_S2I2_S4() throws IOException { + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_MATCHES_ARTIST_FIRST)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_FIRST)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_FIRST)); + } + } + + public void testShouldSearchStarsLessThanThree() throws IOException { + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_STARS_LESS_THAN_THREE)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_STRING)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_FIRST)); + } + } + + + @Test + public void testSearchForAllDocumentsWithIndexPattern() throws IOException { + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_ALL_USER)) { + SearchRequest searchRequest = new SearchRequest("*".concat(FIRST_INDEX_NAME)); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(6)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); + assertThat(searchResponse, searchHitContainsFieldWithValue(2, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(3, FIELD_ARTIST, ARTIST_NO)); + assertThat(searchResponse, searchHitContainsFieldWithValue(4, FIELD_ARTIST, ARTIST_YES)); + assertThat(searchResponse, searchHitContainsFieldWithValue(5, FIELD_ARTIST, ARTIST_UNKNOWN)); + + searchRequest = new SearchRequest("*".concat(SECOND_INDEX_NAME)); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(4)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_NO)); + } + } + + @Test + public void testSearchForAllDocumentsWithAlias() throws IOException { + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_ALL_USER)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_ALIAS); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(6)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); + assertThat(searchResponse, searchHitContainsFieldWithValue(2, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(3, FIELD_ARTIST, ARTIST_NO)); + assertThat(searchResponse, searchHitContainsFieldWithValue(4, FIELD_ARTIST, ARTIST_YES)); + assertThat(searchResponse, searchHitContainsFieldWithValue(5, FIELD_ARTIST, ARTIST_UNKNOWN)); + + searchRequest = new SearchRequest("*".concat(SECOND_INDEX_NAME)); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(4)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_NO)); + } + } + + @Test + public void testAggregateAndComputeStarRatings() throws IOException { + + //DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_MATCHES_ARTIST_FIRST)) { + String aggregationName = "averageStars"; + Song song = FIRST_INDEX_SONGS_BY_ID.get(FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_TWINS)); + + SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); + assertThat(actualAggregation, instanceOf(ParsedAvg.class)); + assertThat(((ParsedAvg) actualAggregation).getValue(), is(song.getStars()*1.0)); + } + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_FIELD_STARS_GREATER_THAN_FIVE)) { + String aggregationName = "averageStars"; + SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); + assertThat(actualAggregation, instanceOf(ParsedAvg.class)); + assertThat(((ParsedAvg) actualAggregation).getValue(), is(4.5)); + } + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_STARS_LESS_THAN_THREE)) { + String aggregationName = "averageStars"; + SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); + assertThat(actualAggregation, instanceOf(ParsedAvg.class)); + assertThat(((ParsedAvg) actualAggregation).getValue(), is(1.5)); + } + } +} 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 45a1e7b53f..5405146263 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java @@ -53,13 +53,13 @@ public void testDlsAggregations() throws Exception { String query = "{"+ - "\"query\" : {"+ - "\"match_all\": {}"+ - "},"+ - "\"aggs\" : {"+ + "\"query\" : {"+ + "\"match_all\": {}"+ + "},"+ + "\"aggs\" : {"+ "\"thesum\" : { \"sum\" : { \"field\" : \"amount\" } }"+ - "}"+ - "}"; + "}"+ + "}"; HttpResponse res; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager", "password"))).getStatusCode()); @@ -113,17 +113,17 @@ public void testDls() throws Exception { String query = - "{"+ - "\"query\": {"+ - "\"range\" : {"+ - "\"amount\" : {"+ - "\"gte\" : 8,"+ - "\"lte\" : 20,"+ - "\"boost\" : 3.0"+ + "{"+ + "\"query\": {"+ + "\"range\" : {"+ + "\"amount\" : {"+ + "\"gte\" : 8,"+ + "\"lte\" : 20,"+ + "\"boost\" : 3.0"+ "}"+ - "}"+ - "}"+ - "}"; + "}"+ + "}"+ + "}"; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("dept_manager", "password"))).getStatusCode()); @@ -133,16 +133,16 @@ public void testDls() throws Exception { query = "{"+ - "\"query\": {"+ - "\"range\" : {"+ - "\"amount\" : {"+ - "\"gte\" : 100,"+ - "\"lte\" : 2000,"+ - "\"boost\" : 2.0"+ - "}"+ + "\"query\": {"+ + "\"range\" : {"+ + "\"amount\" : {"+ + "\"gte\" : 100,"+ + "\"lte\" : 2000,"+ + "\"boost\" : 2.0"+ "}"+ - "}"+ - "}"; + "}"+ + "}"+ + "}"; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("dept_manager", "password"))).getStatusCode()); @@ -179,9 +179,9 @@ public void testDls() throws Exception { //msearch String msearchBody = "{\"index\":\"deals\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"deals\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ + "{\"index\":\"deals\", \"ignore_unavailable\": true}"+System.lineSeparator()+ + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("dept_manager", "password"))).getStatusCode()); @@ -193,16 +193,16 @@ public void testDls() throws Exception { String mgetBody = "{"+ "\"docs\" : ["+ - "{"+ - "\"_index\" : \"deals\","+ - "\"_id\" : \"1\""+ - " },"+ - " {"+ - "\"_index\" : \"deals\","+ - " \"_id\" : \"2\""+ - "}"+ + "{"+ + "\"_index\" : \"deals\","+ + "\"_id\" : \"1\""+ + " },"+ + " {"+ + "\"_index\" : \"deals\","+ + " \"_id\" : \"2\""+ + "}"+ "]"+ - "}"; + "}"; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("dept_manager", "password"))).getStatusCode()); Assert.assertFalse(res.getBody().contains("_opendistro_security_dls_query")); @@ -222,16 +222,16 @@ public void testNonDls() throws Exception { "{"+ "\"_source\": false,"+ - "\"query\": {"+ - "\"range\" : {"+ - "\"amount\" : {"+ - "\"gte\" : 100,"+ - "\"lte\" : 2000,"+ - "\"boost\" : 2.0"+ - "}"+ + "\"query\": {"+ + "\"range\" : {"+ + "\"amount\" : {"+ + "\"gte\" : 100,"+ + "\"lte\" : 2000,"+ + "\"boost\" : 2.0"+ "}"+ - "}"+ - "}"; + "}"+ + "}"+ + "}"; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("dept_manager", "password"))).getStatusCode()); From 8fdc45aec7fa85af0b850d4b9e60b314fe7a020a Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Mon, 3 Apr 2023 18:04:59 -0700 Subject: [PATCH 158/369] Upgrade spotbugs to version 5.0.14 (#2639) Signed-off-by: Ryan Liang --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1f32ff7e33..916f1ec5e3 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,7 @@ plugins { id 'com.netflix.nebula.ospackage' version "11.0.0" id "org.gradle.test-retry" version "1.5.2" id 'eclipse' - id "com.github.spotbugs" version "5.0.13" + id "com.github.spotbugs" version "5.0.14" id "com.google.osdetector" version "1.7.3" } From 4988a74cdeac09be2e2657c6ab0793a63af76065 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Wed, 5 Apr 2023 02:30:07 -0400 Subject: [PATCH 159/369] Remove DLS from FlsDlsAndFieldMasking to avoid duplicates (#2637) * Remove DLS from FlsDlsAndFieldMasking to avoid duplicates with separated out tests Signed-off-by: Stephen Crawford --- ...Test.java => FlsAndFieldMaskingTests.java} | 206 +----------------- 1 file changed, 1 insertion(+), 205 deletions(-) rename src/integrationTest/java/org/opensearch/security/{FlsDlsAndFieldMaskingTest.java => FlsAndFieldMaskingTests.java} (79%) diff --git a/src/integrationTest/java/org/opensearch/security/FlsDlsAndFieldMaskingTest.java b/src/integrationTest/java/org/opensearch/security/FlsAndFieldMaskingTests.java similarity index 79% rename from src/integrationTest/java/org/opensearch/security/FlsDlsAndFieldMaskingTest.java rename to src/integrationTest/java/org/opensearch/security/FlsAndFieldMaskingTests.java index 4bef7ef4f6..692a3d8e4f 100644 --- a/src/integrationTest/java/org/opensearch/security/FlsDlsAndFieldMaskingTest.java +++ b/src/integrationTest/java/org/opensearch/security/FlsAndFieldMaskingTests.java @@ -59,13 +59,11 @@ import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions.Type.ADD; import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; import static org.opensearch.client.RequestOptions.DEFAULT; import static org.opensearch.security.Song.ARTIST_FIRST; -import static org.opensearch.security.Song.ARTIST_NO; import static org.opensearch.security.Song.ARTIST_STRING; import static org.opensearch.security.Song.ARTIST_TWINS; import static org.opensearch.security.Song.FIELD_ARTIST; @@ -101,7 +99,7 @@ @RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) @ThreadLeakScope(ThreadLeakScope.Scope.NONE) -public class FlsDlsAndFieldMaskingTest { +public class FlsAndFieldMaskingTests { static final String FIRST_INDEX_ID_SONG_1 = "INDEX_1_S1"; static final String FIRST_INDEX_ID_SONG_2 = "INDEX_1_S2"; @@ -472,23 +470,6 @@ public void searchForDocuments() throws IOException { assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, song.getArtist())); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); } - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { - SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); - - searchRequest = new SearchRequest(SECOND_INDEX_NAME); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); - } } @Test @@ -524,23 +505,6 @@ public void searchForDocumentsWithIndexPattern() throws IOException { assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, song.getArtist())); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); } - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { - SearchRequest searchRequest = new SearchRequest("*".concat(FIRST_INDEX_NAME)); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); - - searchRequest = new SearchRequest("*".concat(SECOND_INDEX_NAME)); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); - } } @Test @@ -575,23 +539,6 @@ public void searchForDocumentsViaAlias() throws IOException { assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, song.getArtist())); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); } - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { - SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_ALIAS); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); - - searchRequest = new SearchRequest(SECOND_INDEX_ALIAS); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); - } } @Test @@ -612,22 +559,6 @@ public void searchForDocumentsViaFilteredAlias() throws IOException { assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); } - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { - SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); - - searchRequest = new SearchRequest(FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(0)); - } } @Test @@ -663,17 +594,6 @@ public void searchForDocumentsViaAllIndicesAlias() throws IOException { assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); } - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ALL_INDICES_STRING_ARTIST_READER)) { - SearchRequest searchRequest = new SearchRequest(ALL_INDICES_ALIAS); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_STRING)); - assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); - } } @Test @@ -701,23 +621,6 @@ public void scrollOverSearchResults() throws IOException { assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); } - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { - SearchRequest searchRequest = searchRequestWithScroll(FIRST_INDEX_NAME, 2); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containNotEmptyScrollingId()); - - SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); - - SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); - assertThat(scrollResponse, isSuccessfulSearchResponse()); - assertThat(scrollResponse, containNotEmptyScrollingId()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); - } } @Test @@ -739,33 +642,6 @@ public void aggregateDataAndComputeAverage() throws IOException { assertThat(actualAggregation, instanceOf(ParsedAvg.class)); assertThat(((ParsedAvg) actualAggregation).getValue(), is(expectedValue)); } - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { - String aggregationName = "averageStars"; - Song song = FIRST_INDEX_SONGS_BY_ID.get(FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_TWINS)); - - SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); - Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); - assertThat(actualAggregation, instanceOf(ParsedAvg.class)); - assertThat(((ParsedAvg) actualAggregation).getValue(), is(song.getStars()*1.0)); - } - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ALL_INDICES_STARS_LESS_THAN_ZERO_READER)) { - String aggregationName = "averageStars"; - SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); - Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); - assertThat(actualAggregation, instanceOf(ParsedAvg.class)); - assertThat(((ParsedAvg) actualAggregation).getValue(), is(Double.POSITIVE_INFINITY)); - } } @Test @@ -792,21 +668,6 @@ public void getDocument() throws IOException { assertThat(response, documentContainField(FIELD_ARTIST, song.getArtist())); assertThat(response, documentContainField(FIELD_STARS, song.getStars())); } - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { - String songId = FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_TWINS); - - GetResponse response = restHighLevelClient.get(new GetRequest(FIRST_INDEX_NAME, songId), DEFAULT); - - assertThat(response, containDocument(FIRST_INDEX_NAME, songId)); - - songId = FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_STRING); - - response = restHighLevelClient.get(new GetRequest(FIRST_INDEX_NAME, songId), DEFAULT); - - assertThat(response, not(containDocument(FIRST_INDEX_NAME, songId))); - } } @Test @@ -848,32 +709,6 @@ public void multiGetDocuments() throws IOException { )); } } - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { - List indicesToCheck = List.of(FIRST_INDEX_NAME, FIRST_INDEX_ALIAS); - String firstSongId = FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_NO); - String secondSongId = FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_STRING); - - for(String index : indicesToCheck) { - MultiGetRequest request = new MultiGetRequest(); - request.add(new MultiGetRequest.Item(index, firstSongId)); - request.add(new MultiGetRequest.Item(index, secondSongId)); - - MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); - - assertThat(response, isSuccessfulMultiGetResponse()); - assertThat(response, numberOfGetItemResponsesIsEqualTo(2)); - - MultiGetItemResponse[] responses = response.getResponses(); - assertThat(responses[0].getResponse(), allOf( - not(containDocument(FIRST_INDEX_NAME, firstSongId))) - ); - assertThat(responses[1].getResponse(), allOf( - not(containDocument(FIRST_INDEX_NAME, secondSongId))) - ); - } - } } @Test @@ -916,33 +751,6 @@ public void multiSearchDocuments() throws IOException { )); } } - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { - List> indicesToCheck = List.of( - List.of(FIRST_INDEX_NAME, SECOND_INDEX_NAME), - List.of(FIRST_INDEX_ALIAS, SECOND_INDEX_ALIAS) - ); - String firstSongId = FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_TWINS); - String secondSongId = FIND_ID_OF_SONG_WITH_ARTIST.apply(SECOND_INDEX_SONGS_BY_ID, ARTIST_FIRST); - - for (List indices : indicesToCheck) { - MultiSearchRequest request = new MultiSearchRequest(); - indices.forEach(index -> request.add(new SearchRequest(index))); - - MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); - - assertThat(response, isSuccessfulMultiSearchResponse()); - assertThat(response, numberOfSearchItemResponsesIsEqualTo(2)); - - MultiSearchResponse.Item[] responses = response.getResponses(); - - assertThat(responses[0].getResponse(), searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, firstSongId)); - assertThat(responses[0].getResponse(), searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); - assertThat(responses[1].getResponse(), searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, secondSongId)); - assertThat(responses[1].getResponse(), searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); - } - } } @Test @@ -958,18 +766,6 @@ public void getFieldCapabilities() throws IOException { assertThat(response, containsFieldWithNameAndType(FIELD_TITLE, "text")); assertThat(response, containsFieldWithNameAndType(FIELD_LYRICS, "text")); } - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(TWINS_FIRST_ARTIST_READER)) { - FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(FIRST_INDEX_NAME).fields(FIELD_ARTIST, FIELD_TITLE, FIELD_LYRICS); - FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); - - assertThat(response, containsExactlyIndices(FIRST_INDEX_NAME)); - assertThat(response, numberOfFieldsIsEqualTo(3)); - assertThat(response, containsFieldWithNameAndType(FIELD_ARTIST, "text")); - assertThat(response, containsFieldWithNameAndType(FIELD_TITLE, "text")); - assertThat(response, containsFieldWithNameAndType(FIELD_LYRICS, "text")); - } } } From 6446268bdf4fd9f39d0e669aafa227b582dbf4e6 Mon Sep 17 00:00:00 2001 From: Andrey Pleskach Date: Thu, 6 Apr 2023 22:58:45 +0200 Subject: [PATCH 160/369] Separate config option to enable restapi: permissions (#2605) Added config settings plugins.security.restapi.admin.enabled which enables/disables :resapi permissions. Default is false Signed-off-by: Andrey Pleskach --- .../security/OpenSearchSecurityPlugin.java | 3 +- .../dlic/rest/api/AbstractApiAction.java | 6 ++- .../api/RestApiAdminPrivilegesEvaluator.java | 33 +++++++++---- .../dlic/rest/api/SecuritySSLCertsAction.java | 4 -- .../security/support/ConfigConstants.java | 2 +- .../dlic/rest/api/ActionGroupsApiTest.java | 7 +-- .../dlic/rest/api/AllowlistApiTest.java | 5 +- .../dlic/rest/api/NodesDnApiTest.java | 6 ++- .../security/dlic/rest/api/RolesApiTest.java | 15 +++--- .../dlic/rest/api/RolesMappingApiTest.java | 5 +- .../dlic/rest/api/SslCertsApiTest.java | 47 +++++++++---------- .../security/dlic/rest/api/UserApiTest.java | 5 +- 12 files changed, 83 insertions(+), 55 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index ce64299f13..af089584a9 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -1027,7 +1027,8 @@ public List> getSettings() { // OpenSearch Security - REST API settings.add(Setting.listSetting(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here settings.add(Setting.groupSetting(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + ".", Property.NodeScope)); - + settings.add(Setting.boolSetting(ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED, false, Property.NodeScope, Property.Filtered)); + settings.add(Setting.simpleString(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, Property.NodeScope, Property.Filtered)); diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java index 0e98124b6f..fa69be0ad4 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java @@ -66,6 +66,8 @@ import org.opensearch.security.user.User; import org.opensearch.threadpool.ThreadPool; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; + public abstract class AbstractApiAction extends BaseRestHandler { protected final Logger log = LogManager.getLogger(this.getClass()); @@ -94,7 +96,9 @@ protected AbstractApiAction(final Settings settings, final Path configPath, fina this.restApiPrivilegesEvaluator = new RestApiPrivilegesEvaluator(settings, adminDNs, evaluator, principalExtractor, configPath, threadPool); this.restApiAdminPrivilegesEvaluator = - new RestApiAdminPrivilegesEvaluator(threadPool.getThreadContext(), evaluator, adminDNs); + new RestApiAdminPrivilegesEvaluator( + threadPool.getThreadContext(), evaluator, adminDNs, + settings.getAsBoolean(SECURITY_RESTAPI_ADMIN_ENABLED, false)); this.auditLog = auditLog; } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java index c3449e99bb..c8e44ee4ca 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java @@ -29,6 +29,8 @@ import org.opensearch.security.support.WildcardMatcher; import org.opensearch.security.user.User; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; + public class RestApiAdminPrivilegesEvaluator { protected final Logger logger = LogManager.getLogger(RestApiAdminPrivilegesEvaluator.class); @@ -85,13 +87,17 @@ default String build() { private final AdminDNs adminDNs; + private final boolean restapiAdminEnabled; + public RestApiAdminPrivilegesEvaluator( final ThreadContext threadContext, final PrivilegesEvaluator privilegesEvaluator, - final AdminDNs adminDNs) { + final AdminDNs adminDNs, + final boolean restapiAdminEnabled) { this.threadContext = threadContext; this.privilegesEvaluator = privilegesEvaluator; this.adminDNs = adminDNs; + this.restapiAdminEnabled = restapiAdminEnabled; } public boolean isCurrentUserRestApiAdminFor(final Endpoint endpoint, final String action) { @@ -108,20 +114,31 @@ public boolean isCurrentUserRestApiAdminFor(final Endpoint endpoint, final Strin return true; } if (!ENDPOINTS_WITH_PERMISSIONS.containsKey(endpoint)) { - if (logger.isDebugEnabled()) { - logger.debug("No permission found for {} endpoint", endpoint); - } + logger.debug("No permission found for {} endpoint", endpoint); return false; } final String permission = ENDPOINTS_WITH_PERMISSIONS.get(endpoint).build(action); - if (logger.isDebugEnabled()) { - logger.debug("Checking permission {} for endpoint {}", permission, endpoint); - } - return privilegesEvaluator.hasRestAdminPermissions( + final boolean hasAccess = privilegesEvaluator.hasRestAdminPermissions( userAndRemoteAddress.getLeft(), userAndRemoteAddress.getRight(), permission ); + if (logger.isDebugEnabled()) { + logger.debug( + "User {} with permission {} {} access to endpoint {}", + userAndRemoteAddress.getLeft().getName(), + permission, + hasAccess ? "has" : "has no", + endpoint + ); + logger.debug( + "{} set to {}. {} use access decision", + SECURITY_RESTAPI_ADMIN_ENABLED, + restapiAdminEnabled, + restapiAdminEnabled ? "Will" : "Will not" + ); + } + return hasAccess && restapiAdminEnabled; } public boolean containsRestApiAdminPermissions(final Object configObject) { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java index 4168bf4109..1c1fe9b815 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java @@ -72,8 +72,6 @@ public class SecuritySSLCertsAction extends AbstractApiAction { private final boolean certificatesReloadEnabled; - private final RestApiAdminPrivilegesEvaluator restApiAdminPrivilegesEvaluator; - private final boolean httpsEnabled; public SecuritySSLCertsAction(final Settings settings, @@ -91,8 +89,6 @@ public SecuritySSLCertsAction(final Settings settings, final boolean certificatesReloadEnabled) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, privilegesEvaluator, threadPool, auditLog); this.securityKeyStore = securityKeyStore; - this.restApiAdminPrivilegesEvaluator = - new RestApiAdminPrivilegesEvaluator(threadPool.getThreadContext(), privilegesEvaluator, adminDNs); this.certificatesReloadEnabled = certificatesReloadEnabled; this.httpsEnabled = settings.getAsBoolean(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true); } diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index ee83284ca4..375b22b65f 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -248,11 +248,11 @@ public enum RolesMappingResolution { public static final String SECURITY_DLS_MODE = "plugins.security.dls.mode"; // REST API public static final String SECURITY_RESTAPI_ROLES_ENABLED = "plugins.security.restapi.roles_enabled"; + public static final String SECURITY_RESTAPI_ADMIN_ENABLED = "plugins.security.restapi.admin.enabled"; public static final String SECURITY_RESTAPI_ENDPOINTS_DISABLED = "plugins.security.restapi.endpoints_disabled"; public static final String SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX = "plugins.security.restapi.password_validation_regex"; public static final String SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE = "plugins.security.restapi.password_validation_error_message"; - // Illegal Opcodes from here on public static final String SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY = "plugins.security.unsupported.disable_rest_auth_initially"; public static final String SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY = "plugins.security.unsupported.disable_intertransport_auth_initially"; public static final String SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY = "plugins.security.unsupported.passive_intertransport_auth_initially"; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java index a0d1ece77a..abb45df268 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java @@ -29,6 +29,7 @@ import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; public class ActionGroupsApiTest extends AbstractRestApiUnitTest { private final String ENDPOINT; @@ -362,7 +363,7 @@ void verifyPatchForSuperAdmin(final Header[] header, final boolean userAdminCert @Test public void testActionGroupsApiForRestAdmin() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); rh.sendAdminCertificate = false; // create index setupStarfleetIndex(); @@ -380,7 +381,7 @@ public void testActionGroupsApiForRestAdmin() throws Exception { @Test public void testActionGroupsApiForActionGroupsRestApiAdmin() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); rh.sendAdminCertificate = false; // create index setupStarfleetIndex(); @@ -398,7 +399,7 @@ public void testActionGroupsApiForActionGroupsRestApiAdmin() throws Exception { @Test public void testCreateActionGroupWithRestAdminPermissionsForbidden() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); rh.sendAdminCertificate = false; final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); final Header restApiAdminActionGroupsHeader = encodeBasicHeader("rest_api_admin_actiongroups", "rest_api_admin_actiongroups"); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java index 05739cc4ab..50090fcfcc 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java @@ -37,6 +37,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; /** * Testing class to verify that {@link AllowlistApiAction} works correctly. @@ -158,7 +159,7 @@ public void testAllowlistApi() throws Exception { @Test public void testAllowlistApiWithPermissions() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); final Header restApiAllowlistHeader = encodeBasicHeader("rest_api_admin_allowlist", "rest_api_admin_allowlist"); @@ -170,7 +171,7 @@ public void testAllowlistApiWithPermissions() throws Exception { @Test public void testAllowlistApiWithAllowListPermissions() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); final Header restApiAllowlistHeader = encodeBasicHeader("rest_api_admin_allowlist", "rest_api_admin_allowlist"); final Header restApiUserHeader = encodeBasicHeader("test", "test"); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java index 03d16ee78d..ef1042682b 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java @@ -37,6 +37,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; public class NodesDnApiTest extends AbstractRestApiUnitTest { private HttpResponse response; @@ -184,7 +185,10 @@ public void testNodesDnApi() throws Exception { @Test public void testNodesDnApiWithPermissions() throws Exception { - Settings settings = Settings.builder().put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) + Settings settings = + Settings.builder() + .put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) + .put(SECURITY_RESTAPI_ADMIN_ENABLED, true) .build(); setupWithRestRoles(settings); final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java index 529658940a..5a6d1f297c 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java @@ -31,6 +31,7 @@ import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; public class RolesApiTest extends AbstractRestApiUnitTest { private final String ENDPOINT; @@ -77,15 +78,17 @@ public void testAllRolesForSuperAdmin() throws Exception { @Test public void testAllRolesForRestAdmin() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + rh.sendAdminCertificate = false; checkSuperAdminRoles(new Header[]{restApiAdminHeader}); } @Test public void testAllRolesForRolesRestAdmin() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); final Header restApiAdminRolesHeader = encodeBasicHeader("rest_api_admin_roles", "rest_api_admin_roles"); + rh.sendAdminCertificate = false; checkSuperAdminRoles(new Header[]{restApiAdminRolesHeader}); } @@ -519,7 +522,7 @@ void verifyPatchForSuperAdmin(final Header[] header, final boolean sendAdminCert @Test public void testRolesApiWithAllRestApiPermissions() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); @@ -539,7 +542,7 @@ public void testRolesApiWithAllRestApiPermissions() throws Exception { @Test public void testRolesApiWithRestApiRolePermission() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); final Header restApiRolesHeader = encodeBasicHeader("rest_api_admin_roles", "rest_api_admin_roles"); @@ -560,7 +563,7 @@ public void testRolesApiWithRestApiRolePermission() throws Exception { @Test public void testCreateOrUpdateRestApiAdminRoleForbiddenForNonSuperAdmin() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); rh.sendAdminCertificate = false; final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); @@ -632,7 +635,7 @@ public void testCreateOrUpdateRestApiAdminRoleForbiddenForNonSuperAdmin() throws @Test public void testDeleteRestApiAdminRoleForbiddenForNonSuperAdmin() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); rh.sendAdminCertificate = false; final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java index f6405e984a..44e160e151 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java @@ -29,6 +29,7 @@ import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; public class RolesMappingApiTest extends AbstractRestApiUnitTest { private final String ENDPOINT; @@ -98,7 +99,7 @@ public void testRolesMappingApi() throws Exception { @Test public void testRolesMappingApiWithFullPermissions() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); rh.sendAdminCertificate = false; final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); @@ -466,7 +467,7 @@ void verifyNonSuperAdminUser(final Header[] header) throws Exception { @Test public void testChangeRestApiAdminRoleMappingForbiddenForNonSuperAdmin() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); rh.sendAdminCertificate = false; final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java index 2df1fdfaf7..425c2dca50 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java @@ -28,6 +28,7 @@ import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; public class SslCertsApiTest extends AbstractRestApiUnitTest { @@ -83,9 +84,7 @@ public String certsReloadEndpoint(final String certType) { return String.format("%s/api/ssl/%s/reloadcerts", PLUGINS_PREFIX, certType); } - @Test - public void testCertsInfo() throws Exception { - setupWithRestRoles(); + private void verifyHasNoAccess() throws Exception { final Header adminCredsHeader = encodeBasicHeader("admin", "admin"); // No creds, no admin certificate - UNAUTHORIZED rh.sendAdminCertificate = false; @@ -96,17 +95,28 @@ public void testCertsInfo() throws Exception { response = rh.executeGetRequest(certsInfoEndpoint(), adminCredsHeader); Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executeGetRequest(certsInfoEndpoint(), restApiHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + @Test + public void testCertsInfo() throws Exception { + setup(); + verifyHasNoAccess(); sendAdminCert(); - response = rh.executeGetRequest(certsInfoEndpoint()); + HttpResponse response = rh.executeGetRequest(certsInfoEndpoint()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Assert.assertEquals(EXPECTED_CERTIFICATES_BY_TYPE, response.getBody()); + } + + @Test + public void testCertsInfoRestAdmin() throws Exception { + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); + verifyHasNoAccess(); rh.sendAdminCertificate = false; Assert.assertEquals(EXPECTED_CERTIFICATES_BY_TYPE, loadCerts(restApiAdminHeader)); Assert.assertEquals(EXPECTED_CERTIFICATES_BY_TYPE, loadCerts(restApiCertsInfoAdminHeader)); - - response = rh.executeGetRequest(certsInfoEndpoint(), restApiHeader); - Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); } private String loadCerts(final Header... header) throws Exception { @@ -120,23 +130,18 @@ public void testReloadCertsNotAvailableByDefault() throws Exception { setupWithRestRoles(); sendAdminCert(); - verifyReloadCertsNotAvailable(); + verifyReloadCertsNotAvailable(HttpStatus.SC_BAD_REQUEST); rh.sendAdminCertificate = false; - verifyReloadCertsNotAvailable(restApiAdminHeader); - verifyReloadCertsNotAvailable(restApiReloadCertsAdminHeader); - - HttpResponse response = rh.executePutRequest(certsReloadEndpoint(HTTP_CERTS), "{}", restApiHeader); - Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - response = rh.executePutRequest(certsReloadEndpoint(TRANSPORT_CERTS), "{}", restApiHeader); - Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + verifyReloadCertsNotAvailable(HttpStatus.SC_FORBIDDEN, restApiAdminHeader); + verifyReloadCertsNotAvailable(HttpStatus.SC_FORBIDDEN, restApiReloadCertsAdminHeader); } - private void verifyReloadCertsNotAvailable(final Header... header) { + private void verifyReloadCertsNotAvailable(final int expectedStatus, final Header... header) { HttpResponse response = rh.executePutRequest(certsReloadEndpoint(HTTP_CERTS), "{}", header); - Assert.assertEquals(response.getBody(), HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertEquals(response.getBody(), expectedStatus, response.getStatusCode()); response = rh.executePutRequest(certsReloadEndpoint(TRANSPORT_CERTS), "{}", header); - Assert.assertEquals(response.getBody(), HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertEquals(response.getBody(), expectedStatus, response.getStatusCode()); } @Test @@ -154,12 +159,6 @@ public void testReloadCertsWrongCertsType() throws Exception { } - @Test - public void testReloadCerts() throws Exception { - setupWithRestRoles(reloadEnabled()); - } - - private void sendAdminCert() { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index 88eab72766..1ecd96e148 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -33,6 +33,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; import static org.opensearch.security.dlic.rest.api.InternalUsersApiAction.RESTRICTED_FROM_USERNAME; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; public class UserApiTest extends AbstractRestApiUnitTest { @@ -424,7 +425,7 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E @Test public void testUserApiWithRestAdminPermissions() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); rh.sendAdminCertificate = false; final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); // initial configuration @@ -442,7 +443,7 @@ public void testUserApiWithRestAdminPermissions() throws Exception { @Test public void testUserApiWithRestInternalUsersAdminPermissions() throws Exception { - setupWithRestRoles(); + setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); rh.sendAdminCertificate = false; final Header restApiInternalUsersAdminHeader = encodeBasicHeader("rest_api_admin_internalusers", "rest_api_admin_internalusers"); // initial configuration From 24bffc93c8f09bf6eee6cf8d6701fa2edcece4a8 Mon Sep 17 00:00:00 2001 From: Vamsi Manohar Date: Thu, 6 Apr 2023 13:59:39 -0700 Subject: [PATCH 161/369] Adding new system index for ppl/sql datasource configurations (#2650) Signed-off-by: vamsi-amazon --- tools/install_demo_configuration.bat | 2 +- tools/install_demo_configuration.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/install_demo_configuration.bat b/tools/install_demo_configuration.bat index 06fbf5ec51..474014536c 100755 --- a/tools/install_demo_configuration.bat +++ b/tools/install_demo_configuration.bat @@ -315,7 +315,7 @@ echo plugins.security.enable_snapshot_restore_privilege: true >> "%OPENSEARCH_CO echo plugins.security.check_snapshot_restore_write_privileges: true >> "%OPENSEARCH_CONF_FILE%" echo plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"] >> "%OPENSEARCH_CONF_FILE%" echo plugins.security.system_indices.enabled: true >> "%OPENSEARCH_CONF_FILE%" -echo plugins.security.system_indices.indices: [".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models"] >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.system_indices.indices: [".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".ql-datasources", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models"] >> "%OPENSEARCH_CONF_FILE%" :: network.host >nul findstr /b /c:"network.host" "%OPENSEARCH_CONF_FILE%" && ( diff --git a/tools/install_demo_configuration.sh b/tools/install_demo_configuration.sh index ccd620b528..6d6203124a 100755 --- a/tools/install_demo_configuration.sh +++ b/tools/install_demo_configuration.sh @@ -383,7 +383,7 @@ echo "plugins.security.enable_snapshot_restore_privilege: true" | $SUDO_CMD tee echo "plugins.security.check_snapshot_restore_write_privileges: true" | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null echo 'plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null echo 'plugins.security.system_indices.enabled: true' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null -echo 'plugins.security.system_indices.indices: [".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models"]' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null +echo 'plugins.security.system_indices.indices: [".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".ql-datasources", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models"]' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null #network.host if $SUDO_CMD grep --quiet -i "^network.host" "$OPENSEARCH_CONF_FILE"; then From f9439c52b90401f44c297f27d29eafa8f4ecf99d Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Thu, 6 Apr 2023 17:00:18 -0400 Subject: [PATCH 162/369] Update README.md to add slack badge and announcement (#2653) * Update README.md Add slack workspace announcement at top of README for a couple weeks. * Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index eb5ccb4fa4..f0f6321d9d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ [![CI](https://github.com/opensearch-project/security/workflows/CI/badge.svg?branch=main)](https://github.com/opensearch-project/security/actions) [![](https://img.shields.io/github/issues/opensearch-project/security/untriaged?labelColor=red)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"untriaged") [![](https://img.shields.io/github/issues/opensearch-project/security/security%20vulnerability?labelColor=red)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"security%20vulnerability") [![](https://img.shields.io/github/issues/opensearch-project/security)](https://github.com/opensearch-project/security/issues) [![](https://img.shields.io/github/issues-pr/opensearch-project/security)](https://github.com/opensearch-project/security/pulls) [![](https://img.shields.io/codecov/c/gh/opensearch-project/security)](https://app.codecov.io/gh/opensearch-project/security) [![](https://img.shields.io/github/issues/opensearch-project/security/v2.4.0)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"v2.4.0") [![](https://img.shields.io/github/issues/opensearch-project/security/v3.0.0)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"v3.0.0") +[![Slack](https://img.shields.io/badge/Slack-4A154B?&logo=slack&logoColor=white)](https://opensearch.slack.com/archives/C051Y637FKK) + + + +## Announcement: The Slack workspace is live! Please join the [conversation](https://opensearch.slack.com/archives/C051Y637FKK). From 9fca0dae5c1e3fe4746381d9f87c4d4abb0f0fda Mon Sep 17 00:00:00 2001 From: Abhi Kalra <99718513+abhivka7@users.noreply.github.com> Date: Fri, 7 Apr 2023 21:51:26 +0530 Subject: [PATCH 163/369] Dynamic_Tenancy_Configurations changes (#2607) Signed-off-by: Abhi Kalra Co-authored-by: Abhi Kalra --- config/config.yml | 2 + legacy/securityconfig_v6/config.yml | 1 + .../security/OpenSearchSecurityPlugin.java | 10 ++ .../security/action/tenancy/EmptyRequest.java | 35 ++++ .../tenancy/TenancyConfigRestHandler.java | 65 ++++++++ .../tenancy/TenancyConfigRetrieveActions.java | 24 +++ .../TenancyConfigRetrieveResponse.java | 70 ++++++++ .../TenancyConfigRetrieveTransportAction.java | 63 +++++++ .../tenancy/TenancyConfigUpdateAction.java | 26 +++ .../tenancy/TenancyConfigUpdateRequest.java | 69 ++++++++ .../TenancyConfigUpdateTransportAction.java | 155 ++++++++++++++++++ .../action/tenancy/TenancyConfigs.java | 18 ++ .../PrivilegesInterceptorImpl.java | 7 + .../dlic/rest/api/AbstractApiAction.java | 18 +- .../dlic/rest/api/AccountApiAction.java | 2 +- .../dlic/rest/api/AllowlistApiAction.java | 2 +- .../dlic/rest/api/InternalUsersApiAction.java | 2 +- .../dlic/rest/api/MigrateApiAction.java | 10 +- .../rest/api/PatchableResourceApiAction.java | 4 +- .../dlic/rest/api/RolesMappingApiAction.java | 2 +- .../privileges/PrivilegesEvaluator.java | 9 + .../security/rest/DashboardsInfoAction.java | 3 + .../securityconf/DynamicConfigModel.java | 2 + .../securityconf/DynamicConfigModelV6.java | 6 + .../securityconf/DynamicConfigModelV7.java | 6 + .../securityconf/impl/v6/ConfigV6.java | 4 + .../securityconf/impl/v7/ConfigV7.java | 10 +- .../security/support/ConfigConstants.java | 4 + .../test/TenancyDefaultTenantTests.java | 96 +++++++++++ .../test/TenancyMultitenancyEnabledTests.java | 89 ++++++++++ .../TenancyPrivateTenantEnabledTests.java | 101 ++++++++++++ .../security/test/helper/rest/RestHelper.java | 14 +- src/test/resources/auditlog/config.yml | 2 + src/test/resources/cache/config.yml | 2 + src/test/resources/composite_config.yml | 2 + src/test/resources/config.yml | 2 + src/test/resources/config_anon.yml | 2 + src/test/resources/config_clientcert.yml | 2 + src/test/resources/config_disable_all.yml | 2 + src/test/resources/config_dnfof.yml | 2 + src/test/resources/config_invalidlic.yml | 2 + src/test/resources/config_lic.yml | 2 + src/test/resources/config_lic_rk.yml | 2 + src/test/resources/config_multirolespan.yml | 2 + .../resources/config_protected_indices.yml | 2 + src/test/resources/config_proxy.yml | 2 + src/test/resources/config_proxy_custom.yml | 2 + .../config_respect_indices_options.yml | 2 + .../resources/config_rest_impersonation.yml | 2 + src/test/resources/config_system_indices.yml | 2 + .../resources/config_transport_username.yml | 2 + src/test/resources/config_xff.yml | 2 + src/test/resources/dlsfls/config.yml | 2 + src/test/resources/ldap/config.yml | 2 + src/test/resources/ldap/config_ldap2.yml | 2 + src/test/resources/multitenancy/config.yml | 2 + .../multitenancy/config_anonymous.yml | 2 + .../multitenancy/config_basic_auth.yml | 2 + .../resources/multitenancy/config_nodnfof.yml | 2 + src/test/resources/restapi/config.yml | 2 + .../resources/restapi/invalid_config.json | 2 + .../resources/restapi/security_config.json | 2 + .../resources/restapi/securityconfig.json | 2 + .../restapi/securityconfig_nondefault.json | 2 + .../resources/security_passive/config.yml | 2 + 65 files changed, 965 insertions(+), 30 deletions(-) create mode 100644 src/main/java/org/opensearch/security/action/tenancy/EmptyRequest.java create mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRestHandler.java create mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveActions.java create mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveResponse.java create mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveTransportAction.java create mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateAction.java create mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateRequest.java create mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateTransportAction.java create mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigs.java create mode 100644 src/test/java/org/opensearch/security/multitenancy/test/TenancyDefaultTenantTests.java create mode 100644 src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java create mode 100644 src/test/java/org/opensearch/security/multitenancy/test/TenancyPrivateTenantEnabledTests.java diff --git a/config/config.yml b/config/config.yml index 0537ff8406..1493a0d7f1 100644 --- a/config/config.yml +++ b/config/config.yml @@ -68,6 +68,8 @@ config: #kibana: # Kibana multitenancy #multitenancy_enabled: true + #private_tenant_enabled: true + #default_tenant: "" #server_username: kibanaserver #index: '.kibana' http: diff --git a/legacy/securityconfig_v6/config.yml b/legacy/securityconfig_v6/config.yml index da2be1e38d..15d5ee9973 100644 --- a/legacy/securityconfig_v6/config.yml +++ b/legacy/securityconfig_v6/config.yml @@ -60,6 +60,7 @@ opendistro_security: #filtered_alias_mode: warn #kibana: #multitenancy_enabled: true + #server_username: kibanaserver #index: '.kibana' #do_not_fail_on_forbidden: false diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index af089584a9..ffd7f730ed 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -115,6 +115,11 @@ import org.opensearch.search.query.QuerySearchResult; import org.opensearch.security.action.configupdate.ConfigUpdateAction; import org.opensearch.security.action.configupdate.TransportConfigUpdateAction; +import org.opensearch.security.action.tenancy.TenancyConfigRestHandler; +import org.opensearch.security.action.tenancy.TenancyConfigRetrieveActions; +import org.opensearch.security.action.tenancy.TenancyConfigRetrieveTransportAction; +import org.opensearch.security.action.tenancy.TenancyConfigUpdateAction; +import org.opensearch.security.action.tenancy.TenancyConfigUpdateTransportAction; import org.opensearch.security.action.whoami.TransportWhoAmIAction; import org.opensearch.security.action.whoami.WhoAmIAction; import org.opensearch.security.auditlog.AuditLog; @@ -469,6 +474,7 @@ public List getRestHandlers(Settings settings, RestController restC Objects.requireNonNull(cs), Objects.requireNonNull(adminDns), Objects.requireNonNull(cr))); handlers.add(new SecurityConfigUpdateAction(settings, restController, Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); handlers.add(new SecurityWhoAmIAction(settings, restController, Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); + handlers.add(new TenancyConfigRestHandler()); handlers.addAll( SecurityRestApiActions.getHandler( settings, @@ -505,6 +511,10 @@ public UnaryOperator getRestHandlerWrapper(final ThreadContext thre if(!disabled && !SSLConfig.isSslOnlyMode()) { actions.add(new ActionHandler<>(ConfigUpdateAction.INSTANCE, TransportConfigUpdateAction.class)); actions.add(new ActionHandler<>(WhoAmIAction.INSTANCE, TransportWhoAmIAction.class)); + + actions.add(new ActionHandler<>(TenancyConfigRetrieveActions.INSTANCE, TenancyConfigRetrieveTransportAction.class)); + actions.add(new ActionHandler<>(TenancyConfigUpdateAction.INSTANCE, TenancyConfigUpdateTransportAction.class)); + } return actions; } diff --git a/src/main/java/org/opensearch/security/action/tenancy/EmptyRequest.java b/src/main/java/org/opensearch/security/action/tenancy/EmptyRequest.java new file mode 100644 index 0000000000..607216a8c3 --- /dev/null +++ b/src/main/java/org/opensearch/security/action/tenancy/EmptyRequest.java @@ -0,0 +1,35 @@ +/* + * 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.action.tenancy; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.common.io.stream.StreamInput; + +public class EmptyRequest extends ActionRequest { + + public EmptyRequest(final StreamInput in) throws IOException { + super(in); + } + + public EmptyRequest() throws IOException { + super(); + } + + @Override + public ActionRequestValidationException validate() + { + return null; + } +} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRestHandler.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRestHandler.java new file mode 100644 index 0000000000..0a00d16694 --- /dev/null +++ b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRestHandler.java @@ -0,0 +1,65 @@ +/* + * 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.action.tenancy; + +import java.io.IOException; +import java.util.List; + +import com.google.common.collect.ImmutableList; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.rest.RestRequest.Method.PUT; + +public class TenancyConfigRestHandler extends BaseRestHandler { + + public TenancyConfigRestHandler() { + super(); + } + + @Override + public String getName() { + return "Multi Tenancy actions to Retrieve / Update configs."; + } + + @Override + public List routes() { + return ImmutableList.of( + new Route(GET, "/_plugins/_security/api/tenancy/config"), + new Route(PUT, "/_plugins/_security/api/tenancy/config") + ); + } + + @Override + protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient nodeClient) throws IOException { + + switch (request.method()) { + case GET: + return channel -> nodeClient.execute( + TenancyConfigRetrieveActions.INSTANCE, + new EmptyRequest(), + new RestToXContentListener<>(channel)); + case PUT: + return channel -> nodeClient.execute( + TenancyConfigUpdateAction.INSTANCE, + TenancyConfigUpdateRequest.fromXContent(request.contentParser()), + new RestToXContentListener<>(channel)); + default: + throw new RuntimeException("Not implemented"); + } + } + +} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveActions.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveActions.java new file mode 100644 index 0000000000..796f233f13 --- /dev/null +++ b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveActions.java @@ -0,0 +1,24 @@ +/* + * 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.action.tenancy; + +import org.opensearch.action.ActionType; + +public class TenancyConfigRetrieveActions extends ActionType { + + public static final TenancyConfigRetrieveActions INSTANCE = new TenancyConfigRetrieveActions(); + public static final String NAME = "cluster:feature/tenancy/config/read"; + + protected TenancyConfigRetrieveActions() { + super(NAME, TenancyConfigRetrieveResponse::new); + } +} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveResponse.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveResponse.java new file mode 100644 index 0000000000..463cc7d831 --- /dev/null +++ b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveResponse.java @@ -0,0 +1,70 @@ +/* + * 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.action.tenancy; + +import java.io.IOException; + +import org.opensearch.action.ActionResponse; +import org.opensearch.common.Strings; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +public class TenancyConfigRetrieveResponse extends ActionResponse implements ToXContentObject { + + public TenancyConfigs tenancyConfigs = new TenancyConfigs(); + + public TenancyConfigRetrieveResponse(final StreamInput in) throws IOException { + super(in); + this.tenancyConfigs.multitenancy_enabled = in.readOptionalBoolean(); + this.tenancyConfigs.private_tenant_enabled = in.readOptionalBoolean(); + this.tenancyConfigs.default_tenant = in.readOptionalString(); + } + + public TenancyConfigRetrieveResponse(final TenancyConfigs tenancyConfigs) { + this.tenancyConfigs = tenancyConfigs; + } + + public TenancyConfigs getMultitenancyConfig() { + return tenancyConfigs; + } + + public Boolean getMultitenancyEnabled() { return tenancyConfigs.multitenancy_enabled; } + + public Boolean getPrivateTenantEnabled() { return tenancyConfigs.private_tenant_enabled; } + + public String getDefaultTenant() { return tenancyConfigs.default_tenant; } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeBoolean(getMultitenancyEnabled()); + out.writeBoolean(getPrivateTenantEnabled()); + out.writeString(getDefaultTenant()); + } + + @Override + public String toString() { + return Strings.toString(XContentType.JSON, this, true, true); + } + + @Override + public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { + builder.startObject(); + builder.field("multitenancy_enabled", getMultitenancyEnabled()); + builder.field("private_tenant_enabled", getPrivateTenantEnabled()); + builder.field("default_tenant", getDefaultTenant()); + builder.endObject(); + return builder; + } +} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveTransportAction.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveTransportAction.java new file mode 100644 index 0000000000..a68bbae85e --- /dev/null +++ b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveTransportAction.java @@ -0,0 +1,63 @@ +/* + * 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.action.tenancy; + +import java.util.Collections; + +import org.opensearch.action.ActionListener; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.settings.Settings; +import org.opensearch.security.configuration.ConfigurationRepository; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; +import org.opensearch.security.securityconf.impl.v7.ConfigV7; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +public class TenancyConfigRetrieveTransportAction + extends HandledTransportAction { + + private final ConfigurationRepository config; + + @Inject + public TenancyConfigRetrieveTransportAction(final Settings settings, + final TransportService transportService, + final ActionFilters actionFilters, + final ConfigurationRepository config) { + super(TenancyConfigRetrieveActions.NAME, transportService, actionFilters, EmptyRequest::new); + + this.config = config; + } + + /** Load the configuration from the security index and return a copy */ + protected final SecurityDynamicConfiguration load() { + return config.getConfigurationsFromIndex(Collections.singleton(CType.CONFIG), false).get(CType.CONFIG).deepClone(); + } + + @Override + protected void doExecute(final Task task, final EmptyRequest request, final ActionListener listener) { + + // Get the security configuration and lookup the config setting state + final SecurityDynamicConfiguration dynamicConfig = load(); + ConfigV7 config = (ConfigV7)dynamicConfig.getCEntry("config"); + + final TenancyConfigs tenancyConfigs= new TenancyConfigs(); + + tenancyConfigs.multitenancy_enabled = config.dynamic.kibana.multitenancy_enabled; + tenancyConfigs.private_tenant_enabled = config.dynamic.kibana.private_tenant_enabled; + tenancyConfigs.default_tenant = config.dynamic.kibana.default_tenant; + + listener.onResponse(new TenancyConfigRetrieveResponse(tenancyConfigs)); + } +} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateAction.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateAction.java new file mode 100644 index 0000000000..73d515b7d8 --- /dev/null +++ b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateAction.java @@ -0,0 +1,26 @@ +/* + * 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.action.tenancy; + +import org.opensearch.action.ActionType; + +public class TenancyConfigUpdateAction extends ActionType { + + public static final TenancyConfigUpdateAction INSTANCE = new TenancyConfigUpdateAction(); + public static final String NAME = "cluster:feature/tenancy/config/update"; + + + protected TenancyConfigUpdateAction() + { + super(NAME, TenancyConfigRetrieveResponse::new); + } +} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateRequest.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateRequest.java new file mode 100644 index 0000000000..2d03f698a6 --- /dev/null +++ b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateRequest.java @@ -0,0 +1,69 @@ +/* + * 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.action.tenancy; +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.ParseField; +import org.opensearch.core.xcontent.ConstructingObjectParser; +import org.opensearch.core.xcontent.XContentParser; + +public class TenancyConfigUpdateRequest extends ActionRequest { + + private TenancyConfigs tenancyConfigs = new TenancyConfigs(); + + public TenancyConfigUpdateRequest(final StreamInput in) throws IOException { + super(in); + in.readOptionalBoolean(); + in.readOptionalBoolean(); + in.readOptionalString(); + } + + public TenancyConfigUpdateRequest(final Boolean multitenancy_enabled, final Boolean private_tenant_enabled, final String default_tenant) { + super(); + this.tenancyConfigs.multitenancy_enabled = multitenancy_enabled; + this.tenancyConfigs.private_tenant_enabled = private_tenant_enabled; + this.tenancyConfigs.default_tenant = default_tenant; + } + + public TenancyConfigs getTenancyConfigs() { + return tenancyConfigs; + } + + @Override + public ActionRequestValidationException validate() { + if (getTenancyConfigs() == null) { + final ActionRequestValidationException validationException = new ActionRequestValidationException(); + validationException.addValidationError("Missing tenancy configs"); + return validationException; + } + return null; + } + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + TenancyConfigUpdateRequest.class.getName(), + args -> new TenancyConfigUpdateRequest((Boolean)args[0], (Boolean) args[1], (String) args[2]) + ); + + static { + PARSER.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), new ParseField("multitenancy_enabled")); + PARSER.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), new ParseField("private_tenant_enabled")); + PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("default_tenant")); + + } + + public static TenancyConfigUpdateRequest fromXContent(final XContentParser parser) { + return PARSER.apply(parser, null); + } +} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateTransportAction.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateTransportAction.java new file mode 100644 index 0000000000..1d4b563ca4 --- /dev/null +++ b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateTransportAction.java @@ -0,0 +1,155 @@ +/* + * 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.action.tenancy; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.action.ActionListener; +import org.opensearch.action.index.IndexResponse; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.security.configuration.ConfigurationRepository; +import org.opensearch.security.dlic.rest.api.AbstractApiAction; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; +import org.opensearch.security.securityconf.impl.v7.ConfigV7; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.tasks.Task; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; + +public class TenancyConfigUpdateTransportAction extends HandledTransportAction { + + private static final Logger log = LogManager.getLogger(TenancyConfigUpdateTransportAction.class); + + private final String securityIndex; + private final ConfigurationRepository config; + private final Client client; + private final ThreadPool pool; + + @Inject + public TenancyConfigUpdateTransportAction(final Settings settings, + final TransportService transportService, + final ActionFilters actionFilters, + final ConfigurationRepository config, + final ThreadPool pool, + final Client client) { + super(TenancyConfigUpdateAction.NAME, transportService, actionFilters, TenancyConfigUpdateRequest::new); + + this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + + this.config = config; + this.client = client; + this.pool = pool; + } + + /** Load the configuration from the security index and return a copy */ + protected final SecurityDynamicConfiguration load() { + return config.getConfigurationsFromIndex(Collections.singleton(CType.CONFIG), false).get(CType.CONFIG).deepClone(); + } + + private Set getAcceptableDefaultTenants() { + Set acceptableDefaultTenants = new HashSet(); + acceptableDefaultTenants.add(ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME); + acceptableDefaultTenants.add(ConfigConstants.TENANCY_GLOBAL_TENANT_NAME); + acceptableDefaultTenants.add(ConfigConstants.TENANCY_PRIVATE_TENANT_NAME); + return acceptableDefaultTenants; + } + + private Set getAllConfiguredTenantNames() { + + return this.config.getConfiguration(CType.TENANTS).getCEntries().keySet(); + } + + protected void validate(ConfigV7 updatedConfig) { + if(!updatedConfig.dynamic.kibana.private_tenant_enabled && (updatedConfig.dynamic.kibana.default_tenant).equals(ConfigConstants.TENANCY_PRIVATE_TENANT_NAME)) { + throw new IllegalArgumentException("Private tenant can not be disabled if it is the default tenant."); + } + + Set acceptableDefaultTenants = getAcceptableDefaultTenants(); + + if(acceptableDefaultTenants.contains(updatedConfig.dynamic.kibana.default_tenant)) { + return; + } + + Set availableTenants = getAllConfiguredTenantNames(); + + if(!availableTenants.contains(updatedConfig.dynamic.kibana.default_tenant)){ + throw new IllegalArgumentException(updatedConfig.dynamic.kibana.default_tenant + " can not be set to default tenant. Default tenant should be selected from one of the available tenants."); + } + + } + + @Override + protected void doExecute(final Task task, final TenancyConfigUpdateRequest request, final ActionListener listener) { + + // Get the current security config and prepare the config with the updated value + final SecurityDynamicConfiguration dynamicConfig = load(); + final ConfigV7 config = (ConfigV7)dynamicConfig.getCEntry("config"); + + final TenancyConfigs tenancyConfigs = request.getTenancyConfigs(); + if(tenancyConfigs.multitenancy_enabled != null) + { + config.dynamic.kibana.multitenancy_enabled = tenancyConfigs.multitenancy_enabled; + } + + if(tenancyConfigs.private_tenant_enabled != null) + { + config.dynamic.kibana.private_tenant_enabled = tenancyConfigs.private_tenant_enabled; + } + + if(tenancyConfigs.default_tenant != null) + { + config.dynamic.kibana.default_tenant = tenancyConfigs.default_tenant; + } + + validate(config); + + dynamicConfig.putCEntry("config", config); + + // When performing an update to the configuration run as admin + try (final ThreadContext.StoredContext stashedContext = pool.getThreadContext().stashContext()) { + // Update the security configuration and make sure the cluster has fully refreshed + AbstractApiAction.saveAndUpdateConfigs(this.securityIndex, this.client, CType.CONFIG, dynamicConfig, new ActionListener(){ + + @Override + public void onResponse(final IndexResponse response) { + // After processing the request, restore the user context + stashedContext.close(); + try { + // Lookup the current value and notify the listener + client.execute(TenancyConfigRetrieveActions.INSTANCE, new EmptyRequest(), listener); + } catch (IOException ioe) { + log.error(ioe); + listener.onFailure(ioe); + } + } + + @Override + public void onFailure(Exception e) { + log.error(e); + listener.onFailure(e); + } + }); + } + } +} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigs.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigs.java new file mode 100644 index 0000000000..4e8fc41ef4 --- /dev/null +++ b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigs.java @@ -0,0 +1,18 @@ +/* + * 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.action.tenancy; + +public class TenancyConfigs { + public Boolean multitenancy_enabled; + public Boolean private_tenant_enabled; + public String default_tenant; +} diff --git a/src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java b/src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java index 262aadf424..e2f10dfcae 100644 --- a/src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java +++ b/src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java @@ -108,6 +108,13 @@ public ReplaceResult replaceDashboardsIndex(final ActionRequest request, final S final String dashboardsIndexName = config.getDashboardsIndexname();//config.dynamic.kibana.index; String requestedTenant = user.getRequestedTenant(); + if(USER_TENANT.equals(requestedTenant)) { + final boolean private_tenant_enabled = config.isDashboardsPrivateTenantEnabled(); + if(!private_tenant_enabled) { + return ACCESS_DENIED_REPLACE_RESULT; + } + } + final boolean isDebugEnabled = log.isDebugEnabled(); if (isDebugEnabled) { log.debug("raw requestedTenant: '" + requestedTenant + "'"); diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java index fa69be0ad4..a73b174ee2 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java @@ -75,7 +75,7 @@ public abstract class AbstractApiAction extends BaseRestHandler { protected final ConfigurationRepository cl; protected final ClusterService cs; final ThreadPool threadPool; - protected String opendistroIndex; + protected String securityIndexName; private final RestApiPrivilegesEvaluator restApiPrivilegesEvaluator; protected final RestApiAdminPrivilegesEvaluator restApiAdminPrivilegesEvaluator; protected final AuditLog auditLog; @@ -87,7 +87,7 @@ protected AbstractApiAction(final Settings settings, final Path configPath, fina ThreadPool threadPool, AuditLog auditLog) { super(); this.settings = settings; - this.opendistroIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + this.securityIndexName = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); this.cl = cl; @@ -157,7 +157,7 @@ protected void handleDelete(final RestChannel channel, final RestRequest request existingConfiguration.remove(name); if (existed) { - saveAnUpdateConfigs(client, request, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { + AbstractApiAction.saveAndUpdateConfigs(this.securityIndexName, client, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { @Override public void onResponse(IndexResponse response) { @@ -207,7 +207,7 @@ protected void handlePut(final RestChannel channel, final RestRequest request, f } existingConfiguration.putCObject(name, newContent); - saveAnUpdateConfigs(client, request, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { + AbstractApiAction.saveAndUpdateConfigs(this.securityIndexName, client, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { @Override public void onResponse(IndexResponse response) { @@ -270,7 +270,7 @@ protected final SecurityDynamicConfiguration load(final CType config, boolean } protected boolean ensureIndexExists() { - if (!cs.state().metadata().hasConcreteIndex(this.opendistroIndex)) { + if (!cs.state().metadata().hasConcreteIndex(this.securityIndexName)) { return false; } return true; @@ -313,11 +313,8 @@ public final void onFailure(Exception e) { } - protected void saveAnUpdateConfigs(final Client client, final RestRequest request, final CType cType, - final SecurityDynamicConfiguration configuration, OnSucessActionListener actionListener) { - final IndexRequest ir = new IndexRequest(this.opendistroIndex); - - //final String type = "_doc"; + public static void saveAndUpdateConfigs(final String indexName, final Client client, final CType cType, final SecurityDynamicConfiguration configuration, final ActionListener actionListener) { + final IndexRequest ir = new IndexRequest(indexName); final String id = cType.toLCString(); configuration.removeStatic(); @@ -485,6 +482,7 @@ protected void successResponse(RestChannel channel) { try { final XContentBuilder builder = channel.newBuilder(); builder.startObject(); + builder.endObject(); channel.sendResponse( new BytesRestResponse(RestStatus.OK, builder)); } catch (IOException e) { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java index 885a5476af..5e4799d655 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java @@ -224,7 +224,7 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C internalUserEntry.setHash(hash); - saveAnUpdateConfigs(client, request, CType.INTERNALUSERS, internalUser, new OnSucessActionListener(channel) { + AccountApiAction.saveAndUpdateConfigs(this.securityIndexName, client, CType.INTERNALUSERS, internalUser, new OnSucessActionListener(channel) { @Override public void onResponse(IndexResponse response) { successResponse(channel, "'" + username + "' updated."); diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java index b37375a461..cefaeb5c6c 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java @@ -141,7 +141,7 @@ protected void handlePut(final RestChannel channel, final RestRequest request, f boolean existed = existingConfiguration.exists(name); existingConfiguration.putCObject(name, DefaultObjectMapper.readTree(content, existingConfiguration.getImplementingClass())); - saveAnUpdateConfigs(client, request, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { + saveAndUpdateConfigs(this.securityIndexName,client, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { @Override public void onResponse(IndexResponse response) { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java index 417465e353..646e1f81d6 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java @@ -172,7 +172,7 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C // checks complete, create or update the user internalUsersConfiguration.putCObject(username, DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass())); - saveAnUpdateConfigs(client, request, CType.INTERNALUSERS, internalUsersConfiguration, new OnSucessActionListener(channel) { + saveAndUpdateConfigs(this.securityIndexName,client, CType.INTERNALUSERS, internalUsersConfiguration, new OnSucessActionListener(channel) { @Override public void onResponse(IndexResponse response) { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java index 1a1092e2fb..d252515f72 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java @@ -140,8 +140,8 @@ protected void handlePost(RestChannel channel, RestRequest request, Client clien final SecurityDynamicConfiguration auditConfigV7 = Migration.migrateAudit(auditConfigV6); builder.add(auditConfigV7); - final int replicas = cs.state().metadata().index(opendistroIndex).getNumberOfReplicas(); - final String autoExpandReplicas = cs.state().metadata().index(opendistroIndex).getSettings().get(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS); + final int replicas = cs.state().metadata().index(securityIndexName).getNumberOfReplicas(); + final String autoExpandReplicas = cs.state().metadata().index(securityIndexName).getSettings().get(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS); final Builder securityIndexSettings = Settings.builder(); @@ -153,7 +153,7 @@ protected void handlePost(RestChannel channel, RestRequest request, Client clien securityIndexSettings.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1); - client.admin().indices().prepareDelete(this.opendistroIndex).execute(new ActionListener() { + client.admin().indices().prepareDelete(this.securityIndexName).execute(new ActionListener() { @Override public void onResponse(AcknowledgedResponse response) { @@ -161,14 +161,14 @@ public void onResponse(AcknowledgedResponse response) { if (response.isAcknowledged()) { log.debug("opendistro_security index deleted successfully"); - client.admin().indices().prepareCreate(opendistroIndex).setSettings(securityIndexSettings) + client.admin().indices().prepareCreate(securityIndexName).setSettings(securityIndexSettings) .execute(new ActionListener() { @Override public void onResponse(CreateIndexResponse response) { final List> dynamicConfigurations = builder.build(); final ImmutableList.Builder cTypes = ImmutableList.builderWithExpectedSize(dynamicConfigurations.size()); - final BulkRequestBuilder br = client.prepareBulk(opendistroIndex); + final BulkRequestBuilder br = client.prepareBulk(securityIndexName); br.setRefreshPolicy(RefreshPolicy.IMMEDIATE); try { for (SecurityDynamicConfiguration dynamicConfiguration : dynamicConfigurations) { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java index 6d644c1eae..deb56a69c7 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java @@ -162,7 +162,7 @@ private void handleSinglePatch(RestChannel channel, RestRequest request, Client } } - saveAnUpdateConfigs(client, request, getConfigName(), mdc, new OnSucessActionListener(channel){ + saveAndUpdateConfigs(this.securityIndexName,client, getConfigName(), mdc, new OnSucessActionListener(channel){ @Override public void onResponse(IndexResponse response) { @@ -244,7 +244,7 @@ private void handleBulkPatch(RestChannel channel, RestRequest request, Client cl } } - saveAnUpdateConfigs(client, request, getConfigName(), mdc, new OnSucessActionListener(channel) { + saveAndUpdateConfigs(this.securityIndexName,client, getConfigName(), mdc, new OnSucessActionListener(channel) { @Override public void onResponse(IndexResponse response) { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java index b056a25ad2..72928cd0ad 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java @@ -82,7 +82,7 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C } rolesMappingConfiguration.putCObject(name, DefaultObjectMapper.readTree(content, rolesMappingConfiguration.getImplementingClass())); - saveAnUpdateConfigs(client, request, getConfigName(), rolesMappingConfiguration, new OnSucessActionListener(channel) { + saveAndUpdateConfigs(this.securityIndexName,client, getConfigName(), rolesMappingConfiguration, new OnSucessActionListener(channel) { @Override public void onResponse(IndexResponse response) { diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index cceaeb4cb0..2c0e7ac7c0 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -525,6 +525,15 @@ public boolean multitenancyEnabled() { && dcm.isDashboardsMultitenancyEnabled(); } + public boolean privateTenantEnabled() { + return privilegesInterceptor.getClass() != PrivilegesInterceptor.class + && dcm.isDashboardsPrivateTenantEnabled(); + } + + public String dashboardsDefaultTenant() { + return dcm.getDashboardsDefaultTenant(); + } + public boolean notFailOnForbiddenEnabled() { return privilegesInterceptor.getClass() != PrivilegesInterceptor.class && dcm.isDnfofEnabled(); diff --git a/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java b/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java index aa714ebcbb..0fd88e7565 100644 --- a/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java @@ -103,6 +103,9 @@ public void accept(RestChannel channel) throws Exception { builder.field("opensearch_dashboards_mt_enabled", evaluator.multitenancyEnabled()); builder.field("opensearch_dashboards_index", evaluator.dashboardsIndex()); builder.field("opensearch_dashboards_server_user", evaluator.dashboardsServerUsername()); + builder.field("multitenancy_enabled", evaluator.multitenancyEnabled()); + builder.field("private_tenant_enabled", evaluator.privateTenantEnabled()); + builder.field("default_tenant", evaluator.dashboardsDefaultTenant()); builder.endObject(); response = new BytesRestResponse(RestStatus.OK, builder); diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java index 87bcf5241d..f91e768283 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java @@ -68,6 +68,8 @@ public abstract class DynamicConfigModel { public abstract String getDashboardsOpenSearchRole(); public abstract String getDashboardsIndexname(); public abstract boolean isDashboardsMultitenancyEnabled(); + public abstract boolean isDashboardsPrivateTenantEnabled(); + public abstract String getDashboardsDefaultTenant(); public abstract boolean isDnfofEnabled(); public abstract boolean isMultiRolespanEnabled(); public abstract String getFilteredAliasMode(); diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java index c9738c7e70..40b3e3319a 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java @@ -141,6 +141,12 @@ public boolean isDashboardsMultitenancyEnabled() { return config.dynamic.kibana.multitenancy_enabled; } @Override + public boolean isDashboardsPrivateTenantEnabled() { + return config.dynamic.kibana.private_tenant_enabled; + } + @Override + public String getDashboardsDefaultTenant() { return config.dynamic.kibana.default_tenant; } + @Override public boolean isDnfofEnabled() { return config.dynamic.do_not_fail_on_forbidden || config.dynamic.kibana.do_not_fail_on_forbidden; } diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java index f2ec110cbd..6db5fba0a7 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java @@ -141,6 +141,12 @@ public boolean isDashboardsMultitenancyEnabled() { return config.dynamic.kibana.multitenancy_enabled; } @Override + public boolean isDashboardsPrivateTenantEnabled() { + return config.dynamic.kibana.private_tenant_enabled; + } + @Override + public String getDashboardsDefaultTenant() { return config.dynamic.kibana.default_tenant; } + @Override public boolean isDnfofEnabled() { return config.dynamic.do_not_fail_on_forbidden; } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java index f9da5ea314..6599942d34 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java @@ -83,6 +83,10 @@ public static class Kibana { @JsonInclude(JsonInclude.Include.NON_NULL) public boolean multitenancy_enabled = true; + @JsonInclude(JsonInclude.Include.NON_NULL) + public boolean private_tenant_enabled = true; + @JsonInclude(JsonInclude.Include.NON_NULL) + public String default_tenant = ""; public String server_username = "kibanaserver"; public String opendistro_role = null; public String index = ".kibana"; diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java index 0e83590d3e..3029b42abf 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java @@ -69,6 +69,8 @@ public ConfigV7(ConfigV6 c6) { dynamic.kibana.index = c6.dynamic.kibana.index; dynamic.kibana.multitenancy_enabled = c6.dynamic.kibana.multitenancy_enabled; + dynamic.kibana.private_tenant_enabled = true; + dynamic.kibana.default_tenant = ""; dynamic.kibana.server_username = c6.dynamic.kibana.server_username; dynamic.http = new Http(); @@ -135,12 +137,18 @@ public static class Kibana { @JsonInclude(JsonInclude.Include.NON_NULL) public boolean multitenancy_enabled = true; + @JsonInclude(JsonInclude.Include.NON_NULL) + public boolean private_tenant_enabled = true; + @JsonInclude(JsonInclude.Include.NON_NULL) + public String default_tenant = ""; public String server_username = "kibanaserver"; public String opendistro_role = null; public String index = ".kibana"; @Override public String toString() { - return "Kibana [multitenancy_enabled=" + multitenancy_enabled + ", server_username=" + server_username + ", opendistro_role=" + opendistro_role + return "Kibana [multitenancy_enabled=" + multitenancy_enabled + ", private_tenant_enabled=" + + private_tenant_enabled + ", default_tenant=" + default_tenant + ", server_username=" + + server_username + ", opendistro_role=" + opendistro_role + ", index=" + index + "]"; } diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index 375b22b65f..f81d89810b 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -287,6 +287,10 @@ public enum RolesMappingResolution { public static final String SECURITY_SYSTEM_INDICES_KEY = "plugins.security.system_indices.indices"; public static final List SECURITY_SYSTEM_INDICES_DEFAULT = Collections.emptyList(); + public static final String TENANCY_PRIVATE_TENANT_NAME = "Private"; + public static final String TENANCY_GLOBAL_TENANT_NAME = "Global"; + public static final String TENANCY_GLOBAL_TENANT_DEFAULT_NAME = ""; + public static Set getSettingAsSet(final Settings settings, final String key, final List defaultList, final boolean ignoreCaseForNone) { final List list = settings.getAsList(key, defaultList); if (list.size() == 1 && "NONE".equals(ignoreCaseForNone? list.get(0).toUpperCase() : list.get(0))) { diff --git a/src/test/java/org/opensearch/security/multitenancy/test/TenancyDefaultTenantTests.java b/src/test/java/org/opensearch/security/multitenancy/test/TenancyDefaultTenantTests.java new file mode 100644 index 0000000000..b4d6aa4b59 --- /dev/null +++ b/src/test/java/org/opensearch/security/multitenancy/test/TenancyDefaultTenantTests.java @@ -0,0 +1,96 @@ +/* + * 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.multitenancy.test; + +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.junit.Test; + +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.test.SingleClusterTest; +import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.StringContains.containsString; + +public class TenancyDefaultTenantTests extends SingleClusterTest { + private final Header asAdminUser = encodeBasicHeader("admin", "admin"); + private final Header asUser = encodeBasicHeader("kirk", "kirk"); + + @Override + protected String getResourceFolder() { + return "multitenancy"; + } + + @Test + public void testDefaultTenantUpdate() throws Exception { + setup(); + + final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asAdminUser); + assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(getSettingResponse.findValueInJson("default_tenant"), equalTo(ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME)); + + HttpResponse getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); + assertThat(getDashboardsinfoResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(getDashboardsinfoResponse.findValueInJson("default_tenant"), equalTo(ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME)); + + final HttpResponse setPrivateTenantAsDefaultResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"default_tenant\": \"Private\"}", asAdminUser); + assertThat(setPrivateTenantAsDefaultResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); + assertThat(getDashboardsinfoResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(getDashboardsinfoResponse.findValueInJson("default_tenant"), equalTo(ConfigConstants.TENANCY_PRIVATE_TENANT_NAME)); + } + + @Test + public void testDefaultTenant_UpdateFailed() throws Exception { + setup(); + + final HttpResponse disablePrivateTenantResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\":false}", asAdminUser); + assertThat(disablePrivateTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + + final HttpResponse setPrivateTenantAsDefaultFailResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"default_tenant\": \"Private\"}", asAdminUser); + assertThat(setPrivateTenantAsDefaultFailResponse.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + assertThat(setPrivateTenantAsDefaultFailResponse.findValueInJson("error.reason"), containsString("Private tenant can not be disabled if it is the default tenant.")); + + final HttpResponse enablePrivateTenantResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\":true}", asAdminUser); + assertThat(enablePrivateTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + final HttpResponse setPrivateTenantAsDefaultResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"default_tenant\": \"Private\"}", asAdminUser); + assertThat(setPrivateTenantAsDefaultResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + final HttpResponse getSettingResponseAfterUpdate = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asAdminUser); + assertThat(getSettingResponseAfterUpdate.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(getSettingResponseAfterUpdate.findValueInJson("default_tenant"), equalTo(ConfigConstants.TENANCY_PRIVATE_TENANT_NAME)); + + HttpResponse getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); + assertThat(getDashboardsinfoResponse.findValueInJson("default_tenant"),equalTo(ConfigConstants.TENANCY_PRIVATE_TENANT_NAME)); + + final HttpResponse setRandomStringAsDefaultTenant = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"default_tenant\": \"NonExistentTenant\"}", asAdminUser); + assertThat(setRandomStringAsDefaultTenant.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + assertThat(setRandomStringAsDefaultTenant.findValueInJson("error.reason"), containsString("Default tenant should be selected from one of the available tenants.")); + + } + @Test + public void testForbiddenAccess() throws Exception { + setup(); + + final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asUser); + assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + assertThat(getSettingResponse.findValueInJson("error.reason"), containsString("no permissions for [cluster:feature/tenancy/config/read]")); + + final HttpResponse updateSettingResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"default_tenant\": \"Private\"}", asUser); + assertThat(updateSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + assertThat(updateSettingResponse.findValueInJson("error.reason"), containsString("no permissions for [cluster:feature/tenancy/config/update]")); + } +} diff --git a/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java b/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java new file mode 100644 index 0000000000..033d38ed41 --- /dev/null +++ b/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java @@ -0,0 +1,89 @@ +/* + * 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.multitenancy.test; + +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicHeader; +import org.junit.Test; + +import org.opensearch.security.test.SingleClusterTest; +import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.StringContains.containsString; + +public class TenancyMultitenancyEnabledTests extends SingleClusterTest { + private final Header asAdminUser = encodeBasicHeader("admin", "admin"); + private final Header asUser = encodeBasicHeader("kirk", "kirk"); + private final Header onUserTenant = new BasicHeader("securitytenant", "__user__"); + + private static String createIndexPatternDoc(final String title) { + return "{"+ + "\"type\" : \"index-pattern\","+ + "\"updated_at\" : \"2018-09-29T08:56:59.066Z\","+ + "\"index-pattern\" : {"+ + "\"title\" : \"" + title + "\""+ + "}}"; + } + + @Override + protected String getResourceFolder() { + return "multitenancy"; + } + + @Test + public void testMultitenancyDisabled_endToEndTest() throws Exception { + setup(); + + final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asAdminUser); + assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(getSettingResponse.findValueInJson("multitenancy_enabled"), equalTo("true")); + + HttpResponse getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); + assertThat(getDashboardsinfoResponse.findValueInJson("multitenancy_enabled"),equalTo("true")); + + final HttpResponse createDocInGlobalTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("globalIndex"), asAdminUser); + assertThat(createDocInGlobalTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); + final HttpResponse createDocInUserTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("userIndex"), onUserTenant, asAdminUser); + assertThat(createDocInUserTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); + + final HttpResponse searchInUserTenantWithMutlitenancyEnabled = nonSslRestHelper().executeGetRequest(".kibana/_search", onUserTenant, asAdminUser); + assertThat(searchInUserTenantWithMutlitenancyEnabled.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(searchInUserTenantWithMutlitenancyEnabled.findValueInJson("hits.hits[0]._source.index-pattern.title"), equalTo("userIndex")); + + final HttpResponse updateMutlitenancyToDisabled = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"multitenancy_enabled\": \"false\"}", asAdminUser); + assertThat(updateMutlitenancyToDisabled.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(updateMutlitenancyToDisabled.findValueInJson("multitenancy_enabled"), equalTo("false")); + + getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); + assertThat(getDashboardsinfoResponse.findValueInJson("multitenancy_enabled"),equalTo("false")); + + final HttpResponse searchInUserTenantWithMutlitenancyDisabled = nonSslRestHelper().executeGetRequest(".kibana/_search", onUserTenant, asAdminUser); + assertThat(searchInUserTenantWithMutlitenancyDisabled.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(searchInUserTenantWithMutlitenancyDisabled.findValueInJson("hits.hits[0]._source.index-pattern.title"), equalTo("globalIndex")); + } + + @Test + public void testForbiddenAccess() throws Exception { + setup(); + + final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asUser); + assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + assertThat(getSettingResponse.findValueInJson("error.reason"), containsString("no permissions for [cluster:feature/tenancy/config/read]")); + + final HttpResponse updateSettingResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"multitenancy_enabled\": \"false\"}", asUser); + assertThat(updateSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + assertThat(updateSettingResponse.findValueInJson("error.reason"), containsString("no permissions for [cluster:feature/tenancy/config/update]")); + } +} diff --git a/src/test/java/org/opensearch/security/multitenancy/test/TenancyPrivateTenantEnabledTests.java b/src/test/java/org/opensearch/security/multitenancy/test/TenancyPrivateTenantEnabledTests.java new file mode 100644 index 0000000000..c6927dbbf8 --- /dev/null +++ b/src/test/java/org/opensearch/security/multitenancy/test/TenancyPrivateTenantEnabledTests.java @@ -0,0 +1,101 @@ +/* + * 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.multitenancy.test; + +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicHeader; +import org.junit.Test; + +import org.opensearch.security.test.SingleClusterTest; +import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.StringContains.containsString; + +public class TenancyPrivateTenantEnabledTests extends SingleClusterTest { + private final Header asAdminUser = encodeBasicHeader("admin", "admin"); + private final Header asUser = encodeBasicHeader("kirk", "kirk"); + private final Header onUserTenant = new BasicHeader("securitytenant", "__user__"); + + private static String createIndexPatternDoc(final String title) { + return "{"+ + "\"type\" : \"index-pattern\","+ + "\"updated_at\" : \"2018-09-29T08:56:59.066Z\","+ + "\"index-pattern\" : {"+ + "\"title\" : \"" + title + "\""+ + "}}"; + } + + @Override + protected String getResourceFolder() { + return "multitenancy"; + } + + @Test + public void testPrivateTenantDisabled_Update() throws Exception { + setup(); + + final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asAdminUser); + assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(getSettingResponse.findValueInJson("private_tenant_enabled"), equalTo("true")); + + HttpResponse getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); + assertThat(getDashboardsinfoResponse.findValueInJson("private_tenant_enabled"), equalTo("true")); + + final HttpResponse createDocInGlobalTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("globalIndex"), asAdminUser); + assertThat(createDocInGlobalTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); + final HttpResponse createDocInUserTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("userIndex"), onUserTenant, asUser); + assertThat(createDocInUserTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); + + final HttpResponse searchInUserTenantWithPrivateTenantEnabled = nonSslRestHelper().executeGetRequest(".kibana/_search", onUserTenant, asUser); + assertThat(searchInUserTenantWithPrivateTenantEnabled.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(searchInUserTenantWithPrivateTenantEnabled.findValueInJson("hits.hits[0]._source.index-pattern.title"), equalTo("userIndex")); + + final HttpResponse disablePrivateTenantResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\": \"false\"}", asAdminUser); + assertThat(disablePrivateTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(disablePrivateTenantResponse.findValueInJson("private_tenant_enabled"), equalTo("false")); + + getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); + assertThat(getDashboardsinfoResponse.findValueInJson("private_tenant_enabled"),equalTo("false")); + + final HttpResponse searchInUserTenantWithPrivateTenantDisabled = nonSslRestHelper().executeGetRequest(".kibana/_search", onUserTenant, asUser); + assertThat(searchInUserTenantWithPrivateTenantDisabled.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + assertThat(searchInUserTenantWithPrivateTenantDisabled.findValueInJson("error.reason"), containsString("no permissions for [indices:data/read/search] and User")); + + } + + @Test + public void testPrivateTenantDisabled_UpdateFailed() throws Exception { + setup(); + + final HttpResponse setPrivateTenantAsDefaultResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"default_tenant\": \"Private\"}", asAdminUser); + assertThat(setPrivateTenantAsDefaultResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + final HttpResponse updateSettingResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\":false}", asAdminUser); + assertThat(updateSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + assertThat(updateSettingResponse.findValueInJson("error.reason"), containsString("Private tenant can not be disabled if it is the default tenant.")); + } + + @Test + public void testForbiddenAccess() throws Exception { + setup(); + + final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asUser); + assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + assertThat(getSettingResponse.findValueInJson("error.reason"), containsString("no permissions for [cluster:feature/tenancy/config/read]")); + + final HttpResponse updateSettingResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\": false}", asUser); + assertThat(updateSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + assertThat(updateSettingResponse.findValueInJson("error.reason"), containsString("no permissions for [cluster:feature/tenancy/config/update]")); + } +} 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 ff9a5b536b..367332f160 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 @@ -92,8 +92,6 @@ import org.opensearch.security.test.helper.cluster.ClusterInfo; import org.opensearch.security.test.helper.file.FileHelper; -import static org.junit.jupiter.api.Assertions.fail; - public class RestHelper { protected final Logger log = LogManager.getLogger(RestHelper.class); @@ -480,7 +478,7 @@ public String toString() { public String findValueInJson(final String jsonDotPath) { // Make sure its json / then parse it if (!isJsonContentType()) { - fail("Response was expected to be JSON, body was: \n" + body); + throw new RuntimeException("Response was expected to be JSON, body was: \n" + body); } JsonNode currentNode = null; try { @@ -492,7 +490,7 @@ public String findValueInJson(final String jsonDotPath) { // Break the path into parts, and scan into the json object try (final Scanner jsonPathScanner = new Scanner(jsonDotPath).useDelimiter("\\.")) { if (!jsonPathScanner.hasNext()) { - fail("Invalid json dot path '" + jsonDotPath + "', rewrite with '.' characters between path elements."); + throw new RuntimeException("Invalid json dot path '" + jsonDotPath + "', rewrite with '.' characters between path elements."); } do { String pathEntry = jsonPathScanner.next(); @@ -510,23 +508,23 @@ public String findValueInJson(final String jsonDotPath) { } if (!currentNode.has(pathEntry)) { - fail("Unable to resolve '" + jsonDotPath + "', on path entry '" + pathEntry + "' from available fields " + currentNode.toPrettyString()); + throw new RuntimeException("Unable to resolve '" + jsonDotPath + "', on path entry '" + pathEntry + "' from available fields " + currentNode.toPrettyString()); } currentNode = currentNode.get(pathEntry); // if it's an Array lookup we get the requested index item if (arrayEntryIdx > -1) { if(!currentNode.isArray()) { - fail("Unable to resolve '" + jsonDotPath + "', the '" + pathEntry + "' field is not an array " + currentNode.toPrettyString()); + throw new RuntimeException("Unable to resolve '" + jsonDotPath + "', the '" + pathEntry + "' field is not an array " + currentNode.toPrettyString()); } else if (!currentNode.has(arrayEntryIdx)) { - fail("Unable to resolve '" + jsonDotPath + "', index '" + arrayEntryIdx + "' is out of bounds for array '" + pathEntry + "' \n" + currentNode.toPrettyString()); + throw new RuntimeException("Unable to resolve '" + jsonDotPath + "', index '" + arrayEntryIdx + "' is out of bounds for array '" + pathEntry + "' \n" + currentNode.toPrettyString()); } currentNode = currentNode.get(arrayEntryIdx); } } while (jsonPathScanner.hasNext()); if (!currentNode.isValueNode()) { - fail("Unexpected value note, index directly to the object to reference, object\n" + currentNode.toPrettyString()); + throw new RuntimeException("Unexpected value note, index directly to the object to reference, object\n" + currentNode.toPrettyString()); } return currentNode.asText(); } diff --git a/src/test/resources/auditlog/config.yml b/src/test/resources/auditlog/config.yml index 938a38cd38..c1d0c63174 100644 --- a/src/test/resources/auditlog/config.yml +++ b/src/test/resources/auditlog/config.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/cache/config.yml b/src/test/resources/cache/config.yml index 2a48434203..1a5e86fc2f 100644 --- a/src/test/resources/cache/config.yml +++ b/src/test/resources/cache/config.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/composite_config.yml b/src/test/resources/composite_config.yml index f636d823e0..3f6eb78627 100644 --- a/src/test/resources/composite_config.yml +++ b/src/test/resources/composite_config.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config.yml b/src/test/resources/config.yml index 34f4aff093..3663b3c706 100644 --- a/src/test/resources/config.yml +++ b/src/test/resources/config.yml @@ -10,6 +10,8 @@ config: respect_request_indices_options: false kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_anon.yml b/src/test/resources/config_anon.yml index 7c8a037f25..0c2c5ccbcc 100644 --- a/src/test/resources/config_anon.yml +++ b/src/test/resources/config_anon.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_clientcert.yml b/src/test/resources/config_clientcert.yml index 441f6c9e5f..c50d770c26 100644 --- a/src/test/resources/config_clientcert.yml +++ b/src/test/resources/config_clientcert.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_disable_all.yml b/src/test/resources/config_disable_all.yml index 8f8e414672..fef8297b7f 100644 --- a/src/test/resources/config_disable_all.yml +++ b/src/test/resources/config_disable_all.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_dnfof.yml b/src/test/resources/config_dnfof.yml index 84c71d231a..6e0a2dcd93 100644 --- a/src/test/resources/config_dnfof.yml +++ b/src/test/resources/config_dnfof.yml @@ -10,6 +10,8 @@ config: respect_request_indices_options: false kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_invalidlic.yml b/src/test/resources/config_invalidlic.yml index 34f4aff093..3663b3c706 100644 --- a/src/test/resources/config_invalidlic.yml +++ b/src/test/resources/config_invalidlic.yml @@ -10,6 +10,8 @@ config: respect_request_indices_options: false kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_lic.yml b/src/test/resources/config_lic.yml index 34f4aff093..3663b3c706 100644 --- a/src/test/resources/config_lic.yml +++ b/src/test/resources/config_lic.yml @@ -10,6 +10,8 @@ config: respect_request_indices_options: false kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_lic_rk.yml b/src/test/resources/config_lic_rk.yml index 34f4aff093..3663b3c706 100644 --- a/src/test/resources/config_lic_rk.yml +++ b/src/test/resources/config_lic_rk.yml @@ -10,6 +10,8 @@ config: respect_request_indices_options: false kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_multirolespan.yml b/src/test/resources/config_multirolespan.yml index dee3d042b1..388cba2903 100644 --- a/src/test/resources/config_multirolespan.yml +++ b/src/test/resources/config_multirolespan.yml @@ -10,6 +10,8 @@ config: respect_request_indices_options: false kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_protected_indices.yml b/src/test/resources/config_protected_indices.yml index 791656523d..86b01d197c 100644 --- a/src/test/resources/config_protected_indices.yml +++ b/src/test/resources/config_protected_indices.yml @@ -10,6 +10,8 @@ config: respect_request_indices_options: false kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_proxy.yml b/src/test/resources/config_proxy.yml index 52f5880806..b3151d9748 100644 --- a/src/test/resources/config_proxy.yml +++ b/src/test/resources/config_proxy.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_proxy_custom.yml b/src/test/resources/config_proxy_custom.yml index 78e0aa70f3..cf03610e4b 100644 --- a/src/test/resources/config_proxy_custom.yml +++ b/src/test/resources/config_proxy_custom.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_respect_indices_options.yml b/src/test/resources/config_respect_indices_options.yml index 9508c12b34..86e9e6487b 100644 --- a/src/test/resources/config_respect_indices_options.yml +++ b/src/test/resources/config_respect_indices_options.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_rest_impersonation.yml b/src/test/resources/config_rest_impersonation.yml index 36c1b09cf1..a34232ff77 100644 --- a/src/test/resources/config_rest_impersonation.yml +++ b/src/test/resources/config_rest_impersonation.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_system_indices.yml b/src/test/resources/config_system_indices.yml index 791656523d..86b01d197c 100644 --- a/src/test/resources/config_system_indices.yml +++ b/src/test/resources/config_system_indices.yml @@ -10,6 +10,8 @@ config: respect_request_indices_options: false kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_transport_username.yml b/src/test/resources/config_transport_username.yml index 0cd5204005..f473a47320 100644 --- a/src/test/resources/config_transport_username.yml +++ b/src/test/resources/config_transport_username.yml @@ -10,6 +10,8 @@ config: respect_request_indices_options: false kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/config_xff.yml b/src/test/resources/config_xff.yml index 85ceca5e21..ed93efc2b1 100644 --- a/src/test/resources/config_xff.yml +++ b/src/test/resources/config_xff.yml @@ -10,6 +10,8 @@ config: respect_request_indices_options: false kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/dlsfls/config.yml b/src/test/resources/dlsfls/config.yml index 1288ebeea2..cf110efda5 100644 --- a/src/test/resources/dlsfls/config.yml +++ b/src/test/resources/dlsfls/config.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/ldap/config.yml b/src/test/resources/ldap/config.yml index ea8271c6df..9257fa5b50 100644 --- a/src/test/resources/ldap/config.yml +++ b/src/test/resources/ldap/config.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/ldap/config_ldap2.yml b/src/test/resources/ldap/config_ldap2.yml index 51956d4cfc..a9b477fcec 100644 --- a/src/test/resources/ldap/config_ldap2.yml +++ b/src/test/resources/ldap/config_ldap2.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/multitenancy/config.yml b/src/test/resources/multitenancy/config.yml index 704937b9ee..d1514e9cb2 100644 --- a/src/test/resources/multitenancy/config.yml +++ b/src/test/resources/multitenancy/config.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/multitenancy/config_anonymous.yml b/src/test/resources/multitenancy/config_anonymous.yml index 97a43b20a8..1ae5e145fe 100644 --- a/src/test/resources/multitenancy/config_anonymous.yml +++ b/src/test/resources/multitenancy/config_anonymous.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/multitenancy/config_basic_auth.yml b/src/test/resources/multitenancy/config_basic_auth.yml index 289eb19cd2..f60caab0b7 100644 --- a/src/test/resources/multitenancy/config_basic_auth.yml +++ b/src/test/resources/multitenancy/config_basic_auth.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/multitenancy/config_nodnfof.yml b/src/test/resources/multitenancy/config_nodnfof.yml index 462e3e9e64..f4afce87d4 100644 --- a/src/test/resources/multitenancy/config_nodnfof.yml +++ b/src/test/resources/multitenancy/config_nodnfof.yml @@ -11,6 +11,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/restapi/config.yml b/src/test/resources/restapi/config.yml index 899a17a1fc..2ed865657a 100644 --- a/src/test/resources/restapi/config.yml +++ b/src/test/resources/restapi/config.yml @@ -10,6 +10,8 @@ config: license: null kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: diff --git a/src/test/resources/restapi/invalid_config.json b/src/test/resources/restapi/invalid_config.json index 76ac5e7c0f..7bbbf2201f 100644 --- a/src/test/resources/restapi/invalid_config.json +++ b/src/test/resources/restapi/invalid_config.json @@ -7,6 +7,8 @@ "respect_request_indices_options":false, "kibana":{ "multitenancy_enabled":true, + "private_tenant_enabled" : true, + "default_tenant" : "", "server_username":"kibanaserver", "index":".kibana" }, diff --git a/src/test/resources/restapi/security_config.json b/src/test/resources/restapi/security_config.json index 8a230f283c..e8acc0a22a 100644 --- a/src/test/resources/restapi/security_config.json +++ b/src/test/resources/restapi/security_config.json @@ -7,6 +7,8 @@ "respect_request_indices_options":false, "kibana":{ "multitenancy_enabled":true, + "private_tenant_enabled" : true, + "default_tenant" : "", "server_username":"kibanaserver", "index":".kibana" }, diff --git a/src/test/resources/restapi/securityconfig.json b/src/test/resources/restapi/securityconfig.json index 9e7fb32342..4e4b1bba63 100644 --- a/src/test/resources/restapi/securityconfig.json +++ b/src/test/resources/restapi/securityconfig.json @@ -7,6 +7,8 @@ "respect_request_indices_options":false, "kibana":{ "multitenancy_enabled":true, + "private_tenant_enabled" : true, + "default_tenant" : "", "server_username":"kibanaserver", "index":".kibana" }, diff --git a/src/test/resources/restapi/securityconfig_nondefault.json b/src/test/resources/restapi/securityconfig_nondefault.json index c9e6aaec5e..6fb297be37 100644 --- a/src/test/resources/restapi/securityconfig_nondefault.json +++ b/src/test/resources/restapi/securityconfig_nondefault.json @@ -6,6 +6,8 @@ "respect_request_indices_options" : false, "kibana" : { "multitenancy_enabled" : true, + "private_tenant_enabled" : true, + "default_tenant" : "", "server_username" : "kibanaserver", "index" : ".kibana" }, diff --git a/src/test/resources/security_passive/config.yml b/src/test/resources/security_passive/config.yml index 34f4aff093..3663b3c706 100644 --- a/src/test/resources/security_passive/config.yml +++ b/src/test/resources/security_passive/config.yml @@ -10,6 +10,8 @@ config: respect_request_indices_options: false kibana: multitenancy_enabled: true + private_tenant_enabled: true + default_tenant: "" server_username: "kibanaserver" index: ".kibana" http: From 09995422f516e77aae87ff0211e97d969792a0ad Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Wed, 12 Apr 2023 10:57:05 -0400 Subject: [PATCH 164/369] Gradle plugin updates (spotless, ospackage) (#2670) Signed-off-by: Andriy Redko --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 916f1ec5e3..64798b957e 100644 --- a/build.gradle +++ b/build.gradle @@ -52,9 +52,9 @@ plugins { id 'idea' id 'jacoco' id 'maven-publish' - id 'com.diffplug.spotless' version '6.16.0' + id 'com.diffplug.spotless' version '6.18.0' id 'checkstyle' - id 'com.netflix.nebula.ospackage' version "11.0.0" + id 'com.netflix.nebula.ospackage' version "11.1.0" id "org.gradle.test-retry" version "1.5.2" id 'eclipse' id "com.github.spotbugs" version "5.0.14" From bbd43ec56cf1d7913bbe49547b09558ac37bc89f Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 14 Apr 2023 14:34:42 -0400 Subject: [PATCH 165/369] Update certs for SecuritySSLReloadCertsActionTests (#2679) * Update certs for SecuritySSLReloadCertsActionTests Signed-off-by: Craig Perkins * Add otherName back in Signed-off-by: Craig Perkins * Ensure files end in new line Signed-off-by: Craig Perkins --------- Signed-off-by: Craig Perkins --- .../SecuritySSLReloadCertsActionTests.java | 10 +- .../resources/ssl/reload/kirk-keystore.jks | Bin 5579 -> 4315 bytes .../resources/ssl/reload/node-new.crt.pem | 116 ++++++++------- .../resources/ssl/reload/node-new.key.pem | 52 +++---- .../resources/ssl/reload/node-wrong.crt.pem | 116 ++++++++------- .../resources/ssl/reload/node-wrong.key.pem | 52 +++---- src/test/resources/ssl/reload/node.crt.pem | 140 +++++++++--------- src/test/resources/ssl/reload/node.key.pem | 52 +++---- src/test/resources/ssl/reload/root-ca.pem | 44 +++--- .../resources/ssl/reload/spock-keystore.jks | Bin 5581 -> 5302 bytes src/test/resources/ssl/reload/truststore.jks | Bin 1338 -> 1398 bytes 11 files changed, 303 insertions(+), 279 deletions(-) diff --git a/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java b/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java index f7684a9c51..e1c8ec7282 100644 --- a/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java +++ b/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java @@ -53,8 +53,8 @@ public class SecuritySSLReloadCertsActionTests extends SingleClusterTest { "issuer_dn", "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", "subject_dn", "CN=node-1.example.com,OU=SSL,O=Test,L=Test,C=DE", "san", "[[8, 1.2.3.4.5.5], [0, [2.5.4.3, node-1.example.com]], [2, node-1.example.com], [2, localhost], [7, 127.0.0.1]]", - "not_before", "2021-04-12T00:07:08Z", - "not_after", "2023-04-12T00:07:08Z" + "not_before", "2023-04-14T13:22:53Z", + "not_after", "2033-04-11T13:22:53Z" )); private final List> NEW_NODE_CERT_DETAILS = ImmutableList.of( @@ -62,8 +62,8 @@ public class SecuritySSLReloadCertsActionTests extends SingleClusterTest { "issuer_dn", "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", "subject_dn", "CN=node-1.example.com,OU=SSL,O=Test,L=Test,C=DE", "san", "[[8, 1.2.3.4.5.5], [0, [2.5.4.3, node-1.example.com]], [2, node-1.example.com], [2, localhost], [7, 127.0.0.1]]", - "not_before", "2021-04-12T00:09:00Z", - "not_after", "2023-04-12T00:09:00Z" + "not_before", "2023-04-14T13:23:00Z", + "not_after", "2033-04-11T13:23:00Z" ) ); @@ -264,7 +264,7 @@ private void initClusterWithTestCerts() throws Exception { private void initTestCluster(final String transportPemCertFilePath, final String transportPemKeyFilePath, final String httpPemCertFilePath, final String httpPemKeyFilePath, final boolean sslCertReload) throws Exception { final Settings settings = Settings.builder() .putList(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, "CN=kirk,OU=client,O=client,L=Test,C=DE") - .putList(ConfigConstants.SECURITY_NODES_DN, "C=DE,L=Test,O=Test,OU=SSL,CN=node-1.example.com") + .putList(ConfigConstants.SECURITY_NODES_DN, "CN=node-1.example.com,OU=SSL,O=Test,L=Test,C=DE") .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) diff --git a/src/test/resources/ssl/reload/kirk-keystore.jks b/src/test/resources/ssl/reload/kirk-keystore.jks index 6a7daa435e8d2a066237ecd9d12b455b05ccaa91..c4171431d6cd4d57230d292e36491afc41fd2c59 100644 GIT binary patch literal 4315 zcmdUxXIN8fw#QQ-^d&YkmN?uWS_-e>Q%-t|6v@Bg#@zqQZy&h`KR0EoDNKNpFc ztCt%whzZIcMF9Y$AXqrb8H^S}DGmXW0L4HIKp-iAB%EXiFdh2a#q7tIU!EET>kK!z zTwy~wO3;&uOwu-{Lo9o(`Y8LHPAKJgHRMmkmZl%xRHV+$scnxdi+5>I zCUoiQl{A3A#n|?UbLOq-fqb)#zIwh3=Dog(Y{}oOA-#zw(J~3Tbc@SF!C{)d>{noT zcacx9#)guE8m4dQGRCR9O_}eYOLMoO`U1++rcV>3r9pq&$6HOTbZSWxdpFdkK)x;4 zs>@@xuYtNd4m-N`&i|P9k}X+4QbU8itkk0g1oJe|+c-&Gbm}ITp*OXd;gUmoV$f*C zH*7C%Kgsuo;UrrMH>^5&-i_SC{Q2zi<%ICNc(072g+#UE%>&+<#;xBw3$>$Y*B)F%`= zS7eZHKXjd(&*}H26UIFfuj`E1axHeB3n=Y|IG`}{U-U&@FcrM27B%mP*!HXBV)Rh5 zvHQeku*`(jzV9QT^Ff(Ton@I&ZLES!aTZbUJi6BXoAq+HvK$SlIwN0jdg*Ax3eFcE z0Si}s>1nd5XInf3al7?ubiw5?;}E4|=4T5S8;|3Ji&=&AszXt4*B2nmwJ$zhJ3LMb zlTnN;ZO4dvP&UA_`FmLqOX#j4liv(%w`6yb#G-^<^Zp-tmo!e5=hO)_YU`wX$N;vAG1_830nKk-I$ zF;0|Jh1Cqb97d~f84z;6U_mGTxA>!7yD3+*hXs9WbuLIqOc360Jwtwm45EcuUWm1g z-g{+}3<+N>ozXY+N93%!Ps@kbSuIDC-!^^a;#wyoF1GE3=5KhF%!At7p1tt+;+kw8 zxn$ZUDm~?o{1oct-q@P3(Q#Fj zOL0Jty!atGjp9QQ97hyMy(3d*_K2yMW5eXH9gl($%_S+sfLt3f;sxy!4x9!nk_VJB z&Q(m}SoV9bjpwJAPqwv|R=V!Rp9Dl26H?N(Eaf!)iu%8`AgfGLzovP*VwsPlkm%a$ z_RKEVYrZGvEA++2*w>B4n%m_US$l^c@x@y_vlv!lifL9qSviP!5C%Kz%p`THBoj=iTBf9KC&+WgGQO%8t5;wQe!sC1H+%>#LmF>a2xl{`Ru+p! z+fwVWPcz6Wu%VUJvcVU)BRyT`G}Pv9eWHTbw5LoYLkF?1@fB4S+z)jeN@iSlTucv5 zm9xZxlx=;g`LF9QXOgq^b-%NctE>7d9P#ulhZ*cs%JA}L0VXy>od12@WMy9)BMr8! z%(xj(Xb)zcV8FF0-z~2ZW1Kq=u*n9W+6GV}Z!piZSxVg0d@y_m&|yz|r7kx5J6%}P zM#xyYhX`j^iGo`2gfok`1+G%9F`qG5kDR{{VQlx<%b1p^h@&eYo^s&HKB=3iqf+zx^eOc(RbIDvnX|CR%&%$pi*!iChE& zNX;Z=VG6`M4_G)D1q%lmfiCj%d~=+ZA?wL%Ue=kUAK^vLa26QTe@l`f{q5X6(MUKej0FO|MM6nUuld&u z&sB^&kG6+{BwPT-_hXBSn*EPkJjSlh9GHMbNQKtM|q= zOt(I7z5^Oaw@kHsDjz8PHcgVM0aQ38Jkdo%Dk`b6>I;7B_?=bpgedCZ9al083bM(Anr4+TAyW0#m8Uq3oj0JL+tyv`F+n7*5ScXXU&LLFXJwt z8#r6m2j%a&)@ziL%FLmit!)$u1!m+={-7WXi~GNGA1{piukM5X5BC{iFy23`_j?JL`hP9? zQ{Vlk^5V}7oc!kNyl~>$)JfA)S@eWZ$%$0@72&>0pgBsrkn8CaK56pXm%pdKoziTb z>1NCKcuTuN^(vxgB#tas!HHm!oq?(`SO^K@T)}KsMT;`zWEl>xQeuAfjpc|MpUJr< zbV)pKl`6w+ao1nE5gpL5qaYGkUn!a*Kq?`HEd{qh1g_;aC#1l~iu(y{M)bVtoxOGW(8ko6PTG^%;!95PQ_ey++)J3fTZ|>kjiKd7 zE3%0~$e#u-GKLw#IEh`tNlU^^a*>rd^M{o7pJFChR`I*rG`ONF^Pf$`$xi}>F^65m z7i}tgeHRyHc+AzF-jkrbyJLO^qx*555ePgdA%%g7mp}CMPb#pKPUP^%6rtEY=}MAb zC-b&@oOxw%;Cc{8tD=F$^1%>wKaxjH>=`~j^0fmd*&<`+t^XbWt=|0`UpB^sw~QF- z_03~mtDtXNd&TZ_sWh1IK!?>$NZqHO&zv1YCO7y$TWvopA`d#=(O(|t_r!fkR&8Z5 zSrp;|6q=1GAfb}lDxKqY+H{`r+{;-b;t{e|yNH7r`>gu09BV!0QLG}BN%vI$N2&dW zpfn2vR>vMUVew5D;dq-3fBtFxh-_Srd(O)S<&_i|sh>XapsqJB{zN&Qxh$_%+!s4# z$-2^VI!b_k9iFJo@*J`)G^p4N+ohK2^(GOZY(7Rupcnp&3Zz6T0EtxC{+$Yn*`B}F zzvs!~bMJR?=)Wu86HasG4+{Q&q{4q3$Nr25g$5EcbE#U-Ri%Z+DQyMqQMU%1Zp;pH zcvQ`2x>2-FQB8~NL}t?u+(U&9@@;mqh#PaE7IZWp3ABEj-!t$$>8?<%+&u0qQ#u(f z*szL@=I8CFeRA!_K80qvmXupdv%HP@S`@y$Br~pF3MJCqcDuyfiU*h)7PyfQ$Fsa=Arlvr zy+6>7iWoVjU4NaEyo*^m{Wia2mlQb7zH=j{XdN_OiR=qlkc9`IYF;p~T}<{h1|#kl zyd8qW^d(Y!73$DXQ92xlzXG+y-_|4{pHTAKJyD_bdfth*>fA%;GivY2q}gPI9l4W;@6$Joa?DY|qPU=< zl_lBFzF6xTwwF8riMff{O1|xOdGqPEqpMnBlW5lU%iNfc!hqSdG^-c^Z}x?Fv;5N_ z!y*1zoG~p`ySt)Syo;0D#rfHR7VB>ub}{=;3RF{np*L4?SXIK-*jMOV49g(#YRjSWpa(pX0#dFskODQ%xV7FsNCde>iLNKaL6 HQX~Br2}ARH literal 5579 zcmY+ERZtvIkVOY~2<|qx3{G%&2<}6IyK8U_?iSqL-GdG8VS;;r;O-jO?AA){$E$vI zZg*FI9gYzEQCJu_ju8AHL=@(5`S1q}7$lg25PTQ-5PXM!*oGqn!Q%f#2s-c~2pa#e z%D<3>S@J=y*ZG4w2iLRB!X|J3-6 z&F@_3;zXC`tU(h7AxEiO&p!qw$nmb;tBZOOtPZw zjf(TFguhAhsuUUrT&*xtloe85!B7mP_|f2hR8t}KBOxc~oO2<_u49pK$YItWs4yh@ z60c%xw=Vj^$)tCx#d6VikR3sAAZ0p@dJ&nQcqQ|z@tM25JHTZ(ywx;2b?}QYrAOdM z5V}`PNuHM+w~}avoT2J%AKw|bIZ2JLZ}=(9o$H?KJW(z*sBH{)_9*jQdHa|2R=5Wy zAe(i3lyqmR1bbNBxN$q-;WhpTe(*}-ZQ0Rv}`rr|Q z1Q<}9&#SonYix6|r=w%cW2Nyo2D&KqNHAq#68K@DK*BJ;toye2P?D<%F_h2EHrvGt z;^O}*WI9ofM7#T>i^P)MVo{UjL->pn;6cKz{0a#pou6DX(%;0kv4_X7huJ-b9M+t_ zh+>uvl+3ljd+fbTr>RBvz@=tXq3k&~~KaJq+a z;cA><;QLGN%~M`1S)ShL2`nxsS?=*udHzGJvGV$DHl~1^?N0N~UjbO+Or84vf{0q` zX<$#`CSZMq#gf`v1&-ZWw#=+`VULudEoXih?-Z%jozH^wbms^;aAn#{^!m%Ub{cHId>W0H3+T96(PBm@@vf1vpT5A<<>E(t! zU1VobiYNB8{dMHhKOo_rOx_=uOp|Qln7(8)j^D*eSwbG#kXDwv6 zJ(3kBYXsjUzeM&4{BRmTVl!%E;z({y(f|8?AHcQNI9`VJ+MW;gzJ(4K0llSQ#Bytg zHF_u#k#JP`zSO*+sF!g}qgcwvck=V6?(3(pLxe+bnETy+6(UZc!EYo4D&`J~A7z5#eUU^s*oF2!Uf9=UT- z$1&^qtw0mV3va(=)G|)#_`xXDry^ZXtC}sp;7lIpe|~I~Ku?1+P5cUB`XcX?!wI0r zp4XF{q3T=}B4PkAJPvN)C@;Na^VWHWL%OfAacOnJp+eD*y_FCW`e_)wl{@Ds+PK#K znTCbvI|kERa{yX@s4%5+K^(3=(;MF&<#mQ%P#(bv5F_$%Jqe8#*Etmv*I~HV3j>b~ zqX}Jt(P~$`Ni7I{Es8x%R)2gm(|-wR-c+S57GHUBIH7shzyUp?cmj8DFvCv)!Jpu` z{mVx@OqPDAqWh;nLTENgy}U$@YWjs;nUEXwx=AzuKoOG4Da=9mzp6yR<3y!E2G{{? z0ImQ#4(k64@Hz3|akOk4ET}j?a`JQX^YID@{QGf)V7>lt6B1%U2-ev@bOZ|n_*ZuR zFM;`gc18S8yS909ko^u>jTESyd|_ZWA86>}6=+K~(vD*soDTtk6{|D2AdL5zI*Qp*_hB|mLM-aU1`5t^er9MvI$(=_gp6b_t zus1W3nao+Qg}kd%PQ~7_7Xh=&enUkKN!=m6z7BXiZ53>8i=8VM&XlpnDON*MW51#Y zMtZ}vV~+qGoAz|hbeV+ikg-qhmA`CGozI%nq~)Ro-R)0bS*WgL0?6g7}h)O6H-KGA1vr79GPzMYqH{a~N)$ zj!|{j6lCe-eei#cc9I?P^Oww#jDkzdm=`($>Y(pX5F9fJxhu_3>; zGgAp)+4J5fwVo|Mwr9_$RO(&#>V0>EBwrh#C6@S>)n`Me=N@TY(WXFzhgz68K5R#& zCuQ-<eJQjlSg7|(Ms|cV#n}F%ubEUbO%y%>v+jrI4rJ(y0rq6sff3}Jfd7HH@ zOuCarC`E$Uwn@&GRj~vM*xcQpdf%$#Lmu$=aiSJSv?n!!9W@ps_Wd~yFBN7<_?KmD zH_#FWg74r<%APf%bXvw4}$sV$c=-XVc_6Ax<(&a*iM|5}sA;N|Ma--!$bsFQ) z0t5AQd0XdWOv1afXK>*DX`r1r`L3s!sE>>3tK9=ExC3I&^yW2yIrT&2NL@Q$crH+f{t?k@DiC|kR1JgDdgXlQl-T?8cmF%G zpT`OdlKuj%Rx6=C#Utut0#R$&8R$*65fNwKr0*Shy%u29818xu6_5*MU-w3W{UJ#g~_F6@O`rimdvY^n^TxUWk`E#_xN!323M3wVprI zu^=IP(mXAuy@ccQi1gKU>>1O;MNM2SH)zlz3 z>s0v9k>=+lWxt*&s|e}QdwuYr;9Pl2`es`SR}fDGFS4d4hJP<;KA0m^ieUA(zE-`H zt}1FDsa_3IJ42X#0g0Ga$DS#ECV}n9pW?C;j2d z%dFG)TwGkbn6%>-Cc`!I_;^)Qshsrj9 zhw(09K)LV3lj=aT9TcULN5e2YlZjtS4 z6T1{hnQFA;N4DLT1;eV(!c&zIUu$`stI1$u2EQ`m&}3mmi@;~S=9nKr+4iEvH$*=~ z{NdUxKGLDcz5gTYyazf)iMY6thU%nywQk{sv{RXb*FlG|HvStDEg{d20~6+cIhdN0 z(}^Er{Z+?I529R}Qr@1(nOOi?`0#3BpJklq+f29hI}aQch;e>cZx=M4ziXzGmQCE6 zxTzNo$=DdfgNL(&_P6PyJJ9faE)|s2@#TayJrgn)8Y~rnX3pa*+OV^g%9w9|@jC~S zD=jB-^xxpR5OH0i@HmE_1;_J>tL~2eG@JPVV8yv{uR2GStIPf3<}1L;Q3`Mo286QN zrR@2d!i~GRCK)3ss!XVekIUOJqh99Mip$hVjI|qnSmp_+XrF>y zqLf7^o}Yl^*0yPzr6vw7j=3r7Qn~b=$ZD>@1W3f$JnA@BMaF)}51YqbmeQ7rKGjMy z^CD2+Ls_o8;@$N5PV7blYMIR<)bmz$CF5Hvq~Eg3d!+IO^}p<*@O#UnA{a%{0L_6Y z`4ldA4G~wS`k5xY0}ApAFMKO7DWq%@q$embKIL%I!oVA=_YBn)G8Qi@ycm}KT07bS zBe~jY!}-uf#l`-UsXsbfH!EWWxQwICKYpdyz(9WHDi?r65&*$+g=e3BK?8 za|B*HJ0S)}oJzYN^vja887G?P`@bH%-o(cy^BkiR4&@yE8oLWc#-mr+Iok<*b1a2T z{2Lp{X?(dcHPWj~c`&=$rrtO<8Mx3(&w;}#1IFGL`Mp{6=19+I_JP8UZ;G3q`SWmH zhx7D9J|;L%GHVGUp|T$~2f5xglkF90Cn0x~I(&r5S1e_fqz)avfRrTYffe#mM5t8a z>uq9`)t*gRiL)|OllbVhV;ErB*#2GSgaPld?}f{ByaN_)o5qe@(0`mcN-?FYJO zuY|9}lGbT6&7IM=7@xxiQAt5f7ypWdV#LMN(qg2E=4!ar3^=Pn27Ofw(d7F$VdP!)iY*s0rs_VN_L7LTJO3p? z8er*190x(K_3%5}Yv#LYkmkPacB|$^mw==kn?7 zd#LkUH~E!NfO#hwAiRmLxI1-NlV@y={~~@#XE(=m%wthKcm%&8%i4B2LmHedT}wrv z3SsFaN_-I!VKc+J=E9IqW9r)|<_AXTBc(`4O1?%Zq76i{Ihtqt`Hyq;2NNlus~K+% zSR|<44M%@^UxPIQT_V%)+51#+<{5Eq&I;jprm{^vps(ju%xwGmQa+kDWt^D zH+!g!VD>Xz5w~fUV*$Z{>99bHN(CVYdx9*^xX-ztSn}W0^W~!UVf$PA^t7i-boD0E z1}E)coIbd`gkJPt8hQm<2B>fUW?V86HmcXXAdobc#OAmfn;IQ@_Ptem(PH+?-2LHg&bH2%8&!g^Ph&nl#Mn* z_elJr#J|ygUR$Hx(}4Gd~_B%HCMi&2#diNM}n1;Yz+Oi z5YTC{tQ<3(#zRIr5c$AzkurJi2Kaz96gzT=z z@aVCqB0QKv&;Hlb!F^qBuQlkbzS?8s{KhE7R$}smzKc0i2jg@bWoUMxWxJ4+(pWFq zip{(=-wK~fI$u>W&EhIHXkrrpGkD$+O=w4f>G!t!`ovh_XjoV|n{>@tMJEC=d*R$D zrwzz6&r40&0yh6H44Db+thIZ`Ld{U~=8WEj@MH1}p^GLg0$x>nun0Igc_N82H< zhgfA(@N<BsKT_z^w9xpP>GoFBFn#fr>^&R_eY zxJ{Flk8TZsZk9}my_szTJG^(&ayT#oTK6f!NYAK87lg_@cFBn>mV3a2|o&>^Il)P!q+k#mJNLLrKMO z4RyucPl1U~yx>W2PMk+Hj0-9Ls{ZP9HaVuf4bc`9+2PDiuD#^Ggm35UUGk?6os0A@ zmA#2#_#XT2h25PGBkdLRThY)u?mQQn!EnPFm9a|T@1+29v$V(cQeW_;9&NmVwqjfvA!L*sSC=BUckJ@?2%eiuQ^-O;c|%Kd5-0{8;;*wYQ%Y z2uIM4m1(uWQ+oucED_k&rl8jM!wG^XMv-$1(r*iMGU^-2*v8paZzT`N8TzxeOCGYM z5;>O?aW+VIv#>N=nry&`sv)aD){KGk7StXovrcX-ZshjR0%n$=BZInD>*Q3w`}G$M zO!ef0c>nGs3yda>O|v*eIjA{M5D{1~;b2J+ z0PuK@35rInbU|J$Kq+Z{IT94sf+=-)JjGZ5xyO!`d#i*r5$aXCtcz~*B`h3Fc)@=F Dve<%E diff --git a/src/test/resources/ssl/reload/node-new.crt.pem b/src/test/resources/ssl/reload/node-new.crt.pem index dbf6fa430c..2a3596076e 100644 --- a/src/test/resources/ssl/reload/node-new.crt.pem +++ b/src/test/resources/ssl/reload/node-new.crt.pem @@ -1,56 +1,72 @@ -----BEGIN CERTIFICATE----- -MIIE5DCCA8ygAwIBAgIGAXjDaPfJMA0GCSqGSIb3DQEBCwUAMIGVMRMwEQYKCZIm +MIIEEjCCAvqgAwIBAgIUZ60uH7iyqLpaETXOtCGy95Kwu+YwDQYJKoZIhvcNAQEL +BQAwgZUxEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSQwIgYDVQQLDBtFeGFtcGxl +IENvbSBJbmMuIFNpZ25pbmcgQ0ExJDAiBgNVBAMMG0V4YW1wbGUgQ29tIEluYy4g +U2lnbmluZyBDQTAeFw0yMzA0MTQxMzIzMDBaFw0zMzA0MTExMzIzMDBaMFYxCzAJ +BgNVBAYTAkRFMQ0wCwYDVQQHDARUZXN0MQ0wCwYDVQQKDARUZXN0MQwwCgYDVQQL +DANTU0wxGzAZBgNVBAMMEm5vZGUtMS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALuHvhm9ox8mJYXLnvPcV8fFzDOojmvGkTkz08/Z +XaKIaBEG6scJ3qI54+o5e3NyHe3wAGHwTD3TYrLkuvLTGE5XTCGpYTixk8aITfuO +aNNptT38RGYjHMXOkYYFPjU2x/nurS7TSO0UEqy/hTsvPPKisExJhqQ/rNhSppx6 +pZqPKM3vAECy9db+KMzdpesW1z/BZa8GnR6OVjY8Gkl+ooImJoe1IWrzGDhy7wgT +L+DXFTnbKkxL25HpyOBVKj7QWhV9x8EE0r//M8AzDYbRDfHzDmXSOWywlMJ4MbYM +lOcMy4BVkK9DNDVxWm2pzWcvWA7AttwtOWqxCs7hn3+/uMECAwEAAaOBlzCBlDBS +BgNVHREESzBJiAUqAwQFBaAbBgNVBAOgFAwSbm9kZS0xLmV4YW1wbGUuY29tghJu +b2RlLTEuZXhhbXBsZS5jb22CCWxvY2FsaG9zdIcEfwAAATAdBgNVHQ4EFgQUI1aO +IhN1AZ4s+8klOL3Tv1Z3sskwHwYDVR0jBBgwFoAUGXJktco87q1v0BfhouzpDY+j +rlowDQYJKoZIhvcNAQELBQADggEBACFCK5rVc7pk7HXMxa6QDDmdoo5y6+WV2/Ot +s5Eiil4O8fneQapEc4ZRDA+iUS0ANFv+5iXIYysa7O0U7e4MGquwsVg61TSLPXvI +CHLEwG2yqcL+vK3oG3owC+5flPg6VhljxE7FMHJv0Ts6GkxUpgSnouU671fCblN7 +XzM2jf4HDy+17K3xDf9qMG+MkWWZxw69gQDMaT+WmhC8GSCe8lfzcecgUv/HoXhL +Y643y/jQN0xguK2kpQvC6pkCMj+xGhY+alEV/tfSJ8aYr+uUegClGsR7LVkMXiX8 +T3m4rBA4MKI449cWIqc0rHdKS3vGo6QVphSa6wt+nLnoXTiBF5s= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgIUf8RhLrREqQU02WxnXl87059YCKswDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA0MTIyMTAwNDNaFw0zMzA0MDkyMTAwNDNaMIGVMRMwEQYKCZIm iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ RXhhbXBsZSBDb20gSW5jLjEkMCIGA1UECwwbRXhhbXBsZSBDb20gSW5jLiBTaWdu -aW5nIENBMSQwIgYDVQQDDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0EwHhcN -MjEwNDEyMDAwOTAwWhcNMjMwNDEyMDAwOTAwWjBWMQswCQYDVQQGEwJERTENMAsG -A1UEBwwEVGVzdDENMAsGA1UECgwEVGVzdDEMMAoGA1UECwwDU1NMMRswGQYDVQQD -DBJub2RlLTEuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQC+PKQOneEPtQIt4G8yeqrAhFGXUkFDID5bnAb7mk5ALX5B8bWYQa7hER6x -QpP+a7CdPLDm+PLTGkGPT0S7HpcOYli3qtED0zkk8yaCL8ztk3oyLuV97u8x4A+r -OXoO2nmrG9ck8/XZN0ktu8Q87l0EaSMsCwuRJ+A/vFeKIEijzfjhBPjn7smYUpN0 -LprrM+o//OHPsQEXsRHOpjIM7KC8a+luRxAlrxotHO0lWEA05pNHya5fqkhdYQ1M -gR0cb8zTxBB+DAfmHCgzw4rf4kSd11LJbk5CITeSd98xV28TanaDli8BHfDbk3TM -iASv3LJyq5UX/1yD1CG41UH6vpoRAgMBAAGjggF2MIIBcjCBvAYDVR0jBIG0MIGx -gBTS02Hh6adcYFVpsHSBAGZYMvoGRKGBlaSBkjCBjzETMBEGCgmSJomT8ixkARkW -A2NvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1wbGUg -Q29tIEluYy4xITAfBgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEhMB8G -A1UEAwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBggECMB0GA1UdDgQWBBRQWhAY -IPA14V/TdxjMqcvwbdG/UTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF4DAg -BgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwUgYDVR0RBEswSYgFKgME -BQWgGwYDVQQDoBQMEm5vZGUtMS5leGFtcGxlLmNvbYISbm9kZS0xLmV4YW1wbGUu -Y29tgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQELBQADggEBAJVsT7kQ5JYS -dfZfo7Oq6DJfNPPPSUiGNuN8epcEF7jvEYbvZzaxAYZFqGOh7k8jG2/ISjYm6IW/ -8g8sOraYfjY1+An+UFq1Z9ykemYVyzySpHMKn8PqtIdcBzaX0fA8hO6Q0Gsm6V9G -/KpN3696rAUHhgGFSE01fVcq2B5Keuk/RDFLhfGAcDT4XO4ZyT5ouHZzVuXatkgx -OJQCsxtyvEajvXDMFQYddubBZwptM+uoFOe1G3cLYc39tRk+O/RTAcfAWN0VyBfD -/etGwf7991oIOCaNtCzHgGiOr5fjGJ+AvUU1plSVsoArxpHiRBxMG/Fvyb/uN9g+ -o1yGUF5oYqA= +aW5nIENBMSQwIgYDVQQDDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCS3PraJ5Dk0vm33qiG0Cal+S+W +PifErAFaakmgHq2rIjMIVjr9luLSRbrXwRqebuIO4AvVg8LMiwecOWbTVJqXarBP +236AHeBv566GKRGbmVHL4QpvenaIHIXP15tGJjcrneELl2vd7Hgztmx5teo5KHu0 +rCmTJAUsL4GmA7sGJEacuI2TMHKmtPOgQPJ7csdAhRiSnIkAhc8cZQioBi5/IxnT +benJ5GpS2pZ1+FJOr7V9WBtUpa20ycXde+5ciiaQJ7TZNyk0Meyz5/l4GKQ5HwqB +qomNGlIQIZa/w7OeGLaU178O+dikN2aM0mcmS22lFnS5Uy6pUxip8+A1kCZlAgMB +AAGjUzBRMB0GA1UdDgQWBBQZcmS1yjzurW/QF+Gi7OkNj6OuWjAfBgNVHSMEGDAW +gBSio+c9cv0rKRGnHryWwo097Opa/jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQAXptCbI4ejahr1TC4FRwd0YaeYPsnIR3wcujtPW9/vygzGZSBB +KrKijITWY2+SW5fX4nhZeOjD7kv45s4n6FIRtE5ah9Y/bFZecojqwD+1VCAYy0JU +BW3Uvtf+9gbR5iP3Xemyowh89upO388jcI/4kkC6F1TdJh4AoFjOOWUYLkk/v89h +SQ9wjB/fmcwrgzVd7DLvh2KZtM6bXk09zYE7C1TB0sb0L+61fJVbMoFKYo/QW/tM +MmRWGqL/9eXzB882H9lyX2pykXuETpbQirPDnYz5PZYWqJ2xK3aB0lwZ4Ln8zdMY ++svQsZlwymCgT6rngOwMNMN0BCQKuPZsg2zb -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEqTCCA5GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADCBjzETMBEGCgmSJomT8ixk -ARkWA2NvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1w -bGUgQ29tIEluYy4xITAfBgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEh -MB8GA1UEAwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMB4XDTIxMDQxMjAwMDUx -M1oXDTMxMDQxMDAwMDUxM1owgZUxEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJ -kiaJk/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSQw -IgYDVQQLDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0ExJDAiBgNVBAMMG0V4 -YW1wbGUgQ29tIEluYy4gU2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAJjVJ9Ipq0uCE7EE0RRBadkrIm7J8OGa2xeFCEpsw+hqQbmftrm0 -V5XFpRKznv7r9JxFQn4uUM/Tqxt/mZ9Qql6IGWiNeK4e0Hfh+Lc/VD+w+mDqxo52 -zv0afYyyM/1NiOyKV7hO0Akwj25zAfnqs2FbsZzVXtRpgOnfOdfKpUL6fKAxwjex -+0nOOaS5D+unS64sh0mTMVD32iag+tg6aiiW914Jmrkd4yDV3uoXh/FKafRRWHSq -pwO7Xilb+fLRMiQ1N09mTE8Um12G7mVdBaZpbluwtv2tiS52n8zzpKYbohS2m1ir -lJMASh6qEKzHUlzmERhq4Ib7V+p0E+gPadMCAwEAAaOCAQYwggECMBIGA1UdEwEB -/wQIMAYBAf8CAQAwgbwGA1UdIwSBtDCBsYAUrs3v6ngKiRF/4eiBW+3eCSjTVo+h -gZWkgZIwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdl -eGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFt -cGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4g -Um9vdCBDQYIBATAdBgNVHQ4EFgQU0tNh4emnXGBVabB0gQBmWDL6BkQwDgYDVR0P -AQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQAmRbuLhYdGYjdndROJilIQ4DUD -HYYijortmkq6ZNQiB7I2teBZjra2xcKp0qaOkG9nJCzhHvGTw/URjWAxFZ5n5hhx -iBTJZTGY6bk2SWfJxdd3Zld6ybsoaQTO/ZnfuTE0SgxWx1e5q/mRBKvRwF02bJu3 -YNlJMJVuIDxurrUK2CXrgG/BOCYMyiHNmn1Spsh+x+6B9u28CE0M52+HtNJwyQw6 -qPMSNiovXRkqk7E40e7ZY3MS85Pr4L0Us048SswPEpf/+IjV/zTcDP22pQfcjMOX -DZpdAOGgvqhL9t7M1lcxJrI0cIcBNWOPVa3zsCDU7CXF+riL0Va6TvoChVtT +MIIEATCCAumgAwIBAgIUO5pwerTrIJkibcZoY8ZxK8KCDT4wDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA0MTIyMTAwNDNaFw0zMzA0MDkyMTAwNDNaMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgTyzX2ZRIvkAzJysKYCVialXIZkvYyRxu +rtqYawm60gvUKOqEmhDIc2p/ySLnvxcrUx5q20pFoSS6+9rtT3D5wapArOf2v6g8 ++c2f5l2GhiMhxg6rRkvuCUWpSC9rubg4X1rkhaK8pZiLtC9qKLi3ZIG9rNubALZn +XuDD3nQ03pEKG3iy+hUVF7c876J9iFlqJU5RZCTfGKNaRYAnCtm/wWiHVYwAV1Gf +awjLjBfrFwcrJTWJyLxqg8z2DuXWm5Lsb+D8+9rqYZF71BvqS4ej5QLPrGXEeds1 +MX38RRJPYN2SdlMDMomjxRZyDSubZV40mLe+4znmFcGuMhouyDSvAgMBAAGjUzBR +MB0GA1UdDgQWBBSio+c9cv0rKRGnHryWwo097Opa/jAfBgNVHSMEGDAWgBSio+c9 +cv0rKRGnHryWwo097Opa/jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQCdEXxctLUFE0IoO1HDfE6LtNs2+I488P3Cp+hbHvKhHJeeY/48r0Gi57hQ +sR+Bfut3RjmdmKw9RcYylTgtXXmWQ3E6HqXaaZSnaG1TCER3bboxU7HG2IbaZiGu +/25K92rtq9FY0YNhCGYodq3cEzYWhX0pj3yj4N4193aBXrAaxyAALNnQ2Kwi6bw4 +WtS780NIgNFsGeU6H2/0JwDYlZVdhyR0G9uMWJ78fFHKI9iPUw4LvG07coxoZmsU +2tjIuV77HGGH7qufQJQtEFo/aOBEbLZ4i1uef6nYasnsI6tV6jhFhcLvgj7KeJDK +HUzAD6zg6DKj2NaBf5DzROVlZSCz -----END CERTIFICATE----- diff --git a/src/test/resources/ssl/reload/node-new.key.pem b/src/test/resources/ssl/reload/node-new.key.pem index 467b5b01c1..5a9c818430 100644 --- a/src/test/resources/ssl/reload/node-new.key.pem +++ b/src/test/resources/ssl/reload/node-new.key.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+PKQOneEPtQIt -4G8yeqrAhFGXUkFDID5bnAb7mk5ALX5B8bWYQa7hER6xQpP+a7CdPLDm+PLTGkGP -T0S7HpcOYli3qtED0zkk8yaCL8ztk3oyLuV97u8x4A+rOXoO2nmrG9ck8/XZN0kt -u8Q87l0EaSMsCwuRJ+A/vFeKIEijzfjhBPjn7smYUpN0LprrM+o//OHPsQEXsRHO -pjIM7KC8a+luRxAlrxotHO0lWEA05pNHya5fqkhdYQ1MgR0cb8zTxBB+DAfmHCgz -w4rf4kSd11LJbk5CITeSd98xV28TanaDli8BHfDbk3TMiASv3LJyq5UX/1yD1CG4 -1UH6vpoRAgMBAAECggEAAacCsMrPxeRnWrEonhphKc9sawFQpk3dJMwP5ncSZ23N -uzJnhqVJaNSOfPEsWmkijVcV3Ue7yjgf6igA25Q6MSqLBTtjWeJnjGndQ4O5RrYB -eXadUTD2XGtLD4xuAjuFE4aoIX7J+6NGjDJlk7bpUWy9XLhS1yWuxbbz1rQNlXik -a82FOoQiOxjMCBhgRO+zU1x3Cqz5Keb1UWekMUodXgVBx47P4ziYLV3+E0mkyz/D -2uJWawnmn0nL3RdyQd6WBEvRT7o1FzZy45zCaMFn0C9NgzUqmSpvKpp4iQU54ptq -nTQl7+O56QSrt95zEKS+7sjH3sX3rix/GagWj5Z57QKBgQD6SinskXjpd+VdrGAs -TLxu3P2t524XeOsQV9t9kBlW9jQU0tRgEu81z9UGXFPqiERpFRlyoYlvxOJNgAIJ -IOZzA5Vu66cQJZWZOapMmrzHdxO0SCgk3fUyyhe6W/3C7DPHJTFfoVJFY/ZhhZ5w -ZclIupKTszeZj7t786MDCuteLQKBgQDCk7vkLywfqFcdxOy8gQYmBo/xLH+yP7dn -RgQcvXIxF3K4eI3OwFj0Aly6Bmq4b1VUSqlaCqlDF80LLSh2qlIzFA0UOpRFBsaY -19p/lZ3BsTlbCwf2picltRaqZw19AhbErRab3WrYqoHfp4aPMDCcL6AwBvmzX/K/ -j17lBvf99QKBgQCf8TTJUE0MJOoV6kmrBX6E+gPCVdosdcEBOKOyoZsPz1WI2RGO -M4tZv+5Jmkal4V6WKD2S21eQbSiQjfS/EJ8zcF7V+xFPaDUpLYh+W4O3k4ir9FBy -/sPqKOrw1Ehdf8O2xzW9/sEsRqzztQeCnAj8yP1SFXy6TPdqRk8tapTuIQKBgFYp -j3FrRjG6nOEs29xZkjxyeXlFyEJJntCXm6iSZ6e/h2iS0dD1Mi7TgPZLcyS7AGE0 -MqwRvoRcXMsPMzThFUdTILuNeSzb5EO4iiQnP9WOwiRDzUH2r60t9Jx+x+VMcIKl -VzasJO1PC/XbPGXZ5By1pky+OgKpgVg8h7wJivWlAoGBAJ4aOb2iXoNDSchKjXEA -u4odkRpCu7+QLMR053ixLFr25zOOy92SU6F/A+PsYvl/YmjCXqv3CQ3HMUtEWN8N -fQegX9wPz8zoaz20nk6blXHaa93xNHQhTVmgfGYDMLt3xXXy1Obkpd+DoSByJfXz -soIMoVWCKJKYXtyXCDL736ld +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7h74ZvaMfJiWF +y57z3FfHxcwzqI5rxpE5M9PP2V2iiGgRBurHCd6iOePqOXtzch3t8ABh8Ew902Ky +5Lry0xhOV0whqWE4sZPGiE37jmjTabU9/ERmIxzFzpGGBT41Nsf57q0u00jtFBKs +v4U7LzzyorBMSYakP6zYUqaceqWajyjN7wBAsvXW/ijM3aXrFtc/wWWvBp0ejlY2 +PBpJfqKCJiaHtSFq8xg4cu8IEy/g1xU52ypMS9uR6cjgVSo+0FoVfcfBBNK//zPA +Mw2G0Q3x8w5l0jlssJTCeDG2DJTnDMuAVZCvQzQ1cVptqc1nL1gOwLbcLTlqsQrO +4Z9/v7jBAgMBAAECggEAVaAxaMdyB/L/BwxmU6q0Wf8yshOwk/1iqd/EQw9Y9bGa +8stZv+/9u8z7CCOe/qOIjvAa/J0f8srmMhDGW8GPQ5YQqP4R6jUK6IesM8RekMmf +e+IHySsXJCqhv7P9J4cP4ErewJawoa+cp0v7pX54McGfBfY8iBqa7VsvwBswKlWS +fH4VflFrJvAXaqJLW+VRgsLHxBQVzkrhd6xWO2A9QPpRURoyYAl/CjmN6rYutds1 +ZwIL4NNEr1lD5f4rlIBsN3OuGhYPwOkH8zYoqm6SRVFc6PEGqfvVp3us1LgcPwtF +daywuh/rm+K3S6JzfA5Cg8NC/leTLAlg7vMXge/a0QKBgQDzFql+SW6Z01Dl9zWJ +VR0L5fdhSoQloj7M9LLyAMmteVcnsklEKSKfGa+InDHL120mdvayVNFcoBv8a5rp +TaQaecv5uN2vPvBAJL2G8TW0B/XllZhpXPi852zTo8huR5GRUIaoVJ3WzngoBArX +LBeLJJZuIlZ13VvANLGMqSfFUwKBgQDFfaPemJbrdi74EyKrtZ+cz5JsFswXlKti +f0DVit4EC+0fp9gs1XDNRQQkpfNyX+fVn/ViAdH7yoCsVGf5rFJ3Uce5/35rESb3 +eNg3oj2VFNXj1x8ooZxm3fEVc/2Ut014hw0H2L2/t9DBgCgxEV0kd2kFIK6t5OXC +kywmx5xTGwKBgQDo2ednsf2A2fufHSsqLt6Q/Cr4BgIJbp3LhAY5bGnDgvzqaIxn +/yNL1bXgHQZSJTxH4SK0dizKFhBYvdZ47sdoGQ07W0r2yEool0j5DusLVajEz4Rc +QRN/GAuVu0iN4n5c/Q7QBaXgEozkf+LGdFTlExoJy1iYtH5j4dXFUG9pQwKBgEmX +QMoAqDRDjMsKLmA3xKtlOdWnRRQAZUDF1H3+Si7N1uV14PsL6gXDkwCpCidzj2su +OPz1Wq7tzbbpmPkTeoNV6QvpJc11zcRntoI8pZ/47J8DGWxFlIdMarqoxzR0fZzN +DlD/Ne3L7DE+tTtbkg61pF+xxwWrhuZTex9UQG9DAoGASr3b8Ot21zIYyr8BwtFY +k7lcSpEBTmCcXhiAZTgGO0LvdjVl+vwIuJaSH2zAPcrjFOEG0EgVP0HlUTuXq0rf +wGTn9DsqE0tY7MjuiJepaJvk0/AvaOMo4iNgGMf7e51NIQALuYVMaIeZbORxivOX +94w1NTsD3mH6yHYTX4Qm/zs= -----END PRIVATE KEY----- diff --git a/src/test/resources/ssl/reload/node-wrong.crt.pem b/src/test/resources/ssl/reload/node-wrong.crt.pem index e9b670f2f4..d87097bc87 100644 --- a/src/test/resources/ssl/reload/node-wrong.crt.pem +++ b/src/test/resources/ssl/reload/node-wrong.crt.pem @@ -1,56 +1,72 @@ -----BEGIN CERTIFICATE----- -MIIE5DCCA8ygAwIBAgIGAXjDaPfLMA0GCSqGSIb3DQEBCwUAMIGVMRMwEQYKCZIm +MIIEEjCCAvqgAwIBAgIUciADWUgJQl06xdaDRO/xF3WeSLwwDQYJKoZIhvcNAQEL +BQAwgZUxEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSQwIgYDVQQLDBtFeGFtcGxl +IENvbSBJbmMuIFNpZ25pbmcgQ0ExJDAiBgNVBAMMG0V4YW1wbGUgQ29tIEluYy4g +U2lnbmluZyBDQTAeFw0yMzA0MTQxMzIzMDZaFw0zMzA0MTExMzIzMDZaMFYxCzAJ +BgNVBAYTAkRFMQ0wCwYDVQQHDARUZXN0MQ0wCwYDVQQKDARUZXN0MQwwCgYDVQQL +DANTU0wxGzAZBgNVBAMMEm5vZGUtMi5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAJbhPUMpfA0UEaRZO9w2Bydn9rClz+sBueaSXfUZ +FWPpk4orTb958RG3DIxJ5LxRnMXYYxEbVHIAGrrH+s06nJU8iMjP8zki6tqTn3VI +G4DeLaCRmweG832nbd3VkRE0K3CvQoIQoYTe4uoUNtpvs1a7b0keWJfvhxeNpSGF +iZQhZg9hu+jL3MGWQVTH3QVp6rTABU7W+DZjozuG+0OlxBVnmPWu23WG7E+LhWFU +ULyjAg2LCaClLpRHRc1jXdavvVjik+RTvPkETLfhwy2HDBlSkv4Dk9SjiUlhEfpO +Y9HN2rP3yXAvBi47BDPr7hC1oUPbXD7/2x3oxgDUuV0S8N8CAwEAAaOBlzCBlDBS +BgNVHREESzBJiAUqAwQFBaAbBgNVBAOgFAwSbm9kZS0xLmV4YW1wbGUuY29tghJu +b2RlLTIuZXhhbXBsZS5jb22CCWxvY2FsaG9zdIcEfwAAATAdBgNVHQ4EFgQU0tED +HwNuDesU72iN+ZtGavIzKTQwHwYDVR0jBBgwFoAUGXJktco87q1v0BfhouzpDY+j +rlowDQYJKoZIhvcNAQELBQADggEBACavMia/iS7+gOyIXl9d8fUjtwVhCeISahZs +7C1spcwnWtum4yLUKwA7XeG+9k8KmJCxa1A9KjfQ3GiWB15Sa+XCtg3alhEqoNiT +5XiH5vAeSDG+BQrDTWb2hVC2vCiqwR1zszGyXThjYPig0DCxfr4zhAR8Zwe71zgE +Iz5Ydq77m9MGzxqNvofxXo3e85fBaEczkHShxLUid9eOJSN+TK/GxPqIhxNFEcwr +1bNunTf/9YCcQcCPw4cSVbmRjadqJdDNfFQoQGi6L1+JCKMEefpnvaxorSRs0mu/ +hO0hRg5K5Hdb++upzeMaVikl9IYuLRD6kABTyrVphKd2ksO0CBU= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgIUf8RhLrREqQU02WxnXl87059YCKswDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA0MTIyMTAwNDNaFw0zMzA0MDkyMTAwNDNaMIGVMRMwEQYKCZIm iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ RXhhbXBsZSBDb20gSW5jLjEkMCIGA1UECwwbRXhhbXBsZSBDb20gSW5jLiBTaWdu -aW5nIENBMSQwIgYDVQQDDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0EwHhcN -MjEwNDEyMDAwOTAyWhcNMjMwNDEyMDAwOTAyWjBWMQswCQYDVQQGEwJERTENMAsG -A1UEBwwEVGVzdDENMAsGA1UECgwEVGVzdDEMMAoGA1UECwwDU1NMMRswGQYDVQQD -DBJub2RlLTIuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQCdtnQNK63dqK46DDuiww+wjXISHGDM143iE4UgrpnC5DtqmljwQPCKq4Em -QyLQdjNA37LDobINOtcDr9CQwthMMJeopbSFf1Qk97E4Z8DM9PF5AuNAQBoAkFsk -eq2JSLNuCE4lw8RSFjdO+5aCv2kIvcTtP8xiG2k6M98kLYRPES9nBzfiCa1TrDyV -HVIrcdF3bGLyXxUL/Bbbk2lwl5MDQ5FsM5CDAWJUMhKc9ijbyVUqACeebCBWPweF -iLCHVksXT6B1HNl/mLPNIc0HbpCkJ+gASWZTQ7vbOidxR3eJJZs5zmNBbDuwSjYO -pn1D/rAmzwb/YiLIvwFa602ZUgVlAgMBAAGjggF2MIIBcjCBvAYDVR0jBIG0MIGx -gBTS02Hh6adcYFVpsHSBAGZYMvoGRKGBlaSBkjCBjzETMBEGCgmSJomT8ixkARkW -A2NvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1wbGUg -Q29tIEluYy4xITAfBgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEhMB8G -A1UEAwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBggECMB0GA1UdDgQWBBT4Fkad -SpmiN20n0xcPgz0apTcDQjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF4DAg -BgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwUgYDVR0RBEswSYgFKgME -BQWgGwYDVQQDoBQMEm5vZGUtMi5leGFtcGxlLmNvbYISbm9kZS0yLmV4YW1wbGUu -Y29tgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQELBQADggEBABkamc5q63pY -oRKgB4HjUttPSC7iWnLSa6bticyyfF34QO5fLpuRKv99u7GGTUDXtcoQ1ZYUHkhM -6G3E2OWgwAPs9Ag/HjFmR63I1JA0fsPOjMEP9ySHdO4eyCDwjAQLCnJ3XceOVSLW -pqhIKM1696Gc4CtrvZ/5RMe+7VozQ0rm7rkviDhYvsy7LbSmXwKcvFMHohOxvnJE -IWHq/uSzE4CVnS8Go7OOciQq4QY69GNVY5U5XiM9SbscPy+Lx/ayUst/xWPwIgPV -/4gC2UpKn/KhG3Llr8C+4LzehPRC4MSbq52aC3QsGCpm+oJU5ULscl+Apwj3ByhL -YjHbo/j+hxE= +aW5nIENBMSQwIgYDVQQDDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCS3PraJ5Dk0vm33qiG0Cal+S+W +PifErAFaakmgHq2rIjMIVjr9luLSRbrXwRqebuIO4AvVg8LMiwecOWbTVJqXarBP +236AHeBv566GKRGbmVHL4QpvenaIHIXP15tGJjcrneELl2vd7Hgztmx5teo5KHu0 +rCmTJAUsL4GmA7sGJEacuI2TMHKmtPOgQPJ7csdAhRiSnIkAhc8cZQioBi5/IxnT +benJ5GpS2pZ1+FJOr7V9WBtUpa20ycXde+5ciiaQJ7TZNyk0Meyz5/l4GKQ5HwqB +qomNGlIQIZa/w7OeGLaU178O+dikN2aM0mcmS22lFnS5Uy6pUxip8+A1kCZlAgMB +AAGjUzBRMB0GA1UdDgQWBBQZcmS1yjzurW/QF+Gi7OkNj6OuWjAfBgNVHSMEGDAW +gBSio+c9cv0rKRGnHryWwo097Opa/jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQAXptCbI4ejahr1TC4FRwd0YaeYPsnIR3wcujtPW9/vygzGZSBB +KrKijITWY2+SW5fX4nhZeOjD7kv45s4n6FIRtE5ah9Y/bFZecojqwD+1VCAYy0JU +BW3Uvtf+9gbR5iP3Xemyowh89upO388jcI/4kkC6F1TdJh4AoFjOOWUYLkk/v89h +SQ9wjB/fmcwrgzVd7DLvh2KZtM6bXk09zYE7C1TB0sb0L+61fJVbMoFKYo/QW/tM +MmRWGqL/9eXzB882H9lyX2pykXuETpbQirPDnYz5PZYWqJ2xK3aB0lwZ4Ln8zdMY ++svQsZlwymCgT6rngOwMNMN0BCQKuPZsg2zb -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEqTCCA5GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADCBjzETMBEGCgmSJomT8ixk -ARkWA2NvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1w -bGUgQ29tIEluYy4xITAfBgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEh -MB8GA1UEAwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMB4XDTIxMDQxMjAwMDUx -M1oXDTMxMDQxMDAwMDUxM1owgZUxEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJ -kiaJk/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSQw -IgYDVQQLDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0ExJDAiBgNVBAMMG0V4 -YW1wbGUgQ29tIEluYy4gU2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAJjVJ9Ipq0uCE7EE0RRBadkrIm7J8OGa2xeFCEpsw+hqQbmftrm0 -V5XFpRKznv7r9JxFQn4uUM/Tqxt/mZ9Qql6IGWiNeK4e0Hfh+Lc/VD+w+mDqxo52 -zv0afYyyM/1NiOyKV7hO0Akwj25zAfnqs2FbsZzVXtRpgOnfOdfKpUL6fKAxwjex -+0nOOaS5D+unS64sh0mTMVD32iag+tg6aiiW914Jmrkd4yDV3uoXh/FKafRRWHSq -pwO7Xilb+fLRMiQ1N09mTE8Um12G7mVdBaZpbluwtv2tiS52n8zzpKYbohS2m1ir -lJMASh6qEKzHUlzmERhq4Ib7V+p0E+gPadMCAwEAAaOCAQYwggECMBIGA1UdEwEB -/wQIMAYBAf8CAQAwgbwGA1UdIwSBtDCBsYAUrs3v6ngKiRF/4eiBW+3eCSjTVo+h -gZWkgZIwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdl -eGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFt -cGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4g -Um9vdCBDQYIBATAdBgNVHQ4EFgQU0tNh4emnXGBVabB0gQBmWDL6BkQwDgYDVR0P -AQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQAmRbuLhYdGYjdndROJilIQ4DUD -HYYijortmkq6ZNQiB7I2teBZjra2xcKp0qaOkG9nJCzhHvGTw/URjWAxFZ5n5hhx -iBTJZTGY6bk2SWfJxdd3Zld6ybsoaQTO/ZnfuTE0SgxWx1e5q/mRBKvRwF02bJu3 -YNlJMJVuIDxurrUK2CXrgG/BOCYMyiHNmn1Spsh+x+6B9u28CE0M52+HtNJwyQw6 -qPMSNiovXRkqk7E40e7ZY3MS85Pr4L0Us048SswPEpf/+IjV/zTcDP22pQfcjMOX -DZpdAOGgvqhL9t7M1lcxJrI0cIcBNWOPVa3zsCDU7CXF+riL0Va6TvoChVtT +MIIEATCCAumgAwIBAgIUO5pwerTrIJkibcZoY8ZxK8KCDT4wDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA0MTIyMTAwNDNaFw0zMzA0MDkyMTAwNDNaMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgTyzX2ZRIvkAzJysKYCVialXIZkvYyRxu +rtqYawm60gvUKOqEmhDIc2p/ySLnvxcrUx5q20pFoSS6+9rtT3D5wapArOf2v6g8 ++c2f5l2GhiMhxg6rRkvuCUWpSC9rubg4X1rkhaK8pZiLtC9qKLi3ZIG9rNubALZn +XuDD3nQ03pEKG3iy+hUVF7c876J9iFlqJU5RZCTfGKNaRYAnCtm/wWiHVYwAV1Gf +awjLjBfrFwcrJTWJyLxqg8z2DuXWm5Lsb+D8+9rqYZF71BvqS4ej5QLPrGXEeds1 +MX38RRJPYN2SdlMDMomjxRZyDSubZV40mLe+4znmFcGuMhouyDSvAgMBAAGjUzBR +MB0GA1UdDgQWBBSio+c9cv0rKRGnHryWwo097Opa/jAfBgNVHSMEGDAWgBSio+c9 +cv0rKRGnHryWwo097Opa/jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQCdEXxctLUFE0IoO1HDfE6LtNs2+I488P3Cp+hbHvKhHJeeY/48r0Gi57hQ +sR+Bfut3RjmdmKw9RcYylTgtXXmWQ3E6HqXaaZSnaG1TCER3bboxU7HG2IbaZiGu +/25K92rtq9FY0YNhCGYodq3cEzYWhX0pj3yj4N4193aBXrAaxyAALNnQ2Kwi6bw4 +WtS780NIgNFsGeU6H2/0JwDYlZVdhyR0G9uMWJ78fFHKI9iPUw4LvG07coxoZmsU +2tjIuV77HGGH7qufQJQtEFo/aOBEbLZ4i1uef6nYasnsI6tV6jhFhcLvgj7KeJDK +HUzAD6zg6DKj2NaBf5DzROVlZSCz -----END CERTIFICATE----- diff --git a/src/test/resources/ssl/reload/node-wrong.key.pem b/src/test/resources/ssl/reload/node-wrong.key.pem index 3cabf06abf..b8fa1875b5 100644 --- a/src/test/resources/ssl/reload/node-wrong.key.pem +++ b/src/test/resources/ssl/reload/node-wrong.key.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCdtnQNK63dqK46 -DDuiww+wjXISHGDM143iE4UgrpnC5DtqmljwQPCKq4EmQyLQdjNA37LDobINOtcD -r9CQwthMMJeopbSFf1Qk97E4Z8DM9PF5AuNAQBoAkFskeq2JSLNuCE4lw8RSFjdO -+5aCv2kIvcTtP8xiG2k6M98kLYRPES9nBzfiCa1TrDyVHVIrcdF3bGLyXxUL/Bbb -k2lwl5MDQ5FsM5CDAWJUMhKc9ijbyVUqACeebCBWPweFiLCHVksXT6B1HNl/mLPN -Ic0HbpCkJ+gASWZTQ7vbOidxR3eJJZs5zmNBbDuwSjYOpn1D/rAmzwb/YiLIvwFa -602ZUgVlAgMBAAECggEAJ4yR9J2P96ZtrnPT3qCAxjrqMJ16LtQdKeTQPR5bw7I9 -LiHvjKwURgn1FJXAXLtPZgTSzNk3D7dCJRQJPswFtrF2zp1jNBj2jmCoyebGw/7m -sPxm5X1Np/eLS44SB3u6Ny0Q/8pkaFtW2lEf8MC0Q7Nm90HI7I9IJ4bLfcSGSdc2 -H4i4m0bM5XEiRvhiaD7V/wE9MFwCCt+Lwv8gN0ii2fI7b8EY1YfVpmbj9fp+8fOP -yHMuWeiHxbrjGDwFCZwDBD4IyJzuKfX8eHnIjZei6lAiE9kz45lqUbOnYPdidt5O -VfkkHjTMBU4mgthUnyHm2anyi2Ud9WeE2sksVSGn3wKBgQDJS77gC241MSpduscj -ZVBLomhuabu+UORZAFuW6DtW4VdJ3r8Cf8Mh6MWqZAFFsBgdNGfjtgdPP92PPCrV -cXBinVYT13afaE8iyUTXlLscFq5aBieOBoT4saZxcEPkIvVZgPxxyWdkQNIRe5LV -k5G2Lq8qxy86OnC0o7NA7FgECwKBgQDIkprQM6RuXz3Tqr8WfMZuvd9Gs2jNT//k -ItfBfCxxTaNzfNeHj9KmmdmcmiZebqRdKjDoQlpOqEG6CQAv6HOSMQrk03Tp5QCk -1GFo0ilfek58ZhOMoT7eZ5zedJ1OX6RXTqvLNNI0e6Pa0Usx/3CJOtIKyhUmnz8F -vAIouaASTwKBgQCxqGFpOY1l8uOSX9N7wOIyLr9+m2DwolI23uL2+DXPAwjYEHiC -iyI8XkV+kc6xo65UsDj0t2YSIqq3zQF86ianUndzAZoXLKeTaxGQNxtAuh/dIkts -xQI8wAXHXq78vYHPIdEr1/ahe7grZ+X7C8fxg5hj5/IdsRBhzYzaQv2XawKBgGvx -Kkv+XvHfY+C0NZ6ejBQxLyZXi4FjGm3mqoLAlxJrHBodB8k3B8ENb2WuOBP+K4rQ -F/4HJ57JQoYiLe2ahggZSKmZe4Qc8FnEyp+k6wstQWwFa2P1q2X6ERxPWhFBu6oY -9q+nv7DrEWXD2VoRBLl15HBWDUf5z+sAIZFVROZVAoGAXC4p6KMwzCgrlk5Egnl0 -Bd7lcENmjf7LLGhMMCb0LkEgD69tS+mSg1rnlRUgI5N633TriDPcSClpfly+rFnB -ZH/nklLLSfz7lPrbvmSdljrUJrbCtpEs9r6ios9CqkFm7OFnQA+evmSXkitNuUrn -aRMW20eKmflRMwbpysV27ds= +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCW4T1DKXwNFBGk +WTvcNgcnZ/awpc/rAbnmkl31GRVj6ZOKK02/efERtwyMSeS8UZzF2GMRG1RyABq6 +x/rNOpyVPIjIz/M5Iurak591SBuA3i2gkZsHhvN9p23d1ZERNCtwr0KCEKGE3uLq +FDbab7NWu29JHliX74cXjaUhhYmUIWYPYbvoy9zBlkFUx90Faeq0wAVO1vg2Y6M7 +hvtDpcQVZ5j1rtt1huxPi4VhVFC8owINiwmgpS6UR0XNY13Wr71Y4pPkU7z5BEy3 +4cMthwwZUpL+A5PUo4lJYRH6TmPRzdqz98lwLwYuOwQz6+4QtaFD21w+/9sd6MYA +1LldEvDfAgMBAAECggEAAgAolMxnBbFfboN0ZL4eqCobvknkkOQr99+2+ToE65c8 +Spe6ZzCRvr9jnjSYoNqgLoto/OAU5ZIUyKdALxp42w27bpY7TmWlIZLViXOUUcgg +aMGlgZsLfX4L2eYsD0NW1B6uzhS0X5OpBJuSAqF6ikrf9eM+H32gl/Jb3y7rHRiz +DlBrD78zQjduzHiIR1+dkX8j490AQTPPgMGwZ3E1wZjTNCQswk3nq1kx6VNUmSfA +yKVQJivmSguSb3YLlpFjMb62r6zuWU+rpnrY3Gnia4pf4YIXjZLtEiwCW6sayinT +fNFt4L6E/QlcnNwlHlzAHqYxIDgCYqT7CY5psYw9qQKBgQDQCBYGlSKxjBezgvns +XdbnJV/ufqn1sXUxB1Oxt1JVfD+MNuUAV2YCHjR/2pDv+2YoJ/PxWwAc3G1Yl9eU +X1Ey6RTJXZ9EmVVRXkPH2cScUlukaTmBn/xuvV8LqCy01LV78VGmF0gZeIiUWL6I +GvpDMNNpbnA6gCLXOFmn3hRy8wKBgQC5q4nBpqiT9fQXbBKnVDxrz/oL6YrzI/3a +BxI+53fnJJ8fm+pKVQWGi9HKKQMIO/qWBfd8lcJkg7/caF5nxp8gWEOALKw25zCd +CVd5VeWKwSIc3dsjr1s0JcLTc5QlCncJbwJvTQBWmVAljcVp70oaM1gkxjXkxNiV +8/KZUHfNZQKBgDbvC7HtS94Kmm+i1JBgL1GWgwCdng/tLn8l6inxuOIuDzHdhRdT +/PHuO/rPIprcjhtogXhoiV0VsCJG5nydtvl0pzKNcHNS8j6sX8W0ccC91slRqAfc +0XIEu8Z+QF/4E3yJOwBbAYUIWTijPpz+UrvPduROb4BjOdAPZ012zXuVAoGAVnZK +bVKl2FlF9Q5P1XpFtNMiFyAHo9hT4489cOuri4kjTL33qevT6eb8qA7XKaN8uxZG +PM8kulgcJZC4vczh11ci//JNtDePDOIHySo1/ZqYvyaVYRXM/AjFpGCDUYiBDHSU +hq4uBSXaC9LsZruXH4JffqiK+mCsl5AFnl25nH0CgYB3X5VEMgyIx/fooCHOsLAf +Mnsp7vDnk0U8Vpc8r2XvSl3XwSWCdQtikVkc9gm/i4QZVntBofv9SieVbVCn11bG +8bQC56Q3OZTWDMtEzrCYBH3QGgnMzJIGYg8O9BVJdaeLQ/710vDofwPS3WsItTTV +Xw9ZoalN0xV6jf6DfUAWmg== -----END PRIVATE KEY----- diff --git a/src/test/resources/ssl/reload/node.crt.pem b/src/test/resources/ssl/reload/node.crt.pem index 975321099f..8896288ff7 100644 --- a/src/test/resources/ssl/reload/node.crt.pem +++ b/src/test/resources/ssl/reload/node.crt.pem @@ -1,80 +1,72 @@ -----BEGIN CERTIFICATE----- -MIIE5DCCA8ygAwIBAgIGAXjDZzwdMA0GCSqGSIb3DQEBCwUAMIGVMRMwEQYKCZIm -iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ -RXhhbXBsZSBDb20gSW5jLjEkMCIGA1UECwwbRXhhbXBsZSBDb20gSW5jLiBTaWdu -aW5nIENBMSQwIgYDVQQDDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0EwHhcN -MjEwNDEyMDAwNzA4WhcNMjMwNDEyMDAwNzA4WjBWMQswCQYDVQQGEwJERTENMAsG -A1UEBwwEVGVzdDENMAsGA1UECgwEVGVzdDEMMAoGA1UECwwDU1NMMRswGQYDVQQD -DBJub2RlLTEuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQCtbAX3oYQvfgvwQ587eEvnvVC1z8Dd63DRQiJ4d0jWgePj3a5NXo01Ok+a -5CqEZBAXqO3L7sd5xWHjO6Dg29RD8zvMmcHHQgtW+mOS1mPdeM1/yWg2MvxxmHqK -UI5BOxJRK4KVc4jyYvnp/Bns9AEvGKb/Ko79YZOOjs0sx2iNxKIPkvOTtRE8/ta3 -H1G3+e8NdDN5suuaXV36BHA857sqg8kxBxXPwMF36Dh0L6tCUB8oxLSPqAgY6eQN -hC6cprZl8rQlechTrOswOAVTpS0thav5ZFrX6I1MD57O6EosAaE6C3D4pY/FsXyO -hxOuAvo+H8K4O5ddAyQ8MeEvnsMFAgMBAAGjggF2MIIBcjCBvAYDVR0jBIG0MIGx -gBTS02Hh6adcYFVpsHSBAGZYMvoGRKGBlaSBkjCBjzETMBEGCgmSJomT8ixkARkW -A2NvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1wbGUg -Q29tIEluYy4xITAfBgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEhMB8G -A1UEAwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBggECMB0GA1UdDgQWBBQlLRJJ -7EdLiEJVwbNM15af7SW3FzAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF4DAg -BgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwUgYDVR0RBEswSYgFKgME -BQWgGwYDVQQDoBQMEm5vZGUtMS5leGFtcGxlLmNvbYISbm9kZS0xLmV4YW1wbGUu -Y29tgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQELBQADggEBABGqJNfowZpb -r9CifL/GJ5jwZZPAmacg29dz14TOwcv1NY6lD/TDMkN7OXoJQ2iktFJUMCzWlKef -5aYxom2DV5hsSAsPnoTCzXStMbyJAx+DJihhU8HJaQBemZvXBdp9CECJ8PSBm3Uh -k1RYFvJo0VP37sLO9G1mEjhdDo1uWD0XzUkRTlrJ9oW0+T19UdAOCGDgwlJAma8l -yjuHGHTT3XMjQJxUfqSLzeb/E7dmE0zyLp3B5OHu1tm2HJDi72eNNl3CDWN/Kr2o -8soT6flpSIpRKVl3c+wV1aGxCCPsBV4RWe7hrN0/P6/UNdAlbZbZk7XeNvha2b9I -gYHPuTdfjHI= +MIIEEjCCAvqgAwIBAgIUZs7ImPhUL2uB+n01yxn3arZYgtQwDQYJKoZIhvcNAQEL +BQAwgZUxEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSQwIgYDVQQLDBtFeGFtcGxl +IENvbSBJbmMuIFNpZ25pbmcgQ0ExJDAiBgNVBAMMG0V4YW1wbGUgQ29tIEluYy4g +U2lnbmluZyBDQTAeFw0yMzA0MTQxMzIyNTNaFw0zMzA0MTExMzIyNTNaMFYxCzAJ +BgNVBAYTAkRFMQ0wCwYDVQQHDARUZXN0MQ0wCwYDVQQKDARUZXN0MQwwCgYDVQQL +DANTU0wxGzAZBgNVBAMMEm5vZGUtMS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANkMv71J5KlVPv5M2jCibgpLSHHyQvvPbYNcwpkt +Yz+/UwpYySTWkn1mMAfVJuh/kAADSE0RlcEJGIuccrAMVw9g6Nq4Usm/z/wSI5Ew +52SlHEQxSMt9WcFSMQHutxAoeXpCLL0OvFCqGdzQWinDqPKJluH8m98y7VjcwLz4 +FX5p5Dn8dt38OqGhJrTrew6xRjhwtbr+LugbCVUqc5pY7na0SpA6INwXAuga2ZDa +TwJTlVJE30xSfeJtnuDLsw83Y4RHe4XiBd7yDT/IH3yoQmZshBlI2Iojjt73etbP +97e3crlhzyO9EVF0kSp2knqhKci++f8MRKo4HksHY/7cVV0CAwEAAaOBlzCBlDBS +BgNVHREESzBJiAUqAwQFBaAbBgNVBAOgFAwSbm9kZS0xLmV4YW1wbGUuY29tghJu +b2RlLTEuZXhhbXBsZS5jb22CCWxvY2FsaG9zdIcEfwAAATAdBgNVHQ4EFgQUGSHF +6V65gO9+v7PeH/zTkU+fvckwHwYDVR0jBBgwFoAUGXJktco87q1v0BfhouzpDY+j +rlowDQYJKoZIhvcNAQELBQADggEBAAxBs2+e5ALpgrgECmsU9eI7safjJnbo5Zj/ +H8cUaYQkQr+m+ORl63NG+oqvBVqAXT3H2ycXo4bpUZhIZHFWek9CqlAA+//B1n+D +HJ7sQ5760nK26zIoZ4qDE5tVYOJdC6e/+Y39WqqpLHWIPrJpBxzI68My572+g9tM +CL3w1dDTo3P1xYcKiLdJ+eZtLwOxpREQohlUCtd8GMl+lZNIbL93aTPhBzCOqfGc +rq1D1FKL7VNwa5l9F54htJe3x9y865zWhu3xk4q+BE3q7l2yMPDXcu9PCbjg6+ms +YFAMc4avejAa2qaWAN7JqyKbYAq/0OPFSnwyE7hV3sRPewcEuS8= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEqTCCA5GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADCBjzETMBEGCgmSJomT8ixk -ARkWA2NvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1w -bGUgQ29tIEluYy4xITAfBgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEh -MB8GA1UEAwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMB4XDTIxMDQxMjAwMDUx -M1oXDTMxMDQxMDAwMDUxM1owgZUxEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJ -kiaJk/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSQw -IgYDVQQLDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0ExJDAiBgNVBAMMG0V4 -YW1wbGUgQ29tIEluYy4gU2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAJjVJ9Ipq0uCE7EE0RRBadkrIm7J8OGa2xeFCEpsw+hqQbmftrm0 -V5XFpRKznv7r9JxFQn4uUM/Tqxt/mZ9Qql6IGWiNeK4e0Hfh+Lc/VD+w+mDqxo52 -zv0afYyyM/1NiOyKV7hO0Akwj25zAfnqs2FbsZzVXtRpgOnfOdfKpUL6fKAxwjex -+0nOOaS5D+unS64sh0mTMVD32iag+tg6aiiW914Jmrkd4yDV3uoXh/FKafRRWHSq -pwO7Xilb+fLRMiQ1N09mTE8Um12G7mVdBaZpbluwtv2tiS52n8zzpKYbohS2m1ir -lJMASh6qEKzHUlzmERhq4Ib7V+p0E+gPadMCAwEAAaOCAQYwggECMBIGA1UdEwEB -/wQIMAYBAf8CAQAwgbwGA1UdIwSBtDCBsYAUrs3v6ngKiRF/4eiBW+3eCSjTVo+h -gZWkgZIwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdl -eGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFt -cGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4g -Um9vdCBDQYIBATAdBgNVHQ4EFgQU0tNh4emnXGBVabB0gQBmWDL6BkQwDgYDVR0P -AQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQAmRbuLhYdGYjdndROJilIQ4DUD -HYYijortmkq6ZNQiB7I2teBZjra2xcKp0qaOkG9nJCzhHvGTw/URjWAxFZ5n5hhx -iBTJZTGY6bk2SWfJxdd3Zld6ybsoaQTO/ZnfuTE0SgxWx1e5q/mRBKvRwF02bJu3 -YNlJMJVuIDxurrUK2CXrgG/BOCYMyiHNmn1Spsh+x+6B9u28CE0M52+HtNJwyQw6 -qPMSNiovXRkqk7E40e7ZY3MS85Pr4L0Us048SswPEpf/+IjV/zTcDP22pQfcjMOX -DZpdAOGgvqhL9t7M1lcxJrI0cIcBNWOPVa3zsCDU7CXF+riL0Va6TvoChVtT +MIIEBzCCAu+gAwIBAgIUf8RhLrREqQU02WxnXl87059YCKswDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA0MTIyMTAwNDNaFw0zMzA0MDkyMTAwNDNaMIGVMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEkMCIGA1UECwwbRXhhbXBsZSBDb20gSW5jLiBTaWdu +aW5nIENBMSQwIgYDVQQDDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCS3PraJ5Dk0vm33qiG0Cal+S+W +PifErAFaakmgHq2rIjMIVjr9luLSRbrXwRqebuIO4AvVg8LMiwecOWbTVJqXarBP +236AHeBv566GKRGbmVHL4QpvenaIHIXP15tGJjcrneELl2vd7Hgztmx5teo5KHu0 +rCmTJAUsL4GmA7sGJEacuI2TMHKmtPOgQPJ7csdAhRiSnIkAhc8cZQioBi5/IxnT +benJ5GpS2pZ1+FJOr7V9WBtUpa20ycXde+5ciiaQJ7TZNyk0Meyz5/l4GKQ5HwqB +qomNGlIQIZa/w7OeGLaU178O+dikN2aM0mcmS22lFnS5Uy6pUxip8+A1kCZlAgMB +AAGjUzBRMB0GA1UdDgQWBBQZcmS1yjzurW/QF+Gi7OkNj6OuWjAfBgNVHSMEGDAW +gBSio+c9cv0rKRGnHryWwo097Opa/jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQAXptCbI4ejahr1TC4FRwd0YaeYPsnIR3wcujtPW9/vygzGZSBB +KrKijITWY2+SW5fX4nhZeOjD7kv45s4n6FIRtE5ah9Y/bFZecojqwD+1VCAYy0JU +BW3Uvtf+9gbR5iP3Xemyowh89upO388jcI/4kkC6F1TdJh4AoFjOOWUYLkk/v89h +SQ9wjB/fmcwrgzVd7DLvh2KZtM6bXk09zYE7C1TB0sb0L+61fJVbMoFKYo/QW/tM +MmRWGqL/9eXzB882H9lyX2pykXuETpbQirPDnYz5PZYWqJ2xK3aB0lwZ4Ln8zdMY ++svQsZlwymCgT6rngOwMNMN0BCQKuPZsg2zb -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjzETMBEGCgmSJomT8ixk -ARkWA2NvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1w -bGUgQ29tIEluYy4xITAfBgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEh -MB8GA1UEAwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMB4XDTIxMDQxMjAwMDUx -MloXDTMxMDQxMDAwMDUxMlowgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJ -kiaJk/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEw -HwYDVQQLDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1w -bGUgQ29tIEluYy4gUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBALebO2NTc/jFQwmnye/uWHP4YKkgnC1wL1ZxSsQlL6MPaFRJPKjbQyA4llOW -xnemfkyTDW8BjYOVVrnqXHR5yMAg1A2+bTmgzmSePhA549nA575UI+5XsgZAKl3E -EmEhHZ36g3fZRtXEyQ2Y7d4ZhxBrkH51SY5tor0QkPf44R1JYUCLjTcnFeaJhosp -XaMxnLBYGWzQlNx1VL4serRax/sbFC6Ue//bcp97t/RYpaS3L06pv7z4RmTqFeYD -RenZWqp8YNJ4NH1ff9ogLXtVr2voyAcw0Hrb7war/dPPWsG6etL975yKOvy3poAu -Gntp+bkE4yU4ThZDtyV5+PBbmmcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAf -BgNVHSMEGDAWgBSuze/qeAqJEX/h6IFb7d4JKNNWjzAdBgNVHQ4EFgQUrs3v6ngK -iRF/4eiBW+3eCSjTVo8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB -AQB8bZ91ofGDXW/6JuhWtLa+U3c2THrn6qa3cU5iXZo6Lco6rDLUs3jkhqpaHenA -joIBNtvI6IrZx12ARw3F48XG+TvsU7hroG6fPRBdhMpAvLN3JxshgI0ZoUQHrrOb -Jrk6JAacfrVc8KCPryISFx7jh32rkwbBo2CqLV0cRBnl146CSjq8H3tzMfFmku8b -uU+mFLt9hpqlU3yUTk3kS83NY7HimKSdtodMJRtBlh44KRr7Vt1zWTJ78l37epzs -1ZDRpPmNfZRFgQo/2b7DCLGkqHKB7RRNRt/MvTyE5bpr1atZvyFV1O8pM+Xknf6g -KgaBJN+uGgJhlZKPdLi1ROue +MIIEATCCAumgAwIBAgIUO5pwerTrIJkibcZoY8ZxK8KCDT4wDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA0MTIyMTAwNDNaFw0zMzA0MDkyMTAwNDNaMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgTyzX2ZRIvkAzJysKYCVialXIZkvYyRxu +rtqYawm60gvUKOqEmhDIc2p/ySLnvxcrUx5q20pFoSS6+9rtT3D5wapArOf2v6g8 ++c2f5l2GhiMhxg6rRkvuCUWpSC9rubg4X1rkhaK8pZiLtC9qKLi3ZIG9rNubALZn +XuDD3nQ03pEKG3iy+hUVF7c876J9iFlqJU5RZCTfGKNaRYAnCtm/wWiHVYwAV1Gf +awjLjBfrFwcrJTWJyLxqg8z2DuXWm5Lsb+D8+9rqYZF71BvqS4ej5QLPrGXEeds1 +MX38RRJPYN2SdlMDMomjxRZyDSubZV40mLe+4znmFcGuMhouyDSvAgMBAAGjUzBR +MB0GA1UdDgQWBBSio+c9cv0rKRGnHryWwo097Opa/jAfBgNVHSMEGDAWgBSio+c9 +cv0rKRGnHryWwo097Opa/jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQCdEXxctLUFE0IoO1HDfE6LtNs2+I488P3Cp+hbHvKhHJeeY/48r0Gi57hQ +sR+Bfut3RjmdmKw9RcYylTgtXXmWQ3E6HqXaaZSnaG1TCER3bboxU7HG2IbaZiGu +/25K92rtq9FY0YNhCGYodq3cEzYWhX0pj3yj4N4193aBXrAaxyAALNnQ2Kwi6bw4 +WtS780NIgNFsGeU6H2/0JwDYlZVdhyR0G9uMWJ78fFHKI9iPUw4LvG07coxoZmsU +2tjIuV77HGGH7qufQJQtEFo/aOBEbLZ4i1uef6nYasnsI6tV6jhFhcLvgj7KeJDK +HUzAD6zg6DKj2NaBf5DzROVlZSCz -----END CERTIFICATE----- diff --git a/src/test/resources/ssl/reload/node.key.pem b/src/test/resources/ssl/reload/node.key.pem index dc06639b50..4d256fa4cd 100644 --- a/src/test/resources/ssl/reload/node.key.pem +++ b/src/test/resources/ssl/reload/node.key.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtbAX3oYQvfgvw -Q587eEvnvVC1z8Dd63DRQiJ4d0jWgePj3a5NXo01Ok+a5CqEZBAXqO3L7sd5xWHj -O6Dg29RD8zvMmcHHQgtW+mOS1mPdeM1/yWg2MvxxmHqKUI5BOxJRK4KVc4jyYvnp -/Bns9AEvGKb/Ko79YZOOjs0sx2iNxKIPkvOTtRE8/ta3H1G3+e8NdDN5suuaXV36 -BHA857sqg8kxBxXPwMF36Dh0L6tCUB8oxLSPqAgY6eQNhC6cprZl8rQlechTrOsw -OAVTpS0thav5ZFrX6I1MD57O6EosAaE6C3D4pY/FsXyOhxOuAvo+H8K4O5ddAyQ8 -MeEvnsMFAgMBAAECggEADvlYk47t7bbdu4dvOJCUx0P0s9omLlM9uMfT16B/GMx6 -26pAulv7Z9jq1jY0yJ0mOZDDxZvOvAAzyU3VAI1iqOJX67zr+zND7OTuU5ew5Ebh -wZKnraRd4UYX8Jjvi4jY9krCOfrRnJI4v4Ad0fSopIMlqjsnwQ+bkM/aPe9S4wiu -j2bXKPPcpGfQ3z6I2nQjP/Go8G1TziG2OwRvcQfNrt1gzzg9OwR0hRv5hSwaC28u -39raDRe7/p9PbuqAI2z2/gbNRP+eKmoz7KZzThJCv4/RS3osXMt9DFeDZikdJ0Pp -RCGi4Op02wm1PI3A8piG9uUHrJ2Dl9lpLlDOEk3SMQKBgQDsf+oG93E2RQg7UHjs -EIYsfPQpNMT1/b8JDtld6kkfGLoBGZti/Vv6wcgMZaex18c6OyMykD55qkyVdkZ4 -wr4jRqYDG6sq+AqTX/Q+3tSDUZQBbDuBGxYJJpiYVcYGw3iUFfsrb3H59D9GeAfw -Y/l4lEsTdQTAnaQ7iRX1ZDS69QKBgQC7uKbIrUYxNLjah7xYYHXSDisyyewPZndA -OPBlx7kCNorsrLWccHD9PrRZSi95xVIJ2nkUP69bdBYD5rI75lJ2PLQP51QHxYA9 -VRUxEuy+LgdGT0AiM6DiUgXy+nNrnt2DK1Q1uOoJLfg+d86/kK4V8KGSbVABW7I9 -NVrPL9b90QKBgFE+uDTgeIH+QQ+vW3stSgqqem5VdHxVSXVhJoc+3xPRuMR7+YeO -JjUEsSahHXTY/dYhTrwmFXBScrC9ywPdUthngsx6itjeETQDuLKIdlUOh1vdxKtb -lLxcB/v9K2RTbm7hBkIrrOoDYGFjBscscR1dRFVQ0+sfvLW/KE3+uKapAoGBAI0A -M3aEgDaxjS/HyLbxLU5sChKKDN+8bVI18ovgqpx473y6dWdeJeByWvC+gk6K4gY4 -rb2B4GtnFKo1fCb153acPfu8HCgCN0LaR6KmvmBTHm+3U0rdULjuNz281Tk/CM9f -mk8H5/KhLtdtEkRdngUoy5O6u99pSsYi72S0YF4hAoGBAJDegbpbtJEe1MDVAlhF -zFf8/kj3Hd7qd5gDqG6kV2eCSXBeoq3jQajlOeSfykHm9jkGYPnmzfKKTNtOwiQ7 -lTY68VEEhSreUKeys6sfVR0S0xM8w0ty6G3HQdFSBqTHDdUSj74SwKJLUvqM9iUe -oTej+xnI8sKAJDPUv4EiAO7k +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDZDL+9SeSpVT7+ +TNowom4KS0hx8kL7z22DXMKZLWM/v1MKWMkk1pJ9ZjAH1Sbof5AAA0hNEZXBCRiL +nHKwDFcPYOjauFLJv8/8EiORMOdkpRxEMUjLfVnBUjEB7rcQKHl6Qiy9DrxQqhnc +0Fopw6jyiZbh/JvfMu1Y3MC8+BV+aeQ5/Hbd/DqhoSa063sOsUY4cLW6/i7oGwlV +KnOaWO52tEqQOiDcFwLoGtmQ2k8CU5VSRN9MUn3ibZ7gy7MPN2OER3uF4gXe8g0/ +yB98qEJmbIQZSNiKI47e93rWz/e3t3K5Yc8jvRFRdJEqdpJ6oSnIvvn/DESqOB5L +B2P+3FVdAgMBAAECggEADTPDp0LgYBW/ACmH4rTD7BJfigwfW5Zmuzp1h8BarMek +HcQAGs0xGeQ7qwOhxeXvH2ETu8RZueUmE2U6a0+aLWWf+aCD60ukiK5IcDjA9RPp +9T0+Mp/MSYBeBMJoFi6FZ79bXfXl1Hjpm88nPNUZk1nKeLKB1KtnWSQMVoeMic97 +D3wzS/RmcQGIEM5FHuYslTSR7kSqZg83+9ExRHMrvspE+EuoxhkVhBN8UM81a90g +komq1XPURTaqKjXD4/Zy2cstLe9Y//6C9NK0ZxYOpYgwm8glVxmlmAE/4fROBDZR +VZxzQ6A/Drd/r48YOhlroSdKhZ0JLrJv+368Z90bgQKBgQDi1jaz+u7x2BzCbDBd +kCuqdjpfLPBV8EppV6TkLU7DqWTBCDWPxqGgXfVtWrxK3pXWU1iyvNuMZVQsJyu7 +P8lf74RMogMI501WSK6FJ1+lPqlWmVHA3PIEbAdLyWZiygxBOY0Laqt/BAEWMtOX +WDAwUP6aZgciAUSTOZxgQNS5oQKBgQD09GuyZNvaTb8l3NIcSNu9EGvz3Cu7H6fS +SxDpNMxVMHbrTRwRC7/Gmsdm10C+enHq8YG3m/HgiDEvXfIIsjApSKOnNXBMqcFW +nVfRJarjAnYgdxNtQ+VaemBW62gruaakOKAnyxTd9JQZb4xjts+Q0UdGxblIsex0 +9aENCXnaPQKBgHswCF8vzGoMinnPlWiKbhxRvpuHZTHWoCKbVVIRhO/fY3ctRSFC +pu3XePydRRqHzOmrM8VFqRmSweaEk0xKdMsj4T5Y0bsZGjfcmuJ3Iosz/3SnKO3L +T6e1HzU6N984iPU3EvD7Sg1dmFV83soXO3xR0CL7e64s8BfgNptTOfNhAoGAPhra +U1iPBUJA/HCINPOkAsNvG1zvGXplKkQt/XKNe+vVusLKIug/rzL+62wX1jNlPpQM +t2iqYqslDUVcYCgNXeo00+gQmN9RTYyG/1f1g6jUGlcWbdWRCOeOFXuJ5cwkG+7f +bDdpCv0/r1NA3Oc0qRrmX2MyHuQ9d0nvk0abxmECgYABaDswOc7P6HKIdogr/V9/ +d32XKQlp/PME0FZKvNzy67vYrImKQWfaEj6ikXu8NurrNt8zSn8Y5Ijnrjav/jhe +Rq4oH3dRfE3d/yFq0F4DleKdhbDIshp9u68K2+PdAGu9ufbSyBfNftQlkzym0SZI +4aG1P5Bird3jKn9vPxmVew== -----END PRIVATE KEY----- diff --git a/src/test/resources/ssl/reload/root-ca.pem b/src/test/resources/ssl/reload/root-ca.pem index 063b12f65d..1f58a36a9e 100644 --- a/src/test/resources/ssl/reload/root-ca.pem +++ b/src/test/resources/ssl/reload/root-ca.pem @@ -1,24 +1,24 @@ -----BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjzETMBEGCgmSJomT8ixk -ARkWA2NvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1w -bGUgQ29tIEluYy4xITAfBgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEh -MB8GA1UEAwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMB4XDTIxMDQxMjAwMDUx -MloXDTMxMDQxMDAwMDUxMlowgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJ -kiaJk/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEw -HwYDVQQLDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1w -bGUgQ29tIEluYy4gUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBALebO2NTc/jFQwmnye/uWHP4YKkgnC1wL1ZxSsQlL6MPaFRJPKjbQyA4llOW -xnemfkyTDW8BjYOVVrnqXHR5yMAg1A2+bTmgzmSePhA549nA575UI+5XsgZAKl3E -EmEhHZ36g3fZRtXEyQ2Y7d4ZhxBrkH51SY5tor0QkPf44R1JYUCLjTcnFeaJhosp -XaMxnLBYGWzQlNx1VL4serRax/sbFC6Ue//bcp97t/RYpaS3L06pv7z4RmTqFeYD -RenZWqp8YNJ4NH1ff9ogLXtVr2voyAcw0Hrb7war/dPPWsG6etL975yKOvy3poAu -Gntp+bkE4yU4ThZDtyV5+PBbmmcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAf -BgNVHSMEGDAWgBSuze/qeAqJEX/h6IFb7d4JKNNWjzAdBgNVHQ4EFgQUrs3v6ngK -iRF/4eiBW+3eCSjTVo8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB -AQB8bZ91ofGDXW/6JuhWtLa+U3c2THrn6qa3cU5iXZo6Lco6rDLUs3jkhqpaHenA -joIBNtvI6IrZx12ARw3F48XG+TvsU7hroG6fPRBdhMpAvLN3JxshgI0ZoUQHrrOb -Jrk6JAacfrVc8KCPryISFx7jh32rkwbBo2CqLV0cRBnl146CSjq8H3tzMfFmku8b -uU+mFLt9hpqlU3yUTk3kS83NY7HimKSdtodMJRtBlh44KRr7Vt1zWTJ78l37epzs -1ZDRpPmNfZRFgQo/2b7DCLGkqHKB7RRNRt/MvTyE5bpr1atZvyFV1O8pM+Xknf6g -KgaBJN+uGgJhlZKPdLi1ROue +MIIEATCCAumgAwIBAgIUO5pwerTrIJkibcZoY8ZxK8KCDT4wDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA0MTIyMTAwNDNaFw0zMzA0MDkyMTAwNDNaMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgTyzX2ZRIvkAzJysKYCVialXIZkvYyRxu +rtqYawm60gvUKOqEmhDIc2p/ySLnvxcrUx5q20pFoSS6+9rtT3D5wapArOf2v6g8 ++c2f5l2GhiMhxg6rRkvuCUWpSC9rubg4X1rkhaK8pZiLtC9qKLi3ZIG9rNubALZn +XuDD3nQ03pEKG3iy+hUVF7c876J9iFlqJU5RZCTfGKNaRYAnCtm/wWiHVYwAV1Gf +awjLjBfrFwcrJTWJyLxqg8z2DuXWm5Lsb+D8+9rqYZF71BvqS4ej5QLPrGXEeds1 +MX38RRJPYN2SdlMDMomjxRZyDSubZV40mLe+4znmFcGuMhouyDSvAgMBAAGjUzBR +MB0GA1UdDgQWBBSio+c9cv0rKRGnHryWwo097Opa/jAfBgNVHSMEGDAWgBSio+c9 +cv0rKRGnHryWwo097Opa/jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQCdEXxctLUFE0IoO1HDfE6LtNs2+I488P3Cp+hbHvKhHJeeY/48r0Gi57hQ +sR+Bfut3RjmdmKw9RcYylTgtXXmWQ3E6HqXaaZSnaG1TCER3bboxU7HG2IbaZiGu +/25K92rtq9FY0YNhCGYodq3cEzYWhX0pj3yj4N4193aBXrAaxyAALNnQ2Kwi6bw4 +WtS780NIgNFsGeU6H2/0JwDYlZVdhyR0G9uMWJ78fFHKI9iPUw4LvG07coxoZmsU +2tjIuV77HGGH7qufQJQtEFo/aOBEbLZ4i1uef6nYasnsI6tV6jhFhcLvgj7KeJDK +HUzAD6zg6DKj2NaBf5DzROVlZSCz -----END CERTIFICATE----- diff --git a/src/test/resources/ssl/reload/spock-keystore.jks b/src/test/resources/ssl/reload/spock-keystore.jks index e0a0855ce8a239a55bc7871d1c66b065baf29c41..52f27df4af6651c478cbb9bf47d51a91bc5082c5 100644 GIT binary patch literal 5302 zcma)gWmFW7vog|q(#0-xl+Riwn&fMOV)%3n|c?DDSyj|^-9mieqA5snu?2A{ zWu|$ne9t1`4*};bWfs~dxG>zgz48+Wivm?W;-M^+(l$LsC+ zr@+sM*otBTNuHWN)EHIOk#Jf|P5*I6KPe?N7l@5jjHnfodJ;5`_I|?{c6d1$|K7Cm zoA~nV5DP}@l}f&2(H})$R^>AUqUadi{zTW>0J0iNPIpG*>G*qBzT$H{m_y&A;?|=; z@w^B1*l-y>K8)cWWeo!TqS7>yB&d6NS+hUvIJR6lx~5_(FlG2Kdse<|`6tn?fk0Ut z@uAaTe(uwru<@(JthL*#g!2}+yXG2iA-J9GfLXR{eWrJnD;z-o_~(+KM&62Qq=gSn z7RsSXdGrU2?JLly?5;A5+WuJKiVgnV66*wy?x{@#M}YBZ@cXvr23n=@k9P&NpNv!YR&(m8tALQ^hJc7=teqeU&wQ%C98w9h0}B{*@_(^x6BYC@+yV3 z_l*yH&Lt=P&0W>v(}N#TPY*e#OvIiz=9+UyYm*3aYE8y> z3s|c7LUy0;xvqsqxF`|L8_+}mTHJPe%7igj}72W2(zX}7Rei2$X z*5qoLlRa^G8X#BS5I(3`J+C_cdx#gC@C1$Va5gONR720E+~F(BylWdUjo4!gtQpVFU#DEk)y+{fOf zYG${eU=e)=#l2FmUrCJ8Zd)k~N!8HVKIMUE=x!g0U6<<=*}GX2H}|T6scaSKHEp(< z;_J7ML+g=$0P}uVwrFo9EnW5SMuyxQrsEN(Rplsk$sVqrsbq`C7Of^ zYP>1@=HUZE7c9?7m8+5-1?{%KDtpI` zi=ttAXv^)=U0mA$Dd|p3T{CAoU;VBu+=R({nh1<3oSsd9$cj zPyZ7+F(okLzjjGPBSy+bjOCBzgXN9oh~)w1{0~JdMuYpv7XI?(;>ZHu|BFeZ zyb7>_?Id z*Rv3;36A1BK5LqkY1YZ9*CWu{$13VxG)t6-Z3X60n(jE zm2MV>D!4Y4BBy*?yUbzfaC@(I7|oW39~Cg)%JDHlt6MIgfV4Pi$14 z40(56Pjwx$>Qu9;f7ygfYGtWmL%z{8Qu+k!BOvW1x116KI*ruwr!RkyL;~_yo483; zOYm&vH^{8(lrXK-A7?Ue?6PxBPadbbop0JVL(Tr#b)6Vbxh2e1s$O&L8$im}LhYyb z@~R-tc%uXF6l}i|s9)s27QYM`HZ0JS;h1RJ=II*~;}3QcDER0(JHV+e1JwFHPA5t{ z_iBUKr${Tw;=($9*(JBg!GvDj$2D0*zYRITrw zVw?6}xupKbRX4V4w+J$=>d(}tPi(~2z!PUBW0eBfH{&^=Kbga8uURBKoE=+MJZbQ# z+YfG&Rm%9{Fhe%AJ?_U;YN64!S<^Z|>W~Qg!5Im7uNA~{FQR?a6wBC2rC#i02G`+$$M%W*s8;(}iJr$!uOTlo zh>m7*xv!0Os#Bq=X#4>*8Z+2|F9KZflQ%Q$rXV+aT?-Cnl$c9}9_={CB?1D`dC*_K z0wNboj2ziZ8jf@zDrmebAEv^GuNCSonaO7N5X?7IF+bLi0`9-AB(^3$<~_SH4qNA& z+&W5PRC8#FGK38A+IU+g+~oFf`wc^T-*unTP$~DLZ`CSf4ugEYIqD2<2LDiEbJ}4Y zo)ic(y(4TzF6TyVV9cchOj?8|x>3k{EnAsJuD5g}w~&{BL~LRqE(ZJfEryoC@mi>H z6jbr@`-Y@M!Wsq~SFxP~jsoMlS8 z-6&dGzO=-DV>~UZ{0Xe7VM-wG^gNr*$(1qYKRJ4qG@>ULibk_frFsiZ_Z)rF1 z<$Im036lNnHMhuI0IP4a$$7KFb8RCy-$x+L^xn)>n`v zaiay%tc^ZXcgptH2f<6{g}8~#)=>2ig^UC9g7dT&0l~Q(I!ySE!w<~t4UfaUQ5$O{ z$x>vryIv9YHe5=A8)}wscChmA;lkxk5(EfgE=>}(F5o<;;gsb2Y4*TmyRGZWD5e-8 z3fGZkeI~FKMe){vVt}EfDUe3rwsqnhAY!4|K{k8CDn|M~Zd6L6=7XC5aDi*?nU#)k zKB=qX-F9j9Ge2`Zy)q?(nNP-X6xULf-X=4>m{TtqiE^*&jPKyzAT-l;P`2#y?V2gQ zckd{aEee8{^Ryl`2g4*yD`)Tn3q{G zz1*b!iG&Cp&pn{6Y$XiPdz|35 z`)oL+<(Rx^a9pwrlXO}gjwnSz#mfS=>rG=R(JHJ!liBgwa8z@ibqL{f>cNAzNeL^4 zMNThFHDn z{#bmIP~*$k3cgU$mWh73`7FVqD6oB0&(gh1*O+2eFP+W9={!@>ynDUwk21UkWF`);*g_W6?@`0%{Wev)tPI@qdZ+XjtqB=LO3SG*Z%4qBPwFsBlG18wsU z%~^!=+SYv%es$)~UIDb_W4SRWRr(6aDwL2{ZD z`;_{M*-P2UgD;8F)5u%?w(G68V6D|Qara`KgyHQP-HB+v@#3QS8yLvqtuT7=E4ct! zb=^#4*L{@v&BTk_lTpj~7Q4||$lX|4t(WwtgVzQ5-4O0>^u_~{R}%F)#Z_Rp+jWSa z(rTo$SFtcQVRk}Rud#HZ>w5$3a3C}bqDJl%#V8bTYp6BflB;;fZ4CNp=7Ll{nk6w< z6eeHgCnD@jS7a5 z21ETcaJRs2kcH2F1S3WW8Dc9s=%q1*FW)oF#hrbHnJXeNZ^}%G*6pxH>(pc@ z&Ez9Ra$jC2#>2wpC}^>kAb(^}vgia1=2iehW@BNW<-<|+ nuZ%pT)SEwc;L;35obL;0TjnJQzXy zAFTdQDI=o&-xfM53@ii;|I7)7zvpB@ME!sK&v7z%d`*lqjIm1;)ocC~jY8l&|93`| zB3L*;zdAe^exyc#D*h%YLU}^~)BkyV+NW4YX4jJqcDU3Jl=?uCJb0IV?|+zsD`Gq; z*=I zo8dEd&O+W+QBMrFHgRThEit<34MUy&d`7ew{NQs&J;eLe@6h>!JHuVzAP}(MYs|4Y zLmj-4sAqs_?Q(KbX{Zvo%LJFi`UQ5CV&Xf39LZvzI~Zk0#aZmhzPP~1;1khTR-Ijs zfqlK6QR5l)*AK;!79J`J`YXxZ3XVEjXU178-hzo)4z7x5ogFLdN<{p?E5-g>e&&K*#qb;CNs99F*=0J}WoIOx7ID5WcZT)sDyK zt*ih80WhJgJLzS_758y3 za~=%UlT2I5!Rc(bOHMdf!H(%TL}mz8`s+Ibj{fGCXob;>AxlH1|cl7(% zVU7$ww4?3x)GZXtN!Wl?OnYiK*pDf^N8u7a+58*DF+D>;pGBb~fNiZj(^Crz9bIp(-*;Q>r=1vL&2dh%)odjY&JP~kis z%g%d+X}ZYj!r0ZUp>PMSL$4HQyn5c!N2IDAnNDf)Q>zl4yPQO_80cEriwyY~$gXa) z`nqLLylE9?HYTOy_idbf0;c0f^ z(`$&uEyKeh#iz1Iw@-gaYP77#Uvbyj^6a)4SIhKK^Zx+AGsg{P1)bxn{SLH3Xj&2Z zr{p0d=exVqvmY9x9at$Q{om(z*qI$F(1mYR7=V`DLTc~DlTzm@?0je+B<%+^(zVZy zxTyCf>!}T{5|sJ&vzFFPqCROPHF?T#=P7VeJX2@8n&O)>k_FvTl^=V!X+hrwULcrH zY^F5Aze&D4T=@u>AN~-^6Xmu1an39v*v$#7==4i!%Z(hA4nowqt;t&_b>uCf;Ol(E zEw^uA%AP&y&=v{P6XI1nl6$f9s85{hUZ4X6Ch$O}(mX*`NnP#*g8@Y5SDqMhwrCZF zsO3?{G|ViV;9>@(#zcK*nW>RfxPJ9t{!JL;Fm8EoZsMMHlX~g8P31N@6Y>+sl25Lv zO~c#q4&=h@Nv-D#1bCsmkC#l^jbshbL1LV9iW~hIs(S_eiJP-WxpnfosZKeO{e@9T zulrlVC5?#K{rVbi55X3mr*$Ceo_)pS$CdTxEQ0}N-`axXcoFxBNB?>+lIwdN= z3*ZiL16To^IBEV95pdzd<7zv&*iduva|v<@3JCD<3vqIDg0WuyJ%oe^0b`y0gN|Tf z0ROa||0FQ~V_Sq)5>aWgzk(VqOH(+@O$iNs2|4O}VR>h6Su?P`Du`4On9-r~W>*c;P zop046mdct$5}%AXiEE}YSaq+#3l&CqO-QUER9m~%$`@sVZFQRlON4d4iB%Feq>imq zc9!i*MA^-qwhOD0O#B5K=7yK@FA{!|qxc4`O;;Uvj81s4T^Iw9HUZ)4-B3wyiEmK% zwd7hC3t^xzUWu9xRRgLrv6mqw#xwhjeP+(uWBn$?tn4~+{9%Wcm-Y8>z|q$besXW` zr{Buo!>VR@mry5OEiag8k1Tdnt@A1S&IS|UVIuF6z$y^6MwBvUY)SzT;r)Wa^!OJ0 zx6+WXb>Hce*9y&|h)NDw)G>60G_E^!Ncwa<>GzDlJTAjT8*5s7$k*hKR8n7H*=`eP zz{ljl4yKu2oL?*6ivjody%*rFqOm z%k%i>GTFxQ&yz}~%}PWJRgHqedVU}hC=+$%QbcJzSIT-kkKf}?C4O3h<|CB?RJqj? z>Q@W~g4nma_ad`~#fd|s(*>FsIAerd2!oqxLwW4SS#$0e6vnrbCfHPbU(q#9~15i4t1cOsw3156vCaLGFAxEnecuN0zcD|AP)om+$hP z5+WC01n6>Uict6huG=5bdL)JTIz+;U!oJE3qkqfE%doo1tXFBWl|}Xo=9wYl0yKzm z%E`nm_Jm{ObcuAE8l+lNVZ|J8+ov9(94>JH^A_`MtWh`YxWN!&eN9hIu*sx@+7jXC zRhm{G8K$+_KuvM>0OV41#>;q#KgmpD?r8@8OsRM>OZn&>h00%0YHAh&(UjxackR}k zih(^}wg--N+0b$NkUaxN7p8}Z8L^2<+6ERNhkQ~cVg}n-cyG3To0K-s9;-=cO^(kW z)rP|1^1D!7Z-CEFm`Z^Q{a2`w(nV7P`OaAre9gOub$TZ<%K@{xQ}-BkBRl@v#Yd;u z>AyH(-k-t0VX~EYDJu(W_8vPV_U#B=@nBV1nu*7Bu}=h87Z@7%tQVb`BlytBFjru~ z4I6Y;Q31=%o3{vHP%;#+y_3r~cSIgEkSa(&Iy3zt3 zp@2C?zYOa~8R+}>?ZG&~=|{sI zRaK#HMCEVve=`*tAfNVFEjMgS^J_$!)K`QrLe`9*!&@IQLD#vlyZ} z>f6yh@~Wk6Ae4@1*&lZ}TSo$EdK|@<_a!!1L1<0T*y{PeN2=qG7r@ar%hL)DU);+h zj()d~7M2}8#o%ACv4fJ&HoPJ^>Vndp$XIJCXadk^mI8hfZuWj`?)BrV67=2IPpS;L zf3a_xA)~#H{jJaaGLJ@@bwfzY17!MRVBLhUCMS1*>;&sk^_xai4s_okXPOMCaFDtS z@LV2$09^4o17~jrB^T`9OiUHJY`SPMJ?l`4Rc6U``!rgyS{2q0x@#@$iF;sdc?>g1 zT6Bjc!gxl-H(;wjl~A@P)wbi|X7WUET;&^UDn~-(OJhU%qw?oBE;|)H1inrosR(8$ z{=pKWyj03=CC;FEl2(BO$a@}~s|74&>KET?ituOGlCXIL^qAzz|UoXevVdmnP^^VZ?gEb?l8b)oRi zJCD3P6p%ns@`@sfv!%q3r9uOFBCeiba!S^h)mI1n)z*9?F*`R})S!e$ma@cu9BK&P zPWjt`=|9CsF+NH7r-D7{K%kVZZi|K)!MR0gI=e=`v`4|F)sAv zh-&LviF0v+L0f~2Ok0gT0kF+dwcImkvEq7T^pQ2I#q_xZY7r=O3LF>o)JBf==D&>- zxVX2iXt70+y>#WKtz=6(;oWvovH3e;wD6E1Q0lOWa4_Hy`-}=m`>JwGxqj!ZBL{I% z62pn=ocZ7~#=TA0BvLz$E%0n!Fvgct5TmQtCi!uXrj>~#hW2Z(tKgVjGV4U(zxUkEy}=MfinOe7hGm z2p$*BB<1F%84h#Uyp%a#^8>##?j3&z=`M~UKW1Fdr*jQ!H8H4GMcgPX$5Bx-l87^5L1S`Svyh`*EF*b*e7p76^{a4gY zBemmkpLw9tr39CBi-xa{<0#QGNGW5at|0q5=BQbsBA@zEl3gNsR`+Xex5&eW?k@#{ z(j})em%QXsNyZe0)OOF2n9@Mz!tCMS$Fz-=I;$z%{&h)nI^X%yPVsn~&hyTl%^tVg zmXb;YL=OyjwHs7lZaoi7_9`{3a!^hfrTo7AT=6?2cnLts1_;4UW;tx}0iiqll{*Jh z0p$Fze?l*8Ub*wGmTx5I4TS}BL|Yr-QH+5^o-3yucAM#G?i9@urGK@T#EW)g;`dF$ zYkBuYr}p%m9N{Rm^B?YN|SZqZ}l31dQFbJ@T+Glz?yqh7^Ga_74EUtqE_S`a_5xZnq5m!kp3y%B;bW~R5j0pOlWr-NGtRxwsUn+*vwiwJU z(>J`i2b=xQy1A&B8&+KyRR{4EJ_t&IAI1sV$E>I3oRzW#KS>HhA8->2+R&6H09AKa zLtweuJ*z)MFDYldFUQK3=%y%$0Cme%p!u@oXQTk~O1>MAnmlB`VGo6Z_*hmhjpQ7w zuH>mst9rzlHD4{Va%-VYr+=0?Z%0_P;FN5n%Ecd4P;2>0g4+a~ ziORsdQOF%+7fShcxB}Oq5QUv;%muQ(EH=aNO-ia}{j^|TaI6&AyZVtH^g)CQSLIW* zrciG%9D2$RGTSQ*3+B}-4AU-C_+1tcJ=($XI1Np#3z16+q%X7SgzXf$+Q~X7^8F%R z6n-o8k@3>cWgAW-U247Ml}^eAGzV3b>7J z6JuJfp<^;xn=Kjr98!kw8`DWvNIZD-j^(Tci0F{roKtH0CyBkr$JI)rsY*{bX@Oy+ z&NuD_VkC+Ao1L>defWb;UG=|58ML&RQdy)fD{}}UK2vKIj1D&yWleAfmnG*2$#x+u z;9Nyd$E&y5w~A-{1>xHS0{<{}L$eJ)(keu)stFSLftr8thdVIK$xIC;NrKE@foImN zAyY0N5~E!h;>h7eu0dFLh2BNfCqoUjSJl9Z>%2jv0k4pV!L=B{Vz;J_aEf!%aH1e0 z05RcUNf7|>_#t!4;ij!^z)lf%QYU27rs&%$9(a7urBW&jdHUOrq!3F}N|gwNu*WM{ JIG8BNzW{JGZe0KX diff --git a/src/test/resources/ssl/reload/truststore.jks b/src/test/resources/ssl/reload/truststore.jks index 2928417daadc2604e54f1c43d834242d3438bc75..c750f9807aa22339c19d05e510990a57ceef4758 100644 GIT binary patch literal 1398 zcmV-+1&R7Ff(3E{0Ru3C1snzmDuzgg_YDCD0ic2f4FrM(2{3{M1u%jI0R{;whDe6@ z4FLxRpn?SQFoFc`0s#Opf&}LV2`Yw2hW8Bt2LUi<1_>&LNQU+thDZTr0|Wso1Q4`ciP@X6dj$*4Gp|%+mqUPp1aJVBZ45;CRpdiEMy8ut;a?1? zvZJ^gI826MxBEfH1AZ2jXeBUz9J{^*G+(rt~3KPTFaf%h#^J}_IcL`GsdSEm( z3LqB2_Kg)nuh=J!i+aHKaV;(z3K?f<#F}EJp@f=Am+tpJ` zh8=z2Us$nHpwQ%g7>zu*HbU_zspJ|K6Grpsj3}c=pbJ8GXhugda6pwmX+^%yH(H;P zEx#ae_nT6iteH|!qP(-g3b;0PIVidgGdFRJ=MnhC&SmBa;DqpVHP4x}30bDu%Urtl z`R2~uH7fCmKWmShH@qcMNG~GFvdzjgelvsYh)kK%%jj6pLx2O?=epf3){%i&BVfrq zjfTnpIh!F2(OvD@1&U8upq0Vx?}w?qh=${%OUjNB{qwIG=fYC7vQCeOSKg|YBDT^r zlShfFf$eZpR$Lo%Z@rK49}N{_$5D^X*ID5TG$Eep9HIN*K=1~?XnJfQldpTW)a@10Oqghu~aW>{y^apM7 z#lak6)jZ=7OR-KYh%e|qcW9p}6qF?5O%x(XbWQEzQ=pYA5eN2|7oWGgV5@Rfg^1|I z941ktx{@mpSj_tqOhk)mdHjP R>M;BsknAySs>aDsO_j(J{OE=mXZYF<#g`|7Wd zV}1@Uoptd(sLU%!>Ja%Dm^rgxZmXPj4qm_p=fC}7s;{??PX(k%9YpYSGAYgyel5=) zxnW(zZa#~jxFIn7)h150{p+gR<4fM1ta?K42VI3$_)lkIH1>Vitduu=o;V71SM^bK z557wGkDZ6BX@bUa_C2rs2FiSj!gt_0AoP=2!>1%8z$>aU(a%2x+mUE~0DHMzP?wx( z9;=((CImfxMDT+{C*hauz4fii;NYu*hOIyXTJQ8)_&q2M~ zcq{fThEDBQ@Jm6!Os52Vyz?zL8bEs9sJk#tFflL<1_@w>NC9O71OfpC00bakRL2M& z%MK;OJd)R%RX*;j)E6fEb8Q||=|UoL+M)<<|_X#+Q`PXYob E5Xppi#Q*>R literal 1338 zcmV-A1;zR>f(14L0Ru3C1n&k3Duzgg_YDCD0ic2e-~@sM+%SR!*f4?w)CLJEhDe6@ z4FLxRpn?R&FoFcZ0s#Opf&{t-2`Yw2hW8Bt2LUiC1_~;MNQU!X{ux{aM$F0j5^vmT5pcHFgVGH~K zhK$)9^5%9&rj;$09cRk5UGZ=RWs=&88aIXrNV;Q2jvw-E7-{G!@yT$>q>Z+e&~R=A zC;eT+UIuPHFXO;f%V3lT?eN5ebAG!4HB1*dK|7Z&GmBKFd7u63EQlO@v<(!0m7urD zQF$6RFv8i`!M)0FCuHXeMVM>6*(r6gVPpI}W05*4h?*t`tTS8~y=miIbSPm%x5MfM19qhw>`+a_C5%TrFs4oraId8WjUQ(GJXLcH``{mq#+kEFxD`XpOj0(&n& zp7=gh>(mhHX2UXGk8{3(GfvrprG~sH#%2pGEmn9XET>sETX4!|rhh$907Lx}wtE#; zm+vyynw@hQuF|hs_2)a(6JeZJ+*x^6f1|_easv#)e`7hRCs*56eUp6N2aFb6HPWoFEYH#_f$8pF?AVfgzzn!-{Vycy`2XoinBD zfq+wRRfbdYSk@LHC~;baUZA@x6RJZS4B7Z=2P-e3uM);jv1nP%x)N_8>0%3DJlGsv z6(MV%$#O`?0#%6fM=-j9%UZ`|0T-q{i9;teq``1Od!Nf`$9J_a!o1cJ28^Z)qG=aR zrdFb7)mE1CJ2*L!i3G{WE^l3c^(p_U_Jd+ z%+24(QB6%W>~$_Aiu_D{X-{te|0DcD45Q7W{;6D87nrQufYJYuZ63imE2c;J!%0jN zgJswFq`lSoDQXgJ9i5I-_}WX;jfP4!M+-a^izf!&{_tdP??Rj~OrehQL^G9{=(h^Y z51hxr6Np7BZrUMYmVATos@C@OhAgR{)vAGW8ge7R^)Nm#AutIB1uG5%0vZJX1Qb08 w$Vj}&z0Odz%~+~-Msht%(GUa_ Date: Mon, 17 Apr 2023 14:07:34 -0400 Subject: [PATCH 166/369] Fix NPE and add additional graceful error handling (#2687) * Fix NPE and add additional graceful error handling Signed-off-by: Craig Perkins * Add new lines at end of file Signed-off-by: Craig Perkins * Run spotlessApply Signed-off-by: Craig Perkins * Remove unused import Signed-off-by: Craig Perkins * volatile to final Signed-off-by: Craig Perkins --------- Signed-off-by: Craig Perkins --- .../security/OpenSearchSecurityPlugin.java | 14 +- .../GuardedSearchOperationWrapper.java | 85 ++++++++++ .../GuardedSearchOperationWrapperTest.java | 146 ++++++++++++++++++ 3 files changed, 239 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/opensearch/security/support/GuardedSearchOperationWrapper.java create mode 100644 src/test/java/org/opensearch/security/support/GuardedSearchOperationWrapperTest.java diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index ffd7f730ed..362a46843e 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -45,6 +45,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; @@ -98,7 +99,6 @@ import org.opensearch.index.Index; import org.opensearch.index.IndexModule; import org.opensearch.index.cache.query.QueryCache; -import org.opensearch.index.shard.SearchOperationListener; import org.opensearch.indices.IndicesService; import org.opensearch.indices.SystemIndexDescriptor; import org.opensearch.indices.breaker.CircuitBreakerService; @@ -165,6 +165,7 @@ import org.opensearch.security.ssl.transport.SecuritySSLNettyTransport; import org.opensearch.security.ssl.util.SSLConfigConstants; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.support.GuardedSearchOperationWrapper; import org.opensearch.security.support.HeaderHelper; import org.opensearch.security.support.ModuleInfo; import org.opensearch.security.support.ReflectionHelper; @@ -215,7 +216,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin private final List demoCertHashes = new ArrayList(3); private volatile SecurityFilter sf; private volatile IndexResolverReplacer irr; - private volatile NamedXContentRegistry namedXContentRegistry = null; + private final AtomicReference namedXContentRegistry = new AtomicReference<>(NamedXContentRegistry.EMPTY);; private volatile DlsFlsRequestValve dlsFlsValve = null; private volatile Salt salt; private volatile OpensearchDynamicSetting transportPassiveAuthSetting; @@ -569,11 +570,11 @@ public Weight doCache(Weight weight, QueryCachingPolicy policy) { } }); - indexModule.addSearchOperationListener(new SearchOperationListener() { + indexModule.addSearchOperationListener(new GuardedSearchOperationWrapper() { @Override public void onPreQueryPhase(SearchContext context) { - dlsFlsValve.handleSearchContext(context, threadPool, namedXContentRegistry); + dlsFlsValve.handleSearchContext(context, threadPool, namedXContentRegistry.get()); } @Override @@ -643,7 +644,7 @@ public void onQueryPhase(SearchContext searchContext, long tookInNanos) { } } } - }); + }.toListener()); } } @@ -798,6 +799,7 @@ public Collection createComponents(Client localClient, ClusterService cl final PrivilegesInterceptor privilegesInterceptor; + namedXContentRegistry.set(xContentRegistry); if (SSLConfig.isSslOnlyMode()) { dlsFlsValve = new DlsFlsRequestValve.NoopDlsFlsRequestValve(); auditLog = new NullAuditLog(); @@ -822,7 +824,7 @@ public Collection createComponents(Client localClient, ClusterService cl // DLS-FLS is enabled if not client and not disabled and not SSL only. final boolean dlsFlsEnabled = !SSLConfig.isSslOnlyMode(); evaluator = new PrivilegesEvaluator(clusterService, threadPool, cr, resolver, auditLog, - settings, privilegesInterceptor, cih, irr, dlsFlsEnabled, namedXContentRegistry); + settings, privilegesInterceptor, cih, irr, dlsFlsEnabled, namedXContentRegistry.get()); sf = new SecurityFilter(settings, evaluator, adminDns, dlsFlsValve, auditLog, threadPool, cs, compatConfig, irr, xffResolver); diff --git a/src/main/java/org/opensearch/security/support/GuardedSearchOperationWrapper.java b/src/main/java/org/opensearch/security/support/GuardedSearchOperationWrapper.java new file mode 100644 index 0000000000..76d316de2d --- /dev/null +++ b/src/main/java/org/opensearch/security/support/GuardedSearchOperationWrapper.java @@ -0,0 +1,85 @@ +/* + * 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 org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.index.shard.SearchOperationListener; +import org.opensearch.search.internal.ReaderContext; +import org.opensearch.search.internal.SearchContext; +import org.opensearch.transport.TransportRequest; + +/** + * Guarded version of Search Operation Listener to ensure critical request paths succeed + */ +public interface GuardedSearchOperationWrapper { + + static final Logger log = LogManager.getLogger(GuardedSearchOperationWrapper.class); + + void onPreQueryPhase(final SearchContext context); + + void onNewReaderContext(final ReaderContext readerContext); + + void onNewScrollContext(final ReaderContext readerContext); + + void validateReaderContext(final ReaderContext readerContext, final TransportRequest transportRequest); + + void onQueryPhase(final SearchContext searchContext, final long tookInNanos); + + default SearchOperationListener toListener() { + return new InnerSearchOperationListener(this); + } + + static class InnerSearchOperationListener implements SearchOperationListener { + + private GuardedSearchOperationWrapper that; + InnerSearchOperationListener(GuardedSearchOperationWrapper that) { + this.that = that; + } + + @Override + public void onPreQueryPhase(final SearchContext searchContext) { + try { + that.onPreQueryPhase(searchContext); + } catch (final Exception e) { + searchContext.setTask(null); + log.error("Cancelled request due to internal error", e); + } + } + + @Override + public void onNewReaderContext(final ReaderContext readerContext) { + that.onNewReaderContext(readerContext); + } + + @Override + public void onNewScrollContext(final ReaderContext readerContext) { + that.onNewScrollContext(readerContext); + } + + @Override + public void validateReaderContext(final ReaderContext readerContext, final TransportRequest transportRequest) { + that.validateReaderContext(readerContext, transportRequest); + } + + @Override + public void onQueryPhase(final SearchContext searchContext, final long tookInNanos) { + try { + that.onQueryPhase(searchContext, tookInNanos); + } catch (final Exception e) { + searchContext.setTask(null); + log.error("Cancelled request due to internal error", e); + } + } + } +} diff --git a/src/test/java/org/opensearch/security/support/GuardedSearchOperationWrapperTest.java b/src/test/java/org/opensearch/security/support/GuardedSearchOperationWrapperTest.java new file mode 100644 index 0000000000..982d1108ad --- /dev/null +++ b/src/test/java/org/opensearch/security/support/GuardedSearchOperationWrapperTest.java @@ -0,0 +1,146 @@ +/* + * 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.concurrent.atomic.AtomicReference; + +import org.junit.Test; + +import org.opensearch.index.shard.SearchOperationListener; +import org.opensearch.search.internal.ReaderContext; +import org.opensearch.search.internal.SearchContext; +import org.opensearch.transport.TransportRequest; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + + +public class GuardedSearchOperationWrapperTest { + + @Test + public void onNewReaderContextCanThrowException() { + final String expectedExceptionText = "abcd1234"; + + DefaultingGuardedSearchOperationWrapper testWrapper = new DefaultingGuardedSearchOperationWrapper() { + @Override + public void onNewReaderContext(ReaderContext readerContext) { + throw new RuntimeException(expectedExceptionText); + } + }; + + final RuntimeException expectedException = assertThrows(RuntimeException.class, testWrapper::exerciseAllMethods); + + assertThat(expectedException.getMessage(), equalTo(expectedExceptionText)); + } + + @Test + public void onNewScrollContextCanThrowException() { + final String expectedExceptionText = "qwerty978"; + + DefaultingGuardedSearchOperationWrapper testWrapper = new DefaultingGuardedSearchOperationWrapper() { + @Override + public void onNewScrollContext(ReaderContext readerContext) { + throw new RuntimeException(expectedExceptionText); + } + }; + + final RuntimeException expectedException = assertThrows(RuntimeException.class, testWrapper::exerciseAllMethods); + + assertThat(expectedException.getMessage(), equalTo(expectedExceptionText)); + } + + @Test + public void validateReaderContextCanThrowException() { + final String expectedExceptionText = "validationException"; + + DefaultingGuardedSearchOperationWrapper testWrapper = new DefaultingGuardedSearchOperationWrapper() { + @Override + public void validateReaderContext(ReaderContext readerContext, TransportRequest transportRequest) { + throw new RuntimeException(expectedExceptionText); + } + }; + + final RuntimeException expectedException = assertThrows(RuntimeException.class, testWrapper::exerciseAllMethods); + + assertThat(expectedException.getMessage(), equalTo(expectedExceptionText)); + } + + @Test + public void onPreQueryPhaseCannotThrow() { + AtomicReference calledSearchContext = new AtomicReference<>(); + DefaultingGuardedSearchOperationWrapper testWrapper = new DefaultingGuardedSearchOperationWrapper() { + @Override + public void onPreQueryPhase(SearchContext context) { + calledSearchContext.set(context); + throw new RuntimeException("EXCEPTIONAL!"); + } + }; + + testWrapper.exerciseAllMethods(); + + assertThat(calledSearchContext.get(), notNullValue()); + verify(calledSearchContext.get()).setTask(null); + } + + @Test + public void onQueryPhaseCannotThrow() { + AtomicReference calledSearchContext = new AtomicReference<>(); + DefaultingGuardedSearchOperationWrapper testWrapper = new DefaultingGuardedSearchOperationWrapper() { + @Override + public void onQueryPhase(SearchContext context, long tookInNanos) { + calledSearchContext.set(context); + throw new RuntimeException("EXCEPTIONAL!"); + } + }; + + testWrapper.exerciseAllMethods(); + + assertThat(calledSearchContext.get(), notNullValue()); + verify(calledSearchContext.get()).setTask(null); + } + + /** Only use to make testing easier */ + private static class DefaultingGuardedSearchOperationWrapper implements GuardedSearchOperationWrapper { + + @Override + public void onNewReaderContext(ReaderContext readerContext) { + } + + @Override + public void onNewScrollContext(ReaderContext readerContext) { + } + + @Override + public void onPreQueryPhase(SearchContext context) { + } + + @Override + public void onQueryPhase(SearchContext searchContext, long tookInNanos) { + } + + @Override + public void validateReaderContext(ReaderContext readerContext, TransportRequest transportRequest) { + } + + void exerciseAllMethods(){ + final SearchOperationListener sol = this.toListener(); + sol.onNewReaderContext(mock(ReaderContext.class)); + sol.onNewScrollContext(mock(ReaderContext.class)); + sol.onPreQueryPhase(mock(SearchContext.class)); + sol.onQueryPhase(mock(SearchContext.class), 12345L); + sol.validateReaderContext(mock(ReaderContext.class), mock(TransportRequest.class)); + } + } +} From d168c1a319944ca3266fdd4e2263f6228c5b9c25 Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Mon, 17 Apr 2023 18:50:55 -0700 Subject: [PATCH 167/369] Add release notes for 2.7.0 (#2690) * Add release notes for 2.7.0 Signed-off-by: Ryan Liang --- ...ensearch-security.release-notes-2.6.0.0.md | 1 + ...ensearch-security.release-notes-2.7.0.0.md | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-2.7.0.0.md diff --git a/release-notes/opensearch-security.release-notes-2.6.0.0.md b/release-notes/opensearch-security.release-notes-2.6.0.0.md index 748454a95d..a1932db5f3 100644 --- a/release-notes/opensearch-security.release-notes-2.6.0.0.md +++ b/release-notes/opensearch-security.release-notes-2.6.0.0.md @@ -7,6 +7,7 @@ Compatible with OpenSearch 2.6.0 * Add actions cluster:admin/component_template/* to cluster_manage_index_templates ([#2409](https://github.com/opensearch-project/security/pull/2409)) * Publish snapshots to maven ([#2438](https://github.com/opensearch-project/security/pull/2438)) * Integrate k-NN functionality with security plugin ([#2274](https://github.com/opensearch-project/security/pull/2274)) +* Flatten response times ([#2471](https://github.com/opensearch-project/security/pull/2471)) ### Maintenance diff --git a/release-notes/opensearch-security.release-notes-2.7.0.0.md b/release-notes/opensearch-security.release-notes-2.7.0.0.md new file mode 100644 index 0000000000..8d2b8e4f7c --- /dev/null +++ b/release-notes/opensearch-security.release-notes-2.7.0.0.md @@ -0,0 +1,43 @@ +## 2023-04-25 Version 2.7.0.0 + +Compatible with OpenSearch 2.7.0 + +### Features + +* Dynamic tenancy configurations ([#2607](https://github.com/opensearch-project/security/pull/2607)) + +### Bug Fixes + +* Support multitenancy for the anonymous user ([#2459](https://github.com/opensearch-project/security/pull/2459)) +* Fix error message when system index is blocked ([#2525](https://github.com/opensearch-project/security/pull/2525)) +* Fix of OpenSSLTest is not using the OpenSSL Provider ([#2301](https://github.com/opensearch-project/security/pull/2301)) +* Add chmod 0600 to install_demo_configuration bash script ([#2550](https://github.com/opensearch-project/security/pull/2550)) +* Fix SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder" ([#2564](https://github.com/opensearch-project/security/pull/2564)) +* Fix lost privileges during auto initializing of the index ([#2498](https://github.com/opensearch-project/security/pull/2498)) +* Fix NPE and add additional graceful error handling ([#2687](https://github.com/opensearch-project/security/pull/2687)) + +### Enhancements + +* Clock skew tolerance for oidc token validation ([#2482](https://github.com/opensearch-project/security/pull/2482)) +* Adding index template permissions to kibana_server role ([#2503](https://github.com/opensearch-project/security/pull/2503)) +* Add a test in order to catch incorrect handling of index parsing during Snapshot Restoration ([#2384](https://github.com/opensearch-project/security/pull/2384)) +* Expand Dls Tests for easier verification of functionality ([#2634](https://github.com/opensearch-project/security/pull/2634)) +* New system index[.ql-datasources] for ppl/sql datasource configurations ([#2650](https://github.com/opensearch-project/security/pull/2650)) +* Allows for configuration of LDAP referral following ([#2135](https://github.com/opensearch-project/security/pull/2135)) + +### Maintenance + +* Update kafka client to 3.4.0 ([#2484](https://github.com/opensearch-project/security/pull/2484)) +* Update to gradle 8.0.2 ([#2520](https://github.com/opensearch-project/security/pull/2520)) +* XContent Refactor ([#2598](https://github.com/opensearch-project/security/pull/2598)) +* Update json-smart to 2.4.10 and update spring-core to 5.3.26 ([#2630](https://github.com/opensearch-project/security/pull/2630)) +* Update certs for SecuritySSLReloadCertsActionTests ([#2679](https://github.com/opensearch-project/security/pull/2679)) + +### Infrastructure + +* Add auto github release workflow ([#2450](https://github.com/opensearch-project/security/pull/2450)) +* Use correct format for push trigger ([#2474](https://github.com/opensearch-project/security/pull/2474)) + +### Documentation + +* Fix the format of the codeowners file ([#2469](https://github.com/opensearch-project/security/pull/2469)) From 24e08bd1b1b764734338ca9e9d25af46d1e58642 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Fri, 21 Apr 2023 13:10:13 -0400 Subject: [PATCH 168/369] Security User Refactor (#2594) --------- Signed-off-by: Stephen Crawford Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- .../security/OpenSearchSecurityPlugin.java | 12 +- .../dlic/rest/api/InternalUsersApiAction.java | 76 +++----- .../dlic/rest/api/SecurityRestApiActions.java | 4 +- .../opensearch/security/user/UserService.java | 181 ++++++++++++++++++ .../security/user/UserServiceException.java | 20 ++ .../TransportUserInjectorIntegTest.java | 6 +- .../integration/TestAuditlogImpl.java | 6 +- .../security/dlic/rest/api/UserApiTest.java | 65 ++++++- 8 files changed, 306 insertions(+), 64 deletions(-) create mode 100644 src/main/java/org/opensearch/security/user/UserService.java create mode 100644 src/main/java/org/opensearch/security/user/UserServiceException.java diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 362a46843e..87ca878fc6 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -176,6 +176,7 @@ import org.opensearch.security.transport.InterClusterRequestEvaluator; import org.opensearch.security.transport.SecurityInterceptor; import org.opensearch.security.user.User; +import org.opensearch.security.user.UserService; import org.opensearch.tasks.Task; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.RemoteClusterService; @@ -204,6 +205,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin private volatile SecurityRestFilter securityRestHandler; private volatile SecurityInterceptor si; private volatile PrivilegesEvaluator evaluator; + private volatile UserService userService; private volatile ThreadPool threadPool; private volatile ConfigurationRepository cr; private volatile AdminDNs adminDns; @@ -487,6 +489,7 @@ public List getRestHandlers(Settings settings, RestController restC evaluator, threadPool, Objects.requireNonNull(auditLog), sks, + Objects.requireNonNull(userService), sslCertReloadEnabled) ); log.debug("Added {} rest handler(s)", handlers.size()); @@ -813,9 +816,11 @@ public Collection createComponents(Client localClient, ClusterService cl sslExceptionHandler = new AuditLogSslExceptionHandler(auditLog); adminDns = new AdminDNs(settings); - + cr = ConfigurationRepository.create(settings, this.configPath, threadPool, localClient, clusterService, auditLog); + userService = new UserService(cs, cr, settings, localClient); + final XFFResolver xffResolver = new XFFResolver(threadPool); backendRegistry = new BackendRegistry(settings, adminDns, xffResolver, auditLog, threadPool); @@ -872,6 +877,7 @@ public Collection createComponents(Client localClient, ClusterService cl components.add(evaluator); components.add(si); components.add(dcf); + components.add(userService); return components; @@ -1179,7 +1185,6 @@ public static class GuiceHolder implements LifecycleComponent { private static RepositoriesService repositoriesService; private static RemoteClusterService remoteClusterService; private static IndicesService indicesService; - private static PitService pitService; @Inject @@ -1204,7 +1209,8 @@ public static IndicesService getIndicesService() { } public static PitService getPitService() { return pitService; } - + + @Override public void close() { } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java index 646e1f81d6..0551c108a1 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java @@ -14,7 +14,6 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; -import java.util.stream.Collectors; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -32,18 +31,18 @@ import org.opensearch.rest.RestController; import org.opensearch.rest.RestRequest; import org.opensearch.rest.RestRequest.Method; -import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; import org.opensearch.security.dlic.rest.validation.InternalUsersValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; -import org.opensearch.security.securityconf.Hashed; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.ssl.transport.PrincipalExtractor; import org.opensearch.security.support.SecurityJsonNode; +import org.opensearch.security.user.UserService; +import org.opensearch.security.user.UserServiceException; import org.opensearch.threadpool.ThreadPool; import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; @@ -69,13 +68,16 @@ public class InternalUsersApiAction extends PatchableResourceApiAction { new Route(Method.PATCH, "/internalusers/{name}") )); + UserService userService; + @Inject public InternalUsersApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, - ThreadPool threadPool, AuditLog auditLog) { + ThreadPool threadPool, UserService userService, AuditLog auditLog) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + this.userService = userService; } @Override @@ -100,22 +102,7 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C final String username = request.param("name"); - if (username == null || username.length() == 0) { - badRequestResponse(channel, "No " + getResourceName() + " specified."); - return; - } - - final List foundRestrictedContents = RESTRICTED_FROM_USERNAME.stream().filter(username::contains).collect(Collectors.toList()); - if (!foundRestrictedContents.isEmpty()) { - final String restrictedContents = foundRestrictedContents.stream().map(s -> "'" + s + "'").collect(Collectors.joining(",")); - badRequestResponse(channel, "Username has restricted characters " + restrictedContents + " that are not permitted."); - return; - } - - // TODO it might be sensible to consolidate this with the overridden method in - // order to minimize duplicated logic - - final SecurityDynamicConfiguration internalUsersConfiguration = load(getConfigName(), false); + SecurityDynamicConfiguration internalUsersConfiguration = load(getConfigName(), false); if (!isWriteable(channel, internalUsersConfiguration, username)) { return; @@ -128,50 +115,35 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C final List securityRoles = securityJsonNode.get("opendistro_security_roles").asList(); if (securityRoles != null) { for (final String role: securityRoles) { - if (!isValidRolesMapping(channel, role)) return; + if (!isValidRolesMapping(channel, role)) { + return; + } } } - // if password is set, it takes precedence over hash - final String plainTextPassword = securityJsonNode.get("password").asString(); - final String origHash = securityJsonNode.get("hash").asString(); - if (plainTextPassword != null && plainTextPassword.length() > 0) { - contentAsNode.remove("password"); - contentAsNode.put("hash", hash(plainTextPassword.toCharArray())); - } else if (origHash != null && origHash.length() > 0) { - contentAsNode.remove("password"); - } else if (plainTextPassword != null && plainTextPassword.isEmpty() && origHash == null) { - contentAsNode.remove("password"); - } - final boolean userExisted = internalUsersConfiguration.exists(username); // when updating an existing user password hash can be blank, which means no // changes - // sanity checks, hash is mandatory for newly created users - if (!userExisted && securityJsonNode.get("hash").asString() == null) { - badRequestResponse(channel, "Please specify either 'hash' or 'password' when creating a new internal user."); + try { + if (request.hasParam("owner")) { + ((ObjectNode) content).put("owner", request.param("owner")); + } + if (request.hasParam("isEnabled")) { + ((ObjectNode) content).put("isEnabled", request.param("isEnabled")); + } + ((ObjectNode) content).put("name", username); + internalUsersConfiguration = userService.createOrUpdateAccount((ObjectNode) content); + } + catch (UserServiceException ex) { + badRequestResponse(channel, ex.getMessage()); return; } - - // for existing users, hash is optional - if (userExisted && securityJsonNode.get("hash").asString() == null) { - // sanity check, this should usually not happen - final String hash = ((Hashed) internalUsersConfiguration.getCEntry(username)).getHash(); - if (hash == null || hash.length() == 0) { - internalErrorResponse(channel, - "Existing user " + username + " has no password, and no new password or hash was specified."); - return; - } - contentAsNode.put("hash", hash); + catch (IOException ex) { + throw new IOException(ex); } - internalUsersConfiguration.remove(username); - - // checks complete, create or update the user - internalUsersConfiguration.putCObject(username, DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass())); - saveAndUpdateConfigs(this.securityIndexName,client, CType.INTERNALUSERS, internalUsersConfiguration, new OnSucessActionListener(channel) { @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java index 9655ba67ea..e73d28e865 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java @@ -28,6 +28,7 @@ import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.ssl.SecurityKeyStore; import org.opensearch.security.ssl.transport.PrincipalExtractor; +import org.opensearch.security.user.UserService; import org.opensearch.threadpool.ThreadPool; public class SecurityRestApiActions { @@ -44,9 +45,10 @@ public static Collection getHandler(final Settings settings, final ThreadPool threadPool, final AuditLog auditLog, final SecurityKeyStore securityKeyStore, + final UserService userService, final boolean certificatesReloadEnabled) { final List handlers = new ArrayList(16); - handlers.add(new InternalUsersApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); + handlers.add(new InternalUsersApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, userService, auditLog)); handlers.add(new RolesMappingApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); handlers.add(new RolesApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); handlers.add(new ActionGroupsApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); diff --git a/src/main/java/org/opensearch/security/user/UserService.java b/src/main/java/org/opensearch/security/user/UserService.java new file mode 100644 index 0000000000..c6eea8ef4c --- /dev/null +++ b/src/main/java/org/opensearch/security/user/UserService.java @@ -0,0 +1,181 @@ +/* + * 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.user; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableList; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.client.Client; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.settings.Settings; +import org.opensearch.security.DefaultObjectMapper; +import org.opensearch.security.configuration.ConfigurationRepository; +import org.opensearch.security.securityconf.DynamicConfigFactory; +import org.opensearch.security.securityconf.Hashed; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.support.SecurityJsonNode; + +import static org.opensearch.security.dlic.rest.support.Utils.hash; + +/** + * This class handles user registration and operations on behalf of the Security Plugin. + */ +public class UserService { + + protected final Logger log = LogManager.getLogger(this.getClass()); + ClusterService clusterService; + static ConfigurationRepository configurationRepository; + String securityIndex; + Client client; + final static String NO_PASSWORD_OR_HASH_MESSAGE = "Please specify either 'hash' or 'password' when creating a new internal user."; + final static String RESTRICTED_CHARACTER_USE_MESSAGE = "A restricted character(s) was detected in the account name. Please remove: "; + + final static String SERVICE_ACCOUNT_PASSWORD_MESSAGE = "A password cannot be provided for a service account. Failed to register service account: "; + + final static String SERVICE_ACCOUNT_HASH_MESSAGE = "A password hash cannot be provided for service account. Failed to register service account: "; + + final static String NO_ACCOUNT_NAME_MESSAGE = "No account name was specified in the request."; + private static CType getConfigName() { + return CType.INTERNALUSERS; + } + + static final List RESTRICTED_FROM_USERNAME = ImmutableList.of( + ":" // Not allowed in basic auth, see https://stackoverflow.com/a/33391003/533057 + ); + + @Inject + public UserService( + ClusterService clusterService, + ConfigurationRepository configurationRepository, + Settings settings, + Client client + ) { + this.clusterService = clusterService; + this.configurationRepository = configurationRepository; + this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + this.client = client; + } + + /** + * Load data for a given CType + * @param config CType whose data is to be loaded in-memory + * @return configuration loaded with given CType data + */ + protected static final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent) { + SecurityDynamicConfiguration loaded = configurationRepository.getConfigurationsFromIndex(Collections.singleton(config), logComplianceEvent).get(config).deepClone(); + return DynamicConfigFactory.addStatics(loaded); + } + + /** + * This function will handle the creation or update of a user account. + * + * @param contentAsNode An object node of different account configurations. + * @return InternalUserConfiguration with the new/updated user + * @throws UserServiceException + * @throws IOException + */ + public SecurityDynamicConfiguration createOrUpdateAccount(ObjectNode contentAsNode) throws IOException { + + + SecurityJsonNode securityJsonNode = new SecurityJsonNode(contentAsNode); + + final SecurityDynamicConfiguration internalUsersConfiguration = load(getConfigName(), false); + String accountName = securityJsonNode.get("name").asString(); + + if (accountName == null || accountName.length() == 0) { // Fail if field is present but empty + throw new UserServiceException(NO_ACCOUNT_NAME_MESSAGE); + } + + if (!securityJsonNode.get("attributes").get("owner").isNull() && !securityJsonNode.get("attributes").get("owner").equals(accountName)) { // If this is a service account + verifyServiceAccount(securityJsonNode, accountName); + String password = generatePassword(); + contentAsNode.put("hash", hash(password.toCharArray())); + } + + securityJsonNode = new SecurityJsonNode(contentAsNode); + final List foundRestrictedContents = RESTRICTED_FROM_USERNAME.stream().filter(accountName::contains).collect(Collectors.toList()); + if (!foundRestrictedContents.isEmpty()) { + final String restrictedContents = foundRestrictedContents.stream().map(s -> "'" + s + "'").collect(Collectors.joining(",")); + throw new UserServiceException(RESTRICTED_CHARACTER_USE_MESSAGE + restrictedContents); + } + + // if password is set, it takes precedence over hash + final String plainTextPassword = securityJsonNode.get("password").asString(); + final String origHash = securityJsonNode.get("hash").asString(); + if (plainTextPassword != null && plainTextPassword.length() > 0) { + contentAsNode.remove("password"); + contentAsNode.put("hash", hash(plainTextPassword.toCharArray())); + } else if (origHash != null && origHash.length() > 0) { + contentAsNode.remove("password"); + } else if (plainTextPassword != null && plainTextPassword.isEmpty() && origHash == null) { + contentAsNode.remove("password"); + } + + final boolean userExisted = internalUsersConfiguration.exists(accountName); + + // sanity checks, hash is mandatory for newly created users + if (!userExisted && securityJsonNode.get("hash").asString() == null) { + throw new UserServiceException(NO_PASSWORD_OR_HASH_MESSAGE); + } + + // for existing users, hash is optional + if (userExisted && securityJsonNode.get("hash").asString() == null) { + // sanity check, this should usually not happen + final String hash = ((Hashed) internalUsersConfiguration.getCEntry(accountName)).getHash(); + if (hash == null || hash.length() == 0) { + throw new UserServiceException("Existing user " + accountName + " has no password, and no new password or hash was specified."); + } + contentAsNode.put("hash", hash); + } + + internalUsersConfiguration.remove(accountName); + contentAsNode.remove("name"); + + internalUsersConfiguration.putCObject(accountName, DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass())); + return internalUsersConfiguration; + } + + private void verifyServiceAccount(SecurityJsonNode securityJsonNode, String accountName) { + + final String plainTextPassword = securityJsonNode.get("password").asString(); + final String origHash = securityJsonNode.get("hash").asString(); + + if (plainTextPassword != null && plainTextPassword.length() > 0) { + throw new UserServiceException(SERVICE_ACCOUNT_PASSWORD_MESSAGE + accountName); + } + + if (origHash != null && origHash.length() > 0) { + throw new UserServiceException(SERVICE_ACCOUNT_HASH_MESSAGE + accountName); + } + } + + /** + * This will be swapped in for a real solution once one is decided on. + * + * @return A password for a service account. + */ + private String generatePassword() { + String generatedPassword = "superSecurePassword"; + return generatedPassword; + } +} diff --git a/src/main/java/org/opensearch/security/user/UserServiceException.java b/src/main/java/org/opensearch/security/user/UserServiceException.java new file mode 100644 index 0000000000..b8e6843751 --- /dev/null +++ b/src/main/java/org/opensearch/security/user/UserServiceException.java @@ -0,0 +1,20 @@ +/* + * 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.user; + +public class UserServiceException extends RuntimeException { + + public UserServiceException(String message) { + super(message); + } + +} diff --git a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java index 8fa665e8ba..a3e08d5234 100644 --- a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java @@ -106,11 +106,12 @@ public void testSecurityUserInjection() throws Exception { Assert.fail("Expecting exception"); } catch (OpenSearchSecurityException ex) { exception = ex; - log.warn(ex.toString()); + log.debug(ex.toString()); Assert.assertNotNull(exception); - Assert.assertTrue(exception.getMessage().contains("indices:admin/create")); + Assert.assertTrue(exception.getMessage().toString().contains("no permissions for [indices:admin/create]")); } + // 3. with valid backend roles for injected user UserInjectorPlugin.injectedUser = "injectedadmin|injecttest"; try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, @@ -157,6 +158,5 @@ public void testSecurityUserInjectionWithConfigDisabled() throws Exception { // Should pass as the user injection is disabled Assert.assertTrue(cir.isAcknowledged()); } - } } 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 b91ddee9fe..0502f078d9 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java @@ -61,7 +61,7 @@ public static List doThenWaitForMessages(final Runnable action, fi final List missedMessages = new ArrayList<>(); final List messages = new ArrayList<>(); final CountDownLatch latch = resetAuditStorage(expectedCount, messages); - + try { action.run(); final int maxSecondsToWaitForMessages = 1; @@ -104,9 +104,9 @@ public static List doThenWaitForMessages(final Runnable action, fi /** * 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 + * @param messages Where messages will be stored after being recieved */ private static CountDownLatch resetAuditStorage(int expectedMessageCount, List messages) { final CountDownLatch latch = new CountDownLatch(expectedMessageCount); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index 1ecd96e148..d755751e54 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -42,6 +42,33 @@ protected String getEndpointPrefix() { return PLUGINS_PREFIX; } + + private static final String ENABLED_SERVICE_ACCOUNT_BODY = "{" + + " \"attributes\": { \"owner\": \"test_owner\", " + + "\"isEnabled\": \"true\"}" + + " }\n"; + + private static final String DISABLED_SERVICE_ACCOUNT_BODY = "{" + + " \"attributes\": { \"owner\": \"test_owner\", " + + "\"isEnabled\": \"false\"}" + + " }\n"; + private static final String ENABLED_NOT_SERVICE_ACCOUNT_BODY = "{" + + " \"attributes\": { \"owner\": \"user_is_owner_1\", " + + "\"isEnabled\": \"true\"}" + + " }\n"; + private static final String PASSWORD_SERVICE = "{ \"password\" : \"test\"," + + " \"attributes\": { \"owner\": \"test_owner\", " + + "\"isEnabled\": \"true\"}" + + " }\n"; + private static final String HASH_SERVICE = "{ \"owner\" : \"test_owner\"," + + " \"attributes\": { \"owner\": \"test_owner\", " + + "\"isEnabled\": \"true\"}" + + " }\n"; + private static final String PASSWORD_HASH_SERVICE = "{ \"password\" : \"test\", \"hash\" : \"123\"," + + " \"attributes\": { \"owner\": \"test_owner\", " + + "\"isEnabled\": \"true\"}" + + " }\n"; + public UserApiTest(){ ENDPOINT = getEndpointPrefix() + "/api"; } @@ -139,6 +166,7 @@ private void verifyGet(final Header... header) throws Exception { // GET, new URL endpoint in security response = rh.executeGetRequest(ENDPOINT + "/user", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + } private void verifyPut(final Header... header) throws Exception { @@ -194,6 +222,7 @@ private void verifyPut(final Header... header) throws Exception { Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage()); Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("some")); Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("other")); + } private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) throws Exception { @@ -292,6 +321,40 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) addUserWithHash("nagilum", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); + // Add enabled service account then get it + response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceLive", + ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/happyServiceLive", restAdminHeader); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + + + // Add disabled service account + response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceDead", + DISABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); + + // Add enabled non-service account + response = rh.executePutRequest(ENDPOINT + "/internalusers/user_is_owner_1", + ENABLED_NOT_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + // Add service account with password -- Should Fail + + response = rh.executePutRequest(ENDPOINT + "/internalusers/passwordService", + PASSWORD_SERVICE, restAdminHeader); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + + //Add service with hash -- should fail + response = rh.executePutRequest(ENDPOINT + "/internalusers/hashService", + HASH_SERVICE, restAdminHeader); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + + // Add Service account with password & Hash -- should fail + response = rh.executePutRequest(ENDPOINT + "/internalusers/passwordHashService", + PASSWORD_HASH_SERVICE, restAdminHeader); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + // access must be allowed now checkGeneralAccess(HttpStatus.SC_OK, "nagilum", "nagilum"); @@ -417,7 +480,6 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E addUserWithPassword("$1aAAAAAAAAC", "$1aAAAAAAAAC", HttpStatus.SC_CREATED); addUserWithPassword("abc", "abc", HttpStatus.SC_CREATED); - // check tabs in json response = rh.executePutRequest(ENDPOINT + "/internalusers/userwithtabs", "\t{\"hash\": \t \"123\"\t} ", header); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); @@ -478,7 +540,6 @@ public void testPasswordRules() throws Exception { HttpResponse response = rh .executeGetRequest("_plugins/_security/api/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - System.out.println(response.getBody()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(133, settings.size()); From 6ace852f3c422adb60e062c0bf8aedae2db9c8d8 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Tue, 25 Apr 2023 09:28:32 -0400 Subject: [PATCH 169/369] Resolve ImmutableOpenMap issue from core refactor (#2715) --- .../framework/matcher/AliasExistsMatcher.java | 15 +++++++++++---- ...lusterContainTemplateWithAliasMatcher.java | 19 +++++++++++++++---- ...ettingsResponseContainsIndicesMatcher.java | 13 ++++++++++--- .../matcher/IndexMappingIsEqualToMatcher.java | 5 +---- .../matcher/IndexStateIsEqualToMatcher.java | 5 +++-- .../privileges/PrivilegesEvaluator.java | 15 +++++++++------ 6 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java index ef2ffb365b..4999da8e95 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java @@ -10,12 +10,15 @@ package org.opensearch.test.framework.matcher; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import org.hamcrest.Description; import org.hamcrest.TypeSafeDiagnosingMatcher; @@ -23,7 +26,6 @@ import org.opensearch.action.admin.indices.alias.get.GetAliasesResponse; import org.opensearch.client.Client; import org.opensearch.cluster.metadata.AliasMetadata; -import org.opensearch.common.collect.ImmutableOpenMap; import static java.util.Objects.requireNonNull; import static java.util.Spliterator.IMMUTABLE; @@ -41,8 +43,13 @@ public AliasExistsMatcher(String aliasName) { protected boolean matchesSafely(Client client, Description mismatchDescription) { try { GetAliasesResponse response = client.admin().indices().getAliases(new GetAliasesRequest(aliasName)).get(); - ImmutableOpenMap> aliases = response.getAliases(); - Set actualAliasNames = StreamSupport.stream(spliteratorUnknownSize(aliases.valuesIt(), IMMUTABLE), false) + + final Map> aliases = new HashMap<>(); + for (ObjectObjectCursor> cursor : response.getAliases()) { + aliases.put(cursor.key, cursor.value); + } + + Set actualAliasNames = StreamSupport.stream(spliteratorUnknownSize(aliases.values().iterator(), IMMUTABLE), false) .flatMap(Collection::stream) .map(AliasMetadata::getAlias) .collect(Collectors.toSet()); @@ -53,7 +60,7 @@ protected boolean matchesSafely(Client client, Description mismatchDescription) } return true; } catch (InterruptedException | ExecutionException e) { - mismatchDescription.appendText("Error occured during checking if cluster contains alias ") + mismatchDescription.appendText("Error occurred during checking if cluster contains alias ") .appendValue(e); return false; } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java index 907fb30257..d59792e715 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java @@ -9,11 +9,14 @@ */ package org.opensearch.test.framework.matcher; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import org.hamcrest.Description; import org.hamcrest.TypeSafeDiagnosingMatcher; @@ -21,7 +24,6 @@ import org.opensearch.action.admin.indices.template.get.GetIndexTemplatesResponse; import org.opensearch.client.Client; import org.opensearch.cluster.metadata.AliasMetadata; -import org.opensearch.common.collect.ImmutableOpenMap; import static java.util.Objects.requireNonNull; @@ -55,15 +57,24 @@ protected boolean matchesSafely(Client client, Description mismatchDescription) private Set getAliases(GetIndexTemplatesResponse response) { return response.getIndexTemplates() .stream() - .map(metadata -> metadata.getAliases()) + .map(metadata -> { + Map aliases = new HashMap<>(); + for (ObjectObjectCursor cursor : metadata.getAliases()) { + aliases.put(cursor.key, cursor.value); + } + return aliases; + }) .flatMap(aliasMap -> aliasNames(aliasMap)) .collect(Collectors.toSet()); } - private Stream aliasNames(ImmutableOpenMap aliasMap) { - return StreamSupport.stream(aliasMap.keys().spliterator(), false).map(objectCursor -> objectCursor.value); + private Stream aliasNames(Map aliasMap) { + Iterable> iterable = () -> aliasMap.entrySet().iterator(); + return StreamSupport.stream(iterable.spliterator(), false) + .map(entry -> entry.getValue().getAlias()); } + @Override public void describeTo(Description description) { description.appendText("template ").appendValue(templateName).appendText(" exists and "); diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java index 15c558d32f..79ac1c64ac 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java @@ -9,11 +9,14 @@ */ package org.opensearch.test.framework.matcher; +import java.util.HashMap; +import java.util.Map; + +import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import org.hamcrest.Description; import org.hamcrest.TypeSafeDiagnosingMatcher; import org.opensearch.action.admin.indices.settings.get.GetSettingsResponse; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.settings.Settings; import static java.util.Objects.isNull; @@ -31,12 +34,16 @@ class GetSettingsResponseContainsIndicesMatcher extends TypeSafeDiagnosingMatche @Override protected boolean matchesSafely(GetSettingsResponse response, Description mismatchDescription) { - ImmutableOpenMap indexToSettings = response.getIndexToSettings(); + + final Map indexToSettings = new HashMap<>(); + for (ObjectObjectCursor cursor : response.getIndexToSettings()) { + indexToSettings.put(cursor.key, cursor.value); + } for (String index : expectedIndices) { if (!indexToSettings.containsKey(index)) { mismatchDescription .appendText("Response contains settings of indices: ") - .appendValue(indexToSettings.keys()); + .appendValue(indexToSettings.keySet()); return false; } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexMappingIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexMappingIsEqualToMatcher.java index 9621aff449..b90defef7d 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexMappingIsEqualToMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexMappingIsEqualToMatcher.java @@ -17,8 +17,6 @@ import org.opensearch.action.admin.indices.mapping.get.GetMappingsRequest; import org.opensearch.action.admin.indices.mapping.get.GetMappingsResponse; import org.opensearch.client.Client; -import org.opensearch.cluster.metadata.MappingMetadata; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.index.IndexNotFoundException; import org.opensearch.test.framework.cluster.LocalCluster; @@ -44,8 +42,7 @@ protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescri GetMappingsResponse response = client.admin().indices() .getMappings(new GetMappingsRequest().indices(expectedIndexName)).actionGet(); - ImmutableOpenMap actualMappings = response.mappings(); - Map actualIndexMapping = actualMappings.get(expectedIndexName).getSourceAsMap(); + Map actualIndexMapping = response.getMappings().get(expectedIndexName).sourceAsMap(); if (!expectedMapping.equals(actualIndexMapping)) { mismatchDescription.appendText("Actual mapping ").appendValue(actualIndexMapping).appendText(" does not match expected"); diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexStateIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexStateIsEqualToMatcher.java index a9538b4b8b..ecff3fe2df 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexStateIsEqualToMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexStateIsEqualToMatcher.java @@ -9,6 +9,8 @@ */ package org.opensearch.test.framework.matcher; +import java.util.Map; + import org.hamcrest.Description; import org.hamcrest.TypeSafeDiagnosingMatcher; @@ -16,7 +18,6 @@ import org.opensearch.action.admin.cluster.state.ClusterStateResponse; import org.opensearch.client.Client; import org.opensearch.cluster.metadata.IndexMetadata; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.test.framework.cluster.LocalCluster; import static java.util.Objects.requireNonNull; @@ -37,7 +38,7 @@ protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescri ClusterStateRequest clusterStateRequest = new ClusterStateRequest().indices(expectedIndexName); ClusterStateResponse clusterStateResponse = client.admin().cluster().state(clusterStateRequest).actionGet(); - ImmutableOpenMap indicesMetadata = clusterStateResponse.getState().getMetadata().indices(); + Map indicesMetadata = clusterStateResponse.getState().getMetadata().indices(); if (!indicesMetadata.containsKey(expectedIndexName)) { mismatchDescription.appendValue("Index does not exist"); } diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 2c0e7ac7c0..b77b7a000f 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -37,6 +38,7 @@ import java.util.StringJoiner; import java.util.regex.Pattern; +import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import org.apache.logging.log4j.LogManager; @@ -78,7 +80,6 @@ import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.Strings; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.concurrent.ThreadContext; @@ -378,8 +379,6 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin presponse.allowed = true; return presponse; } - - } } @@ -632,6 +631,7 @@ public static boolean isClusterPerm(String action0) { ) ; } + @SuppressWarnings("unchecked") private boolean checkFilteredAliases(Resolved requestedResolved, String action, boolean isDebugEnabled) { final String faMode = dcm.getFilteredAliasMode();// getConfigSettings().dynamic.filtered_alias_mode; @@ -649,7 +649,7 @@ private boolean checkFilteredAliases(Resolved requestedResolved, String action, indexMetaDataCollection = new Iterable() { @Override public Iterator iterator() { - return clusterService.state().getMetadata().getIndices().valuesIt(); + return clusterService.state().getMetadata().getIndices().values().iterator(); } }; } else { @@ -674,14 +674,17 @@ public Iterator iterator() { final List filteredAliases = new ArrayList(); - final ImmutableOpenMap aliases = indexMetaData.getAliases(); + final Map aliases = new HashMap<>(); + for (ObjectObjectCursor cursor : indexMetaData.getAliases()) { + aliases.put(cursor.key, cursor.value); + } if(aliases != null && aliases.size() > 0) { if (isDebugEnabled) { log.debug("Aliases for {}: {}", indexMetaData.getIndex().getName(), aliases); } - final Iterator it = aliases.keysIt(); + final Iterator it = aliases.keySet().iterator(); while(it.hasNext()) { final String alias = it.next(); final AliasMetadata aliasMetadata = aliases.get(alias); From 6997f97f950683c1125ac5b0bb797c40fe8877e8 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 26 Apr 2023 17:45:15 -0400 Subject: [PATCH 170/369] Switch from ImmutableOpenMap to Map in AliasExistsMatcher (#2725) * Remove references to ObjectObjectCursor Signed-off-by: Craig Perkins --- .../test/framework/matcher/AliasExistsMatcher.java | 10 ++-------- .../ClusterContainTemplateWithAliasMatcher.java | 10 +--------- .../GetSettingsResponseContainsIndicesMatcher.java | 12 +++--------- .../security/privileges/PrivilegesEvaluator.java | 7 +------ 4 files changed, 7 insertions(+), 32 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java index 4999da8e95..4d8c81b037 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java @@ -10,7 +10,6 @@ package org.opensearch.test.framework.matcher; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -18,7 +17,6 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import org.hamcrest.Description; import org.hamcrest.TypeSafeDiagnosingMatcher; @@ -44,11 +42,7 @@ protected boolean matchesSafely(Client client, Description mismatchDescription) try { GetAliasesResponse response = client.admin().indices().getAliases(new GetAliasesRequest(aliasName)).get(); - final Map> aliases = new HashMap<>(); - for (ObjectObjectCursor> cursor : response.getAliases()) { - aliases.put(cursor.key, cursor.value); - } - + Map> aliases = response.getAliases(); Set actualAliasNames = StreamSupport.stream(spliteratorUnknownSize(aliases.values().iterator(), IMMUTABLE), false) .flatMap(Collection::stream) .map(AliasMetadata::getAlias) @@ -61,7 +55,7 @@ protected boolean matchesSafely(Client client, Description mismatchDescription) return true; } catch (InterruptedException | ExecutionException e) { mismatchDescription.appendText("Error occurred during checking if cluster contains alias ") - .appendValue(e); + .appendValue(e); return false; } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java index d59792e715..861aaa72e7 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java @@ -9,14 +9,12 @@ */ package org.opensearch.test.framework.matcher; -import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import org.hamcrest.Description; import org.hamcrest.TypeSafeDiagnosingMatcher; @@ -57,13 +55,7 @@ protected boolean matchesSafely(Client client, Description mismatchDescription) private Set getAliases(GetIndexTemplatesResponse response) { return response.getIndexTemplates() .stream() - .map(metadata -> { - Map aliases = new HashMap<>(); - for (ObjectObjectCursor cursor : metadata.getAliases()) { - aliases.put(cursor.key, cursor.value); - } - return aliases; - }) + .map(metadata -> metadata.getAliases()) .flatMap(aliasMap -> aliasNames(aliasMap)) .collect(Collectors.toSet()); } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java index 79ac1c64ac..01f14dd044 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java @@ -9,14 +9,11 @@ */ package org.opensearch.test.framework.matcher; -import java.util.HashMap; -import java.util.Map; - -import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import org.hamcrest.Description; import org.hamcrest.TypeSafeDiagnosingMatcher; import org.opensearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.settings.Settings; import static java.util.Objects.isNull; @@ -35,15 +32,12 @@ class GetSettingsResponseContainsIndicesMatcher extends TypeSafeDiagnosingMatche @Override protected boolean matchesSafely(GetSettingsResponse response, Description mismatchDescription) { - final Map indexToSettings = new HashMap<>(); - for (ObjectObjectCursor cursor : response.getIndexToSettings()) { - indexToSettings.put(cursor.key, cursor.value); - } + final ImmutableOpenMap indexToSettings = response.getIndexToSettings(); for (String index : expectedIndices) { if (!indexToSettings.containsKey(index)) { mismatchDescription .appendText("Response contains settings of indices: ") - .appendValue(indexToSettings.keySet()); + .appendValue(indexToSettings.keys()); return false; } } diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index b77b7a000f..8f490d180d 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -29,7 +29,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -38,7 +37,6 @@ import java.util.StringJoiner; import java.util.regex.Pattern; -import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import org.apache.logging.log4j.LogManager; @@ -674,10 +672,7 @@ public Iterator iterator() { final List filteredAliases = new ArrayList(); - final Map aliases = new HashMap<>(); - for (ObjectObjectCursor cursor : indexMetaData.getAliases()) { - aliases.put(cursor.key, cursor.value); - } + final Map aliases = indexMetaData.getAliases(); if(aliases != null && aliases.size() > 0) { if (isDebugEnabled) { From 739074dad8df31d0c6d23e36f89030d75d869baf Mon Sep 17 00:00:00 2001 From: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Date: Fri, 28 Apr 2023 11:48:32 -0400 Subject: [PATCH 171/369] Removes a missed reference for ImmutableOpenMap (#2726) Signed-off-by: Darshit Chanpura --- .../matcher/GetSettingsResponseContainsIndicesMatcher.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java index 01f14dd044..e4ed4ac6c4 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java @@ -9,11 +9,12 @@ */ package org.opensearch.test.framework.matcher; +import java.util.Map; + import org.hamcrest.Description; import org.hamcrest.TypeSafeDiagnosingMatcher; import org.opensearch.action.admin.indices.settings.get.GetSettingsResponse; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.settings.Settings; import static java.util.Objects.isNull; @@ -32,12 +33,12 @@ class GetSettingsResponseContainsIndicesMatcher extends TypeSafeDiagnosingMatche @Override protected boolean matchesSafely(GetSettingsResponse response, Description mismatchDescription) { - final ImmutableOpenMap indexToSettings = response.getIndexToSettings(); + final Map indexToSettings = response.getIndexToSettings(); for (String index : expectedIndices) { if (!indexToSettings.containsKey(index)) { mismatchDescription .appendText("Response contains settings of indices: ") - .appendValue(indexToSettings.keys()); + .appendValue(indexToSettings.keySet()); return false; } } From 3a54873ed5790d373814c65134270b6c343d4aa7 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Tue, 2 May 2023 16:25:19 -0400 Subject: [PATCH 172/369] Fix triage query (#2728) Signed-off-by: Stephen Crawford --- TRIAGING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TRIAGING.md b/TRIAGING.md index 88ed2412ff..2c4ea32fdf 100644 --- a/TRIAGING.md +++ b/TRIAGING.md @@ -24,7 +24,7 @@ Meetings are lightly structured as follows: 1. Announcements: If there are any announcements to be made they will happen at the start of the meeting. 2. Review of new issues: The meetings always start with reviewing all untriaged [issues](https://github.com/search?q=label%3Auntriaged+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=created&o=desc) for the security and security-dashboards repositories. -3. Untriaged items: Review any [issues](https://github.com/search?q=-label%3Atriaged+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=created&o=desc) that might have had the 'untriaged' label removed but require additional triage discussion. +3. Untriaged items: Review any [issues](https://github.com/search?q=-label%3Atriaged+is%3Aopen++is%3Aissue+repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues) that might have had the 'untriaged' label removed but require additional triage discussion. 4. Open discussion: Next, we open the floor in case anyone wants to highlight an issue. 5. Backlog discussion: Then, we review issues from the [backlogs](https://github.com/search?q=label%3A%22sprint+backlog%22+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=created&o=desc) of the security and security-dashboards repositories. 6. Least recent discussed issue: Finally, to close out the meeting we will [review the oldest](https://github.com/search?q=+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=updated&o=asc) issues from both repositories, security and security-dashboards, to help identify issues that have languished. From a5489a2e8e33afb4f028e3fc873b542ef1a2bc2b Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Tue, 2 May 2023 16:50:38 -0700 Subject: [PATCH 173/369] Add diagram of how transport actions are checked (#2660) * Add diagram of how transport actions are checked Signed-off-by: Peter Nied Signed-off-by: Peter Nied --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index f0f6321d9d..b9ca3b80da 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ OpenSearch Security is a plugin for OpenSearch that offers encryption, authentic - [Test and Build](#test-and-build) - [Config hot reloading](#config-hot-reloading) - [Onboarding new APIs](#onboarding-new-apis) + - [System Index Protection](#system-index-protection) - [Contributing](#contributing) - [Getting Help](#getting-help) - [Code of Conduct](#code-of-conduct) @@ -119,6 +120,38 @@ It is common practice to create new transport actions to perform different tasks 2. Register the action in the [OpenSearch Security plugin](https://github.com/opensearch-project/security). Each new action is registered in the plugin as a new permission. Usually, plugins will define different roles for their plugin (e.g., read-only access, write access). Each role will contain a set of permissions. An example of adding a new permission to the `anomaly_read_access` role for the [Anomaly Detection plugin](https://github.com/opensearch-project/anomaly-detection) can be found in [this PR](https://github.com/opensearch-project/security/pull/997/files). 3. Register the action in the [OpenSearch Dashboards Security plugin](https://github.com/opensearch-project/security-dashboards-plugin). This plugin maintains the full list of possible permissions, so users can see all of them when creating new roles or searching permissions via Dashboards. An example of adding different permissions can be found in [this PR](https://github.com/opensearch-project/security-dashboards-plugin/pull/689/files). +```mermaid +sequenceDiagram + participant Client + participant OpenSearch + participant SecurityPlugin + participant Cluster as Plugin + + Client->>OpenSearch: Request + OpenSearch->>SecurityPlugin: Request + SecurityPlugin->>SecurityPlugin: Add Auth information to request context + OpenSearch->>Cluster: Client Request + Cluster->>SecurityPlugin: Execute transport layer action + SecurityPlugin->>SecurityPlugin: Check if action is allowed + alt Allowed + SecurityPlugin->>OpenSearch: Continue request + OpenSearch-->>Cluster: Transport layer action result + else Denied + SecurityPlugin-->>OpenSearch: Return 403 Forbidden + OpenSearch-->>Client: 403 Forbidden + end + alt Plugin run outside user context + Cluster->>Cluster: Stash context + Cluster->>SecurityPlugin: Execute transport layer action outside user context + SecurityPlugin-->>SecurityPlugin: Check if action is allowed + SecurityPlugin->>OpenSearch: Continue request + OpenSearch-->>Cluster: Transport layer action result + Cluster->>Cluster: Restore user context + end + Cluster-->>SecurityPlugin: Result + SecurityPlugin-->>OpenSearch: Result + OpenSearch-->>Client: Result +``` ### System Index Protection From 11636ce0239bf946b6ded248058483763c37e183 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 2 May 2023 22:29:08 -0400 Subject: [PATCH 174/369] Use hostname verification for SecurityAdmin (#2541) * Use hostname verification for SecurityAdmin Signed-off-by: Craig Perkins --- .../security/tools/SecurityAdmin.java | 1 + .../security/SecurityAdminTests.java | 65 +++++++++++++++++ .../securityadmin/certificate_generation.md | 23 ++++++ src/test/resources/securityadmin/kirk.crt.pem | 69 ++++++++++++++++++ src/test/resources/securityadmin/kirk.key.pem | 28 ++++++++ src/test/resources/securityadmin/node.crt.pem | 71 +++++++++++++++++++ src/test/resources/securityadmin/node.key.pem | 28 ++++++++ src/test/resources/securityadmin/root-ca.pem | 24 +++++++ 8 files changed, 309 insertions(+) create mode 100644 src/test/resources/securityadmin/certificate_generation.md create mode 100644 src/test/resources/securityadmin/kirk.crt.pem create mode 100644 src/test/resources/securityadmin/kirk.key.pem create mode 100644 src/test/resources/securityadmin/node.crt.pem create mode 100644 src/test/resources/securityadmin/node.key.pem create mode 100644 src/test/resources/securityadmin/root-ca.pem diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 161ad72528..649b2c2b03 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -1410,6 +1410,7 @@ private static RestHighLevelClient getRestHighLevelClient(SSLContext sslContext, .setSslContext(sslContext) .setTlsVersions(supportedProtocols) .setCiphers(supportedCipherSuites) + .setHostnameVerifier(hnv) // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 .setTlsDetailsFactory(new Factory() { @Override diff --git a/src/test/java/org/opensearch/security/SecurityAdminTests.java b/src/test/java/org/opensearch/security/SecurityAdminTests.java index e7953c508a..bd032fc332 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminTests.java @@ -19,6 +19,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; @@ -37,6 +38,10 @@ import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; import org.opensearch.security.tools.SecurityAdmin; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.matchesPattern; +import static org.junit.Assert.assertThrows; + public class SecurityAdminTests extends SingleClusterTest { @Test @@ -71,6 +76,66 @@ public void testSecurityAdmin() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); } + @Test + public void testSecurityAdminHostnameVerificationEnforced() throws Exception { + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/root-ca.pem")) + .put("plugins.security.ssl.http.pemcert_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.crt.pem")) + .put("plugins.security.ssl.http.pemkey_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.key.pem")) + .putList("plugins.security.authcz.admin_dn", List.of("CN=kirk,OU=client,O=client,L=test,C=de")) + .build(); + setup(Settings.EMPTY, null, settings, false); + + final String prefix = getResourceFolder()==null?"securityadmin/":getResourceFolder()+"/securityadmin/"; + + List argsAsList = new ArrayList<>(); + argsAsList.add("-cacert"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"root-ca.pem").toFile().getAbsolutePath()); + argsAsList.add("-cert"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk.crt.pem").toFile().getAbsolutePath()); + argsAsList.add("-key"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk.key.pem").toFile().getAbsolutePath()); + argsAsList.add("-p"); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); + argsAsList.add("-icl"); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); + + final IOException expectedException = assertThrows(IOException.class, () -> SecurityAdmin.execute(argsAsList.toArray(new String[0]))); + final String expectedMessagePattern = "Certificate for <.+> doesn't match any of the subject alternative names: \\[node-.\\.example\\.com\\]"; + assertThat(expectedException.getMessage(), matchesPattern(expectedMessagePattern)); + } + + @Test + public void testSecurityAdminHostnameVerificationNotEnforced() throws Exception { + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/root-ca.pem")) + .put("plugins.security.ssl.http.pemcert_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.crt.pem")) + .put("plugins.security.ssl.http.pemkey_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.key.pem")) + .putList("plugins.security.authcz.admin_dn", List.of("CN=kirk,OU=client,O=client,L=test,C=de")) + .build(); + setup(Settings.EMPTY, null, settings, false); + + final String prefix = getResourceFolder()==null?"securityadmin/":getResourceFolder()+"/securityadmin/"; + + List argsAsList = new ArrayList<>(); + argsAsList.add("-cacert"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"root-ca.pem").toFile().getAbsolutePath()); + argsAsList.add("-cert"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk.crt.pem").toFile().getAbsolutePath()); + argsAsList.add("-key"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk.key.pem").toFile().getAbsolutePath()); + argsAsList.add("-p"); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); + argsAsList.add("-icl"); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); + argsAsList.add("-nhnv"); + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + Assert.assertEquals(0, returnCode); + } + @Test public void testSecurityAdminInvalidCert() throws Exception { final Settings settings = Settings.builder() diff --git a/src/test/resources/securityadmin/certificate_generation.md b/src/test/resources/securityadmin/certificate_generation.md new file mode 100644 index 0000000000..ae60ae5a07 --- /dev/null +++ b/src/test/resources/securityadmin/certificate_generation.md @@ -0,0 +1,23 @@ +# Script to generate certificates for SecurityAdmin Tests + +``` +openssl genrsa -out root-ca-key.pem 2048 +openssl req -x509 -sha256 -new -nodes -key root-ca-key.pem -subj "/DC=com/DC=example/O=Example Com Inc./OU=Example Com Inc. Root CA/CN=Example Com Inc. Root CA" -days 3650 -out root-ca.pem +openssl genrsa -out signing-key.pem 2048 +openssl req -x509 -sha256 -new -nodes -CA root-ca.pem -CAkey root-ca-key.pem -key signing-key.pem -subj "/DC=com/DC=example/O=Example Com Inc./OU=Example Com Inc. Signing CA/CN=Example Com Inc. Signing CA" -days 3650 -out signing.pem + +openssl genrsa -out node-key-temp.pem 2048 +openssl pkcs8 -inform PEM -outform PEM -in node-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out node.key.pem +openssl req -new -key node.key.pem -subj "/C=DE/L=Test/O=Test/OU=SSL/CN=node-1.example.com" -out node.csr +openssl x509 -req -days 3650 -extfile <(printf "subjectAltName=DNS:node-1.example.com,IP:127.0.0.1") -in node.csr -out node.crt.pem -CA signing.pem -CAkey signing-key.pem + +# CN=kirk,OU=client,O=client,L=Test,C=DE +openssl genrsa -out kirk-key-temp.pem 2048 +openssl pkcs8 -inform PEM -outform PEM -in kirk-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out kirk.key.pem +openssl req -new -key kirk.key.pem -subj "/C=DE/L=Test/O=client/OU=client/CN=kirk" -out kirk.csr +openssl x509 -req -days 3650 -in kirk.csr -out kirk.crt.pem -CA signing.pem -CAkey signing-key.pem +``` + +For `kirk.crt.pem` and `node.crt.pem` all certificates in the chain including `root-ca.pem` and `signing.pem` need to be included in the file. + +When bundling the certificates together in the same file the root certificate is placed at the bottom and the lowest level certificate (the node certificate) on the top. diff --git a/src/test/resources/securityadmin/kirk.crt.pem b/src/test/resources/securityadmin/kirk.crt.pem new file mode 100644 index 0000000000..b126e36c56 --- /dev/null +++ b/src/test/resources/securityadmin/kirk.crt.pem @@ -0,0 +1,69 @@ +-----BEGIN CERTIFICATE----- +MIIDajCCAlICFCVxBZmleOHXHqoyn6dQlHVWZ/t8MA0GCSqGSIb3DQEBCwUAMIGV +MRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZ +MBcGA1UECgwQRXhhbXBsZSBDb20gSW5jLjEkMCIGA1UECwwbRXhhbXBsZSBDb20g +SW5jLiBTaWduaW5nIENBMSQwIgYDVQQDDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25p +bmcgQ0EwHhcNMjMwNTAyMTc1NzA2WhcNMzMwNDI5MTc1NzA2WjBNMQswCQYDVQQG +EwJERTENMAsGA1UEBwwEVGVzdDEPMA0GA1UECgwGY2xpZW50MQ8wDQYDVQQLDAZj +bGllbnQxDTALBgNVBAMMBGtpcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDFYEoC+qyqLKhNpSAj3qUfhGRNmoHlpDRG2Zq+wAx6e24pODNGtyrtswF7 +7Nf3HgODMrFMCg/gJC6U78VbI4hPO63E+nQr3Q2h7kdn7E4t1VJOUY4YFROyvayD +epDWmIGwer0H+Wd+7t6TrQod/Hj5do3og5IgBaK1AS4OExanmuJ10WrfzctS9dg4 +xY2RT7pmNWVeOA1IdkPRu5T7jr72n66jSuwqbTiS+vQHdqgZsXUC+DtvMtRmRYo0 +QT4nndNYA72FFKH9bmKiLvNyeTMAn45fE+ebiZGFTcK7e5hZ+l6YTWvUlGoS54t+ +2kNxTaHl3NXr9KCwF7lT/HoS42RnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFNe +E2ClU0OxVk5nWmQUnr3MmsFDaBe/0CfGBHLcixqRenaGlwGcrUB4B2mYF3xkGRhF +xrd2lJy3bMxYxl5Zp63atdK5s7JnHSatPFGxwJJ/9BRDeZtx0X42mCspb1ho+0yV +bUVYOiy3G/Nt7erfRb8a6ZlWk3Ri2HZ/OG3jQnQCLPstNZ5DeRlM33ltiHj3EDlz +PyRgp+n89FLKZjImY4zJdjBKdfky2PKKZGJJ+57L+fIu/2TR17Qeaxf4cRa6DWtX +8fwRHkrj9MVLvdASLwFKfdEefw/uTPLigwdrydjy+AFogfpmBvJ9CXqCq81lSROr +Pzbo7NaChtZ6Mxgd3fU= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgIUC1KT/DcaMNL7fjY7g361424pyWowDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA1MDIxNzU3MDVaFw0zMzA0MjkxNzU3MDVaMIGVMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEkMCIGA1UECwwbRXhhbXBsZSBDb20gSW5jLiBTaWdu +aW5nIENBMSQwIgYDVQQDDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxgE4w07GMwoHzxYlXSmNYv3Lx +iPvftzWrFfA15dyHcCrYnoTN+re5uTi8L40Obzr7BzL41mFHED85EfvGYl02MCai +jA2MHE7vh1JxKJ4oLMI0jtXYaBPXDb96qcfCMC+Vce0RH+I815nip1Amf/M/jMov +KlGGYSipa4otfj8ZrinMItFucpY/mEsFgzO+yQ+Go2gzyJlNeJXIuhsfdvkq76X8 ++oNrltHL1f/Y1HP7qV5eZn6uODSesHGK1VCLg7UIRk3aZRmAo5ZwTV5xb3rKhxDJ +mvBtK77TcG9CA3MXOM204G7n85aYN+Xrb+c5xQjzsR6bo1O4I37fn8sSP9/hAgMB +AAGjUzBRMB0GA1UdDgQWBBTotToiEVpUwaXfM1lPhT4Cf+wxpzAfBgNVHSMEGDAW +gBTeMJiA4CPf0XcafDPDTzO+iylLfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQAfiHlJtB+NWDS68BRnpil500PPQzB0edqhO3h/2tnXmxtXKbH/ +0sKgCvPn3tEK8y5WrzC2UDB2F594TlGUWHiMwy1SwMkrP8gUpDS2syisaORyQ7/1 +aOktoD0eBZEWFJtGLCPR7uz/KtbZo6QsAZYhxqdE9Dn2Dw2d0YcIFogCG9ohAIWm +6Hss2CTs2z1YKXJqYb+o0jwFDRF0H7cd5HeFaMQIkz9hLURInUU3JYemONzaxc26 +peelfPOmDySzaQjqn4lQRXXze79fMs3G5hD+f8WvQqtJkSGS1CXnLvZ8PBf12jxZ +0SkzxLWyKOSz6M3tXonaxsLMXF+4dM48+NOm +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEATCCAumgAwIBAgIUUe5xSfjzHNOkaqCRf5AIYXQQM3cwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA1MDIxNzU3MDVaFw0zMzA0MjkxNzU3MDVaMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTQTojW8vphvADeNvMhFyfV0p7EA77bxQf +XBzbwGXqjeS4X1WeisbOi+HvBvrmg3olzzA2vVH+5gT+5S6Q62BX4oyCyyqoK/3n +gc+8JBLGpACEeLQotLE238L8wzM+L4WblZretvAi85JZ09ur0yZ7C6QE3QeGMRrL +9OjHuCtzSAJO3t8uuf+IwDMM/8k822reski+iVsNxHVsBkTDFbHbVKFuHadqaMRp +G2wFINnSi4L/hMAQtIvJasjiW26kZKLd8WckDYGgZaFc1l46RR7Pj/lULBCdc86X +INuL1M411RjB08tqMTTjqvQhMWlv+qVkoVlyx97iFKWo5gNz2FbRAgMBAAGjUzBR +MB0GA1UdDgQWBBTeMJiA4CPf0XcafDPDTzO+iylLfzAfBgNVHSMEGDAWgBTeMJiA +4CPf0XcafDPDTzO+iylLfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQAz7tZirV9htIc3bNE0IxJ1F1oMfQChH4kgZiw8coLZ6dElzUzBhF3JZEyL +CDxnI0Q94l+Wg6KGUNSAqlYcXbcWYhgml0B6oCGp30GlyhbK16OrapKcHitjYoKB +rNtf5H4Ks0/I9YK9NKCLrFPsp9Qt5qStQuhZcumJbct8irXLPmrVTLKrIqCkBmP5 +7P7v9Vud5/TxWTjLUZo+eS/AkJurOdDZDf+lVmpcbsez6HsSusNu5E7BDwLcPIFQ +MukDp/SRLInq8I8cA5t5U+tiQgsUCdLMIaLQ72EJuCId9XB8oyhP/rOJy+xwNnLW +ZngkAWtN8JWNoaA8FkLYbJOGLikP +-----END CERTIFICATE----- diff --git a/src/test/resources/securityadmin/kirk.key.pem b/src/test/resources/securityadmin/kirk.key.pem new file mode 100644 index 0000000000..ea7127c156 --- /dev/null +++ b/src/test/resources/securityadmin/kirk.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDFYEoC+qyqLKhN +pSAj3qUfhGRNmoHlpDRG2Zq+wAx6e24pODNGtyrtswF77Nf3HgODMrFMCg/gJC6U +78VbI4hPO63E+nQr3Q2h7kdn7E4t1VJOUY4YFROyvayDepDWmIGwer0H+Wd+7t6T +rQod/Hj5do3og5IgBaK1AS4OExanmuJ10WrfzctS9dg4xY2RT7pmNWVeOA1IdkPR +u5T7jr72n66jSuwqbTiS+vQHdqgZsXUC+DtvMtRmRYo0QT4nndNYA72FFKH9bmKi +LvNyeTMAn45fE+ebiZGFTcK7e5hZ+l6YTWvUlGoS54t+2kNxTaHl3NXr9KCwF7lT +/HoS42RnAgMBAAECggEADtRZOzgSWQbZ7luFuqwzw9ZyotIFCHf55Yjb85ECXwF/ +GWG7mIiSlSFp7yGwaES9BtJ8N7ZZ0wFk7pPFRD+7MhjNyYr3x4PoTk5U1x4OEauB +b5j5EB4lSLyvhYFj+Huk4tmV8k9u0z6nQnkx1Wbuv++EYf/grr89plPcXfpZLWZ3 +0y84mR/ENYXtbtPSwLgMR6OY+q36hqc2a9+si4j/P0jf37jfyE8AlTGWEGdKQaN8 +iLLfyXpKxHn8zweZ3kWzOS2mZhya15S9F5YUS+d3TOt1J/3EcxMu/UJMxWBN/Tea +JvMZUgMTVyg4RN/MOzvjOUDKOUdgGIdPI6G9/nfs4QKBgQDzQPqxJBgy5BUVXOAO +/lT4QvMFnKiPxedDkSHHwPXwx+dqL6Bk/1mco3P6VDX5OfB4xp0NWkjekfyU01Ab +jvULO2MaH3MEiQuDuYs3NDY+lT/YGdlB7Asd8AMr8Clu5DHTjyz6KefS+stOfbrn +smAmpk/TdTM/lQfhZPiU4FVhSwKBgQDPt+dfqw2zm+T5g3KeJ/Ej5UsThiNJseIh +KF5NlPrRfsdIybkcbDtIiHlJ3qVn6Tq8zEvEANdwYd3/7orFilUahc9zlA7z9O2L +tRu8I9zjI3vjArzO0Wkh1NxDoGzQX6giTHDpL7+m/bjc3pj1RXjlP8IRHx7j6w3H ++ciKuTaz1QKBgG1UBBg/f7zHtA4g6vbyKiBWfsFD8qKDsPg2L3eG60KnpgOcmjsq +ZQ04jXSyCnwUJVcy9P0+Wcfm1x3Qh42LR+kfbOAdyGT+bzVp2/8YsVSZYdNvcqzl +OO3gpJxH2WdkmlxaWj2pPe8eFugVLD7cdciJMRF5+GmYQq1z4yGOXfFXAoGAV2yA +fhxhPOntGjL/x57p+ACmc4YuTfMHSItT/XUph4jDWVhFh7fpz6JY4gVKOozIAvQ9 +IzZzdkJKjFAaqf+JyArvgCadkIHShM1p6epyKksh9i6NxsIObIXJWtEnWyAXhLAF +ia9mC2OYLaWmXPyrYFlQVaJyftzMRRFVHUXMxy0CgYBT2+xAwAMxj4HVtwBthQXH +0Akawz9Gvw7/BJOGBRzJMM6H5qqejWbA/FKmvySdK2IrQ6dISjrVMr7VtjMrwumM +JO3T54O4iuFpNck1z+d4BZjvWpsxpPRg4RktwhMtJ2BZYh43aI4pFfxs6YFga+ZK +vMm+70bHS8AskkmaxAFzHw== +-----END PRIVATE KEY----- diff --git a/src/test/resources/securityadmin/node.crt.pem b/src/test/resources/securityadmin/node.crt.pem new file mode 100644 index 0000000000..adb2996edb --- /dev/null +++ b/src/test/resources/securityadmin/node.crt.pem @@ -0,0 +1,71 @@ +-----BEGIN CERTIFICATE----- +MIID4TCCAsmgAwIBAgIUKmE8oZm3QVdGZWbKLY8S4F05UugwDQYJKoZIhvcNAQEL +BQAwgZUxEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSQwIgYDVQQLDBtFeGFtcGxl +IENvbSBJbmMuIFNpZ25pbmcgQ0ExJDAiBgNVBAMMG0V4YW1wbGUgQ29tIEluYy4g +U2lnbmluZyBDQTAeFw0yMzA1MDIxODI0MzFaFw0zMzA0MjkxODI0MzFaMFYxCzAJ +BgNVBAYTAkRFMQ0wCwYDVQQHDARUZXN0MQ0wCwYDVQQKDARUZXN0MQwwCgYDVQQL +DANTU0wxGzAZBgNVBAMMEm5vZGUtMS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKfvsczlQQAPg0iivG6O5kazZjS8z3dM6b/hmtw7 +NSlihZeXgPOoyd1BttCNB1fo/TsnikHggWRVHj8v01kNJtKmvgdtTYJIUJEZJdEH +NHFXmxR+3YhmZr1qKdkihB0Z7rv+oYrHe2MOg8jtt3VPZuOIZZXfIJWw93CajXaO +zNv/sNdPdaPI8tTJIzihYNVcMtx5koPS66xB2Rwp06MfvJ568BAXxl2QSRjWFX4v +CFOzKL8nZtr779HemWFABzCllzff7xLcjWbkaoTk7gPrgh6vzW0hE2pLAbNnlre3 +rommG/zzKgmytZ+PTvbEyhiuNjVgcCG2vhGu8myRYgoGZw8CAwEAAaNnMGUwIwYD +VR0RBBwwGoISbm9kZS0xLmV4YW1wbGUuY29thwR/AAABMB0GA1UdDgQWBBQ04XN1 +I1bs3kMQrOhNfL/rxZpeUzAfBgNVHSMEGDAWgBTotToiEVpUwaXfM1lPhT4Cf+wx +pzANBgkqhkiG9w0BAQsFAAOCAQEADAaqiuJBzK8/PYN30Xx6QnKnwj4GlzzSVY/O +AzNgfUL7QH1k6tBNlgyFom2UozIZvFuCdfJg6X5+BFWXSh4LuvODzudWUQM3bjh4 +JZJYOTTOmT6lBi+KAPbwpirj+XUVvqNlAew81b0n63uWh8yeGBa/5G0G28Dyu6Ma +E1jZDmKVLZqGByhM46IUxov7aDFk4elM2nH0JVZpFPbTjjN5bLefqwIW8Y7NjU9s +8Zvv4fFtsoKqPhFhPAMTjlg7bMOqtWIw95w0L1fRz9yCuN0LAjlqCFKDGLmaWqFK +GaQKbw86KKrPfl8y9yqBgfUN69NmE5qhgmobu0so5Yy5V69BwQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgIUC1KT/DcaMNL7fjY7g361424pyWowDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA1MDIxNzU3MDVaFw0zMzA0MjkxNzU3MDVaMIGVMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEkMCIGA1UECwwbRXhhbXBsZSBDb20gSW5jLiBTaWdu +aW5nIENBMSQwIgYDVQQDDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxgE4w07GMwoHzxYlXSmNYv3Lx +iPvftzWrFfA15dyHcCrYnoTN+re5uTi8L40Obzr7BzL41mFHED85EfvGYl02MCai +jA2MHE7vh1JxKJ4oLMI0jtXYaBPXDb96qcfCMC+Vce0RH+I815nip1Amf/M/jMov +KlGGYSipa4otfj8ZrinMItFucpY/mEsFgzO+yQ+Go2gzyJlNeJXIuhsfdvkq76X8 ++oNrltHL1f/Y1HP7qV5eZn6uODSesHGK1VCLg7UIRk3aZRmAo5ZwTV5xb3rKhxDJ +mvBtK77TcG9CA3MXOM204G7n85aYN+Xrb+c5xQjzsR6bo1O4I37fn8sSP9/hAgMB +AAGjUzBRMB0GA1UdDgQWBBTotToiEVpUwaXfM1lPhT4Cf+wxpzAfBgNVHSMEGDAW +gBTeMJiA4CPf0XcafDPDTzO+iylLfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQAfiHlJtB+NWDS68BRnpil500PPQzB0edqhO3h/2tnXmxtXKbH/ +0sKgCvPn3tEK8y5WrzC2UDB2F594TlGUWHiMwy1SwMkrP8gUpDS2syisaORyQ7/1 +aOktoD0eBZEWFJtGLCPR7uz/KtbZo6QsAZYhxqdE9Dn2Dw2d0YcIFogCG9ohAIWm +6Hss2CTs2z1YKXJqYb+o0jwFDRF0H7cd5HeFaMQIkz9hLURInUU3JYemONzaxc26 +peelfPOmDySzaQjqn4lQRXXze79fMs3G5hD+f8WvQqtJkSGS1CXnLvZ8PBf12jxZ +0SkzxLWyKOSz6M3tXonaxsLMXF+4dM48+NOm +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEATCCAumgAwIBAgIUUe5xSfjzHNOkaqCRf5AIYXQQM3cwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA1MDIxNzU3MDVaFw0zMzA0MjkxNzU3MDVaMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTQTojW8vphvADeNvMhFyfV0p7EA77bxQf +XBzbwGXqjeS4X1WeisbOi+HvBvrmg3olzzA2vVH+5gT+5S6Q62BX4oyCyyqoK/3n +gc+8JBLGpACEeLQotLE238L8wzM+L4WblZretvAi85JZ09ur0yZ7C6QE3QeGMRrL +9OjHuCtzSAJO3t8uuf+IwDMM/8k822reski+iVsNxHVsBkTDFbHbVKFuHadqaMRp +G2wFINnSi4L/hMAQtIvJasjiW26kZKLd8WckDYGgZaFc1l46RR7Pj/lULBCdc86X +INuL1M411RjB08tqMTTjqvQhMWlv+qVkoVlyx97iFKWo5gNz2FbRAgMBAAGjUzBR +MB0GA1UdDgQWBBTeMJiA4CPf0XcafDPDTzO+iylLfzAfBgNVHSMEGDAWgBTeMJiA +4CPf0XcafDPDTzO+iylLfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQAz7tZirV9htIc3bNE0IxJ1F1oMfQChH4kgZiw8coLZ6dElzUzBhF3JZEyL +CDxnI0Q94l+Wg6KGUNSAqlYcXbcWYhgml0B6oCGp30GlyhbK16OrapKcHitjYoKB +rNtf5H4Ks0/I9YK9NKCLrFPsp9Qt5qStQuhZcumJbct8irXLPmrVTLKrIqCkBmP5 +7P7v9Vud5/TxWTjLUZo+eS/AkJurOdDZDf+lVmpcbsez6HsSusNu5E7BDwLcPIFQ +MukDp/SRLInq8I8cA5t5U+tiQgsUCdLMIaLQ72EJuCId9XB8oyhP/rOJy+xwNnLW +ZngkAWtN8JWNoaA8FkLYbJOGLikP +-----END CERTIFICATE----- diff --git a/src/test/resources/securityadmin/node.key.pem b/src/test/resources/securityadmin/node.key.pem new file mode 100644 index 0000000000..fd82ed2d2c --- /dev/null +++ b/src/test/resources/securityadmin/node.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCn77HM5UEAD4NI +orxujuZGs2Y0vM93TOm/4ZrcOzUpYoWXl4DzqMndQbbQjQdX6P07J4pB4IFkVR4/ +L9NZDSbSpr4HbU2CSFCRGSXRBzRxV5sUft2IZma9ainZIoQdGe67/qGKx3tjDoPI +7bd1T2bjiGWV3yCVsPdwmo12jszb/7DXT3WjyPLUySM4oWDVXDLceZKD0uusQdkc +KdOjH7yeevAQF8ZdkEkY1hV+LwhTsyi/J2ba++/R3plhQAcwpZc33+8S3I1m5GqE +5O4D64Ier81tIRNqSwGzZ5a3t66Jphv88yoJsrWfj072xMoYrjY1YHAhtr4RrvJs +kWIKBmcPAgMBAAECggEAMkXkISVkHwOF1qG47RPkRbgA2brIFLu2ohWEiXdEA96V +hXr6RHb77ztz4dzGHQAHhsTgc7YkpgeBJYNIrrjsLVVzP7/t2xmQ3M79biTNAz0p +lKoh4WpeSUfVvUXC7P9NY4PnkicDffTjaKwZJoodj/HOD16bX5R5joEF5j77fsQA +3DkCD9JxNKxRXz0lH5ICExItQEFweDDT749LunEBC2BDHQ6UDO1JGC7Js9D/ri9C +sNB/1OMO0AV/g7V0pMD6GlkO48UVYK9kxBZQAgI9YiRtlkVyD4Uy/CxzptJepkRW +uqx4lD5F98bKpwleqsPNkFCj1ifXd8WAFAD4wkC8mQKBgQC8+JLgFVSmBzzbQSmM +3iaqcm2wkhWIESV/rEtHvgJCtBZYT1NBekWdZmODfyU0Q2E/lPAYH2LgT+jgoMZ/ +JkvkrMAv8Cgh4J3Jklku29R8YyTYdouje62a69p6u02rbZFDQEeVfVVEHApnngpd +YVmOm9eSBTBya+1lpO3H394UwwKBgQDjgRNhLoO8ce1V+Txmad1/rwfst364adXf +UsfxTIP6wJTQXdm1ZVeqswKaGsVAaq6fX83XLT1H47c5newoll3V8KPLjAiAy/VR +MDMKj2Rr4ojOshdiN+5wiPOVAgmrGg8eTH0wdNchtUAbI31fzzC9n8uYl146bax3 +I4NwyOMPxQKBgQCOdhtMQeh57lTrullXsJaXwwJ8rfT7immpsbtjD5Tmsptx4gOT +Bln7CpiVJsJmfzGOXHsQxICnOLcIuUxLyRRIBhAxU6z9tTdfIiyHzgSH7bp2UhB9 +pBzCAXLJOfGY/lYXzBrrUPx6B2W0rgmEUoLQpx5CIBVg/YqQKWF1YIktPwKBgQDF +7nSn5koi15O/atoL2CsnfWaNoo+TbjDu3RyraQCiVo6iQiS5VvRQxPGMlaHri2Vl +r3psrSVVuF6euDDQlxIIohY/bxOuysQh4Kdnlp2t5ydTfUou366JJf2WNHGo9UEW +AUIhuGW7I/AkLFpV0vL6513A4mDOwMB93t3qcDxsaQKBgAyXuK+6SpVkGj0HgGBH +PyAzkoAoTcnv5gBDs/p4MiAigkAl3WqhoouWYUP2nCiQZmA4rbXcNVdClebHBD64 +jmgfXo8E2i+MHhbAWyNSWrHL7punrcdOFETw1JrvFxhPORnDLwpyfSL9DC3JfNmD +RBM+Z8nCKBWcmxtCpMqmdbdR +-----END PRIVATE KEY----- diff --git a/src/test/resources/securityadmin/root-ca.pem b/src/test/resources/securityadmin/root-ca.pem new file mode 100644 index 0000000000..eed8cd0b06 --- /dev/null +++ b/src/test/resources/securityadmin/root-ca.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEATCCAumgAwIBAgIUUe5xSfjzHNOkaqCRf5AIYXQQM3cwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA1MDIxNzU3MDVaFw0zMzA0MjkxNzU3MDVaMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTQTojW8vphvADeNvMhFyfV0p7EA77bxQf +XBzbwGXqjeS4X1WeisbOi+HvBvrmg3olzzA2vVH+5gT+5S6Q62BX4oyCyyqoK/3n +gc+8JBLGpACEeLQotLE238L8wzM+L4WblZretvAi85JZ09ur0yZ7C6QE3QeGMRrL +9OjHuCtzSAJO3t8uuf+IwDMM/8k822reski+iVsNxHVsBkTDFbHbVKFuHadqaMRp +G2wFINnSi4L/hMAQtIvJasjiW26kZKLd8WckDYGgZaFc1l46RR7Pj/lULBCdc86X +INuL1M411RjB08tqMTTjqvQhMWlv+qVkoVlyx97iFKWo5gNz2FbRAgMBAAGjUzBR +MB0GA1UdDgQWBBTeMJiA4CPf0XcafDPDTzO+iylLfzAfBgNVHSMEGDAWgBTeMJiA +4CPf0XcafDPDTzO+iylLfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQAz7tZirV9htIc3bNE0IxJ1F1oMfQChH4kgZiw8coLZ6dElzUzBhF3JZEyL +CDxnI0Q94l+Wg6KGUNSAqlYcXbcWYhgml0B6oCGp30GlyhbK16OrapKcHitjYoKB +rNtf5H4Ks0/I9YK9NKCLrFPsp9Qt5qStQuhZcumJbct8irXLPmrVTLKrIqCkBmP5 +7P7v9Vud5/TxWTjLUZo+eS/AkJurOdDZDf+lVmpcbsez6HsSusNu5E7BDwLcPIFQ +MukDp/SRLInq8I8cA5t5U+tiQgsUCdLMIaLQ72EJuCId9XB8oyhP/rOJy+xwNnLW +ZngkAWtN8JWNoaA8FkLYbJOGLikP +-----END CERTIFICATE----- From 1ff823f07256e6bbeaef558910a643fec0cc0037 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 2 May 2023 23:30:37 -0400 Subject: [PATCH 175/369] Identify extension Transport requests and permit handshake and extension registration actions (#2599) * Identify extension Transport requests and permit handshake and extension registration actions Signed-off-by: Craig Perkins --- .../security/OpenSearchSecurityPlugin.java | 8 +++++++- .../security/support/ConfigConstants.java | 2 ++ .../security/support/HeaderHelper.java | 4 ++++ .../transport/SecurityRequestHandler.java | 16 +++++++++++++--- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 87ca878fc6..ed600af62b 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -94,6 +94,7 @@ import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; +import org.opensearch.extensions.ExtensionsManager; import org.opensearch.http.HttpServerTransport; import org.opensearch.http.HttpServerTransport.Dispatcher; import org.opensearch.index.Index; @@ -1187,13 +1188,16 @@ public static class GuiceHolder implements LifecycleComponent { private static IndicesService indicesService; private static PitService pitService; + private static ExtensionsManager extensionsManager; + @Inject public GuiceHolder(final RepositoriesService repositoriesService, - final TransportService remoteClusterService, IndicesService indicesService, PitService pitService) { + final TransportService remoteClusterService, IndicesService indicesService, PitService pitService, ExtensionsManager extensionsManager) { GuiceHolder.repositoriesService = repositoriesService; GuiceHolder.remoteClusterService = remoteClusterService.getRemoteClusterService(); GuiceHolder.indicesService = indicesService; GuiceHolder.pitService = pitService; + GuiceHolder.extensionsManager = extensionsManager; } public static RepositoriesService getRepositoriesService() { @@ -1210,6 +1214,8 @@ public static IndicesService getIndicesService() { public static PitService getPitService() { return pitService; } + public static ExtensionsManager getExtensionsManager() { return extensionsManager; } + @Override public void close() { diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index f81d89810b..fab88ecea9 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -97,6 +97,8 @@ public class ConfigConstants { public static final String OPENDISTRO_SECURITY_SSL_TRANSPORT_TRUSTED_CLUSTER_REQUEST = OPENDISTRO_SECURITY_CONFIG_PREFIX+"ssl_transport_trustedcluster_request"; + public static final String OPENDISTRO_SECURITY_SSL_TRANSPORT_EXTENSION_REQUEST = OPENDISTRO_SECURITY_CONFIG_PREFIX+"ssl_transport_extension_request"; + /** * Set by the SSL plugin, this is the peer node certificate on the transport layer diff --git a/src/main/java/org/opensearch/security/support/HeaderHelper.java b/src/main/java/org/opensearch/security/support/HeaderHelper.java index e9ff50c6af..d679548c2b 100644 --- a/src/main/java/org/opensearch/security/support/HeaderHelper.java +++ b/src/main/java/org/opensearch/security/support/HeaderHelper.java @@ -44,6 +44,10 @@ public static boolean isDirectRequest(final ThreadContext context) { return "direct".equals(context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_CHANNEL_TYPE)) || context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_CHANNEL_TYPE) == null; } + + public static boolean isExtensionRequest(final ThreadContext context) { + return context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_EXTENSION_REQUEST) == Boolean.TRUE; + } public static String getSafeFromHeader(final ThreadContext context, final String headerName) { diff --git a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java index d534e04f90..90fc8308ff 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java +++ b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java @@ -41,7 +41,9 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.extensions.ExtensionsManager; import org.opensearch.search.internal.ShardSearchRequest; +import org.opensearch.security.OpenSearchSecurityPlugin; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.auditlog.AuditLog.Origin; import org.opensearch.security.ssl.SslExceptionHandler; @@ -195,6 +197,7 @@ else if(!Strings.isNullOrEmpty(injectedUserHeader)) { //also allow when issued from a remote cluster for cross cluster search if ( !HeaderHelper.isInterClusterRequest(getThreadContext()) && !HeaderHelper.isTrustedClusterRequest(getThreadContext()) + && !HeaderHelper.isExtensionRequest(getThreadContext()) && !task.getAction().equals("internal:transport/handshake") && (task.getAction().startsWith("internal:") || task.getAction().contains("["))) { @@ -216,14 +219,14 @@ else if(!Strings.isNullOrEmpty(injectedUserHeader)) { transportChannel.sendResponse(ex); return; } else { - if(getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN) == null) { getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, Origin.TRANSPORT.toString()); } //network intercluster request or cross search cluster request if(HeaderHelper.isInterClusterRequest(getThreadContext()) - || HeaderHelper.isTrustedClusterRequest(getThreadContext())) { + || HeaderHelper.isTrustedClusterRequest(getThreadContext()) + || HeaderHelper.isExtensionRequest(getThreadContext())) { final String userHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER); final String injectedRolesHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_HEADER); @@ -256,7 +259,6 @@ else if(!Strings.isNullOrEmpty(injectedUserHeader)) { } } else { - //this is a netty request from a non-server node (maybe also be internal: or a shard request) //and therefore issued by a transport client @@ -326,6 +328,14 @@ protected void addAdditionalContextValues(final String action, final TransportRe } } + String extensionUniqueId = getThreadContext().getHeader("extension_unique_id"); + if (extensionUniqueId != null) { + ExtensionsManager extManager = OpenSearchSecurityPlugin.GuiceHolder.getExtensionsManager(); + if (extManager.getExtensionIdMap().containsKey(extensionUniqueId)) { + getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_EXTENSION_REQUEST, Boolean.TRUE); + } + } + super.addAdditionalContextValues(action, request, localCerts, peerCerts, principal); } } From 14bb8a3fa5d46967912cf4521ed519d6621459e8 Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Wed, 3 May 2023 06:37:53 -0700 Subject: [PATCH 176/369] update security-analytics roles to add correlation engine apis (#2732) Signed-off-by: Subhobrata Dey --- config/roles.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/roles.yml b/config/roles.yml index fd485423fe..00ebdf6edb 100644 --- a/config/roles.yml +++ b/config/roles.yml @@ -297,6 +297,8 @@ security_analytics_read_access: reserved: true cluster_permissions: - 'cluster:admin/opensearch/securityanalytics/alerts/get' + - 'cluster:admin/opensearch/securityanalytics/correlations/findings' + - 'cluster:admin/opensearch/securityanalytics/correlations/list' - 'cluster:admin/opensearch/securityanalytics/detector/get' - 'cluster:admin/opensearch/securityanalytics/detector/search' - 'cluster:admin/opensearch/securityanalytics/findings/get' @@ -310,6 +312,7 @@ security_analytics_full_access: reserved: true cluster_permissions: - 'cluster:admin/opensearch/securityanalytics/alerts/*' + - 'cluster:admin/opensearch/securityanalytics/correlations/*' - 'cluster:admin/opensearch/securityanalytics/detector/*' - 'cluster:admin/opensearch/securityanalytics/findings/*' - 'cluster:admin/opensearch/securityanalytics/mapping/*' From 1201335bd1c95fc2bb7e9b2210ffe5e029457d25 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Wed, 3 May 2023 11:25:01 -0400 Subject: [PATCH 177/369] [Extensions] Generate auth tokens for service accounts (#2716) * Generate auth tokens for service accounts Signed-off-by: Stephen Crawford Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- build.gradle | 64 +++++------ .../security/OpenSearchSecurityPlugin.java | 78 ++++++------- .../dlic/rest/api/InternalUsersApiAction.java | 91 ++++++++++++++- .../securityconf/impl/v7/InternalUserV7.java | 36 +++++- .../opensearch/security/user/UserService.java | 106 +++++++++++++++++- .../TransportUserInjectorIntegTest.java | 6 +- .../integration/TestAuditlogImpl.java | 28 ++--- .../security/dlic/rest/api/UserApiTest.java | 97 +++++++++++----- 8 files changed, 379 insertions(+), 127 deletions(-) diff --git a/build.gradle b/build.gradle index 64798b957e..91672ba066 100644 --- a/build.gradle +++ b/build.gradle @@ -75,16 +75,16 @@ licenseFile = rootProject.file('LICENSE.txt') noticeFile = rootProject.file('NOTICE.txt') spotless { - java { - // note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports - importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') - targetExclude('src/integrationTest/**') - } - format("integrationTest", JavaExtension) { - target('src/integrationTest/java/**/*.java') - importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') - indentWithTabs(4) - } + java { + // note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports + importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') + targetExclude('src/integrationTest/**') + } + format("integrationTest", JavaExtension) { + target('src/integrationTest/java/**/*.java') + importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') + indentWithTabs(4) + } } spotbugs { @@ -135,17 +135,17 @@ test { } jacoco { excludes = [ - "com.sun.jndi.dns.*", - "com.sun.security.sasl.gsskerb.*", - "java.sql.*", - "javax.script.*", - "org.jcp.xml.dsig.internal.dom.*", - "sun.nio.cs.ext.*", - "sun.security.ec.*", - "sun.security.jgss.*", - "sun.security.pkcs11.*", - "sun.security.smartcardio.*", - "sun.util.resources.provider.*" + "com.sun.jndi.dns.*", + "com.sun.security.sasl.gsskerb.*", + "java.sql.*", + "javax.script.*", + "org.jcp.xml.dsig.internal.dom.*", + "sun.nio.cs.ext.*", + "sun.security.ec.*", + "sun.security.jgss.*", + "sun.security.pkcs11.*", + "sun.security.smartcardio.*", + "sun.util.resources.provider.*" ] } } @@ -159,17 +159,17 @@ task opensslTest(type: Test) { } jacoco { excludes = [ - "com.sun.jndi.dns.*", - "com.sun.security.sasl.gsskerb.*", - "java.sql.*", - "javax.script.*", - "org.jcp.xml.dsig.internal.dom.*", - "sun.nio.cs.ext.*", - "sun.security.ec.*", - "sun.security.jgss.*", - "sun.security.pkcs11.*", - "sun.security.smartcardio.*", - "sun.util.resources.provider.*" + "com.sun.jndi.dns.*", + "com.sun.security.sasl.gsskerb.*", + "java.sql.*", + "javax.script.*", + "org.jcp.xml.dsig.internal.dom.*", + "sun.nio.cs.ext.*", + "sun.security.ec.*", + "sun.security.jgss.*", + "sun.security.pkcs11.*", + "sun.security.smartcardio.*", + "sun.util.resources.provider.*" ] } } diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index ed600af62b..41db464d55 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -334,7 +334,7 @@ public Object run() { final List filesWithWrongPermissions = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { - final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); + final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().filter(p -> checkFilePermissions(p)).collect(Collectors.toList()); @@ -364,7 +364,7 @@ public List run() { final List files = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { - final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); + final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().map(p -> sha256(p)).collect(Collectors.toList()); @@ -461,8 +461,8 @@ private boolean checkFilePermissions(final Path p) { @Override public List getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings, - IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter, - IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster) { + IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster) { final List handlers = new ArrayList(1); @@ -670,7 +670,7 @@ public List getTransportInterceptors(NamedWriteableRegistr @Override public TransportRequestHandler interceptHandler(String action, String executor, - boolean forceExecution, TransportRequestHandler actualHandler) { + boolean forceExecution, TransportRequestHandler actualHandler) { return new TransportRequestHandler() { @@ -689,7 +689,7 @@ public AsyncSender interceptSender(AsyncSender sender) { @Override public void sendRequest(Connection connection, String action, - TransportRequest request, TransportRequestOptions options, TransportResponseHandler handler) { + TransportRequest request, TransportRequestOptions options, TransportResponseHandler handler) { si.sendRequestDecorate(sender, connection, action, request, options, handler); } }; @@ -702,7 +702,7 @@ public void sendRequest(Connection connection, Str @Override public Map> getTransports(Settings settings, ThreadPool threadPool, PageCacheRecycler pageCacheRecycler, - CircuitBreakerService circuitBreakerService, NamedWriteableRegistry namedWriteableRegistry, NetworkService networkService) { + CircuitBreakerService circuitBreakerService, NamedWriteableRegistry namedWriteableRegistry, NetworkService networkService) { Map> transports = new HashMap>(); if(SSLConfig.isSslOnlyMode()) { @@ -719,12 +719,12 @@ public Map> getTransports(Settings settings, ThreadP @Override public Map> getHttpTransports(Settings settings, ThreadPool threadPool, BigArrays bigArrays, - PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, - NetworkService networkService, Dispatcher dispatcher, ClusterSettings clusterSettings) { + PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, + NetworkService networkService, Dispatcher dispatcher, ClusterSettings clusterSettings) { if(SSLConfig.isSslOnlyMode()) { return super.getHttpTransports(settings, threadPool, bigArrays, pageCacheRecycler, circuitBreakerService, xContentRegistry, - networkService, dispatcher, clusterSettings); + networkService, dispatcher, clusterSettings); } if(!disabled) { @@ -750,9 +750,9 @@ public Map> getHttpTransports(Settings set @Override public Collection createComponents(Client localClient, ClusterService clusterService, ThreadPool threadPool, - ResourceWatcherService resourceWatcherService, ScriptService scriptService, NamedXContentRegistry xContentRegistry, - Environment environment, NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry, - IndexNameExpressionResolver indexNameExpressionResolver, Supplier repositoriesServiceSupplier) { + ResourceWatcherService resourceWatcherService, ScriptService scriptService, NamedXContentRegistry xContentRegistry, + Environment environment, NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, Supplier repositoriesServiceSupplier) { SSLConfig.registerClusterSettingsChangeListener(clusterService.getClusterSettings()); if(SSLConfig.isSslOnlyMode()) { @@ -833,7 +833,7 @@ public Collection createComponents(Client localClient, ClusterService cl settings, privilegesInterceptor, cih, irr, dlsFlsEnabled, namedXContentRegistry.get()); sf = new SecurityFilter(settings, evaluator, adminDns, dlsFlsValve, auditLog, threadPool, cs, compatConfig, irr, xffResolver); - + final String principalExtractorClass = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PRINCIPAL_EXTRACTOR_CLASS, null); if(principalExtractorClass == null) { @@ -897,8 +897,8 @@ public Settings additionalSettings() { builder.put(super.additionalSettings()); if(!SSLConfig.isSslOnlyMode()){ - builder.put(NetworkModule.TRANSPORT_TYPE_KEY, "org.opensearch.security.ssl.http.netty.SecuritySSLNettyTransport"); - builder.put(NetworkModule.HTTP_TYPE_KEY, "org.opensearch.security.http.SecurityHttpServerTransport"); + builder.put(NetworkModule.TRANSPORT_TYPE_KEY, "org.opensearch.security.ssl.http.netty.SecuritySSLNettyTransport"); + builder.put(NetworkModule.HTTP_TYPE_KEY, "org.opensearch.security.http.SecurityHttpServerTransport"); } return builder.build(); } @@ -906,7 +906,7 @@ public Settings additionalSettings() { public List> getSettings() { List> settings = new ArrayList>(); settings.addAll(super.getSettings()); - + settings.add(Setting.boolSetting(ConfigConstants.SECURITY_SSL_ONLY, false, Property.NodeScope, Property.Filtered)); // currently dual mode is supported only when ssl_only is enabled, but this stance would change in future @@ -924,26 +924,26 @@ public List> getSettings() { if(!SSLConfig.isSslOnlyMode()) { settings.add(Setting.listSetting(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here - + settings.add(Setting.simpleString(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, Property.NodeScope, Property.Filtered)); settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_IMPERSONATION_DN+".", Property.NodeScope)); //not filtered here - + settings.add(Setting.simpleString(ConfigConstants.SECURITY_CERT_OID, Property.NodeScope, Property.Filtered)); - + settings.add(Setting.simpleString(ConfigConstants.SECURITY_CERT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS, Property.NodeScope, Property.Filtered)); settings.add(Setting.listSetting(ConfigConstants.SECURITY_NODES_DN, Collections.emptyList(), Function.identity(), Property.NodeScope));//not filtered here settings.add(Setting.boolSetting(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, false, Property.NodeScope));//not filtered here - + settings.add(Setting.boolSetting(ConfigConstants.SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, ConfigConstants.SECURITY_DEFAULT_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, Property.NodeScope, Property.Filtered)); settings.add(Setting.boolSetting(ConfigConstants.SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, ConfigConstants.SECURITY_DEFAULT_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, Property.NodeScope, Property.Filtered)); - + settings.add(Setting.boolSetting(ConfigConstants.SECURITY_DISABLED, false, Property.NodeScope, Property.Filtered)); - + settings.add(Setting.intSetting(ConfigConstants.SECURITY_CACHE_TTL_MINUTES, 60, 0, Property.NodeScope, Property.Filtered)); - + //Security settings.add(Setting.boolSetting(ConfigConstants.SECURITY_ADVANCED_MODULES_ENABLED, true, Property.NodeScope, Property.Filtered)); settings.add(Setting.boolSetting(ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES, false, Property.NodeScope, Property.Filtered)); @@ -951,10 +951,10 @@ public List> getSettings() { settings.add(Setting.boolSetting(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, true, Property.NodeScope, Property.Filtered)); settings.add(Setting.boolSetting(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, false, Property.NodeScope, Property.Filtered)); settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".", Property.NodeScope)); //not filtered here - + settings.add(Setting.simpleString(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, Property.NodeScope, Property.Filtered)); settings.add(Setting.boolSetting(ConfigConstants.SECURITY_DISABLE_ENVVAR_REPLACEMENT, false, Property.NodeScope, Property.Filtered)); - + // Security - Audit settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT, Property.NodeScope, Property.Filtered)); settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_ROUTES + ".", Property.NodeScope)); @@ -976,7 +976,7 @@ public List> getSettings() { settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false, Property.NodeScope, Property.Filtered)); settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS, true, Property.NodeScope, Property.Filtered)); - + final BiFunction> boolSettingNodeScopeFiltered = (String keyWithNamespace, Boolean value) -> Setting.boolSetting(keyWithNamespace, value, Property.NodeScope, Property.Filtered); Arrays.stream(FilterEntries.values()).map(filterEntry -> { @@ -1001,12 +1001,12 @@ public List> getSettings() { throw new RuntimeException("Please add support for new FilterEntries value '" + filterEntry.name() + "'"); } }).forEach(settings::add); - - + + // Security - Audit - Sink settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, Property.NodeScope, Property.Filtered)); - + // External OpenSearch settings.add(Setting.listSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_HTTP_ENDPOINTS, Lists.newArrayList("localhost:9200"), Function.identity(), Property.NodeScope)); //not filtered here settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME, Property.NodeScope, Property.Filtered)); @@ -1024,25 +1024,25 @@ public List> getSettings() { settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_JKS_CERT_ALIAS, Property.NodeScope, Property.Filtered)); settings.add(Setting.listSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_CIPHERS, Collections.emptyList(), Function.identity(), Property.NodeScope));//not filtered here settings.add(Setting.listSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_PROTOCOLS, Collections.emptyList(), Function.identity(), Property.NodeScope));//not filtered here - + // Webhooks settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_URL, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_FORMAT, Property.NodeScope, Property.Filtered)); settings.add(Setting.boolSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_SSL_VERIFY, true, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, Property.NodeScope, Property.Filtered)); - + // Log4j settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_LOG4J_LOGGER_NAME, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_LOG4J_LEVEL, Property.NodeScope, Property.Filtered)); - - + + // Kerberos settings.add(Setting.simpleString(ConfigConstants.SECURITY_KERBEROS_KRB5_FILEPATH, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(ConfigConstants.SECURITY_KERBEROS_ACCEPTOR_KEYTAB_FILEPATH, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(ConfigConstants.SECURITY_KERBEROS_ACCEPTOR_PRINCIPAL, Property.NodeScope, Property.Filtered)); - - + + // OpenSearch Security - REST API settings.add(Setting.listSetting(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here settings.add(Setting.groupSetting(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + ".", Property.NodeScope)); @@ -1051,7 +1051,7 @@ public List> getSettings() { settings.add(Setting.simpleString(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, Property.NodeScope, Property.Filtered)); - + // Compliance settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here @@ -1084,7 +1084,7 @@ public List> getSettings() { settings.add(Setting.boolSetting(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, false, Property.NodeScope, Property.Filtered)); settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG, false, Property.NodeScope, Property.Filtered)); } - + return settings; } @@ -1099,7 +1099,7 @@ public List getSettingsFilter() { settingsFilter.add("plugins.security.*"); return settingsFilter; } - + @Override public void onNodeStarted() { log.info("Node started"); diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java index 0551c108a1..6c1169f35a 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java @@ -31,12 +31,14 @@ import org.opensearch.rest.RestController; import org.opensearch.rest.RestRequest; import org.opensearch.rest.RestRequest.Method; +import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; import org.opensearch.security.dlic.rest.validation.InternalUsersValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; +import org.opensearch.security.securityconf.Hashed; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.ssl.transport.PrincipalExtractor; @@ -50,18 +52,20 @@ public class InternalUsersApiAction extends PatchableResourceApiAction { static final List RESTRICTED_FROM_USERNAME = ImmutableList.of( - ":" // Not allowed in basic auth, see https://stackoverflow.com/a/33391003/533057 + ":" // Not allowed in basic auth, see https://stackoverflow.com/a/33391003/533057 ); private static final List routes = addRoutesPrefix(ImmutableList.of( new Route(Method.GET, "/user/{name}"), new Route(Method.GET, "/user/"), + new Route(Method.POST, "/user/{name}/authtoken"), new Route(Method.DELETE, "/user/{name}"), new Route(Method.PUT, "/user/{name}"), // corrected mapping, introduced in OpenSearch Security new Route(Method.GET, "/internalusers/{name}"), new Route(Method.GET, "/internalusers/"), + new Route(Method.POST, "/internalusers/{name}/authtoken"), new Route(Method.DELETE, "/internalusers/{name}"), new Route(Method.PUT, "/internalusers/{name}"), new Route(Method.PATCH, "/internalusers/"), @@ -127,11 +131,11 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C // changes try { - if (request.hasParam("owner")) { - ((ObjectNode) content).put("owner", request.param("owner")); + if (request.hasParam("service")) { + ((ObjectNode) content).put("service", request.param("service")); } - if (request.hasParam("isEnabled")) { - ((ObjectNode) content).put("isEnabled", request.param("isEnabled")); + if (request.hasParam("enabled")) { + ((ObjectNode) content).put("enabled", request.param("enabled")); } ((ObjectNode) content).put("name", username); internalUsersConfiguration = userService.createOrUpdateAccount((ObjectNode) content); @@ -144,8 +148,28 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C throw new IOException(ex); } + // for existing users, hash is optional + if (userExisted && securityJsonNode.get("hash").asString() == null) { + // sanity check, this should usually not happen + final String hash = ((Hashed) internalUsersConfiguration.getCEntry(username)).getHash(); + if (hash == null || hash.length() == 0) { + internalErrorResponse(channel, + "Existing user " + username + " has no password, and no new password or hash was specified."); + return; + } + contentAsNode.put("hash", hash); + } + + internalUsersConfiguration.remove(username); + + // checks complete, create or update the user + Object userData = DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass()); + internalUsersConfiguration.putCObject(username, userData); + + saveAndUpdateConfigs(this.securityIndexName,client, CType.INTERNALUSERS, internalUsersConfiguration, new OnSucessActionListener(channel) { + @Override public void onResponse(IndexResponse response) { if (userExisted) { @@ -158,6 +182,63 @@ public void onResponse(IndexResponse response) { }); } + /** + * Overrides the GET request functionality to allow for the special case of requesting an auth token. + * + * @param channel The channel the request is coming through + * @param request The request itself + * @param client The client executing the request + * @param content The content of the request parsed into a node + * @throws IOException when parsing of configuration files fails (should not happen) + */ + @Override + protected void handlePost(final RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException{ + + final String username = request.param("name"); + + final SecurityDynamicConfiguration internalUsersConfiguration = load(getConfigName(), true); + filter(internalUsersConfiguration); // Hides hashes + + // no specific resource requested + if (username == null || username.length() == 0) { + + notImplemented(channel, Method.POST); + return; + } + + final boolean userExisted = internalUsersConfiguration.exists(username); + + if (!userExisted) { + notFound(channel, "Resource '" + username + "' not found."); + return; + } + + String authToken = ""; + try { + if (request.uri().contains("/internalusers/" + username + "/authtoken") && request.uri().endsWith("/authtoken")) { // Handle auth token fetching + + authToken = userService.generateAuthToken(username); + } else { // Not an auth token request + + notImplemented(channel, Method.POST); + return; + } + } catch (UserServiceException ex) { + badRequestResponse(channel, ex.getMessage()); + return; + } + catch (IOException ex) { + throw new IOException(ex); + } + + if (!authToken.isEmpty()) { + createdResponse(channel, "'" + username + "' authtoken generated " + authToken); + } else { + badRequestResponse(channel, "'" + username + "' authtoken failed to be created."); + } + } + + @Override protected void filter(SecurityDynamicConfiguration builder) { super.filter(builder); diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java index 4a1467f3a6..b697a9485b 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java @@ -44,6 +44,8 @@ public class InternalUserV7 implements Hideable, Hashed, StaticDefinable { private String hash; private boolean reserved; private boolean hidden; + private boolean service; + private boolean enabled; @JsonProperty(value = "static") private boolean _static; private List backend_roles = Collections.emptyList(); @@ -58,7 +60,20 @@ private InternalUserV7(String hash, boolean reserved, boolean hidden, List backend_roles, Map attributes, Boolean enabled, Boolean service) { + super(); + this.hash = hash; + this.reserved = reserved; + this.hidden = hidden; + this.backend_roles = backend_roles; + this.attributes = attributes; + this.enabled = enabled; + this.service = service; + } public InternalUserV7() { super(); @@ -80,7 +95,6 @@ public String getHash() { public void setHash(String hash) { this.hash = hash; } - public boolean isHidden() { @@ -114,9 +128,17 @@ public void setAttributes(Map attributes) { this.attributes = attributes; } + public boolean enabled() { + return this.enabled; + } + + public boolean service() { + return this.service; + } + @Override public String toString() { - return "InternalUserV7 [hash=" + hash + ", reserved=" + reserved + ", hidden=" + hidden + ", _static=" + _static + ", backend_roles=" + return "InternalUserV7 [hash=" + hash + ", enabled=" + enabled + ", service=" + service + ", reserved=" + reserved + ", hidden=" + hidden + ", _static=" + _static + ", backend_roles=" + backend_roles + ", attributes=" + attributes + ", description=" + description + "]"; } @@ -134,6 +156,14 @@ public void setDescription(String description) { this.description = description; } + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public void setService(boolean service) { + this.service = service; + } + public boolean isReserved() { return reserved; } diff --git a/src/main/java/org/opensearch/security/user/UserService.java b/src/main/java/org/opensearch/security/user/UserService.java index c6eea8ef4c..4a4193590e 100644 --- a/src/main/java/org/opensearch/security/user/UserService.java +++ b/src/main/java/org/opensearch/security/user/UserService.java @@ -12,19 +12,29 @@ package org.opensearch.security.user; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.ImmutableList; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.ExceptionsHelper; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentHelper; +import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.configuration.ConfigurationRepository; import org.opensearch.security.securityconf.DynamicConfigFactory; @@ -46,6 +56,8 @@ public class UserService { static ConfigurationRepository configurationRepository; String securityIndex; Client client; + + User tokenUser; final static String NO_PASSWORD_OR_HASH_MESSAGE = "Please specify either 'hash' or 'password' when creating a new internal user."; final static String RESTRICTED_CHARACTER_USE_MESSAGE = "A restricted character(s) was detected in the account name. Please remove: "; @@ -54,7 +66,10 @@ public class UserService { final static String SERVICE_ACCOUNT_HASH_MESSAGE = "A password hash cannot be provided for service account. Failed to register service account: "; final static String NO_ACCOUNT_NAME_MESSAGE = "No account name was specified in the request."; - private static CType getConfigName() { + + final static String FAILED_ACCOUNT_RETRIEVAL_MESSAGE = "The account specified could not be accessed at this time."; + final static String AUTH_TOKEN_GENERATION_MESSAGE = "An auth token could not be generated for the specified account."; + private static CType getUserConfigName() { return CType.INTERNALUSERS; } @@ -96,20 +111,25 @@ protected static final SecurityDynamicConfiguration load(final CType config, */ public SecurityDynamicConfiguration createOrUpdateAccount(ObjectNode contentAsNode) throws IOException { - SecurityJsonNode securityJsonNode = new SecurityJsonNode(contentAsNode); - final SecurityDynamicConfiguration internalUsersConfiguration = load(getConfigName(), false); + final SecurityDynamicConfiguration internalUsersConfiguration = load(getUserConfigName(), false); String accountName = securityJsonNode.get("name").asString(); if (accountName == null || accountName.length() == 0) { // Fail if field is present but empty throw new UserServiceException(NO_ACCOUNT_NAME_MESSAGE); } - if (!securityJsonNode.get("attributes").get("owner").isNull() && !securityJsonNode.get("attributes").get("owner").equals(accountName)) { // If this is a service account + SecurityJsonNode attributeNode = securityJsonNode.get("attributes"); + + if (!attributeNode.get("service").isNull() && attributeNode.get("service").asString().equalsIgnoreCase("true")) + { // If this is a service account verifyServiceAccount(securityJsonNode, accountName); String password = generatePassword(); contentAsNode.put("hash", hash(password.toCharArray())); + contentAsNode.put("service", "true"); + } else{ + contentAsNode.put("service", "false"); } securityJsonNode = new SecurityJsonNode(contentAsNode); @@ -131,6 +151,10 @@ public SecurityDynamicConfiguration createOrUpdateAccount(ObjectNode contentA contentAsNode.remove("password"); } + if (!attributeNode.get("enabled").isNull()) { + contentAsNode.put("enabled", securityJsonNode.get("enabled").asString()); + } + final boolean userExisted = internalUsersConfiguration.exists(accountName); // sanity checks, hash is mandatory for newly created users @@ -157,6 +181,7 @@ public SecurityDynamicConfiguration createOrUpdateAccount(ObjectNode contentA private void verifyServiceAccount(SecurityJsonNode securityJsonNode, String accountName) { + final String plainTextPassword = securityJsonNode.get("password").asString(); final String origHash = securityJsonNode.get("hash").asString(); @@ -178,4 +203,77 @@ private String generatePassword() { String generatedPassword = "superSecurePassword"; return generatedPassword; } + + /** + * This function retrieves the auth token associated with a service account. + * Fails if the provided account is not a service account or account is not enabled. + * + * @param accountName A string representing the name of the account + * @return A string auth token + */ + public String generateAuthToken(String accountName) throws IOException { + + final SecurityDynamicConfiguration internalUsersConfiguration = load(getUserConfigName(), false); + + if (!internalUsersConfiguration.exists(accountName)) { + throw new UserServiceException(FAILED_ACCOUNT_RETRIEVAL_MESSAGE); + } + + String authToken = null; + try { + DefaultObjectMapper mapper = new DefaultObjectMapper(); + JsonNode accountDetails = mapper.readTree(internalUsersConfiguration.getCEntry(accountName).toString()); + final ObjectNode contentAsNode = (ObjectNode) accountDetails; + SecurityJsonNode securityJsonNode = new SecurityJsonNode(contentAsNode); + + Optional.ofNullable(securityJsonNode.get("service")) + .map(SecurityJsonNode::asString) + .filter("true"::equalsIgnoreCase) + .orElseThrow(() -> new UserServiceException(AUTH_TOKEN_GENERATION_MESSAGE)); + + + Optional.ofNullable(securityJsonNode.get("enabled")) + .map(SecurityJsonNode::asString) + .filter("true"::equalsIgnoreCase) + .orElseThrow(() -> new UserServiceException(AUTH_TOKEN_GENERATION_MESSAGE)); + + // Generate a new password for the account and store the hash of it + String plainTextPassword = generatePassword(); + contentAsNode.put("hash", hash(plainTextPassword.toCharArray())); + contentAsNode.put("enabled", "true"); + contentAsNode.put("service", "true"); + + // Update the internal user associated with the auth token + internalUsersConfiguration.remove(accountName); + contentAsNode.remove("name"); + internalUsersConfiguration.putCObject(accountName, DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass())); + saveAndUpdateConfigs(getUserConfigName().toString(), client, CType.INTERNALUSERS, internalUsersConfiguration); + + + authToken = Base64.getUrlEncoder().encodeToString((accountName + ":" + plainTextPassword).getBytes(StandardCharsets.UTF_8)); + return authToken; + + } catch (JsonProcessingException ex) { + throw new UserServiceException(FAILED_ACCOUNT_RETRIEVAL_MESSAGE); + } catch (Exception e) { + throw new UserServiceException(AUTH_TOKEN_GENERATION_MESSAGE); + } + } + + public static void saveAndUpdateConfigs(final String indexName, final Client client, final CType cType, final SecurityDynamicConfiguration configuration) { + final IndexRequest ir = new IndexRequest(indexName); + final String id = cType.toLCString(); + + configuration.removeStatic(); + + try { + client.index(ir.id(id) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .setIfSeqNo(configuration.getSeqNo()) + .setIfPrimaryTerm(configuration.getPrimaryTerm()) + .source(id, XContentHelper.toXContent(configuration, XContentType.JSON, false))); + } catch (IOException e) { + throw ExceptionsHelper.convertToOpenSearchException(e); + } + } } diff --git a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java index a3e08d5234..fc61a3f127 100644 --- a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java @@ -73,7 +73,7 @@ public void testSecurityUserInjection() throws Exception { .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) .build(); setup(clusterNodeSettings, new DynamicSecurityConfig().setSecurityRolesMapping("roles_transport_inject_user.yml"), Settings.EMPTY); - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") @@ -128,7 +128,7 @@ public void testSecurityUserInjectionWithConfigDisabled() throws Exception { .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false) .build(); setup(clusterNodeSettings, new DynamicSecurityConfig().setSecurityRolesMapping("roles_transport_inject_user.yml"), Settings.EMPTY); - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") @@ -148,7 +148,7 @@ public void testSecurityUserInjectionWithConfigDisabled() throws Exception { CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-1")).actionGet(); Assert.assertTrue(cir.isAcknowledged()); } - + // with invalid backend roles UserInjectorPlugin.injectedUser = "ttt|kkk"; try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, 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 0502f078d9..ba094e23a1 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java @@ -82,13 +82,13 @@ public static List doThenWaitForMessages(final Runnable action, fi 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(); + .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); } @@ -155,13 +155,13 @@ public List getFoundMessages() { 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(); + .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/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index d755751e54..60c2ccde6f 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -12,6 +12,7 @@ package org.opensearch.security.dlic.rest.api; import java.net.URLEncoder; +import java.util.Base64; import java.util.List; import org.apache.hc.core5.http.Header; @@ -37,36 +38,38 @@ public class UserApiTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } + final int USER_SETTING_SIZE = 7 * 19; // Lines per account entry * number of accounts + private static final String ENABLED_SERVICE_ACCOUNT_BODY = "{" - + " \"attributes\": { \"owner\": \"test_owner\", " - + "\"isEnabled\": \"true\"}" + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"true\"}" + " }\n"; private static final String DISABLED_SERVICE_ACCOUNT_BODY = "{" - + " \"attributes\": { \"owner\": \"test_owner\", " - + "\"isEnabled\": \"false\"}" + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"false\"}" + " }\n"; private static final String ENABLED_NOT_SERVICE_ACCOUNT_BODY = "{" - + " \"attributes\": { \"owner\": \"user_is_owner_1\", " - + "\"isEnabled\": \"true\"}" + + " \"attributes\": { \"service\": \"false\", " + + "\"enabled\": \"true\"}" + " }\n"; private static final String PASSWORD_SERVICE = "{ \"password\" : \"test\"," - + " \"attributes\": { \"owner\": \"test_owner\", " - + "\"isEnabled\": \"true\"}" + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"true\"}" + " }\n"; private static final String HASH_SERVICE = "{ \"owner\" : \"test_owner\"," - + " \"attributes\": { \"owner\": \"test_owner\", " - + "\"isEnabled\": \"true\"}" + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"true\"}" + " }\n"; private static final String PASSWORD_HASH_SERVICE = "{ \"password\" : \"test\", \"hash\" : \"123\"," - + " \"attributes\": { \"owner\": \"test_owner\", " - + "\"isEnabled\": \"true\"}" + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"true\"}" + " }\n"; public UserApiTest(){ @@ -86,7 +89,7 @@ public void testSecurityRoles() throws Exception { .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(133, settings.size()); + Assert.assertEquals(USER_SETTING_SIZE, settings.size()); response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/newuser\", \"value\": {\"password\": \"newuser\", \"opendistro_security_roles\": [\"opendistro_security_all_access\"] } }]", new Header[0]); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); @@ -99,9 +102,9 @@ public void testSecurityRoles() throws Exception { @Test public void testParallelPutRequests() throws Exception { - + setup(); - + rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; @@ -136,7 +139,7 @@ public void testUserApi() throws Exception { HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(133, settings.size()); + Assert.assertEquals(USER_SETTING_SIZE, settings.size()); verifyGet(); verifyPut(); verifyPatch(true); @@ -334,13 +337,8 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) DISABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); - // Add enabled non-service account - response = rh.executePutRequest(ENDPOINT + "/internalusers/user_is_owner_1", - ENABLED_NOT_SERVICE_ACCOUNT_BODY, restAdminHeader); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // Add service account with password -- Should Fail - response = rh.executePutRequest(ENDPOINT + "/internalusers/passwordService", PASSWORD_SERVICE, restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); @@ -409,6 +407,51 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) Assert.assertTrue(settings.get("nagilum.hash").equals("")); } + private void verifyAuthToken(final boolean sendAdminCert, Header... restAdminHeader) throws Exception { + + // Add enabled service account then generate auth token + + rh.sendAdminCertificate = sendAdminCert; + HttpResponse response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceLive", + ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executeGetRequest(ENDPOINT + "/internalusers/happyServiceLive", restAdminHeader); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + + response = rh.executePostRequest(ENDPOINT + "/internalusers/happyServiceLive/authtoken", + ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); + String tokenFromResponse = response.getBody(); + byte[] decodedResponse = Base64.getUrlDecoder().decode(tokenFromResponse); + String[] decodedResponseString = new String(decodedResponse).split(":", 2); + String username = decodedResponseString[0]; + String password = decodedResponseString[1]; + Assert.assertEquals("Username is: " + username,username, "happyServiceLive"); + + // Add disabled service account then try to get its auth token + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceDead", + DISABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); + + response = rh.executePostRequest(ENDPOINT + "/internalusers/happyServiceDead/authtoken", + ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + + + // Add enabled non-service account + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePutRequest(ENDPOINT + "/internalusers/user_is_owner_1", + ENABLED_NOT_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + response = rh.executePostRequest(ENDPOINT + "/internalusers/user_is_owner_1/authtoken", + ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + + } + private void verifyRoles(final boolean sendAdminCert, Header... header) throws Exception { // wrong datatypes in roles file rh.sendAdminCertificate = sendAdminCert; @@ -494,7 +537,7 @@ public void testUserApiWithRestAdminPermissions() throws Exception { HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString(), restApiAdminHeader); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(133, settings.size()); + Assert.assertEquals(USER_SETTING_SIZE, settings.size()); verifyGet(restApiAdminHeader); verifyPut(restApiAdminHeader); verifyPatch(false, restApiAdminHeader); @@ -512,7 +555,7 @@ public void testUserApiWithRestInternalUsersAdminPermissions() throws Exception HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString(), restApiInternalUsersAdminHeader); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(133, settings.size()); + Assert.assertEquals(USER_SETTING_SIZE, settings.size()); verifyGet(restApiInternalUsersAdminHeader); verifyPut(restApiInternalUsersAdminHeader); verifyPatch(false, restApiInternalUsersAdminHeader); @@ -541,7 +584,7 @@ public void testPasswordRules() throws Exception { .executeGetRequest("_plugins/_security/api/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(133, settings.size()); + Assert.assertEquals(USER_SETTING_SIZE, settings.size()); addUserWithPassword("tooshoort", "", HttpStatus.SC_BAD_REQUEST); addUserWithPassword("tooshoort", "123", HttpStatus.SC_BAD_REQUEST); @@ -621,7 +664,7 @@ public void testUserApiWithDots() throws Exception { .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(133, settings.size()); + Assert.assertEquals(USER_SETTING_SIZE, settings.size()); addUserWithPassword(".my.dotuser0", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); @@ -722,7 +765,7 @@ public void testUserApiForNonSuperAdmin() throws Exception { // Put reserved role is forbidden for non-superadmin response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{ \"opendistro_security_roles\": [\"opendistro_security_reserved\"]}", - new Header[0]); + new Header[0]); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(settings.get("message"), "Resource 'opendistro_security_reserved' is read-only."); From 26cafdc70630aab849580cb893e5918b170f7ec8 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Wed, 3 May 2023 16:25:16 -0400 Subject: [PATCH 178/369] Update to Gradle 8.1.1 (#2738) Signed-off-by: Andriy Redko --- bwc-test/gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 62076 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 ++- bwc-test/gradlew | 7 ++++--- gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 7 ++++--- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/bwc-test/gradle/wrapper/gradle-wrapper.jar b/bwc-test/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch delta 8979 zcmY*fV{{$d(moANW81db*tXT!Nn`UgX2ZtD$%&n`v2C-lt;YD?@2-14?EPcUv!0n* z`^Ws4HP4i8L%;4p*JkD-J9ja2aKi!sX@~#-MY5?EPBK~fXAl)Ti}^QGH@6h+V+|}F zv=1RqQxhWW9!hTvYE!)+*m%jEL^9caK;am9X8QP~a9X0N6(=WSX8KF#WpU-6TjyR3 zpKhscivP97d$DGc{KI(f#g07u{Jr0wn#+qNr}yW}2N3{Kx0lCq%p4LBKil*QDTEyR zg{{&=GAy_O0VJ(8ZbtS4tPeeeILKK(M?HtQY!6K^wt zxsPH>E%g%V@=!B;kWF54$xjC&4hO!ZEG0QFMHLqe!tgH;%vO62BQj||nokbX&2kxF zzg#N!2M|NxFL#YdwOL8}>iDLr%2=!LZvk_&`AMrm7Zm%#_{Ot_qw=HkdVg{f9hYHF zlRF*9kxo~FPfyBD!^d6MbD?BRZj(4u9j!5}HFUt+$#Jd48Fd~ahe@)R9Z2M1t%LHa z_IP|tDb0CDl(fsEbvIYawJLJ7hXfpVw)D-)R-mHdyn5uZYefN0rZ-#KDzb`gsow;v zGX>k|g5?D%Vn_}IJIgf%nAz{@j0FCIEVWffc1Z+lliA}L+WJY=MAf$GeI7xw5YD1) z;BJn$T;JI5vTbZ&4aYfmd-XPQd)YQ~d({>(^5u>Y^5rfxEUDci9I5?dXp6{zHG=Tc z6$rLd^C~60=K4ptlZ%Fl-%QLc-x{y=zU$%&4ZU}4&Yu?jF4eqB#kTHhty`Aq=kJE% zzq(5OS9o1t-)}S}`chh1Uu-Sl?ljxMDVIy5j`97Eqg7L~Ak9NSZ?!5M>5TRMXfD#} zFlMmFnr%?ra>vkvJQjmWa8oB{63qPo1L#LAht%FG|6CEe9KP2&VNe_HNb7M}pd*!t zpGL0vzCU02%iK@AKWxP^64fz-U#%u~D+FV?*KdPY9C_9{Ggn;Y;;iKE0b|}KmC&f(WIDcFtvRPDju z?Dc&_dP4*hh!%!6(nYB*TEJs<4zn*V0Nw1O4VzYaNZul>anE2Feb@T$XkI?)u6VK$bg* z22AY7|Ju!_jwc2@JX(;SUE>VDWRD|d56WYUGLAAwPYXU9K&NgY{t{dyMskUBgV%@p zMVcFn>W|hJA?3S?$k!M|1S2e1A&_~W2p$;O2Wpn`$|8W(@~w>RR4kxHdEr`+q|>m@ zTYp%Ut+g`T#HkyE5zw<5uhFvt2=k5fM3!8OxvGgMRS|t7RaJn7!2$r_-~a%C7@*Dq zGUp2g0N^HzLU=%bROVFi2J;#`7#WGTUI$r!(wmbJlbS`E#ZpNp7vOR#TwPQWNf$IW zoX>v@6S8n6+HhUZB7V^A`Y9t4ngdfUFZrDOayMVvg&=RY4@0Z~L|vW)DZTIvqA)%D zi!pa)8L7BipsVh5-LMH4bmwt2?t88YUfIRf!@8^gX$xpKTE^WpM!-=3?UVw^Cs`Y7 z2b<*~Q=1uqs79{h&H_8+X%><4qSbz_cSEa;Hkdmtq5uwGTY+|APD{i_zYhLXqT7HO zT^Am_tW?Cmn%N~MC0!9mYt-~WK;hj-SnayMwqAAHo#^ALwkg0>72&W}5^4%|Z|@T; zwwBQTg*&eXC}j8 zra77(XC^p&&o;KrZ$`_)C$@SDWT+p$3!;ZB#yhnK{CxQc&?R}ZQMcp`!!eXLLhiP8W zM=McHAMnUMlar8XLXk&jx#HBH3U0jbhJuqa~#l`aB)N6;WI(Im322o#{K&92l6(K z)(;=;-m!%9@j#WSA1uniU(^x(UTi+%idMd)x*!*Hub0Rg7DblI!cqo9QUZf29Y#?XN!K!|ovJ7~!^H}!zsaMl(57lpztQ7V zyo#`qJ4jv1zGAW2uIkU3o&7_=lYWz3=SR!sgfuYp{Um<*H%uW8MdUT2&o*QKjD3PEH zHz;H}qCN~`GFsJ_xz$9xga*@VzJTH7-3lggkBM&7xlz5#qWfkgi=#j%{&f-NMsaSv zeIZ60Jpw}QV+t`ovOJxVhYCXe8E7r*eLCJ{lP6sqc}BYrhjXlt(6e9nw=2Le1gOT0 zZX!q9r#DZ&8_cAhWPeq~CJkGvpRU&q8>rR@RBW4~@3j1X>RBum#U z1wjcEdB`|@sXAWxk2*TOj> zr(j{nr1;Mk3x^gvAtZsahY=ou{eAJi-d(XISF-?+Q6{Um4+lu?aA=S33@k=6^OT?F z8TE`ha;q@=ZQ-dlt!q49;Wjjl<&Yee^!h5MFkd)Oj=fsvxytK%!B z-P#YJ)8^dMi=wpKmt43|apX6v2dNXzZ-WHlLEh`JoKFNjCK7LhO^P5XW?Y~rjGcIpv$2v41rE}~0{aj9NVpDXGdD6W8{fyzioQdu&xkn8 zhT*^NY0zv>Om?h3XAku3p-4SHkK@fXrpi{T=@#bwY76TsD4$tAHAhXAStdb$odc z02~lZyb!fG_7qrU_F5 zoOG|pEwdyDhLXDwlU>T|;LF@ACJk(qZ*2h6GB@33mKk};HO^CQM(N7@Ml5|8IeHzt zdG4f$q}SNYA4P=?jV!mJ%3hRKwi&!wFptWZRq4bpV9^b7&L>nW%~Y|junw!jHj%85 z3Ck6%`Y=Abvrujnm{`OtE0uQkeX@3JPzj#iO#eNoAX6cDhM+cc2mLk8;^bG62mtjQ zj|kxI2W|4n{VqMqB?@YnA0y}@Mju)&j3UQ4tSdH=Eu?>i7A50b%i$pc{YJki7ubq7 zVTDqdkGjeAuZdF)KBwR6LZob}7`2935iKIU2-I;88&?t16c-~TNWIcQ8C_cE_F1tv z*>4<_kimwX^CQtFrlk)i!3-+2zD|=!D43Qqk-LtpPnX#QQt%eullxHat97k=00qR|b2|M}`q??yf+h~};_PJ2bLeEeteO3rh+H{9otNQDki^lu)(`a~_x(8NWLE*rb%T=Z~s?JC|G zXNnO~2SzW)H}p6Zn%WqAyadG=?$BXuS(x-2(T!E&sBcIz6`w=MdtxR<7M`s6-#!s+ znhpkcNMw{c#!F%#O!K*?(Hl(;Tgl9~WYBB(P@9KHb8ZkLN>|}+pQ)K#>ANpV1IM{Q z8qL^PiNEOrY*%!7Hj!CwRT2CN4r(ipJA%kCc&s;wOfrweu)H!YlFM z247pwv!nFWbTKq&zm4UVH^d?H2M276ny~@v5jR2>@ihAmcdZI-ah(&)7uLQM5COqg?hjX2<75QU4o5Q7 zZ5gG;6RMhxLa5NFTXgegSXb0a%aPdmLL4=`ox2smE)lDn^!;^PNftzTf~n{NH7uh_ zc9sKmx@q1InUh_BgI3C!f>`HnO~X`9#XTI^Yzaj1928gz8ClI!WIB&2!&;M18pf0T zsZ81LY3$-_O`@4$vrO`Cb&{apkvUwrA0Z49YfZYD)V4;c2&`JPJuwN_o~2vnyW_b! z%yUSS5K{a*t>;WJr&$A_&}bLTTXK23<;*EiNHHF-F<#hy8v2eegrqnE=^gt+|8R5o z_80IY4&-!2`uISX6lb0kCVmkQ{D}HMGUAkCe`I~t2~99(<#}{E;{+Y0!FU>leSP(M zuMoSOEfw3OC5kQ~Y2)EMlJceJlh}p?uw}!cq?h44=b2k@T1;6KviZGc_zbeTtTE$@EDwUcjxd#fpK=W*U@S#U|YKz{#qbb*|BpcaU!>6&Ir zhsA+ywgvk54%Nj>!!oH>MQ+L~36v1pV%^pOmvo7sT|N}$U!T6l^<3W2 z6}mT7Cl=IQo%Y~d%l=+;vdK)yW!C>Es-~b^E?IjUU4h6<86tun6rO#?!37B)M8>ph zJ@`~09W^@5=}sWg8`~ew=0>0*V^b9eG=rBIGbe3Ko$pj!0CBUTmF^Q}l7|kCeB(pX zi6UvbUJWfKcA&PDq?2HrMnJBTW#nm$(vPZE;%FRM#ge$S)i4!y$ShDwduz@EPp3H? z`+%=~-g6`Ibtrb=QsH3w-bKCX1_aGKo4Q7n-zYp->k~KE!(K@VZder&^^hIF6AhiG z;_ig2NDd_hpo!W1Un{GcB@e{O@P3zHnj;@SzYCxsImCHJS5I&^s-J6?cw92qeK8}W zk<_SvajS&d_tDP~>nhkJSoN>UZUHs?)bDY`{`;D^@wMW0@!H1I_BYphly0iqq^Jp; z_aD>eHbu@e6&PUQ4*q*ik0i*$Ru^_@`Mbyrscb&`8|c=RWZ>Ybs16Q?Cj1r6RQA5! zOeuxfzWm(fX!geO(anpBCOV|a&mu|$4cZ<*{pb1F{`-cm1)yB6AGm7b=GV@r*DataJ^I!>^lCvS_@AftZiwtpszHmq{UVl zKL9164tmF5g>uOZ({Jg~fH~QyHd#h#E;WzSYO~zt)_ZMhefdm5*H1K-#=_kw#o%ch zgX|C$K4l4IY8=PV6Q{T8dd`*6MG-TlsTEaA&W{EuwaoN+-BDdSL2>|lwiZ++4eR8h zNS1yJdbhAWjW4k`i1KL)l#G*Y=a0ouTbg8R1aUU`8X7p*AnO+uaNF9mwa+ooA)hlj zR26XBpQ-{6E9;PQAvq2<%!M1;@Q%r@xZ16YRyL&v}9F`Nnx#RLUc<78w$S zZElh==Rnr2u<*qKY|aUR9(A|{cURqP81O-1a@X)khheokEhC}BS-g~|zRbn-igmID z$Ww!O0-j!t(lx>-JH+0KW3*Bgafpm>%n=`(ZLa^TWd*-je!Xi7H*bZ8pz`HPFYeC? zk>`W)4Cj6*A3A8g$MEhp*<@qO&&>3<4YI%0YAMmQvD3 z${78Fa2mqiI>P7|gE)xs$cg3~^?UBb4y6B4Z#0Fzy zN8Gf!c+$uPS`VRB=wRV1f)>+PEHBYco<1?ceXET}Q-tKI=E`21<15xTe@%Bhk$v09 zVpoL_wNuw)@^O+C@VCeuWM}(%C(%lTJ}7n)JVV!^0H!3@)ydq#vEt;_*+xos$9i?{ zCw5^ZcNS&GzaeBmPg6IKrbT`OSuKg$wai+5K}$mTO-Z$s3Y+vb3G}x%WqlnQS1;|Z zlZ$L{onq1Ag#5JrM)%6~ToQ}NmM2A(7X5gy$nVI=tQFOm;7|Oeij{xb_KU{d@%)2z zsVqzTl@XPf(a95;P;oBm9Hlpo`9)D9>G>!Bj=ZmX{ces=aC~E^$rTO5hO$#X65jEA zMj1(p+HXdOh7FAV;(_)_RR#P>&NW?&4C7K1Y$C$i**g;KOdu|JI_Ep zV-N$wuDRkn6=k|tCDXU%d=YvT!M1nU?JY;Pl`dxQX5+660TX7~q@ukEKc!Iqy2y)KuG^Q-Y%$;SR&Mv{%=CjphG1_^dkUM=qI*3Ih^Bk621n`6;q(D;nB_y|~ zW*1ps&h|wcET!#~+Ptsiex~YVhDiIREiw1=uwlNpPyqDZ`qqv9GtKwvxnFE}ME93fD9(Iq zz=f&4ZpD~+qROW6Y2AjPj9pH*r_pS_f@tLl88dbkO9LG0+|4*Xq(Eo7fr5MVg{n<+p>H{LGr}UzToqfk_x6(2YB~-^7>%X z+331Ob|NyMST64u|1dK*#J>qEW@dKNj-u}3MG)ZQi~#GzJ_S4n5lb7vu&>;I-M49a z0Uc#GD-KjO`tQ5ftuSz<+`rT)cLio$OJDLtC`t)bE+Nu@Rok2;`#zv1=n z7_CZr&EhVy{jq(eJPS)XA>!7t<&ormWI~w0@Y#VKjK)`KAO~3|%+{ z$HKIF?86~jH*1p=`j#}8ON0{mvoiN7fS^N+TzF~;9G0_lQ?(OT8!b1F8a~epAH#uA zSN+goE<-psRqPXdG7}w=ddH=QAL|g}x5%l-`Kh69D4{M?jv!l))<@jxLL$Eg2vt@E zc6w`$?_z%awCE~ca)9nMvj($VH%2!?w3c(5Y4&ZC2q#yQ=r{H2O839eoBJ{rfMTs8 zn2aL6e6?;LY#&(BvX_gC6uFK`0yt zJbUATdyz5d3lRyV!rwbj0hVg#KHdK0^A7_3KA%gKi#F#-^K%1XQbeF49arI2LA|Bj z?=;VxKbZo(iQmHB5eAg=8IPRqyskQNR!&KEPrGv&kMr(8`4oe?vd?sIZJK+JY04kc zXWk)4N|~*|0$4sUV3U6W6g+Z3;nN<~n4H17QT*%MCLt_huVl@QkV`A`jyq<|q=&F_ zPEOotTu9?zGKaPJ#9P&ljgW!|Vxhe+l85%G5zpD5kAtn*ZC})qEy!v`_R}EcOn)&# z-+B52@Zle@$!^-N@<_=LKF}fqQkwf1rE(OQP&8!En}jqr-l0A0K>77K8{zT%wVpT~ zMgDx}RUG$jgaeqv*E~<#RT?Q)(RGi8bUm(1X?2OAG2!LbBR+u1r7$}s=lKqu&VjXP zUw3L9DH({yj)M%OqP%GC+$}o0iG|*hN-Ecv3bxS|Mxpmz*%x`w7~=o9BKfEVzr~K- zo&Fh`wZ{#1Jd5QFM4&!PabL!tf%TfJ4wi;45AqWe$x}8*c2cgqua`(6@ErE&P{K5M zQfwGQ4Qg&M3r4^^$B?_AdLzqtxn5nb#kItDY?BTW z#hShspeIDJ1FDmfq@dz1TT`OV;SS0ImUp`P6GzOqB3dPfzf?+w^40!Wn*4s!E;iHW zNzpDG+Vmtnh%CyfAX>X z{Y=vt;yb z;TBRZpw##Kh$l<8qq5|3LkrwX%MoxqWwclBS6|7LDM(I31>$_w=;{=HcyWlak3xM1 z_oaOa)a;AtV{*xSj6v|x%a42{h@X-cr%#HO5hWbuKRGTZS)o=^Id^>H5}0p_(BEXX zx3VnRUj6&1JjDI);c=#EYcsg;D5TFlhe)=nAycR1N)YSHQvO+P5hKe9T0ggZT{oF@ z#i3V4TpQlO1A8*TWn|e}UWZ(OU;Isd^ zb<#Vj`~W_-S_=lDR#223!xq8sRjAAVSY2MhRyUyHa-{ql=zyMz?~i_c&dS>eb>s>#q#$UI+!&6MftpQvxHA@f|k2(G9z zAQCx-lJ-AT;PnX%dY5}N$m6tFt5h6;Mf78TmFUN9#4*qBNg4it3-s22P+|Rw zG@X%R0sm*X07ZZEOJRbDkcjr}tvaVWlrwJ#7KYEw&X`2lDa@qb!0*SHa%+-FU!83q zY{R15$vfL56^Nj42#vGQlQ%coT4bLr2s5Y0zBFp8u&F(+*%k4xE1{s75Q?P(SL7kf zhG?3rfM9V*b?>dOpwr%uGH7Xfk1HZ!*k`@CNM77g_mGN=ucMG&QX19B!%y77w?g#b z%k3x6q_w_%ghL;9Zk_J#V{hxK%6j`?-`UN?^e%(L6R#t#97kZaOr1{&<8VGVs1O>} z6~!myW`ja01v%qy%WI=8WI!cf#YA8KNRoU>`_muCqpt_;F@rkVeDY}F7puI_wBPH9 zgRGre(X_z4PUO5!VDSyg)bea1x_a7M z4AJ?dd9rf{*P`AY+w?g_TyJlB5Nks~1$@PxdtpUGGG##7j<$g&BhKq0mXTva{;h5E ztcN!O17bquKEDC#;Yw2yE>*=|WdZT9+ycgUR^f?~+TY-E552AZlzYn{-2CLRV9mn8 z+zNoWLae^P{co`F?)r;f!C=nnl*1+DI)mZY!frp~f%6tX2g=?zQL^d-j^t1~+xYgK zv;np&js@X=_e7F&&ZUX|N6Q2P0L=fWoBuh*L7$3~$-A)sdy6EQ@Pd-)|7lDA@%ra2 z4jL@^w92&KC>H(=v2j!tVE_3w0KogtrNjgPBsTvW F{TFmrHLU;u delta 8469 zcmY*q~ZGqoW{=01$bgB@1Nex`%9%S2I04)5Jw9+UyLS&r+9O2bq{gY;dCa zHW3WY0%Dem?S7n5JZO%*yiT9fb!XGk9^Q`o-EO{a^j%&)ZsxsSN@2k2eFx1*psqn0e*crIbAO}Rd~_BifMu*q7SUn{>WD$=7n_$uiQ0wGc$?u1hM%gf??nL?m22h!8{ zYmFMLvx6fjz*nwF^tAqx1uv0yEW9-tcIV5Q{HNh`9PMsuqD8VE%oAs5FsWa0mLV$L zPAF5e^$tJ8_Kwp!$N1M<#Z154n!X6hFpk8)eMLu; zaXS71&`24 zV`x~}yAxBw##Oj@qo_@DcBqc+2TB&=bJyZWTeR55zG<{Z@T^hSbMdm~Ikkr?4{7WT zcjPyu>0sDjl7&?TL@ z)cW?lW@Pfwu#nm7E1%6*nBIzQrKhHl`t54$-m>j8f%0vVr?N0PTz`}VrYAl+8h^O~ zuWQj@aZSZmGPtcVjGq-EQ1V`)%x{HZ6pT-tZttJOQm?q-#KzchbH>>5-jEX*K~KDa z#oO&Qf4$@}ZGQ7gxn<;D$ziphThbi6zL^YC;J#t0GCbjY)NHdqF=M4e(@|DUPY_=F zLcX1HAJ+O-3VkU#LW`4;=6szwwo%^R4#UK}HdAXK` z{m!VZj5q9tVYL=^TqPH*6?>*yr>VxyYF4tY{~?qJ*eIoIU0}-TLepzga4g}}D7#Qu zn;6I;l!`xaL^8r*Tz*h`^(xJCnuVR_O@Gl*Q}y$lp%!kxD`%zN19WTIf`VX*M=cDp z*s4<9wP|ev;PARRV`g$R*QV@rr%Ku~z(2-s>nt{JI$357vnFAz9!ZsiiH#4wOt+!1 zM;h;EN__zBn)*-A^l!`b?b*VI-?)Sj6&Ov3!j9k$5+#w)M>`AExCm0!#XL+E{Bp)s;Hochs+-@@)7_XDMPby#p<9mLu+S{8e2Jn`1`1nrffBfy4u)p7FFQWzgYt zXC}GypRdkTUS+mP!jSH$K71PYI%QI-{m;DvlRb*|4GMPmvURv0uD2bvS%FOSe_$4zc--*>gfRMKN|D ztP^WFfGEkcm?sqXoyRmuCgb?bSG17#QSv4~XsbPH>BE%;bZQ_HQb?q%CjykL7CWDf z!rtrPk~46_!{V`V<;AjAza;w-F%t1^+b|r_um$#1cHZ1|WpVUS&1aq?Mnss|HVDRY z*sVYNB+4#TJAh4#rGbr}oSnxjD6_LIkanNvZ9_#bm?$HKKdDdg4%vxbm-t@ZcKr#x z6<$$VPNBpWM2S+bf5IBjY3-IY2-BwRfW_DonEaXa=h{xOH%oa~gPW6LTF26Y*M)$N z=9i`Y8};Qgr#zvU)_^yU5yB;9@yJjrMvc4T%}a|jCze826soW-d`V~eo%RTh)&#XR zRe<8$42S2oz|NVcB%rG(FP2U&X>3 z4M^}|K{v64>~rob;$GO55t;Nb&T+A3u(>P6;wtp6DBGWbX|3EZBDAM2DCo&4w|WGpi;~qUY?Ofg$pX&`zR~)lr)8}z^U3U38Nrtnmf~e7$i=l>+*R%hQgDrj%P7F zIjyBCj2$Td=Fp=0Dk{=8d6cIcW6zhK!$>k*uC^f}c6-NR$ zd<)oa+_fQDyY-}9DsPBvh@6EvLZ}c)C&O-+wY|}RYHbc2cdGuNcJ7#yE}9=!Vt-Q~ z4tOePK!0IJ0cW*jOkCO? zS-T!bE{5LD&u!I4tqy;dI*)#e^i)uIDxU?8wK1COP3Qk{$vM3Sm8(F2VwM?1A+dle z6`M6bbZye|kew%w9l`GS74yhLluJU5R=#!&zGwB7lmTt}&eCt0g(-a;Mom-{lL6u~ zFgjyUs1$K*0R51qQTW_165~#WRrMxiUx{0F#+tvgtcjV$U|Z}G*JWo6)8f!+(4o>O zuaAxLfUl;GHI}A}Kc>A8h^v6C-9bb}lw@rtA*4Q8)z>0oa6V1>N4GFyi&v69#x&CwK*^!w&$`dv zQKRMKcN$^=$?4to7X4I`?PKGi(=R}d8cv{74o|9FwS zvvTg0D~O%bQpbp@{r49;r~5`mcE^P<9;Zi$?4LP-^P^kuY#uBz$F!u1d{Ens6~$Od zf)dV+8-4!eURXZZ;lM4rJw{R3f1Ng<9nn2_RQUZDrOw5+DtdAIv*v@3ZBU9G)sC&y!vM28daSH7(SKNGcV z&5x#e#W2eY?XN@jyOQiSj$BlXkTG3uAL{D|PwoMp$}f3h5o7b4Y+X#P)0jlolgLn9xC%zr3jr$gl$8?II`DO6gIGm;O`R`bN{;DlXaY4b`>x6xH=Kl@ z!>mh~TLOo)#dTb~F;O z8hpjW9Ga?AX&&J+T#RM6u*9x{&%I8m?vk4eDWz^l2N_k(TbeBpIwcV4FhL(S$4l5p z@{n7|sax){t!3t4O!`o(dYCNh90+hl|p%V_q&cwBzT*?Nu*D0wZ)fPXv z@*;`TO7T0WKtFh8~mQx;49VG_`l`g|&VK}LysK%eU4})Cvvg3YN)%;zI?;_Nr z)5zuU1^r3h;Y+mJov*->dOOj>RV^u2*|RraaQWsY5N?Uu)fKJOCSL2^G=RB%(4K{* zx!^cB@I|kJR`b+5IK}(6)m=O{49P5E^)!XvD5zVuzJH{01^#$@Cn514w41BB;FAoS2SYl3SRrOBDLfl5MvgA3 zU6{T?BW}l~8vU;q@p9IOM(=;WdioeQmt?X|=L9kyM&ZsNc*-Knv8@U*O96T@4ZiJ$ zeFL2}pw_~Tm3d4#q!zZS0km@vYgym33C0h(6D)6|Y)*UXI^T`(QPQh$WF?&h(3QYh zqGw@?BTk@VA_VxK@z?a@UrMhY zUD16oqx4$$6J_k0HnXgARm}N#(^yA1MLdbwmEqHnX*JdHN>$5k2E|^_bL< zGf5Z+D!9dXR>^(5F&5gIew1%kJtFUwI5P1~I$4LL_6)3RPzw|@2vV;Q^MeQUKzc=KxSTTX`}u%z?h~;qI#%dE@OZwehZyDBsWTc&tOC1c%HS#AyTJ= zQixj=BNVaRS*G!;B$}cJljeiVQabC25O+xr4A+32HVb;@+%r}$^u4-R?^3yij)0xb z86i@aoVxa%?bfOE;Bgvm&8_8K(M-ZEj*u9ms_Hk#2eL`PSnD#At!0l{f!v`&Kg}M$n(&R)?AigC5Z?T7Jv^lrDL!yYS{4 zq_H}oezX-Svu>dp)wE@khE@aR5vY=;{C-8Hws++5LDpArYd)U47jc-;f~07_TPa^1 zO`0+uIq)@?^!%JXCDid+nt|c@NG1+ce@ijUX&@rV9UiT|m+t-nqVB7?&UX*|{yDBFw9x52&dTh@;CL)Q?6s1gL=CUQTX7#TJPs9cpw<4>GFMUKo|f{! z&(%2hP6ghr%UFVO-N^v9l|tKy>&e%8us}wT0N*l(tezoctVtLmNdGPOF6oaAGJI5R zZ*|k@z3H!~Mm9fXw{bbP6?lV-j#Rfgnjf++O7*|5vz2#XK;kk ztJbi%r0{U5@QwHYfwdjtqJ6?;X{Ul3?W0O0bZ$k*y z4jWsNedRoCb7_|>nazmq{T3Y_{<5IO&zQ?9&uS@iL+|K|eXy^F>-60HDoVvovHelY zy6p(}H^7b+$gu@7xLn_^oQryjVu#pRE5&-w5ZLCK&)WJ5jJF{B>y;-=)C;xbF#wig zNxN^>TwzZbV+{+M?}UfbFSe#(x$c)|d_9fRLLHH?Xbn!PoM{(+S5IEFRe4$aHg~hP zJYt`h&?WuNs4mVAmk$yeM;8?R6;YBMp8VilyM!RXWj<95=yp=4@y?`Ua8 znR^R?u&g%`$Wa~usp|pO$aMF-en!DrolPjD_g#{8X1f=#_7hH8i|WF+wMqmxUm*!G z*4p980g{sgR9?{}B+a0yiOdR()tWE8u)vMPxAdK)?$M+O_S+;nB34@o<%lGJbXbP` z5)<({mNpHp&45UvN`b&K5SD#W){}6Y_d4v~amZPGg|3GdlWDB;;?a=Z{dd zELTfXnjCqq{Dgbh9c%LjK!Epi1TGI{A7AP|eg2@TFQiUd4Bo!JsCqsS-8ml`j{gM& zEd7yU`djX!EX2I{WZq=qasFzdDWD`Z?ULFVIP!(KQP=fJh5QC9D|$JGV95jv)!sYWY?irpvh06rw&O?iIvMMj=X zr%`aa(|{Ad=Vr9%Q(61{PB-V_(3A%p&V#0zGKI1O(^;tkS{>Y<`Ql@_-b7IOT&@?l zavh?#FW?5otMIjq+Bp?Lq)w7S(0Vp0o!J*~O1>av;)Cdok@h&JKaoHDV6IVtJ?N#XY=lknPN+SN8@3Gb+D-X*y5pQ)wnIpQlRR!Rd)@0LdA85}1 zu7W6tJ*p26ovz+`YCPePT>-+p@T_QsW$uE`McLlXb;k}!wwWuh$YC4qHRd=RS!s>2 zo39VCB-#Ew?PAYOx`x!@0qa5lZKrE?PJEwVfkww#aB_$CLKlkzHSIi4p3#IeyA@u@ z`x^!`0HJxe>#V7+Grku^in>Ppz|TD*`Ca4X%R3Yo|J=!)l$vYks|KhG{1CEfyuzK( zLjCz{5l}9>$J=FC?59^85awK0$;^9t9UxwOU8kP7ReVCc*rPOr(9uMY*aCZi2=JBu z(D0svsJRB&a9nY;6|4kMr1Er5kUVOh1TuBwa3B2C<+rS|xJo&Lnx3K-*P83eXQCJ= z(htQSA3hgOMcs`#NdYB17#zP_1N_P0peHrNo1%NsYn=;PgLXTic6b#{Y0Z~x9Ffav z^3eO+diquPfo1AXW*>G(JcGn{yN?segqKL$Wc9po(Kex z#tw_};zd++we+MPhOOgaXSmguul67JOvBysmg?wRf=OUeh(XyRcyY@8RTV@xck_c~ zLFMWAWb4^7xwR)3iO1PIs1<}L3CMJ1L-}s=>_y!`!FvYf^pJO|&nII{!Dz+b?=bUd zPJUUn))z)-TcpqKF(1tr-x1;lS?SB@mT#O7skl0sER{a|d?&>EKKaw* zQ>D^m*pNgV`54BKv?knU-T5bcvBKnI@KZo^UYjKp{2hpCo?_6v(Sg77@nQa{tSKbn zUgMtF>A3hndGocRY+Snm#)Q4%`|Qq3YTOU^uG}BGlz!B=zb?vB16sN&6J`L(k1r+$ z5G6E9tJ~Iwd!d!NH7Q%Z@BR@0e{p6#XF2))?FLAVG`npIjih*I+0!f6;+DM zLOP-qDsm9=ZrI!lfSDn%XuF17$j~gZE@I}S(Ctw&Te75P5?Fj%FLT;p-tm33FaUQc z5cR;$SwV|N0xmjox3V~XL3sV?YN}U0kkfmygW@a5JOCGgce6JyzGmgN$?NM%4;wEhUMg0uTTB~L==1Fvc(6)KMLmU z(12l^#g&9OpF7+Ll30F6(q=~>NIY=-YUJJ}@&;!RYnq*xA9h!iMi`t;B2SUqbyNGn zye@*0#Uu`OQy%utS%IA%$M1f4B|bOH={!3K1=Tc7Ra|%qZgZ{mjAGKXb)}jUu1mQ_ zRW7<;tkHv(m7E0m>**8D;+2ddTL>EcH_1YqCaTTu_#6Djm z*64!w#=Hz<>Fi1n+P}l#-)0e0P4o+D8^^Mk& zhHeJoh2paKlO+8r?$tx`qEcm|PSt6|1$1q?r@VvvMd1!*zAy3<`X9j?ZI|;jE-F(H zIn1+sm(zAnoJArtytHC|0&F0`i*dy-PiwbD-+j`ezvd4C`%F1y^7t}2aww}ZlPk)t z=Y`tm#jNM$d`pG%F42Xmg_pZnEnvC%avz=xNs!=6b%%JSuc(WObezkCeZ#C|3PpXj zkR8hDPyTIUv~?<%*)6=8`WfPPyB9goi+p$1N2N<%!tS2wopT2x`2IZi?|_P{GA|I5 z?7DP*?Gi#2SJZ!x#W9Npm)T;=;~Swyeb*!P{I^s@o5m_3GS2Lg?VUeBdOeae7&s5$ zSL_VuTJih_fq7g8O8b0g+GbmE+xG}^Wx`g~{mWTyr@=h zKlAymoHeZa`DgR?Pj8Yc+I|MrSB>X*ts#wNFOJxs!3aGE)xeTHlF`fC5^g(DTacl$ zx!ezQJdwIyc$8RyNS~Wh{0pp>8NcW)*J=7AQYdT?(QhJuq4u`QniZ!%6l{KWp-0Xp z4ZC6(E(_&c$$U_cmGFslsyX6(62~m*z8Yx2p+F5xmD%6A7eOnx`1lJA-Mrc#&xZWJ zzXV{{OIgzYaq|D4k^j%z|8JB8GnRu3hw#8Z@({sSmsF(x>!w0Meg5y(zg!Z0S^0k# z5x^g1@L;toCK$NB|FnbtS4tPeeeILKK(M?HtQY!6K^wt zxsPH>E%g%V@=!B;kWF54$xjC&4hO!ZEG0QFMHLqe!tgH;%vO62BQj||nokbX&2kxF zzg#N!2M|NxFL#YdwOL8}>iDLr%2=!LZvk_&`AMrm7Zm%#_{Ot_qw=HkdVg{f9hYHF zlRF*9kxo~FPfyBD!^d6MbD?BRZj(4u9j!5}HFUt+$#Jd48Fd~ahe@)R9Z2M1t%LHa z_IP|tDb0CDl(fsEbvIYawJLJ7hXfpVw)D-)R-mHdyn5uZYefN0rZ-#KDzb`gsow;v zGX>k|g5?D%Vn_}IJIgf%nAz{@j0FCIEVWffc1Z+lliA}L+WJY=MAf$GeI7xw5YD1) z;BJn$T;JI5vTbZ&4aYfmd-XPQd)YQ~d({>(^5u>Y^5rfxEUDci9I5?dXp6{zHG=Tc z6$rLd^C~60=K4ptlZ%Fl-%QLc-x{y=zU$%&4ZU}4&Yu?jF4eqB#kTHhty`Aq=kJE% zzq(5OS9o1t-)}S}`chh1Uu-Sl?ljxMDVIy5j`97Eqg7L~Ak9NSZ?!5M>5TRMXfD#} zFlMmFnr%?ra>vkvJQjmWa8oB{63qPo1L#LAht%FG|6CEe9KP2&VNe_HNb7M}pd*!t zpGL0vzCU02%iK@AKWxP^64fz-U#%u~D+FV?*KdPY9C_9{Ggn;Y;;iKE0b|}KmC&f(WIDcFtvRPDju z?Dc&_dP4*hh!%!6(nYB*TEJs<4zn*V0Nw1O4VzYaNZul>anE2Feb@T$XkI?)u6VK$bg* z22AY7|Ju!_jwc2@JX(;SUE>VDWRD|d56WYUGLAAwPYXU9K&NgY{t{dyMskUBgV%@p zMVcFn>W|hJA?3S?$k!M|1S2e1A&_~W2p$;O2Wpn`$|8W(@~w>RR4kxHdEr`+q|>m@ zTYp%Ut+g`T#HkyE5zw<5uhFvt2=k5fM3!8OxvGgMRS|t7RaJn7!2$r_-~a%C7@*Dq zGUp2g0N^HzLU=%bROVFi2J;#`7#WGTUI$r!(wmbJlbS`E#ZpNp7vOR#TwPQWNf$IW zoX>v@6S8n6+HhUZB7V^A`Y9t4ngdfUFZrDOayMVvg&=RY4@0Z~L|vW)DZTIvqA)%D zi!pa)8L7BipsVh5-LMH4bmwt2?t88YUfIRf!@8^gX$xpKTE^WpM!-=3?UVw^Cs`Y7 z2b<*~Q=1uqs79{h&H_8+X%><4qSbz_cSEa;Hkdmtq5uwGTY+|APD{i_zYhLXqT7HO zT^Am_tW?Cmn%N~MC0!9mYt-~WK;hj-SnayMwqAAHo#^ALwkg0>72&W}5^4%|Z|@T; zwwBQTg*&eXC}j8 zra77(XC^p&&o;KrZ$`_)C$@SDWT+p$3!;ZB#yhnK{CxQc&?R}ZQMcp`!!eXLLhiP8W zM=McHAMnUMlar8XLXk&jx#HBH3U0jbhJuqa~#l`aB)N6;WI(Im322o#{K&92l6(K z)(;=;-m!%9@j#WSA1uniU(^x(UTi+%idMd)x*!*Hub0Rg7DblI!cqo9QUZf29Y#?XN!K!|ovJ7~!^H}!zsaMl(57lpztQ7V zyo#`qJ4jv1zGAW2uIkU3o&7_=lYWz3=SR!sgfuYp{Um<*H%uW8MdUT2&o*QKjD3PEH zHz;H}qCN~`GFsJ_xz$9xga*@VzJTH7-3lggkBM&7xlz5#qWfkgi=#j%{&f-NMsaSv zeIZ60Jpw}QV+t`ovOJxVhYCXe8E7r*eLCJ{lP6sqc}BYrhjXlt(6e9nw=2Le1gOT0 zZX!q9r#DZ&8_cAhWPeq~CJkGvpRU&q8>rR@RBW4~@3j1X>RBum#U z1wjcEdB`|@sXAWxk2*TOj> zr(j{nr1;Mk3x^gvAtZsahY=ou{eAJi-d(XISF-?+Q6{Um4+lu?aA=S33@k=6^OT?F z8TE`ha;q@=ZQ-dlt!q49;Wjjl<&Yee^!h5MFkd)Oj=fsvxytK%!B z-P#YJ)8^dMi=wpKmt43|apX6v2dNXzZ-WHlLEh`JoKFNjCK7LhO^P5XW?Y~rjGcIpv$2v41rE}~0{aj9NVpDXGdD6W8{fyzioQdu&xkn8 zhT*^NY0zv>Om?h3XAku3p-4SHkK@fXrpi{T=@#bwY76TsD4$tAHAhXAStdb$odc z02~lZyb!fG_7qrU_F5 zoOG|pEwdyDhLXDwlU>T|;LF@ACJk(qZ*2h6GB@33mKk};HO^CQM(N7@Ml5|8IeHzt zdG4f$q}SNYA4P=?jV!mJ%3hRKwi&!wFptWZRq4bpV9^b7&L>nW%~Y|junw!jHj%85 z3Ck6%`Y=Abvrujnm{`OtE0uQkeX@3JPzj#iO#eNoAX6cDhM+cc2mLk8;^bG62mtjQ zj|kxI2W|4n{VqMqB?@YnA0y}@Mju)&j3UQ4tSdH=Eu?>i7A50b%i$pc{YJki7ubq7 zVTDqdkGjeAuZdF)KBwR6LZob}7`2935iKIU2-I;88&?t16c-~TNWIcQ8C_cE_F1tv z*>4<_kimwX^CQtFrlk)i!3-+2zD|=!D43Qqk-LtpPnX#QQt%eullxHat97k=00qR|b2|M}`q??yf+h~};_PJ2bLeEeteO3rh+H{9otNQDki^lu)(`a~_x(8NWLE*rb%T=Z~s?JC|G zXNnO~2SzW)H}p6Zn%WqAyadG=?$BXuS(x-2(T!E&sBcIz6`w=MdtxR<7M`s6-#!s+ znhpkcNMw{c#!F%#O!K*?(Hl(;Tgl9~WYBB(P@9KHb8ZkLN>|}+pQ)K#>ANpV1IM{Q z8qL^PiNEOrY*%!7Hj!CwRT2CN4r(ipJA%kCc&s;wOfrweu)H!YlFM z247pwv!nFWbTKq&zm4UVH^d?H2M276ny~@v5jR2>@ihAmcdZI-ah(&)7uLQM5COqg?hjX2<75QU4o5Q7 zZ5gG;6RMhxLa5NFTXgegSXb0a%aPdmLL4=`ox2smE)lDn^!;^PNftzTf~n{NH7uh_ zc9sKmx@q1InUh_BgI3C!f>`HnO~X`9#XTI^Yzaj1928gz8ClI!WIB&2!&;M18pf0T zsZ81LY3$-_O`@4$vrO`Cb&{apkvUwrA0Z49YfZYD)V4;c2&`JPJuwN_o~2vnyW_b! z%yUSS5K{a*t>;WJr&$A_&}bLTTXK23<;*EiNHHF-F<#hy8v2eegrqnE=^gt+|8R5o z_80IY4&-!2`uISX6lb0kCVmkQ{D}HMGUAkCe`I~t2~99(<#}{E;{+Y0!FU>leSP(M zuMoSOEfw3OC5kQ~Y2)EMlJceJlh}p?uw}!cq?h44=b2k@T1;6KviZGc_zbeTtTE$@EDwUcjxd#fpK=W*U@S#U|YKz{#qbb*|BpcaU!>6&Ir zhsA+ywgvk54%Nj>!!oH>MQ+L~36v1pV%^pOmvo7sT|N}$U!T6l^<3W2 z6}mT7Cl=IQo%Y~d%l=+;vdK)yW!C>Es-~b^E?IjUU4h6<86tun6rO#?!37B)M8>ph zJ@`~09W^@5=}sWg8`~ew=0>0*V^b9eG=rBIGbe3Ko$pj!0CBUTmF^Q}l7|kCeB(pX zi6UvbUJWfKcA&PDq?2HrMnJBTW#nm$(vPZE;%FRM#ge$S)i4!y$ShDwduz@EPp3H? z`+%=~-g6`Ibtrb=QsH3w-bKCX1_aGKo4Q7n-zYp->k~KE!(K@VZder&^^hIF6AhiG z;_ig2NDd_hpo!W1Un{GcB@e{O@P3zHnj;@SzYCxsImCHJS5I&^s-J6?cw92qeK8}W zk<_SvajS&d_tDP~>nhkJSoN>UZUHs?)bDY`{`;D^@wMW0@!H1I_BYphly0iqq^Jp; z_aD>eHbu@e6&PUQ4*q*ik0i*$Ru^_@`Mbyrscb&`8|c=RWZ>Ybs16Q?Cj1r6RQA5! zOeuxfzWm(fX!geO(anpBCOV|a&mu|$4cZ<*{pb1F{`-cm1)yB6AGm7b=GV@r*DataJ^I!>^lCvS_@AftZiwtpszHmq{UVl zKL9164tmF5g>uOZ({Jg~fH~QyHd#h#E;WzSYO~zt)_ZMhefdm5*H1K-#=_kw#o%ch zgX|C$K4l4IY8=PV6Q{T8dd`*6MG-TlsTEaA&W{EuwaoN+-BDdSL2>|lwiZ++4eR8h zNS1yJdbhAWjW4k`i1KL)l#G*Y=a0ouTbg8R1aUU`8X7p*AnO+uaNF9mwa+ooA)hlj zR26XBpQ-{6E9;PQAvq2<%!M1;@Q%r@xZ16YRyL&v}9F`Nnx#RLUc<78w$S zZElh==Rnr2u<*qKY|aUR9(A|{cURqP81O-1a@X)khheokEhC}BS-g~|zRbn-igmID z$Ww!O0-j!t(lx>-JH+0KW3*Bgafpm>%n=`(ZLa^TWd*-je!Xi7H*bZ8pz`HPFYeC? zk>`W)4Cj6*A3A8g$MEhp*<@qO&&>3<4YI%0YAMmQvD3 z${78Fa2mqiI>P7|gE)xs$cg3~^?UBb4y6B4Z#0Fzy zN8Gf!c+$uPS`VRB=wRV1f)>+PEHBYco<1?ceXET}Q-tKI=E`21<15xTe@%Bhk$v09 zVpoL_wNuw)@^O+C@VCeuWM}(%C(%lTJ}7n)JVV!^0H!3@)ydq#vEt;_*+xos$9i?{ zCw5^ZcNS&GzaeBmPg6IKrbT`OSuKg$wai+5K}$mTO-Z$s3Y+vb3G}x%WqlnQS1;|Z zlZ$L{onq1Ag#5JrM)%6~ToQ}NmM2A(7X5gy$nVI=tQFOm;7|Oeij{xb_KU{d@%)2z zsVqzTl@XPf(a95;P;oBm9Hlpo`9)D9>G>!Bj=ZmX{ces=aC~E^$rTO5hO$#X65jEA zMj1(p+HXdOh7FAV;(_)_RR#P>&NW?&4C7K1Y$C$i**g;KOdu|JI_Ep zV-N$wuDRkn6=k|tCDXU%d=YvT!M1nU?JY;Pl`dxQX5+660TX7~q@ukEKc!Iqy2y)KuG^Q-Y%$;SR&Mv{%=CjphG1_^dkUM=qI*3Ih^Bk621n`6;q(D;nB_y|~ zW*1ps&h|wcET!#~+Ptsiex~YVhDiIREiw1=uwlNpPyqDZ`qqv9GtKwvxnFE}ME93fD9(Iq zz=f&4ZpD~+qROW6Y2AjPj9pH*r_pS_f@tLl88dbkO9LG0+|4*Xq(Eo7fr5MVg{n<+p>H{LGr}UzToqfk_x6(2YB~-^7>%X z+331Ob|NyMST64u|1dK*#J>qEW@dKNj-u}3MG)ZQi~#GzJ_S4n5lb7vu&>;I-M49a z0Uc#GD-KjO`tQ5ftuSz<+`rT)cLio$OJDLtC`t)bE+Nu@Rok2;`#zv1=n z7_CZr&EhVy{jq(eJPS)XA>!7t<&ormWI~w0@Y#VKjK)`KAO~3|%+{ z$HKIF?86~jH*1p=`j#}8ON0{mvoiN7fS^N+TzF~;9G0_lQ?(OT8!b1F8a~epAH#uA zSN+goE<-psRqPXdG7}w=ddH=QAL|g}x5%l-`Kh69D4{M?jv!l))<@jxLL$Eg2vt@E zc6w`$?_z%awCE~ca)9nMvj($VH%2!?w3c(5Y4&ZC2q#yQ=r{H2O839eoBJ{rfMTs8 zn2aL6e6?;LY#&(BvX_gC6uFK`0yt zJbUATdyz5d3lRyV!rwbj0hVg#KHdK0^A7_3KA%gKi#F#-^K%1XQbeF49arI2LA|Bj z?=;VxKbZo(iQmHB5eAg=8IPRqyskQNR!&KEPrGv&kMr(8`4oe?vd?sIZJK+JY04kc zXWk)4N|~*|0$4sUV3U6W6g+Z3;nN<~n4H17QT*%MCLt_huVl@QkV`A`jyq<|q=&F_ zPEOotTu9?zGKaPJ#9P&ljgW!|Vxhe+l85%G5zpD5kAtn*ZC})qEy!v`_R}EcOn)&# z-+B52@Zle@$!^-N@<_=LKF}fqQkwf1rE(OQP&8!En}jqr-l0A0K>77K8{zT%wVpT~ zMgDx}RUG$jgaeqv*E~<#RT?Q)(RGi8bUm(1X?2OAG2!LbBR+u1r7$}s=lKqu&VjXP zUw3L9DH({yj)M%OqP%GC+$}o0iG|*hN-Ecv3bxS|Mxpmz*%x`w7~=o9BKfEVzr~K- zo&Fh`wZ{#1Jd5QFM4&!PabL!tf%TfJ4wi;45AqWe$x}8*c2cgqua`(6@ErE&P{K5M zQfwGQ4Qg&M3r4^^$B?_AdLzqtxn5nb#kItDY?BTW z#hShspeIDJ1FDmfq@dz1TT`OV;SS0ImUp`P6GzOqB3dPfzf?+w^40!Wn*4s!E;iHW zNzpDG+Vmtnh%CyfAX>X z{Y=vt;yb z;TBRZpw##Kh$l<8qq5|3LkrwX%MoxqWwclBS6|7LDM(I31>$_w=;{=HcyWlak3xM1 z_oaOa)a;AtV{*xSj6v|x%a42{h@X-cr%#HO5hWbuKRGTZS)o=^Id^>H5}0p_(BEXX zx3VnRUj6&1JjDI);c=#EYcsg;D5TFlhe)=nAycR1N)YSHQvO+P5hKe9T0ggZT{oF@ z#i3V4TpQlO1A8*TWn|e}UWZ(OU;Isd^ zb<#Vj`~W_-S_=lDR#223!xq8sRjAAVSY2MhRyUyHa-{ql=zyMz?~i_c&dS>eb>s>#q#$UI+!&6MftpQvxHA@f|k2(G9z zAQCx-lJ-AT;PnX%dY5}N$m6tFt5h6;Mf78TmFUN9#4*qBNg4it3-s22P+|Rw zG@X%R0sm*X07ZZEOJRbDkcjr}tvaVWlrwJ#7KYEw&X`2lDa@qb!0*SHa%+-FU!83q zY{R15$vfL56^Nj42#vGQlQ%coT4bLr2s5Y0zBFp8u&F(+*%k4xE1{s75Q?P(SL7kf zhG?3rfM9V*b?>dOpwr%uGH7Xfk1HZ!*k`@CNM77g_mGN=ucMG&QX19B!%y77w?g#b z%k3x6q_w_%ghL;9Zk_J#V{hxK%6j`?-`UN?^e%(L6R#t#97kZaOr1{&<8VGVs1O>} z6~!myW`ja01v%qy%WI=8WI!cf#YA8KNRoU>`_muCqpt_;F@rkVeDY}F7puI_wBPH9 zgRGre(X_z4PUO5!VDSyg)bea1x_a7M z4AJ?dd9rf{*P`AY+w?g_TyJlB5Nks~1$@PxdtpUGGG##7j<$g&BhKq0mXTva{;h5E ztcN!O17bquKEDC#;Yw2yE>*=|WdZT9+ycgUR^f?~+TY-E552AZlzYn{-2CLRV9mn8 z+zNoWLae^P{co`F?)r;f!C=nnl*1+DI)mZY!frp~f%6tX2g=?zQL^d-j^t1~+xYgK zv;np&js@X=_e7F&&ZUX|N6Q2P0L=fWoBuh*L7$3~$-A)sdy6EQ@Pd-)|7lDA@%ra2 z4jL@^w92&KC>H(=v2j!tVE_3w0KogtrNjgPBsTvW F{TFmrHLU;u delta 8469 zcmY*q~ZGqoW{=01$bgB@1Nex`%9%S2I04)5Jw9+UyLS&r+9O2bq{gY;dCa zHW3WY0%Dem?S7n5JZO%*yiT9fb!XGk9^Q`o-EO{a^j%&)ZsxsSN@2k2eFx1*psqn0e*crIbAO}Rd~_BifMu*q7SUn{>WD$=7n_$uiQ0wGc$?u1hM%gf??nL?m22h!8{ zYmFMLvx6fjz*nwF^tAqx1uv0yEW9-tcIV5Q{HNh`9PMsuqD8VE%oAs5FsWa0mLV$L zPAF5e^$tJ8_Kwp!$N1M<#Z154n!X6hFpk8)eMLu; zaXS71&`24 zV`x~}yAxBw##Oj@qo_@DcBqc+2TB&=bJyZWTeR55zG<{Z@T^hSbMdm~Ikkr?4{7WT zcjPyu>0sDjl7&?TL@ z)cW?lW@Pfwu#nm7E1%6*nBIzQrKhHl`t54$-m>j8f%0vVr?N0PTz`}VrYAl+8h^O~ zuWQj@aZSZmGPtcVjGq-EQ1V`)%x{HZ6pT-tZttJOQm?q-#KzchbH>>5-jEX*K~KDa z#oO&Qf4$@}ZGQ7gxn<;D$ziphThbi6zL^YC;J#t0GCbjY)NHdqF=M4e(@|DUPY_=F zLcX1HAJ+O-3VkU#LW`4;=6szwwo%^R4#UK}HdAXK` z{m!VZj5q9tVYL=^TqPH*6?>*yr>VxyYF4tY{~?qJ*eIoIU0}-TLepzga4g}}D7#Qu zn;6I;l!`xaL^8r*Tz*h`^(xJCnuVR_O@Gl*Q}y$lp%!kxD`%zN19WTIf`VX*M=cDp z*s4<9wP|ev;PARRV`g$R*QV@rr%Ku~z(2-s>nt{JI$357vnFAz9!ZsiiH#4wOt+!1 zM;h;EN__zBn)*-A^l!`b?b*VI-?)Sj6&Ov3!j9k$5+#w)M>`AExCm0!#XL+E{Bp)s;Hochs+-@@)7_XDMPby#p<9mLu+S{8e2Jn`1`1nrffBfy4u)p7FFQWzgYt zXC}GypRdkTUS+mP!jSH$K71PYI%QI-{m;DvlRb*|4GMPmvURv0uD2bvS%FOSe_$4zc--*>gfRMKN|D ztP^WFfGEkcm?sqXoyRmuCgb?bSG17#QSv4~XsbPH>BE%;bZQ_HQb?q%CjykL7CWDf z!rtrPk~46_!{V`V<;AjAza;w-F%t1^+b|r_um$#1cHZ1|WpVUS&1aq?Mnss|HVDRY z*sVYNB+4#TJAh4#rGbr}oSnxjD6_LIkanNvZ9_#bm?$HKKdDdg4%vxbm-t@ZcKr#x z6<$$VPNBpWM2S+bf5IBjY3-IY2-BwRfW_DonEaXa=h{xOH%oa~gPW6LTF26Y*M)$N z=9i`Y8};Qgr#zvU)_^yU5yB;9@yJjrMvc4T%}a|jCze826soW-d`V~eo%RTh)&#XR zRe<8$42S2oz|NVcB%rG(FP2U&X>3 z4M^}|K{v64>~rob;$GO55t;Nb&T+A3u(>P6;wtp6DBGWbX|3EZBDAM2DCo&4w|WGpi;~qUY?Ofg$pX&`zR~)lr)8}z^U3U38Nrtnmf~e7$i=l>+*R%hQgDrj%P7F zIjyBCj2$Td=Fp=0Dk{=8d6cIcW6zhK!$>k*uC^f}c6-NR$ zd<)oa+_fQDyY-}9DsPBvh@6EvLZ}c)C&O-+wY|}RYHbc2cdGuNcJ7#yE}9=!Vt-Q~ z4tOePK!0IJ0cW*jOkCO? zS-T!bE{5LD&u!I4tqy;dI*)#e^i)uIDxU?8wK1COP3Qk{$vM3Sm8(F2VwM?1A+dle z6`M6bbZye|kew%w9l`GS74yhLluJU5R=#!&zGwB7lmTt}&eCt0g(-a;Mom-{lL6u~ zFgjyUs1$K*0R51qQTW_165~#WRrMxiUx{0F#+tvgtcjV$U|Z}G*JWo6)8f!+(4o>O zuaAxLfUl;GHI}A}Kc>A8h^v6C-9bb}lw@rtA*4Q8)z>0oa6V1>N4GFyi&v69#x&CwK*^!w&$`dv zQKRMKcN$^=$?4to7X4I`?PKGi(=R}d8cv{74o|9FwS zvvTg0D~O%bQpbp@{r49;r~5`mcE^P<9;Zi$?4LP-^P^kuY#uBz$F!u1d{Ens6~$Od zf)dV+8-4!eURXZZ;lM4rJw{R3f1Ng<9nn2_RQUZDrOw5+DtdAIv*v@3ZBU9G)sC&y!vM28daSH7(SKNGcV z&5x#e#W2eY?XN@jyOQiSj$BlXkTG3uAL{D|PwoMp$}f3h5o7b4Y+X#P)0jlolgLn9xC%zr3jr$gl$8?II`DO6gIGm;O`R`bN{;DlXaY4b`>x6xH=Kl@ z!>mh~TLOo)#dTb~F;O z8hpjW9Ga?AX&&J+T#RM6u*9x{&%I8m?vk4eDWz^l2N_k(TbeBpIwcV4FhL(S$4l5p z@{n7|sax){t!3t4O!`o(dYCNh90+hl|p%V_q&cwBzT*?Nu*D0wZ)fPXv z@*;`TO7T0WKtFh8~mQx;49VG_`l`g|&VK}LysK%eU4})Cvvg3YN)%;zI?;_Nr z)5zuU1^r3h;Y+mJov*->dOOj>RV^u2*|RraaQWsY5N?Uu)fKJOCSL2^G=RB%(4K{* zx!^cB@I|kJR`b+5IK}(6)m=O{49P5E^)!XvD5zVuzJH{01^#$@Cn514w41BB;FAoS2SYl3SRrOBDLfl5MvgA3 zU6{T?BW}l~8vU;q@p9IOM(=;WdioeQmt?X|=L9kyM&ZsNc*-Knv8@U*O96T@4ZiJ$ zeFL2}pw_~Tm3d4#q!zZS0km@vYgym33C0h(6D)6|Y)*UXI^T`(QPQh$WF?&h(3QYh zqGw@?BTk@VA_VxK@z?a@UrMhY zUD16oqx4$$6J_k0HnXgARm}N#(^yA1MLdbwmEqHnX*JdHN>$5k2E|^_bL< zGf5Z+D!9dXR>^(5F&5gIew1%kJtFUwI5P1~I$4LL_6)3RPzw|@2vV;Q^MeQUKzc=KxSTTX`}u%z?h~;qI#%dE@OZwehZyDBsWTc&tOC1c%HS#AyTJ= zQixj=BNVaRS*G!;B$}cJljeiVQabC25O+xr4A+32HVb;@+%r}$^u4-R?^3yij)0xb z86i@aoVxa%?bfOE;Bgvm&8_8K(M-ZEj*u9ms_Hk#2eL`PSnD#At!0l{f!v`&Kg}M$n(&R)?AigC5Z?T7Jv^lrDL!yYS{4 zq_H}oezX-Svu>dp)wE@khE@aR5vY=;{C-8Hws++5LDpArYd)U47jc-;f~07_TPa^1 zO`0+uIq)@?^!%JXCDid+nt|c@NG1+ce@ijUX&@rV9UiT|m+t-nqVB7?&UX*|{yDBFw9x52&dTh@;CL)Q?6s1gL=CUQTX7#TJPs9cpw<4>GFMUKo|f{! z&(%2hP6ghr%UFVO-N^v9l|tKy>&e%8us}wT0N*l(tezoctVtLmNdGPOF6oaAGJI5R zZ*|k@z3H!~Mm9fXw{bbP6?lV-j#Rfgnjf++O7*|5vz2#XK;kk ztJbi%r0{U5@QwHYfwdjtqJ6?;X{Ul3?W0O0bZ$k*y z4jWsNedRoCb7_|>nazmq{T3Y_{<5IO&zQ?9&uS@iL+|K|eXy^F>-60HDoVvovHelY zy6p(}H^7b+$gu@7xLn_^oQryjVu#pRE5&-w5ZLCK&)WJ5jJF{B>y;-=)C;xbF#wig zNxN^>TwzZbV+{+M?}UfbFSe#(x$c)|d_9fRLLHH?Xbn!PoM{(+S5IEFRe4$aHg~hP zJYt`h&?WuNs4mVAmk$yeM;8?R6;YBMp8VilyM!RXWj<95=yp=4@y?`Ua8 znR^R?u&g%`$Wa~usp|pO$aMF-en!DrolPjD_g#{8X1f=#_7hH8i|WF+wMqmxUm*!G z*4p980g{sgR9?{}B+a0yiOdR()tWE8u)vMPxAdK)?$M+O_S+;nB34@o<%lGJbXbP` z5)<({mNpHp&45UvN`b&K5SD#W){}6Y_d4v~amZPGg|3GdlWDB;;?a=Z{dd zELTfXnjCqq{Dgbh9c%LjK!Epi1TGI{A7AP|eg2@TFQiUd4Bo!JsCqsS-8ml`j{gM& zEd7yU`djX!EX2I{WZq=qasFzdDWD`Z?ULFVIP!(KQP=fJh5QC9D|$JGV95jv)!sYWY?irpvh06rw&O?iIvMMj=X zr%`aa(|{Ad=Vr9%Q(61{PB-V_(3A%p&V#0zGKI1O(^;tkS{>Y<`Ql@_-b7IOT&@?l zavh?#FW?5otMIjq+Bp?Lq)w7S(0Vp0o!J*~O1>av;)Cdok@h&JKaoHDV6IVtJ?N#XY=lknPN+SN8@3Gb+D-X*y5pQ)wnIpQlRR!Rd)@0LdA85}1 zu7W6tJ*p26ovz+`YCPePT>-+p@T_QsW$uE`McLlXb;k}!wwWuh$YC4qHRd=RS!s>2 zo39VCB-#Ew?PAYOx`x!@0qa5lZKrE?PJEwVfkww#aB_$CLKlkzHSIi4p3#IeyA@u@ z`x^!`0HJxe>#V7+Grku^in>Ppz|TD*`Ca4X%R3Yo|J=!)l$vYks|KhG{1CEfyuzK( zLjCz{5l}9>$J=FC?59^85awK0$;^9t9UxwOU8kP7ReVCc*rPOr(9uMY*aCZi2=JBu z(D0svsJRB&a9nY;6|4kMr1Er5kUVOh1TuBwa3B2C<+rS|xJo&Lnx3K-*P83eXQCJ= z(htQSA3hgOMcs`#NdYB17#zP_1N_P0peHrNo1%NsYn=;PgLXTic6b#{Y0Z~x9Ffav z^3eO+diquPfo1AXW*>G(JcGn{yN?segqKL$Wc9po(Kex z#tw_};zd++we+MPhOOgaXSmguul67JOvBysmg?wRf=OUeh(XyRcyY@8RTV@xck_c~ zLFMWAWb4^7xwR)3iO1PIs1<}L3CMJ1L-}s=>_y!`!FvYf^pJO|&nII{!Dz+b?=bUd zPJUUn))z)-TcpqKF(1tr-x1;lS?SB@mT#O7skl0sER{a|d?&>EKKaw* zQ>D^m*pNgV`54BKv?knU-T5bcvBKnI@KZo^UYjKp{2hpCo?_6v(Sg77@nQa{tSKbn zUgMtF>A3hndGocRY+Snm#)Q4%`|Qq3YTOU^uG}BGlz!B=zb?vB16sN&6J`L(k1r+$ z5G6E9tJ~Iwd!d!NH7Q%Z@BR@0e{p6#XF2))?FLAVG`npIjih*I+0!f6;+DM zLOP-qDsm9=ZrI!lfSDn%XuF17$j~gZE@I}S(Ctw&Te75P5?Fj%FLT;p-tm33FaUQc z5cR;$SwV|N0xmjox3V~XL3sV?YN}U0kkfmygW@a5JOCGgce6JyzGmgN$?NM%4;wEhUMg0uTTB~L==1Fvc(6)KMLmU z(12l^#g&9OpF7+Ll30F6(q=~>NIY=-YUJJ}@&;!RYnq*xA9h!iMi`t;B2SUqbyNGn zye@*0#Uu`OQy%utS%IA%$M1f4B|bOH={!3K1=Tc7Ra|%qZgZ{mjAGKXb)}jUu1mQ_ zRW7<;tkHv(m7E0m>**8D;+2ddTL>EcH_1YqCaTTu_#6Djm z*64!w#=Hz<>Fi1n+P}l#-)0e0P4o+D8^^Mk& zhHeJoh2paKlO+8r?$tx`qEcm|PSt6|1$1q?r@VvvMd1!*zAy3<`X9j?ZI|;jE-F(H zIn1+sm(zAnoJArtytHC|0&F0`i*dy-PiwbD-+j`ezvd4C`%F1y^7t}2aww}ZlPk)t z=Y`tm#jNM$d`pG%F42Xmg_pZnEnvC%avz=xNs!=6b%%JSuc(WObezkCeZ#C|3PpXj zkR8hDPyTIUv~?<%*)6=8`WfPPyB9goi+p$1N2N<%!tS2wopT2x`2IZi?|_P{GA|I5 z?7DP*?Gi#2SJZ!x#W9Npm)T;=;~Swyeb*!P{I^s@o5m_3GS2Lg?VUeBdOeae7&s5$ zSL_VuTJih_fq7g8O8b0g+GbmE+xG}^Wx`g~{mWTyr@=h zKlAymoHeZa`DgR?Pj8Yc+I|MrSB>X*ts#wNFOJxs!3aGE)xeTHlF`fC5^g(DTacl$ zx!ezQJdwIyc$8RyNS~Wh{0pp>8NcW)*J=7AQYdT?(QhJuq4u`QniZ!%6l{KWp-0Xp z4ZC6(E(_&c$$U_cmGFslsyX6(62~m*z8Yx2p+F5xmD%6A7eOnx`1lJA-Mrc#&xZWJ zzXV{{OIgzYaq|D4k^j%z|8JB8GnRu3hw#8Z@({sSmsF(x>!w0Meg5y(zg!Z0S^0k# z5x^g1@L;toCK$NB|Fn Date: Fri, 5 May 2023 10:14:47 -0400 Subject: [PATCH 179/369] Use updated method to lookup extension by unique id (#2741) Signed-off-by: Craig Perkins --- .../opensearch/security/transport/SecurityRequestHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java index 90fc8308ff..e9c3520665 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java +++ b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java @@ -331,7 +331,7 @@ protected void addAdditionalContextValues(final String action, final TransportRe String extensionUniqueId = getThreadContext().getHeader("extension_unique_id"); if (extensionUniqueId != null) { ExtensionsManager extManager = OpenSearchSecurityPlugin.GuiceHolder.getExtensionsManager(); - if (extManager.getExtensionIdMap().containsKey(extensionUniqueId)) { + if (extManager.lookupInitializedExtensionById(extensionUniqueId).isPresent()) { getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_EXTENSION_REQUEST, Boolean.TRUE); } } From 54d47ab181c4c782af37c96521238f7dbaf50cb2 Mon Sep 17 00:00:00 2001 From: Rishikesh Pasham <62345295+Rishikesh1159@users.noreply.github.com> Date: Fri, 5 May 2023 15:03:10 -0400 Subject: [PATCH 180/369] Upgrade spring-core from 5.3.26 to 5.3.27. (#2717) Signed-off-by: Rishikesh1159 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 91672ba066..8cfe97ea80 100644 --- a/build.gradle +++ b/build.gradle @@ -462,7 +462,7 @@ dependencies { testCompileOnly 'org.apiguardian:apiguardian-api:1.0.0' // Kafka test execution testRuntimeOnly 'org.springframework.retry:spring-retry:1.3.3' - testRuntimeOnly ('org.springframework:spring-core:5.3.26') { + testRuntimeOnly ('org.springframework:spring-core:5.3.27') { exclude(group:'org.springframework', module: 'spring-jcl' ) } testRuntimeOnly 'org.scala-lang:scala-library:2.13.9' From f4def32f2d0a6346c3c6752a605b327d12a21d99 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Tue, 9 May 2023 14:20:41 -0700 Subject: [PATCH 181/369] Limit the use of extensions term in the codebase (#2748) * Limit the use of extensions term in the codebase Extensions are a big part of what we are building into OpenSearch and Security. While we need these features we should try to build them as generically as possible, this checkstyle scan will help enforce this and also allow for working around the term usage with comments as we do have places where extensions need to be treated differently. Signed-off-by: Peter Nied --- checkstyle/sun_checks.xml | 9 ++++++++- .../org/opensearch/security/SnapshotSteps.java | 4 ++-- .../framework/certificate/CertificateMetadata.java | 3 ++- .../framework/certificate/CertificatesIssuer.java | 2 ++ .../test/framework/certificate/PublicKeyUsage.java | 2 ++ .../framework/certificate/TestCertificates.java | 14 +++++++------- .../security/OpenSearchSecurityPlugin.java | 6 ++++++ .../configuration/DlsFilterLevelActionHandler.java | 10 +++++----- .../security/ssl/DefaultSecurityKeyStore.java | 2 ++ .../security/ssl/util/CertificateValidator.java | 2 ++ .../org/opensearch/security/ssl/util/TLSUtil.java | 4 ++++ .../security/support/ConfigConstants.java | 3 ++- .../opensearch/security/support/HeaderHelper.java | 3 ++- .../opensearch/security/tools/SecurityAdmin.java | 2 ++ .../transport/OIDClusterRequestEvaluator.java | 4 ++++ .../security/transport/SecurityRequestHandler.java | 8 ++++++++ 16 files changed, 60 insertions(+), 18 deletions(-) diff --git a/checkstyle/sun_checks.xml b/checkstyle/sun_checks.xml index 5ffbedaf5a..8df7afffc7 100644 --- a/checkstyle/sun_checks.xml +++ b/checkstyle/sun_checks.xml @@ -204,6 +204,13 @@ + + + + + + + @@ -211,7 +218,7 @@ - + diff --git a/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java b/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java index 0fbaf78435..f346bedd31 100644 --- a/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java +++ b/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java @@ -41,7 +41,7 @@ public SnapshotSteps(RestHighLevelClient restHighLevelClient) { // CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here public org.opensearch.action.support.master.AcknowledgedResponse createSnapshotRepository(String repositoryName, String snapshotDirPath, String type) - //CS-ENFORCE-SINGLE + // CS-ENFORCE-SINGLE throws IOException { PutRepositoryRequest createRepositoryRequest = new PutRepositoryRequest().name(repositoryName).type(type) .settings(Map.of("location", snapshotDirPath)); @@ -70,7 +70,7 @@ public org.opensearch.action.support.master.AcknowledgedResponse deleteSnapshotR return snapshotClient.deleteRepository(request, DEFAULT); } - //CS-SUPPRESS-SINGLE: RegexpSingleline: It is not possible to use phrase "cluster manager" instead of master here + //CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here public org.opensearch.action.support.master.AcknowledgedResponse deleteSnapshot(String repositoryName, String snapshotName) throws IOException { //CS-ENFORCE-SINGLE return snapshotClient.delete(new DeleteSnapshotRequest(repositoryName, snapshotName), DEFAULT); diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java index 75f933e0d3..fbd2c1f8e8 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java @@ -9,7 +9,7 @@ */ package org.opensearch.test.framework.certificate; - +// CS-SUPPRESS-SINGLE: RegexpSingleline Extension is used to refer to certificate extensions, keeping this rule disable for the whole file import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -218,3 +218,4 @@ ExtendedKeyUsage getExtendedKeyUsage() { return new ExtendedKeyUsage(usages); } } +// CS-ENFORCE-SINGLE diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java index 887e197369..8d821a4571 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java @@ -26,6 +26,7 @@ package org.opensearch.test.framework.certificate; +// CS-SUPPRESS-SINGLE: RegexpSingleline Extension is used to refer to certificate extensions, keeping this rule disable for the whole file import java.math.BigInteger; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; @@ -222,3 +223,4 @@ private BigInteger generateNextCertificateSerialNumber() { return BigInteger.valueOf(ID_COUNTER.incrementAndGet()); } } +// CS-ENFORCE-SINGLE diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java index 8e9aba68e5..9ee2ec8a02 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java @@ -14,6 +14,7 @@ import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.KeyUsage; +// CS-SUPPRESS-SINGLE: RegexpSingleline Extension is used to refer to certificate extensions /** * The class is associated with certificate extensions related to key usages. These extensions are defined by * RFC 5280 and describes allowed usage of public kay which is embedded in @@ -25,6 +26,7 @@ * * @see RFC 5280 */ +// CS-ENFORCE-SINGLE enum PublicKeyUsage { DIGITAL_SIGNATURE(KeyUsage.digitalSignature), KEY_CERT_SIGN(KeyUsage.keyCertSign), diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java index 14e6357330..bc5f5f267d 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java @@ -60,8 +60,8 @@ public class TestCertificates { private static final String CA_SUBJECT = "DC=com,DC=example,O=Example Com Inc.,OU=Example Com Inc. Root CA,CN=Example Com Inc. Root CA"; private static final String ADMIN_DN = "CN=kirk,OU=client,O=client,L=test,C=de"; private static final int CERTIFICATE_VALIDITY_DAYS = 365; - private static final String CERTIFICATE_FILE_EXTENSION = ".cert"; - private static final String KEY_FILE_EXTENSION = ".key"; + private static final String CERTIFICATE_FILE_EXT = ".cert"; + private static final String KEY_FILE_EXT = ".key"; private final CertificateData caCertificate; private final CertificateData adminCertificate; private final List nodeCertificates; @@ -106,7 +106,7 @@ public CertificateData createSelfSignedCertificate(String distinguishedName) { * @return file which contains certificate in PEM format, defined by RFC 1421 */ public File getRootCertificate() { - return createTempFile("root", CERTIFICATE_FILE_EXTENSION, caCertificate.certificateInPemFormat()); + return createTempFile("root", CERTIFICATE_FILE_EXT, caCertificate.certificateInPemFormat()); } public CertificateData getRootCertificateData() { @@ -120,7 +120,7 @@ public CertificateData getRootCertificateData() { */ public File getNodeCertificate(int node) { CertificateData certificateData = getNodeCertificateData(node); - return createTempFile("node-" + node, CERTIFICATE_FILE_EXTENSION, certificateData.certificateInPemFormat()); + return createTempFile("node-" + node, CERTIFICATE_FILE_EXT, certificateData.certificateInPemFormat()); } public CertificateData getNodeCertificateData(int node) { @@ -178,7 +178,7 @@ public CertificateData getLdapCertificateData() { */ public File getNodeKey(int node, String privateKeyPassword) { CertificateData certificateData = nodeCertificates.get(node); - return createTempFile("node-" + node, KEY_FILE_EXTENSION, certificateData.privateKeyInPemFormat(privateKeyPassword)); + return createTempFile("node-" + node, KEY_FILE_EXT, certificateData.privateKeyInPemFormat(privateKeyPassword)); } /** @@ -187,7 +187,7 @@ public File getNodeKey(int node, String privateKeyPassword) { * @return file which contains certificate in PEM format, defined by RFC 1421 */ public File getAdminCertificate() { - return createTempFile("admin", CERTIFICATE_FILE_EXTENSION, adminCertificate.certificateInPemFormat()); + return createTempFile("admin", CERTIFICATE_FILE_EXT, adminCertificate.certificateInPemFormat()); } public CertificateData getAdminCertificateData() { @@ -202,7 +202,7 @@ public CertificateData getAdminCertificateData() { * by RFC 1421 */ public File getAdminKey(String privateKeyPassword) { - return createTempFile("admin", KEY_FILE_EXTENSION, adminCertificate.privateKeyInPemFormat(privateKeyPassword)); + return createTempFile("admin", KEY_FILE_EXT, adminCertificate.privateKeyInPemFormat(privateKeyPassword)); } public String[] getAdminDNs() { diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 41db464d55..815f96f0c5 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -26,6 +26,7 @@ package org.opensearch.security; +// CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions import java.io.IOException; import java.nio.file.Files; import java.nio.file.LinkOption; @@ -192,6 +193,7 @@ import org.opensearch.transport.TransportResponseHandler; import org.opensearch.transport.TransportService; import org.opensearch.watcher.ResourceWatcherService; +// CS-ENFORCE-SINGLE public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin implements ClusterPlugin, MapperPlugin { @@ -1188,6 +1190,7 @@ public static class GuiceHolder implements LifecycleComponent { private static IndicesService indicesService; private static PitService pitService; + // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions private static ExtensionsManager extensionsManager; @Inject @@ -1199,6 +1202,7 @@ public GuiceHolder(final RepositoriesService repositoriesService, GuiceHolder.pitService = pitService; GuiceHolder.extensionsManager = extensionsManager; } + // CS-ENFORCE-SINGLE public static RepositoriesService getRepositoriesService() { return repositoriesService; @@ -1214,7 +1218,9 @@ public static IndicesService getIndicesService() { public static PitService getPitService() { return pitService; } + // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions public static ExtensionsManager getExtensionsManager() { return extensionsManager; } + // CS-ENFORCE-SINGLE @Override diff --git a/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java b/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java index c0ecd6b9be..4bb27a6cbb 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java @@ -142,7 +142,7 @@ private boolean handle() { threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_FILTER_LEVEL_DLS_DONE, request.toString()); try { - if (!createQueryExtension()) { + if (!modifyQuery()) { return true; } @@ -186,7 +186,7 @@ private boolean handle(SearchRequest searchRequest, StoredContext ctx) { if (localClusterAlias != null) { try { - createQueryExtension(localClusterAlias); + modifyQuery(localClusterAlias); } catch (Exception e) { log.error("Unable to handle filter level DLS", e); listener.onFailure(new OpenSearchSecurityException("Unable to handle filter level DLS", e)); @@ -387,11 +387,11 @@ private GetResult searchHitToGetResult(SearchHit hit) { documentFields, metadataFields); } - private boolean createQueryExtension() throws IOException { - return createQueryExtension(null); + private boolean modifyQuery() throws IOException { + return modifyQuery(null); } - private boolean createQueryExtension(String localClusterAlias) throws IOException { + private boolean modifyQuery(String localClusterAlias) throws IOException { Map> filterLevelQueries = evaluatedDlsFlsConfig.getDlsQueriesByIndex(); BoolQueryBuilder dlsQueryBuilder = QueryBuilders.boolQuery().minimumShouldMatch(1); diff --git a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java index 8b704c84d3..01e9714d02 100644 --- a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java +++ b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java @@ -110,8 +110,10 @@ private void printJCEWarnings() { final int aesMaxKeyLength = Cipher.getMaxAllowedKeyLength("AES"); if (aesMaxKeyLength < 256) { + // CS-SUPPRESS-SINGLE: RegexpSingleline Java Cryptography Extension is unrelated to OpenSearch extensions log.info("AES-256 not supported, max key length for AES is {} bit." + " (This is not an issue, it just limits possible encryption strength. To enable AES 256, install 'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files')", aesMaxKeyLength); + // CS-ENFORCE-SINGLE } } catch (final NoSuchAlgorithmException e) { log.error("AES encryption not supported (SG 1). ", e); diff --git a/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java b/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java index 81d625126b..0a12ffc2b5 100644 --- a/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java +++ b/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java @@ -52,6 +52,7 @@ import java.util.HashSet; import java.util.Set; +// CS-SUPPRESS-SINGLE: RegexpSingleline certification extensions is unrelated to OpenSearch extensions /** * Convenience class to handle validation of certificates, aliases and keystores * @@ -62,6 +63,7 @@ * IMPORTANT: at least one of the above mechanisms *MUST* be configured and * operational, otherwise certificate validation *WILL FAIL* unconditionally. */ +// CS-ENFORCE-SINGLE public class CertificateValidator { diff --git a/src/main/java/org/opensearch/security/ssl/util/TLSUtil.java b/src/main/java/org/opensearch/security/ssl/util/TLSUtil.java index fc2c258888..89f6f7ff3b 100644 --- a/src/main/java/org/opensearch/security/ssl/util/TLSUtil.java +++ b/src/main/java/org/opensearch/security/ssl/util/TLSUtil.java @@ -21,7 +21,9 @@ public class TLSUtil { private static final int SSL_CONTENT_TYPE_ALERT = 21; private static final int SSL_CONTENT_TYPE_HANDSHAKE = 22; private static final int SSL_CONTENT_TYPE_APPLICATION_DATA = 23; + // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions heartbeat needs special handling by security extension private static final int SSL_CONTENT_TYPE_EXTENSION_HEARTBEAT = 24; + // CS-ENFORCE-SINGLE private static final int SSL_RECORD_HEADER_LENGTH = 5; private TLSUtil() { @@ -39,9 +41,11 @@ public static boolean isTLS(ByteBuf buffer) { case SSL_CONTENT_TYPE_ALERT: case SSL_CONTENT_TYPE_HANDSHAKE: case SSL_CONTENT_TYPE_APPLICATION_DATA: + // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions heartbeat needs special handling by security extension case SSL_CONTENT_TYPE_EXTENSION_HEARTBEAT: tls = true; break; + // CS-ENFORCE-SINGLE default: // SSLv2 or bad data tls = false; diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index fab88ecea9..7ed780f413 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -97,8 +97,9 @@ public class ConfigConstants { public static final String OPENDISTRO_SECURITY_SSL_TRANSPORT_TRUSTED_CLUSTER_REQUEST = OPENDISTRO_SECURITY_CONFIG_PREFIX+"ssl_transport_trustedcluster_request"; + // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions public static final String OPENDISTRO_SECURITY_SSL_TRANSPORT_EXTENSION_REQUEST = OPENDISTRO_SECURITY_CONFIG_PREFIX+"ssl_transport_extension_request"; - + // CS-ENFORCE-SINGLE /** * Set by the SSL plugin, this is the peer node certificate on the transport layer diff --git a/src/main/java/org/opensearch/security/support/HeaderHelper.java b/src/main/java/org/opensearch/security/support/HeaderHelper.java index d679548c2b..a4ee123347 100644 --- a/src/main/java/org/opensearch/security/support/HeaderHelper.java +++ b/src/main/java/org/opensearch/security/support/HeaderHelper.java @@ -45,10 +45,11 @@ public static boolean isDirectRequest(final ThreadContext context) { || context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_CHANNEL_TYPE) == null; } + // CS-SUPPRESS-SINGLE: RegexpSingleline Java Cryptography Extension is unrelated to OpenSearch extensions public static boolean isExtensionRequest(final ThreadContext context) { return context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_EXTENSION_REQUEST) == Boolean.TRUE; } - + // CS-ENFORCE-SINGLE public static String getSafeFromHeader(final ThreadContext context, final String headerName) { diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 649b2c2b03..2427a48f8a 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -210,8 +210,10 @@ public static int execute(final String[] args) throws Exception { options.addOption( "nhnv", "disable-host-name-verification", false, "Disable hostname verification" ); options.addOption(Option.builder("ts").longOpt("truststore").hasArg().argName("file").desc("Path to truststore (JKS/PKCS12 format)").build()); options.addOption(Option.builder("ks").longOpt("keystore").hasArg().argName("file").desc("Path to keystore (JKS/PKCS12 format").build()); + // CS-SUPPRESS-SINGLE: RegexpSingleline file extensions is unrelated to OpenSearch extensions options.addOption(Option.builder("tst").longOpt("truststore-type").hasArg().argName("type").desc("JKS or PKCS12, if not given we use the file extension to dectect the type").build()); options.addOption(Option.builder("kst").longOpt("keystore-type").hasArg().argName("type").desc("JKS or PKCS12, if not given we use the file extension to dectect the type").build()); + // CS-ENFORCE-SINGLE options.addOption(Option.builder("tspass").longOpt("truststore-password").hasArg().argName("password").desc("Truststore password").build()); options.addOption(Option.builder("kspass").longOpt("keystore-password").hasArg().argName("password").desc("Keystore password").build()); options.addOption(Option.builder("cd").longOpt("configdir").hasArg().argName("directory").desc("Directory for config files").build()); diff --git a/src/main/java/org/opensearch/security/transport/OIDClusterRequestEvaluator.java b/src/main/java/org/opensearch/security/transport/OIDClusterRequestEvaluator.java index 46bf36b27a..71a1ea3275 100644 --- a/src/main/java/org/opensearch/security/transport/OIDClusterRequestEvaluator.java +++ b/src/main/java/org/opensearch/security/transport/OIDClusterRequestEvaluator.java @@ -33,11 +33,13 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.transport.TransportRequest; +// CS-SUPPRESS-SINGLE: RegexpSingleline Java Cryptography Extension is unrelated to OpenSearch extensions /** * Implementation to evaluate a certificate extension with a given OID * and value to the same value found on the peer certificate * */ +// CS-ENFORCE-SINGLE public final class OIDClusterRequestEvaluator implements InterClusterRequestEvaluator { private final String certOid; @@ -49,8 +51,10 @@ public OIDClusterRequestEvaluator(final Settings settings) { public boolean isInterClusterRequest(TransportRequest request, X509Certificate[] localCerts, X509Certificate[] peerCerts, final String principal) { if (localCerts != null && localCerts.length > 0 && peerCerts != null && peerCerts.length > 0) { + // CS-SUPPRESS-SINGLE: RegexpSingleline Java Cryptography Extension is unrelated to OpenSearch extensions final byte[] localValue = localCerts[0].getExtensionValue(certOid); final byte[] peerValue = peerCerts[0].getExtensionValue(certOid); + // CS-ENFORCE-SINGLE if (localValue != null && peerValue != null) { return Arrays.equals(localValue, peerValue); } diff --git a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java index e9c3520665..860f955292 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java +++ b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java @@ -26,6 +26,7 @@ package org.opensearch.security.transport; +// CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions import java.net.InetSocketAddress; import java.security.cert.X509Certificate; import java.util.Objects; @@ -62,6 +63,7 @@ import org.opensearch.transport.TransportRequestHandler; import static org.opensearch.security.OpenSearchSecurityPlugin.isActionTraceEnabled; +// CS-ENFORCE-SINGLE public class SecurityRequestHandler extends SecuritySSLRequestHandler { @@ -195,11 +197,13 @@ else if(!Strings.isNullOrEmpty(injectedUserHeader)) { //if the incoming request is an internal:* or a shard request allow only if request was sent by a server node //if transport channel is not a netty channel but a direct or local channel (e.g. send via network) then allow it (regardless of beeing a internal: or shard request) //also allow when issued from a remote cluster for cross cluster search + // CS-SUPPRESS-SINGLE: RegexpSingleline Used to allow/disallow TLS connections to extensions if ( !HeaderHelper.isInterClusterRequest(getThreadContext()) && !HeaderHelper.isTrustedClusterRequest(getThreadContext()) && !HeaderHelper.isExtensionRequest(getThreadContext()) && !task.getAction().equals("internal:transport/handshake") && (task.getAction().startsWith("internal:") || task.getAction().contains("["))) { + // CS-ENFORCE-SINGLE auditLog.logMissingPrivileges(task.getAction(), request, task); log.error("Internal or shard requests ("+task.getAction()+") not allowed from a non-server node for transport type "+transportChannel.getChannelType()); @@ -224,9 +228,11 @@ else if(!Strings.isNullOrEmpty(injectedUserHeader)) { } //network intercluster request or cross search cluster request + // CS-SUPPRESS-SINGLE: RegexpSingleline Used to allow/disallow TLS connections to extensions if(HeaderHelper.isInterClusterRequest(getThreadContext()) || HeaderHelper.isTrustedClusterRequest(getThreadContext()) || HeaderHelper.isExtensionRequest(getThreadContext())) { + // CS-ENFORCE-SINGLE final String userHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER); final String injectedRolesHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_HEADER); @@ -328,6 +334,7 @@ protected void addAdditionalContextValues(final String action, final TransportRe } } + // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions String extensionUniqueId = getThreadContext().getHeader("extension_unique_id"); if (extensionUniqueId != null) { ExtensionsManager extManager = OpenSearchSecurityPlugin.GuiceHolder.getExtensionsManager(); @@ -335,6 +342,7 @@ protected void addAdditionalContextValues(final String action, final TransportRe getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_EXTENSION_REQUEST, Boolean.TRUE); } } + // CS-ENFORCE-SINGLE super.addAdditionalContextValues(action, request, localCerts, peerCerts, principal); } From 9d758f9166e6af76589ab403a6ee5ec22792bc64 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 10 May 2023 02:31:35 -0400 Subject: [PATCH 182/369] Use ExtensionsManager.lookupExtensionSettingsById when verifying extension unique id (#2749) Signed-off-by: Craig Perkins --- .../opensearch/security/transport/SecurityRequestHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java index 860f955292..c6c80ba50e 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java +++ b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java @@ -338,7 +338,7 @@ protected void addAdditionalContextValues(final String action, final TransportRe String extensionUniqueId = getThreadContext().getHeader("extension_unique_id"); if (extensionUniqueId != null) { ExtensionsManager extManager = OpenSearchSecurityPlugin.GuiceHolder.getExtensionsManager(); - if (extManager.lookupInitializedExtensionById(extensionUniqueId).isPresent()) { + if (extManager.lookupExtensionSettingsById(extensionUniqueId).isPresent()) { getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_EXTENSION_REQUEST, Boolean.TRUE); } } From 63aa09887c9a652f9cca3346e293ba772e9d35c3 Mon Sep 17 00:00:00 2001 From: Dave Lago Date: Thu, 11 May 2023 11:57:48 -0400 Subject: [PATCH 183/369] Adding tenets to CONTRIBUTING.md (#2762) Signed-off-by: Dave Lago --- CONTRIBUTING.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 202f8977b7..a7e7d89e9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,3 +8,25 @@ OpenSearch is a community project that is built and maintained by people just li Visit the following link(s) for more information on specific practices: - [Triaging](./TRIAGING.md) + +## How we work + +#### Quality and security form the foundation of our efforts +* We deliver quality security solutions by understanding issues thoroughly, acting iteratively, and validating solutions through rigorous testing. +* We hold security to the highest standards of quality. Quality is the first step in building trust with users, stakeholders, and our community as a whole. +* We move swiftly to solve problems. But we don’t sacrifice quality to achieve quick wins, meet performance indicators, or get the bragging rights related to launching popular, high-profile features. + + + +#### Privacy won’t be compromised +* Privacy is a key part of security, and we make sure it isn’t compromised in the pursuit of creating benefits. +* When we make decisions, we carefully consider any potential impacts to privacy and make sure those decisions protect our users’ and stakeholders’ data. +* We maintain a focus on creating software that empowers cluster administrators to keep their sensitive data safe. + + + +#### Transparent collaboration creates secure outcomes +* Transparent collaboration promotes community inclusion, creates a space for concise and authentic communication, and supports accountability. +* We operate through transparent collaboration. We believe a secure product is built through diverse perspectives, knowledge sharing, candid discussions, and doing our work in the open when and where it’s safe to do so. +* We are relationship builders who create safe, respectful, and accessible spaces for everyone so we can engage and work towards the common goal of building secure solutions. +* When circumstances do require privacy, we make every effort to quickly resolve those requirements and return circumstances to a state of full visibility with our community and collaborators. From 48c4f960293db4f7cc73f733e070c4e048af1b6d Mon Sep 17 00:00:00 2001 From: Sean Kao Date: Fri, 12 May 2023 12:27:10 -0700 Subject: [PATCH 184/369] Add default roles for sql plugin (#2729) Signed-off-by: Sean Kao --- config/roles.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/config/roles.yml b/config/roles.yml index 00ebdf6edb..e4eb3ac535 100644 --- a/config/roles.yml +++ b/config/roles.yml @@ -136,6 +136,19 @@ observability_full_access: - 'cluster:admin/opensearch/observability/delete' - 'cluster:admin/opensearch/observability/get' +# Allows users to all PPL functionality +ppl_full_access: + reserved: true + cluster_permissions: + - 'cluster:admin/opensearch/ppl' + index_permissions: + - index_patterns: + - '*' + allowed_actions: + - 'indices:admin/mappings/get' + - 'indices:data/read/search*' + - 'indices:monitor/settings/get' + # Allows users to read and download Reports reports_instances_read_access: reserved: true @@ -228,6 +241,16 @@ cross_cluster_replication_follower_full_access: - "indices:admin/plugins/replication/index/update" - "indices:admin/plugins/replication/index/status_check" +# Allows users to use all cross cluster search functionality at remote cluster +cross_cluster_search_remote_full_access: + reserved: true + index_permissions: + - index_patterns: + - '*' + allowed_actions: + - 'indices:admin/shards/search_shards' + - 'indices:data/read/search' + # Allow users to read ML stats/models/tasks ml_read_access: reserved: true From 7c4e06d323135ef8ab616ff083c420ba26287f18 Mon Sep 17 00:00:00 2001 From: Paras Jain Date: Sat, 13 May 2023 07:17:35 +0530 Subject: [PATCH 185/369] `deserializeSafeFromHeader` uses `context.getHeader(headerName)` instead of `context.getHeaders()` (#2768) --- .../org/opensearch/security/support/HeaderHelper.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/org/opensearch/security/support/HeaderHelper.java b/src/main/java/org/opensearch/security/support/HeaderHelper.java index a4ee123347..1a7484a781 100644 --- a/src/main/java/org/opensearch/security/support/HeaderHelper.java +++ b/src/main/java/org/opensearch/security/support/HeaderHelper.java @@ -27,7 +27,6 @@ package org.opensearch.security.support; import java.io.Serializable; -import java.util.Map; import com.google.common.base.Strings; @@ -57,15 +56,8 @@ public static String getSafeFromHeader(final ThreadContext context, final String return null; } - String headerValue = null; - - Map headers = context.getHeaders(); - if (!headers.containsKey(headerName) || (headerValue = headers.get(headerName)) == null) { - return null; - } - if (isInterClusterRequest(context) || isTrustedClusterRequest(context) || isDirectRequest(context)) { - return headerValue; + return context.getHeader(headerName); } return null; From 94db5dbb0e2351b6d2b31b2a0969863acbad9ebe Mon Sep 17 00:00:00 2001 From: Andrey Pleskach Date: Mon, 15 May 2023 17:49:51 +0200 Subject: [PATCH 186/369] Fix multitency config update (#2758) Moved multi-tenancy to REST API implementation Signed-off-by: Andrey Pleskach --- .../security/OpenSearchSecurityPlugin.java | 10 - .../security/action/tenancy/EmptyRequest.java | 35 --- .../tenancy/TenancyConfigRestHandler.java | 65 ------ .../tenancy/TenancyConfigRetrieveActions.java | 24 -- .../TenancyConfigRetrieveResponse.java | 70 ------ .../TenancyConfigRetrieveTransportAction.java | 63 ----- .../tenancy/TenancyConfigUpdateAction.java | 26 --- .../tenancy/TenancyConfigUpdateRequest.java | 69 ------ .../TenancyConfigUpdateTransportAction.java | 155 ------------- .../action/tenancy/TenancyConfigs.java | 18 -- .../rest/api/MultiTenancyConfigApiAction.java | 217 ++++++++++++++++++ .../dlic/rest/api/SecurityRestApiActions.java | 1 + .../MultiTenancyConfigValidator.java | 32 +++ .../security/support/ConfigConstants.java | 4 +- .../rest/api/MultiTenancyConfigApiTest.java | 170 ++++++++++++++ .../test/TenancyDefaultTenantTests.java | 96 -------- .../test/TenancyMultitenancyEnabledTests.java | 43 ++-- .../TenancyPrivateTenantEnabledTests.java | 55 ++--- .../resources/multitenancy/internal_users.yml | 6 + src/test/resources/multitenancy/roles.yml | 2 + .../resources/multitenancy/roles_mapping.yml | 5 + 21 files changed, 472 insertions(+), 694 deletions(-) delete mode 100644 src/main/java/org/opensearch/security/action/tenancy/EmptyRequest.java delete mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRestHandler.java delete mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveActions.java delete mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveResponse.java delete mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveTransportAction.java delete mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateAction.java delete mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateRequest.java delete mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateTransportAction.java delete mode 100644 src/main/java/org/opensearch/security/action/tenancy/TenancyConfigs.java create mode 100644 src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java create mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiTest.java delete mode 100644 src/test/java/org/opensearch/security/multitenancy/test/TenancyDefaultTenantTests.java diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 815f96f0c5..2db8b5d2eb 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -117,11 +117,6 @@ import org.opensearch.search.query.QuerySearchResult; import org.opensearch.security.action.configupdate.ConfigUpdateAction; import org.opensearch.security.action.configupdate.TransportConfigUpdateAction; -import org.opensearch.security.action.tenancy.TenancyConfigRestHandler; -import org.opensearch.security.action.tenancy.TenancyConfigRetrieveActions; -import org.opensearch.security.action.tenancy.TenancyConfigRetrieveTransportAction; -import org.opensearch.security.action.tenancy.TenancyConfigUpdateAction; -import org.opensearch.security.action.tenancy.TenancyConfigUpdateTransportAction; import org.opensearch.security.action.whoami.TransportWhoAmIAction; import org.opensearch.security.action.whoami.WhoAmIAction; import org.opensearch.security.auditlog.AuditLog; @@ -480,7 +475,6 @@ public List getRestHandlers(Settings settings, RestController restC Objects.requireNonNull(cs), Objects.requireNonNull(adminDns), Objects.requireNonNull(cr))); handlers.add(new SecurityConfigUpdateAction(settings, restController, Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); handlers.add(new SecurityWhoAmIAction(settings, restController, Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); - handlers.add(new TenancyConfigRestHandler()); handlers.addAll( SecurityRestApiActions.getHandler( settings, @@ -518,10 +512,6 @@ public UnaryOperator getRestHandlerWrapper(final ThreadContext thre if(!disabled && !SSLConfig.isSslOnlyMode()) { actions.add(new ActionHandler<>(ConfigUpdateAction.INSTANCE, TransportConfigUpdateAction.class)); actions.add(new ActionHandler<>(WhoAmIAction.INSTANCE, TransportWhoAmIAction.class)); - - actions.add(new ActionHandler<>(TenancyConfigRetrieveActions.INSTANCE, TenancyConfigRetrieveTransportAction.class)); - actions.add(new ActionHandler<>(TenancyConfigUpdateAction.INSTANCE, TenancyConfigUpdateTransportAction.class)); - } return actions; } diff --git a/src/main/java/org/opensearch/security/action/tenancy/EmptyRequest.java b/src/main/java/org/opensearch/security/action/tenancy/EmptyRequest.java deleted file mode 100644 index 607216a8c3..0000000000 --- a/src/main/java/org/opensearch/security/action/tenancy/EmptyRequest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.action.tenancy; - -import java.io.IOException; - -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.common.io.stream.StreamInput; - -public class EmptyRequest extends ActionRequest { - - public EmptyRequest(final StreamInput in) throws IOException { - super(in); - } - - public EmptyRequest() throws IOException { - super(); - } - - @Override - public ActionRequestValidationException validate() - { - return null; - } -} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRestHandler.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRestHandler.java deleted file mode 100644 index 0a00d16694..0000000000 --- a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRestHandler.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.action.tenancy; - -import java.io.IOException; -import java.util.List; - -import com.google.common.collect.ImmutableList; - -import org.opensearch.client.node.NodeClient; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; - -import static org.opensearch.rest.RestRequest.Method.GET; -import static org.opensearch.rest.RestRequest.Method.PUT; - -public class TenancyConfigRestHandler extends BaseRestHandler { - - public TenancyConfigRestHandler() { - super(); - } - - @Override - public String getName() { - return "Multi Tenancy actions to Retrieve / Update configs."; - } - - @Override - public List routes() { - return ImmutableList.of( - new Route(GET, "/_plugins/_security/api/tenancy/config"), - new Route(PUT, "/_plugins/_security/api/tenancy/config") - ); - } - - @Override - protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient nodeClient) throws IOException { - - switch (request.method()) { - case GET: - return channel -> nodeClient.execute( - TenancyConfigRetrieveActions.INSTANCE, - new EmptyRequest(), - new RestToXContentListener<>(channel)); - case PUT: - return channel -> nodeClient.execute( - TenancyConfigUpdateAction.INSTANCE, - TenancyConfigUpdateRequest.fromXContent(request.contentParser()), - new RestToXContentListener<>(channel)); - default: - throw new RuntimeException("Not implemented"); - } - } - -} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveActions.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveActions.java deleted file mode 100644 index 796f233f13..0000000000 --- a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveActions.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.action.tenancy; - -import org.opensearch.action.ActionType; - -public class TenancyConfigRetrieveActions extends ActionType { - - public static final TenancyConfigRetrieveActions INSTANCE = new TenancyConfigRetrieveActions(); - public static final String NAME = "cluster:feature/tenancy/config/read"; - - protected TenancyConfigRetrieveActions() { - super(NAME, TenancyConfigRetrieveResponse::new); - } -} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveResponse.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveResponse.java deleted file mode 100644 index 463cc7d831..0000000000 --- a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveResponse.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.action.tenancy; - -import java.io.IOException; - -import org.opensearch.action.ActionResponse; -import org.opensearch.common.Strings; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.xcontent.XContentType; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; - -public class TenancyConfigRetrieveResponse extends ActionResponse implements ToXContentObject { - - public TenancyConfigs tenancyConfigs = new TenancyConfigs(); - - public TenancyConfigRetrieveResponse(final StreamInput in) throws IOException { - super(in); - this.tenancyConfigs.multitenancy_enabled = in.readOptionalBoolean(); - this.tenancyConfigs.private_tenant_enabled = in.readOptionalBoolean(); - this.tenancyConfigs.default_tenant = in.readOptionalString(); - } - - public TenancyConfigRetrieveResponse(final TenancyConfigs tenancyConfigs) { - this.tenancyConfigs = tenancyConfigs; - } - - public TenancyConfigs getMultitenancyConfig() { - return tenancyConfigs; - } - - public Boolean getMultitenancyEnabled() { return tenancyConfigs.multitenancy_enabled; } - - public Boolean getPrivateTenantEnabled() { return tenancyConfigs.private_tenant_enabled; } - - public String getDefaultTenant() { return tenancyConfigs.default_tenant; } - - @Override - public void writeTo(final StreamOutput out) throws IOException { - out.writeBoolean(getMultitenancyEnabled()); - out.writeBoolean(getPrivateTenantEnabled()); - out.writeString(getDefaultTenant()); - } - - @Override - public String toString() { - return Strings.toString(XContentType.JSON, this, true, true); - } - - @Override - public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { - builder.startObject(); - builder.field("multitenancy_enabled", getMultitenancyEnabled()); - builder.field("private_tenant_enabled", getPrivateTenantEnabled()); - builder.field("default_tenant", getDefaultTenant()); - builder.endObject(); - return builder; - } -} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveTransportAction.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveTransportAction.java deleted file mode 100644 index a68bbae85e..0000000000 --- a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigRetrieveTransportAction.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.action.tenancy; - -import java.util.Collections; - -import org.opensearch.action.ActionListener; -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.common.inject.Inject; -import org.opensearch.common.settings.Settings; -import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.securityconf.impl.CType; -import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; -import org.opensearch.security.securityconf.impl.v7.ConfigV7; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -public class TenancyConfigRetrieveTransportAction - extends HandledTransportAction { - - private final ConfigurationRepository config; - - @Inject - public TenancyConfigRetrieveTransportAction(final Settings settings, - final TransportService transportService, - final ActionFilters actionFilters, - final ConfigurationRepository config) { - super(TenancyConfigRetrieveActions.NAME, transportService, actionFilters, EmptyRequest::new); - - this.config = config; - } - - /** Load the configuration from the security index and return a copy */ - protected final SecurityDynamicConfiguration load() { - return config.getConfigurationsFromIndex(Collections.singleton(CType.CONFIG), false).get(CType.CONFIG).deepClone(); - } - - @Override - protected void doExecute(final Task task, final EmptyRequest request, final ActionListener listener) { - - // Get the security configuration and lookup the config setting state - final SecurityDynamicConfiguration dynamicConfig = load(); - ConfigV7 config = (ConfigV7)dynamicConfig.getCEntry("config"); - - final TenancyConfigs tenancyConfigs= new TenancyConfigs(); - - tenancyConfigs.multitenancy_enabled = config.dynamic.kibana.multitenancy_enabled; - tenancyConfigs.private_tenant_enabled = config.dynamic.kibana.private_tenant_enabled; - tenancyConfigs.default_tenant = config.dynamic.kibana.default_tenant; - - listener.onResponse(new TenancyConfigRetrieveResponse(tenancyConfigs)); - } -} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateAction.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateAction.java deleted file mode 100644 index 73d515b7d8..0000000000 --- a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateAction.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.action.tenancy; - -import org.opensearch.action.ActionType; - -public class TenancyConfigUpdateAction extends ActionType { - - public static final TenancyConfigUpdateAction INSTANCE = new TenancyConfigUpdateAction(); - public static final String NAME = "cluster:feature/tenancy/config/update"; - - - protected TenancyConfigUpdateAction() - { - super(NAME, TenancyConfigRetrieveResponse::new); - } -} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateRequest.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateRequest.java deleted file mode 100644 index 2d03f698a6..0000000000 --- a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateRequest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.action.tenancy; -import java.io.IOException; - -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.core.ParseField; -import org.opensearch.core.xcontent.ConstructingObjectParser; -import org.opensearch.core.xcontent.XContentParser; - -public class TenancyConfigUpdateRequest extends ActionRequest { - - private TenancyConfigs tenancyConfigs = new TenancyConfigs(); - - public TenancyConfigUpdateRequest(final StreamInput in) throws IOException { - super(in); - in.readOptionalBoolean(); - in.readOptionalBoolean(); - in.readOptionalString(); - } - - public TenancyConfigUpdateRequest(final Boolean multitenancy_enabled, final Boolean private_tenant_enabled, final String default_tenant) { - super(); - this.tenancyConfigs.multitenancy_enabled = multitenancy_enabled; - this.tenancyConfigs.private_tenant_enabled = private_tenant_enabled; - this.tenancyConfigs.default_tenant = default_tenant; - } - - public TenancyConfigs getTenancyConfigs() { - return tenancyConfigs; - } - - @Override - public ActionRequestValidationException validate() { - if (getTenancyConfigs() == null) { - final ActionRequestValidationException validationException = new ActionRequestValidationException(); - validationException.addValidationError("Missing tenancy configs"); - return validationException; - } - return null; - } - - private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( - TenancyConfigUpdateRequest.class.getName(), - args -> new TenancyConfigUpdateRequest((Boolean)args[0], (Boolean) args[1], (String) args[2]) - ); - - static { - PARSER.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), new ParseField("multitenancy_enabled")); - PARSER.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), new ParseField("private_tenant_enabled")); - PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("default_tenant")); - - } - - public static TenancyConfigUpdateRequest fromXContent(final XContentParser parser) { - return PARSER.apply(parser, null); - } -} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateTransportAction.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateTransportAction.java deleted file mode 100644 index 1d4b563ca4..0000000000 --- a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigUpdateTransportAction.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.action.tenancy; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.action.ActionListener; -import org.opensearch.action.index.IndexResponse; -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.client.Client; -import org.opensearch.common.inject.Inject; -import org.opensearch.common.settings.Settings; -import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.api.AbstractApiAction; -import org.opensearch.security.securityconf.impl.CType; -import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; -import org.opensearch.security.securityconf.impl.v7.ConfigV7; -import org.opensearch.security.support.ConfigConstants; -import org.opensearch.tasks.Task; -import org.opensearch.threadpool.ThreadPool; -import org.opensearch.transport.TransportService; - -public class TenancyConfigUpdateTransportAction extends HandledTransportAction { - - private static final Logger log = LogManager.getLogger(TenancyConfigUpdateTransportAction.class); - - private final String securityIndex; - private final ConfigurationRepository config; - private final Client client; - private final ThreadPool pool; - - @Inject - public TenancyConfigUpdateTransportAction(final Settings settings, - final TransportService transportService, - final ActionFilters actionFilters, - final ConfigurationRepository config, - final ThreadPool pool, - final Client client) { - super(TenancyConfigUpdateAction.NAME, transportService, actionFilters, TenancyConfigUpdateRequest::new); - - this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); - - this.config = config; - this.client = client; - this.pool = pool; - } - - /** Load the configuration from the security index and return a copy */ - protected final SecurityDynamicConfiguration load() { - return config.getConfigurationsFromIndex(Collections.singleton(CType.CONFIG), false).get(CType.CONFIG).deepClone(); - } - - private Set getAcceptableDefaultTenants() { - Set acceptableDefaultTenants = new HashSet(); - acceptableDefaultTenants.add(ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME); - acceptableDefaultTenants.add(ConfigConstants.TENANCY_GLOBAL_TENANT_NAME); - acceptableDefaultTenants.add(ConfigConstants.TENANCY_PRIVATE_TENANT_NAME); - return acceptableDefaultTenants; - } - - private Set getAllConfiguredTenantNames() { - - return this.config.getConfiguration(CType.TENANTS).getCEntries().keySet(); - } - - protected void validate(ConfigV7 updatedConfig) { - if(!updatedConfig.dynamic.kibana.private_tenant_enabled && (updatedConfig.dynamic.kibana.default_tenant).equals(ConfigConstants.TENANCY_PRIVATE_TENANT_NAME)) { - throw new IllegalArgumentException("Private tenant can not be disabled if it is the default tenant."); - } - - Set acceptableDefaultTenants = getAcceptableDefaultTenants(); - - if(acceptableDefaultTenants.contains(updatedConfig.dynamic.kibana.default_tenant)) { - return; - } - - Set availableTenants = getAllConfiguredTenantNames(); - - if(!availableTenants.contains(updatedConfig.dynamic.kibana.default_tenant)){ - throw new IllegalArgumentException(updatedConfig.dynamic.kibana.default_tenant + " can not be set to default tenant. Default tenant should be selected from one of the available tenants."); - } - - } - - @Override - protected void doExecute(final Task task, final TenancyConfigUpdateRequest request, final ActionListener listener) { - - // Get the current security config and prepare the config with the updated value - final SecurityDynamicConfiguration dynamicConfig = load(); - final ConfigV7 config = (ConfigV7)dynamicConfig.getCEntry("config"); - - final TenancyConfigs tenancyConfigs = request.getTenancyConfigs(); - if(tenancyConfigs.multitenancy_enabled != null) - { - config.dynamic.kibana.multitenancy_enabled = tenancyConfigs.multitenancy_enabled; - } - - if(tenancyConfigs.private_tenant_enabled != null) - { - config.dynamic.kibana.private_tenant_enabled = tenancyConfigs.private_tenant_enabled; - } - - if(tenancyConfigs.default_tenant != null) - { - config.dynamic.kibana.default_tenant = tenancyConfigs.default_tenant; - } - - validate(config); - - dynamicConfig.putCEntry("config", config); - - // When performing an update to the configuration run as admin - try (final ThreadContext.StoredContext stashedContext = pool.getThreadContext().stashContext()) { - // Update the security configuration and make sure the cluster has fully refreshed - AbstractApiAction.saveAndUpdateConfigs(this.securityIndex, this.client, CType.CONFIG, dynamicConfig, new ActionListener(){ - - @Override - public void onResponse(final IndexResponse response) { - // After processing the request, restore the user context - stashedContext.close(); - try { - // Lookup the current value and notify the listener - client.execute(TenancyConfigRetrieveActions.INSTANCE, new EmptyRequest(), listener); - } catch (IOException ioe) { - log.error(ioe); - listener.onFailure(ioe); - } - } - - @Override - public void onFailure(Exception e) { - log.error(e); - listener.onFailure(e); - } - }); - } - } -} diff --git a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigs.java b/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigs.java deleted file mode 100644 index 4e8fc41ef4..0000000000 --- a/src/main/java/org/opensearch/security/action/tenancy/TenancyConfigs.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.action.tenancy; - -public class TenancyConfigs { - public Boolean multitenancy_enabled; - public Boolean private_tenant_enabled; - public String default_tenant; -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java new file mode 100644 index 0000000000..e5ec82c245 --- /dev/null +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java @@ -0,0 +1,217 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import org.opensearch.action.index.IndexResponse; +import org.opensearch.client.Client; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.bytes.BytesReference; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestController; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.RestStatus; +import org.opensearch.security.auditlog.AuditLog; +import org.opensearch.security.configuration.AdminDNs; +import org.opensearch.security.configuration.ConfigurationRepository; +import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; +import org.opensearch.security.dlic.rest.validation.MultiTenancyConfigValidator; +import org.opensearch.security.privileges.PrivilegesEvaluator; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; +import org.opensearch.security.securityconf.impl.v7.ConfigV7; +import org.opensearch.security.ssl.transport.PrincipalExtractor; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.threadpool.ThreadPool; + +import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.rest.RestRequest.Method.PUT; +import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; + + +public class MultiTenancyConfigApiAction extends AbstractApiAction { + + private static final List ROUTES = addRoutesPrefix( + ImmutableList.of( + new Route(GET, "/tenancy/config"), + new Route(PUT, "/tenancy/config") + ) + ); + + private final static Set ACCEPTABLE_DEFAULT_TENANTS = ImmutableSet.of( + ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME, + ConfigConstants.TENANCY_GLOBAL_TENANT_NAME, + ConfigConstants.TENANCY_PRIVATE_TENANT_NAME + ); + + @Override + public String getName() { + return "Multi Tenancy actions to Retrieve / Update configs."; + } + + @Override + public List routes() { + return ROUTES; + } + + public MultiTenancyConfigApiAction( + final Settings settings, final Path configPath, + final RestController controller, final Client client, + final AdminDNs adminDNs, final ConfigurationRepository cl, + final ClusterService cs, final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, final ThreadPool threadPool, + final AuditLog auditLog) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + } + + @Override + protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... params) { + return new MultiTenancyConfigValidator(request, ref, settings, params); + } + + @Override + protected Endpoint getEndpoint() { + return Endpoint.TENANTS; + } + + @Override + protected String getResourceName() { + return null; + } + + @Override + protected CType getConfigName() { + return CType.CONFIG; + } + + @Override + protected void handleDelete(final RestChannel channel, + final RestRequest request, + final Client client, + final JsonNode content) throws IOException { + notImplemented(channel, RestRequest.Method.DELETE); + } + + private void multitenancyResponse(final ConfigV7 config, final RestChannel channel) { + try (final XContentBuilder contentBuilder = channel.newBuilder()) { + channel.sendResponse( + new BytesRestResponse( + RestStatus.OK, + contentBuilder + .startObject() + .field( + MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY, + config.dynamic.kibana.default_tenant + ).field( + MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY, + config.dynamic.kibana.private_tenant_enabled + ).field( + MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY, + config.dynamic.kibana.multitenancy_enabled + ).endObject() + ) + ); + } catch (final Exception e) { + internalErrorResponse(channel, e.getMessage()); + log.error("Error handle request ", e); + } + } + + + @Override + protected void handleGet(final RestChannel channel, + final RestRequest request, + final Client client, + final JsonNode content) throws IOException { + final SecurityDynamicConfiguration dynamicConfiguration = load(CType.CONFIG, false); + final ConfigV7 config = (ConfigV7) dynamicConfiguration.getCEntry(CType.CONFIG.toLCString()); + multitenancyResponse(config, channel); + } + + @Override + protected void handlePut(final RestChannel channel, + final RestRequest request, + final Client client, + final JsonNode content) throws IOException { + final SecurityDynamicConfiguration dynamicConfiguration = (SecurityDynamicConfiguration) + load(CType.CONFIG, false); + final ConfigV7 config = dynamicConfiguration.getCEntry(CType.CONFIG.toLCString()); + updateAndValidatesValues(config, content); + dynamicConfiguration.putCEntry(CType.CONFIG.toLCString(), config); + saveAndUpdateConfigs( + this.securityIndexName, + client, + getConfigName(), + dynamicConfiguration, + new OnSucessActionListener<>(channel) { + @Override + public void onResponse(IndexResponse response) { + multitenancyResponse(config, channel); + } + } + ); + } + + private void updateAndValidatesValues(final ConfigV7 config, final JsonNode jsonContent) { + if (Objects.nonNull(jsonContent.findValue(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY))) { + config.dynamic.kibana.default_tenant = + jsonContent.findValue(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY).asText(); + } + if (Objects.nonNull(jsonContent.findValue(MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY))) { + config.dynamic.kibana.private_tenant_enabled = + jsonContent.findValue(MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY).booleanValue(); + } + if (Objects.nonNull(jsonContent.findValue(MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY))) { + config.dynamic.kibana.multitenancy_enabled = + jsonContent.findValue(MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY).asBoolean(); + } + final String defaultTenant = + Optional.ofNullable(config.dynamic.kibana.default_tenant) + .map(String::toLowerCase) + .orElse(""); + + if (!config.dynamic.kibana.private_tenant_enabled + && ConfigConstants.TENANCY_PRIVATE_TENANT_NAME.equals(defaultTenant)) { + throw new IllegalArgumentException("Private tenant can not be disabled if it is the default tenant."); + } + + if (ACCEPTABLE_DEFAULT_TENANTS.contains(defaultTenant)) { + return; + } + + final Set availableTenants = + cl.getConfiguration(CType.TENANTS) + .getCEntries() + .keySet() + .stream() + .map(String::toLowerCase) + .collect(Collectors.toSet()); + if (!availableTenants.contains(defaultTenant)) { + throw new IllegalArgumentException(config.dynamic.kibana.default_tenant + " can not be set to default tenant. Default tenant should be selected from one of the available tenants."); + } + } + +} diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java index e73d28e865..cd489cab4d 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java @@ -64,6 +64,7 @@ public static Collection getHandler(final Settings settings, handlers.add(new WhitelistApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); handlers.add(new AllowlistApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); handlers.add(new AuditApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); + handlers.add(new MultiTenancyConfigApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); handlers.add(new SecuritySSLCertsAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog, securityKeyStore, certificatesReloadEnabled)); return Collections.unmodifiableCollection(handlers); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java new file mode 100644 index 0000000000..42870f1c13 --- /dev/null +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +package org.opensearch.security.dlic.rest.validation; + +import org.opensearch.common.bytes.BytesReference; +import org.opensearch.common.settings.Settings; +import org.opensearch.rest.RestRequest; + +public class MultiTenancyConfigValidator extends AbstractConfigurationValidator { + + public static final String DEFAULT_TENANT_JSON_PROPERTY = "default_tenant"; + public static final String PRIVATE_TENANT_ENABLED_JSON_PROPERTY = "private_tenant_enabled"; + public static final String MULTITENANCY_ENABLED_JSON_PROPERTY = "multitenancy_enabled"; + + + public MultiTenancyConfigValidator(RestRequest request, BytesReference ref, Settings opensearchSettings, Object... param) { + super(request, ref, opensearchSettings, param); + this.payloadMandatory = true; + allowedKeys.put(DEFAULT_TENANT_JSON_PROPERTY, DataType.STRING); + allowedKeys.put(PRIVATE_TENANT_ENABLED_JSON_PROPERTY, DataType.BOOLEAN); + allowedKeys.put(MULTITENANCY_ENABLED_JSON_PROPERTY, DataType.BOOLEAN); + } + +} diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index 7ed780f413..a58639bc0a 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -290,8 +290,8 @@ public enum RolesMappingResolution { public static final String SECURITY_SYSTEM_INDICES_KEY = "plugins.security.system_indices.indices"; public static final List SECURITY_SYSTEM_INDICES_DEFAULT = Collections.emptyList(); - public static final String TENANCY_PRIVATE_TENANT_NAME = "Private"; - public static final String TENANCY_GLOBAL_TENANT_NAME = "Global"; + public static final String TENANCY_PRIVATE_TENANT_NAME = "private"; + public static final String TENANCY_GLOBAL_TENANT_NAME = "global"; public static final String TENANCY_GLOBAL_TENANT_DEFAULT_NAME = ""; public static Set getSettingAsSet(final Settings settings, final String key, final List defaultList, final boolean ignoreCaseForNone) { diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiTest.java new file mode 100644 index 0000000000..24aa8737c6 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiTest.java @@ -0,0 +1,170 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api; + +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.junit.Test; + +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.StringContains.containsString; + +public class MultiTenancyConfigApiTest extends AbstractRestApiUnitTest { + + private static final Header ADMIN_FULL_ACCESS_USER = encodeBasicHeader("admin_all_access", "admin_all_access"); + private static final Header USER_NO_REST_API_ACCESS = encodeBasicHeader("admin", "admin"); + + private void verifyTenantUpdate(final Header... header) throws Exception { + final HttpResponse getSettingResponse = rh.executeGetRequest("/_plugins/_security/api/tenancy/config", header); + assertThat(getSettingResponse.getBody(), getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat( + getSettingResponse.getBody(), + getSettingResponse.findValueInJson("default_tenant"), + equalTo(ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME) + ); + + HttpResponse getDashboardsinfoResponse = rh.executeGetRequest("/_plugins/_security/dashboardsinfo", header); + assertThat(getDashboardsinfoResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat( + getDashboardsinfoResponse.getBody(), + getDashboardsinfoResponse.findValueInJson("default_tenant"), + equalTo(ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME) + ); + + final HttpResponse setPrivateTenantAsDefaultResponse = rh.executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"default_tenant\": \"Private\"}", header + ); + assertThat( + setPrivateTenantAsDefaultResponse.getBody(), + setPrivateTenantAsDefaultResponse.getStatusCode(), + equalTo(HttpStatus.SC_OK) + ); + getDashboardsinfoResponse = rh.executeGetRequest("/_plugins/_security/dashboardsinfo", ADMIN_FULL_ACCESS_USER); + assertThat(getDashboardsinfoResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(getDashboardsinfoResponse.findValueInJson("default_tenant"), equalTo("Private")); + } + + @Test + public void testUpdateSuperAdmin() throws Exception { + setupWithRestRoles(); + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendAdminCertificate = true; + verifyTenantUpdate(); + } + + @Test + public void testUpdateRestAPIAdmin() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + verifyTenantUpdate(ADMIN_FULL_ACCESS_USER); + } + + + private void verifyTenantUpdateFailed(final Header... header) throws Exception { + final HttpResponse disablePrivateTenantResponse = rh.executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"private_tenant_enabled\":false}", header + ); + assertThat(disablePrivateTenantResponse.getBody(), disablePrivateTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + final HttpResponse setPrivateTenantAsDefaultFailResponse = rh.executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"default_tenant\": \"Private\"}", header + ); + assertThat(setPrivateTenantAsDefaultFailResponse.getBody(), setPrivateTenantAsDefaultFailResponse.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + assertThat( + setPrivateTenantAsDefaultFailResponse.getBody(), + setPrivateTenantAsDefaultFailResponse.findValueInJson("error.reason"), + containsString("Private tenant can not be disabled if it is the default tenant.") + ); + + final HttpResponse enablePrivateTenantResponse = rh.executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"private_tenant_enabled\":true}", + header + ); + assertThat(enablePrivateTenantResponse.getBody(), enablePrivateTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + final HttpResponse setPrivateTenantAsDefaultResponse = rh.executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"default_tenant\": \"Private\"}", + header + ); + assertThat(setPrivateTenantAsDefaultResponse.getBody(), setPrivateTenantAsDefaultResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + final HttpResponse updatePrivateSettingResponse = + rh.executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\":false}", header); + assertThat(updatePrivateSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + assertThat(updatePrivateSettingResponse.findValueInJson("error.reason"), containsString("Private tenant can not be disabled if it is the default tenant.")); + + final HttpResponse getSettingResponseAfterUpdate = rh.executeGetRequest("/_plugins/_security/api/tenancy/config", header); + assertThat(getSettingResponseAfterUpdate.getBody(), getSettingResponseAfterUpdate.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat( + getSettingResponseAfterUpdate.getBody(), + getSettingResponseAfterUpdate.findValueInJson("default_tenant"), + equalTo("Private") + ); + + final HttpResponse getDashboardsinfoResponse = rh.executeGetRequest("/_plugins/_security/dashboardsinfo", header); + assertThat( + getDashboardsinfoResponse.getBody(), + getDashboardsinfoResponse.findValueInJson("default_tenant"), + equalTo("Private") + ); + + final HttpResponse setRandomStringAsDefaultTenant = rh.executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"default_tenant\": \"NonExistentTenant\"}", + header + ); + assertThat(setRandomStringAsDefaultTenant.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + assertThat(setPrivateTenantAsDefaultFailResponse.getBody(), + setRandomStringAsDefaultTenant.findValueInJson("error.reason"), + containsString("Default tenant should be selected from one of the available tenants.") + ); + } + + @Test + public void testDefaultTenantUpdateFailedSuperAdmin() throws Exception { + setupWithRestRoles(); + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendAdminCertificate = true; + verifyTenantUpdateFailed(); + } + + @Test + public void testDefaultTenantUpdateFailedRestAPIAdmin() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + verifyTenantUpdateFailed(ADMIN_FULL_ACCESS_USER); + } + + @Test + public void testForbiddenAccess() throws Exception { + setupWithRestRoles(); + + rh.sendAdminCertificate = false; + HttpResponse getSettingResponse = rh.executeGetRequest("/_plugins/_security/api/tenancy/config", USER_NO_REST_API_ACCESS); + assertThat(getSettingResponse.getBody(), getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + HttpResponse updateSettingResponse = rh.executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"default_tenant\": \"Private\"}", USER_NO_REST_API_ACCESS + ); + assertThat(getSettingResponse.getBody(), updateSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + } + + +} diff --git a/src/test/java/org/opensearch/security/multitenancy/test/TenancyDefaultTenantTests.java b/src/test/java/org/opensearch/security/multitenancy/test/TenancyDefaultTenantTests.java deleted file mode 100644 index b4d6aa4b59..0000000000 --- a/src/test/java/org/opensearch/security/multitenancy/test/TenancyDefaultTenantTests.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.multitenancy.test; - -import org.apache.hc.core5.http.Header; -import org.apache.hc.core5.http.HttpStatus; -import org.junit.Test; - -import org.opensearch.security.support.ConfigConstants; -import org.opensearch.security.test.SingleClusterTest; -import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsEqual.equalTo; -import static org.hamcrest.core.StringContains.containsString; - -public class TenancyDefaultTenantTests extends SingleClusterTest { - private final Header asAdminUser = encodeBasicHeader("admin", "admin"); - private final Header asUser = encodeBasicHeader("kirk", "kirk"); - - @Override - protected String getResourceFolder() { - return "multitenancy"; - } - - @Test - public void testDefaultTenantUpdate() throws Exception { - setup(); - - final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asAdminUser); - assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); - assertThat(getSettingResponse.findValueInJson("default_tenant"), equalTo(ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME)); - - HttpResponse getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); - assertThat(getDashboardsinfoResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); - assertThat(getDashboardsinfoResponse.findValueInJson("default_tenant"), equalTo(ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME)); - - final HttpResponse setPrivateTenantAsDefaultResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"default_tenant\": \"Private\"}", asAdminUser); - assertThat(setPrivateTenantAsDefaultResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); - getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); - assertThat(getDashboardsinfoResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); - assertThat(getDashboardsinfoResponse.findValueInJson("default_tenant"), equalTo(ConfigConstants.TENANCY_PRIVATE_TENANT_NAME)); - } - - @Test - public void testDefaultTenant_UpdateFailed() throws Exception { - setup(); - - final HttpResponse disablePrivateTenantResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\":false}", asAdminUser); - assertThat(disablePrivateTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); - - - final HttpResponse setPrivateTenantAsDefaultFailResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"default_tenant\": \"Private\"}", asAdminUser); - assertThat(setPrivateTenantAsDefaultFailResponse.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); - assertThat(setPrivateTenantAsDefaultFailResponse.findValueInJson("error.reason"), containsString("Private tenant can not be disabled if it is the default tenant.")); - - final HttpResponse enablePrivateTenantResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\":true}", asAdminUser); - assertThat(enablePrivateTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); - - final HttpResponse setPrivateTenantAsDefaultResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"default_tenant\": \"Private\"}", asAdminUser); - assertThat(setPrivateTenantAsDefaultResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); - - final HttpResponse getSettingResponseAfterUpdate = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asAdminUser); - assertThat(getSettingResponseAfterUpdate.getStatusCode(), equalTo(HttpStatus.SC_OK)); - assertThat(getSettingResponseAfterUpdate.findValueInJson("default_tenant"), equalTo(ConfigConstants.TENANCY_PRIVATE_TENANT_NAME)); - - HttpResponse getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); - assertThat(getDashboardsinfoResponse.findValueInJson("default_tenant"),equalTo(ConfigConstants.TENANCY_PRIVATE_TENANT_NAME)); - - final HttpResponse setRandomStringAsDefaultTenant = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"default_tenant\": \"NonExistentTenant\"}", asAdminUser); - assertThat(setRandomStringAsDefaultTenant.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); - assertThat(setRandomStringAsDefaultTenant.findValueInJson("error.reason"), containsString("Default tenant should be selected from one of the available tenants.")); - - } - @Test - public void testForbiddenAccess() throws Exception { - setup(); - - final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asUser); - assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - assertThat(getSettingResponse.findValueInJson("error.reason"), containsString("no permissions for [cluster:feature/tenancy/config/read]")); - - final HttpResponse updateSettingResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"default_tenant\": \"Private\"}", asUser); - assertThat(updateSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - assertThat(updateSettingResponse.findValueInJson("error.reason"), containsString("no permissions for [cluster:feature/tenancy/config/update]")); - } -} diff --git a/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java b/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java index 033d38ed41..bd9664a84c 100644 --- a/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java +++ b/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java @@ -16,17 +16,19 @@ import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Test; +import org.opensearch.common.settings.Settings; +import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; -import static org.hamcrest.core.StringContains.containsString; public class TenancyMultitenancyEnabledTests extends SingleClusterTest { - private final Header asAdminUser = encodeBasicHeader("admin", "admin"); - private final Header asUser = encodeBasicHeader("kirk", "kirk"); - private final Header onUserTenant = new BasicHeader("securitytenant", "__user__"); + + private static final Header AS_REST_API_USER = encodeBasicHeader("user_rest_api_access", "user_rest_api_access"); + private static final Header AS_USER = encodeBasicHeader("admin", "admin"); + private static final Header ON_USER_TENANT = new BasicHeader("securitytenant", "__user__"); private static String createIndexPatternDoc(final String title) { return "{"+ @@ -44,46 +46,37 @@ protected String getResourceFolder() { @Test public void testMultitenancyDisabled_endToEndTest() throws Exception { - setup(); + setup(Settings.EMPTY, + new DynamicSecurityConfig(), + Settings.builder().put("plugins.security.restapi.roles_enabled.0", "security_rest_api_access").build(), + true); - final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asAdminUser); + final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", AS_REST_API_USER); assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(getSettingResponse.findValueInJson("multitenancy_enabled"), equalTo("true")); - HttpResponse getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); + HttpResponse getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", AS_USER); assertThat(getDashboardsinfoResponse.findValueInJson("multitenancy_enabled"),equalTo("true")); - final HttpResponse createDocInGlobalTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("globalIndex"), asAdminUser); + final HttpResponse createDocInGlobalTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("globalIndex"), AS_USER); assertThat(createDocInGlobalTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); - final HttpResponse createDocInUserTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("userIndex"), onUserTenant, asAdminUser); + final HttpResponse createDocInUserTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("userIndex"), ON_USER_TENANT, AS_USER); assertThat(createDocInUserTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); - final HttpResponse searchInUserTenantWithMutlitenancyEnabled = nonSslRestHelper().executeGetRequest(".kibana/_search", onUserTenant, asAdminUser); + final HttpResponse searchInUserTenantWithMutlitenancyEnabled = nonSslRestHelper().executeGetRequest(".kibana/_search", ON_USER_TENANT, AS_USER); assertThat(searchInUserTenantWithMutlitenancyEnabled.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(searchInUserTenantWithMutlitenancyEnabled.findValueInJson("hits.hits[0]._source.index-pattern.title"), equalTo("userIndex")); - final HttpResponse updateMutlitenancyToDisabled = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"multitenancy_enabled\": \"false\"}", asAdminUser); + final HttpResponse updateMutlitenancyToDisabled = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"multitenancy_enabled\": \"false\"}", AS_REST_API_USER); assertThat(updateMutlitenancyToDisabled.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(updateMutlitenancyToDisabled.findValueInJson("multitenancy_enabled"), equalTo("false")); - getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); + getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", AS_USER); assertThat(getDashboardsinfoResponse.findValueInJson("multitenancy_enabled"),equalTo("false")); - final HttpResponse searchInUserTenantWithMutlitenancyDisabled = nonSslRestHelper().executeGetRequest(".kibana/_search", onUserTenant, asAdminUser); + final HttpResponse searchInUserTenantWithMutlitenancyDisabled = nonSslRestHelper().executeGetRequest(".kibana/_search", ON_USER_TENANT, AS_USER); assertThat(searchInUserTenantWithMutlitenancyDisabled.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(searchInUserTenantWithMutlitenancyDisabled.findValueInJson("hits.hits[0]._source.index-pattern.title"), equalTo("globalIndex")); } - @Test - public void testForbiddenAccess() throws Exception { - setup(); - - final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asUser); - assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - assertThat(getSettingResponse.findValueInJson("error.reason"), containsString("no permissions for [cluster:feature/tenancy/config/read]")); - - final HttpResponse updateSettingResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"multitenancy_enabled\": \"false\"}", asUser); - assertThat(updateSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - assertThat(updateSettingResponse.findValueInJson("error.reason"), containsString("no permissions for [cluster:feature/tenancy/config/update]")); - } } diff --git a/src/test/java/org/opensearch/security/multitenancy/test/TenancyPrivateTenantEnabledTests.java b/src/test/java/org/opensearch/security/multitenancy/test/TenancyPrivateTenantEnabledTests.java index c6927dbbf8..599586239c 100644 --- a/src/test/java/org/opensearch/security/multitenancy/test/TenancyPrivateTenantEnabledTests.java +++ b/src/test/java/org/opensearch/security/multitenancy/test/TenancyPrivateTenantEnabledTests.java @@ -16,6 +16,8 @@ import org.apache.hc.core5.http.message.BasicHeader; import org.junit.Test; +import org.opensearch.common.settings.Settings; +import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; @@ -24,9 +26,10 @@ import static org.hamcrest.core.StringContains.containsString; public class TenancyPrivateTenantEnabledTests extends SingleClusterTest { - private final Header asAdminUser = encodeBasicHeader("admin", "admin"); - private final Header asUser = encodeBasicHeader("kirk", "kirk"); - private final Header onUserTenant = new BasicHeader("securitytenant", "__user__"); + private static final Header AS_REST_API_USER = encodeBasicHeader("user_rest_api_access", "user_rest_api_access"); + private static final Header AS_ADMIN_USER = encodeBasicHeader("admin", "admin"); + private static final Header AS_USER = encodeBasicHeader("kirk", "kirk"); + private static final Header ON_USER_TENANT = new BasicHeader("securitytenant", "__user__"); private static String createIndexPatternDoc(final String title) { return "{"+ @@ -43,59 +46,39 @@ protected String getResourceFolder() { } @Test - public void testPrivateTenantDisabled_Update() throws Exception { - setup(); + public void testPrivateTenantDisabled_Update_EndToEnd() throws Exception { + setup(Settings.EMPTY, + new DynamicSecurityConfig(), + Settings.builder().put("plugins.security.restapi.roles_enabled.0", "security_rest_api_access").build(), + true); - final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asAdminUser); + final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", AS_REST_API_USER); assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(getSettingResponse.findValueInJson("private_tenant_enabled"), equalTo("true")); - HttpResponse getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); + HttpResponse getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", AS_ADMIN_USER); assertThat(getDashboardsinfoResponse.findValueInJson("private_tenant_enabled"), equalTo("true")); - final HttpResponse createDocInGlobalTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("globalIndex"), asAdminUser); + final HttpResponse createDocInGlobalTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("globalIndex"), AS_ADMIN_USER); assertThat(createDocInGlobalTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); - final HttpResponse createDocInUserTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("userIndex"), onUserTenant, asUser); + final HttpResponse createDocInUserTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("userIndex"), ON_USER_TENANT, AS_USER); assertThat(createDocInUserTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); - final HttpResponse searchInUserTenantWithPrivateTenantEnabled = nonSslRestHelper().executeGetRequest(".kibana/_search", onUserTenant, asUser); + final HttpResponse searchInUserTenantWithPrivateTenantEnabled = nonSslRestHelper().executeGetRequest(".kibana/_search", ON_USER_TENANT, AS_USER); assertThat(searchInUserTenantWithPrivateTenantEnabled.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(searchInUserTenantWithPrivateTenantEnabled.findValueInJson("hits.hits[0]._source.index-pattern.title"), equalTo("userIndex")); - final HttpResponse disablePrivateTenantResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\": \"false\"}", asAdminUser); + final HttpResponse disablePrivateTenantResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\": \"false\"}", AS_REST_API_USER); assertThat(disablePrivateTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(disablePrivateTenantResponse.findValueInJson("private_tenant_enabled"), equalTo("false")); - getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", asAdminUser); + getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", AS_ADMIN_USER); assertThat(getDashboardsinfoResponse.findValueInJson("private_tenant_enabled"),equalTo("false")); - final HttpResponse searchInUserTenantWithPrivateTenantDisabled = nonSslRestHelper().executeGetRequest(".kibana/_search", onUserTenant, asUser); + final HttpResponse searchInUserTenantWithPrivateTenantDisabled = nonSslRestHelper().executeGetRequest(".kibana/_search", ON_USER_TENANT, AS_USER); assertThat(searchInUserTenantWithPrivateTenantDisabled.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); assertThat(searchInUserTenantWithPrivateTenantDisabled.findValueInJson("error.reason"), containsString("no permissions for [indices:data/read/search] and User")); } - @Test - public void testPrivateTenantDisabled_UpdateFailed() throws Exception { - setup(); - - final HttpResponse setPrivateTenantAsDefaultResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"default_tenant\": \"Private\"}", asAdminUser); - assertThat(setPrivateTenantAsDefaultResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); - final HttpResponse updateSettingResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\":false}", asAdminUser); - assertThat(updateSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); - assertThat(updateSettingResponse.findValueInJson("error.reason"), containsString("Private tenant can not be disabled if it is the default tenant.")); - } - - @Test - public void testForbiddenAccess() throws Exception { - setup(); - - final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", asUser); - assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - assertThat(getSettingResponse.findValueInJson("error.reason"), containsString("no permissions for [cluster:feature/tenancy/config/read]")); - - final HttpResponse updateSettingResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\": false}", asUser); - assertThat(updateSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - assertThat(updateSettingResponse.findValueInJson("error.reason"), containsString("no permissions for [cluster:feature/tenancy/config/update]")); - } } diff --git a/src/test/resources/multitenancy/internal_users.yml b/src/test/resources/multitenancy/internal_users.yml index 809d268710..af96b2b8ee 100644 --- a/src/test/resources/multitenancy/internal_users.yml +++ b/src/test/resources/multitenancy/internal_users.yml @@ -156,3 +156,9 @@ user_tenant_parameters_substitution: attributes: attribute1: "tenant_parameters_substitution" description: "PR# 819 / Issue#817" +user_rest_api_access: + hash: "$2y$12$aHkyhk95XbrMCByYYVAlrek1thXpTDuVKJW01vdLYPh6kyR36j7x6" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Access" diff --git a/src/test/resources/multitenancy/roles.yml b/src/test/resources/multitenancy/roles.yml index efc17f7464..1baa77cc8b 100644 --- a/src/test/resources/multitenancy/roles.yml +++ b/src/test/resources/multitenancy/roles.yml @@ -2,6 +2,8 @@ _meta: type: "roles" config_version: 2 +security_rest_api_access: + reserved: true opendistro_security_own_index: reserved: false hidden: false diff --git a/src/test/resources/multitenancy/roles_mapping.yml b/src/test/resources/multitenancy/roles_mapping.yml index a7d2867db1..397171a360 100644 --- a/src/test/resources/multitenancy/roles_mapping.yml +++ b/src/test/resources/multitenancy/roles_mapping.yml @@ -2,6 +2,11 @@ _meta: type: "rolesmapping" config_version: 2 +security_rest_api_access: + reserved: false + hidden: false + users: + - "user_rest_api_access" opendistro_security_human_resources: reserved: false hidden: false From 15860b65f33861f875b62ed57b6d8b7ab3c8a65d Mon Sep 17 00:00:00 2001 From: Andrey Pleskach Date: Mon, 15 May 2023 20:23:41 +0200 Subject: [PATCH 187/369] Add score based password verification (#2557) Signed-off-by: Andrey Pleskach --- build.gradle | 1 + .../security/SecurityConfigurationTests.java | 4 +- .../security/OpenSearchSecurityPlugin.java | 14 ++ .../AbstractConfigurationValidator.java | 22 +- .../rest/validation/CredentialsValidator.java | 52 ++--- .../rest/validation/PasswordValidator.java | 185 ++++++++++++++++ .../security/support/ConfigConstants.java | 4 +- .../RestApiComplianceAuditlogTest.java | 13 +- .../rest/api/AbstractRestApiUnitTest.java | 15 +- .../dlic/rest/api/AccountApiTest.java | 2 +- .../dlic/rest/api/ActionGroupsApiTest.java | 38 ++-- .../security/dlic/rest/api/RolesApiTest.java | 39 ++-- .../dlic/rest/api/RolesMappingApiTest.java | 52 ++--- .../security/dlic/rest/api/UserApiTest.java | 201 ++++++++++++------ .../validation/PasswordValidatorTest.java | 197 +++++++++++++++++ 15 files changed, 661 insertions(+), 178 deletions(-) create mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/validation/PasswordValidatorTest.java diff --git a/build.gradle b/build.gradle index 8cfe97ea80..ef187e290f 100644 --- a/build.gradle +++ b/build.gradle @@ -395,6 +395,7 @@ dependencies { implementation ('org.opensaml:opensaml-saml-impl:3.4.5') { exclude(group: 'org.apache.velocity', module: 'velocity') } + implementation "com.nulab-inc:zxcvbn:1.7.0" testImplementation 'org.opensaml:opensaml-messaging-impl:3.4.5' implementation 'org.opensaml:opensaml-messaging-api:3.4.5' runtimeOnly 'org.opensaml:opensaml-profile-api:3.4.5' diff --git a/src/integrationTest/java/org/opensearch/security/SecurityConfigurationTests.java b/src/integrationTest/java/org/opensearch/security/SecurityConfigurationTests.java index 2caf05536b..9d86266f8e 100644 --- a/src/integrationTest/java/org/opensearch/security/SecurityConfigurationTests.java +++ b/src/integrationTest/java/org/opensearch/security/SecurityConfigurationTests.java @@ -49,10 +49,10 @@ public class SecurityConfigurationTests { .roles(new Role("limited-role").indexPermissions("indices:data/read/search", "indices:data/read/get").on("user-${user.name}")); public static final String LIMITED_USER_INDEX = "user-" + LIMITED_USER.getName(); public static final String ADDITIONAL_USER_1 = "additional00001"; - public static final String ADDITIONAL_PASSWORD_1 = ADDITIONAL_USER_1; + public static final String ADDITIONAL_PASSWORD_1 = "user 1 fair password"; public static final String ADDITIONAL_USER_2 = "additional2"; - public static final String ADDITIONAL_PASSWORD_2 = ADDITIONAL_USER_2; + public static final String ADDITIONAL_PASSWORD_2 = "user 2 fair password"; public static final String CREATE_USER_BODY = "{\"password\": \"%s\",\"opendistro_security_roles\": []}"; public static final String INTERNAL_USERS_RESOURCE = "_plugins/_security/api/internalusers/"; public static final String ID_1 = "one"; diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 2db8b5d2eb..89e8ec31ac 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -138,6 +138,7 @@ import org.opensearch.security.configuration.Salt; import org.opensearch.security.configuration.SecurityFlsDlsIndexSearcherWrapper; import org.opensearch.security.dlic.rest.api.SecurityRestApiActions; +import org.opensearch.security.dlic.rest.validation.PasswordValidator; import org.opensearch.security.filter.SecurityFilter; import org.opensearch.security.filter.SecurityRestFilter; import org.opensearch.security.http.SecurityHttpServerTransport; @@ -1043,6 +1044,19 @@ public List> getSettings() { settings.add(Setting.simpleString(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, Property.NodeScope, Property.Filtered)); settings.add(Setting.simpleString(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.intSetting( + ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, + -1, -1, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, + PasswordValidator.ScoreStrength.STRONG.name(), + PasswordValidator.ScoreStrength::fromConfiguration, + Property.NodeScope, Property.Filtered + ) + ); // Compliance settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java index 81942d9c11..e3221de7e6 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java @@ -249,9 +249,19 @@ public XContentBuilder errorsAsXContent(RestChannel channel) { break; case INVALID_PASSWORD: builder.field("status", "error"); - builder.field("reason", opensearchSettings.get(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, + builder.field("reason", opensearchSettings.get( + ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, "Password does not match minimum criteria")); break; + case WEAK_PASSWORD: + case SIMILAR_PASSWORD: + builder.field("status", "error"); + builder.field( + "reason", + opensearchSettings.get( + ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, + errorType.message)); + break; case WRONG_DATATYPE: builder.field("status", "error"); builder.field("reason", ErrorType.WRONG_DATATYPE.getMessage()); @@ -289,8 +299,14 @@ public static enum DataType { } public static enum ErrorType { - NONE("ok"), INVALID_CONFIGURATION("Invalid configuration"), INVALID_PASSWORD("Invalid password"), WRONG_DATATYPE("Wrong datatype"), - BODY_NOT_PARSEABLE("Could not parse content of request."), PAYLOAD_NOT_ALLOWED("Request body not allowed for this action."), + NONE("ok"), + INVALID_CONFIGURATION("Invalid configuration"), + INVALID_PASSWORD("Invalid password"), + WEAK_PASSWORD("Weak password"), + SIMILAR_PASSWORD("Password is similar to user name"), + WRONG_DATATYPE("Wrong datatype"), + BODY_NOT_PARSEABLE("Could not parse content of request."), + PAYLOAD_NOT_ALLOWED("Request body not allowed for this action."), PAYLOAD_MANDATORY("Request body required for this action."), SECURITY_NOT_INITIALIZED("Security index not initialized"), NULL_ARRAY_ELEMENT("`null` is not allowed as json array element"); diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java index db95660448..a54f1947f0 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java @@ -12,7 +12,6 @@ package org.opensearch.security.dlic.rest.validation; import java.util.Map; -import java.util.regex.Pattern; import org.opensearch.common.Strings; import org.opensearch.common.bytes.BytesReference; @@ -22,17 +21,21 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.rest.RestRequest; import org.opensearch.security.ssl.util.Utils; -import org.opensearch.security.support.ConfigConstants; /** * Validator for validating password and hash present in the payload */ public class CredentialsValidator extends AbstractConfigurationValidator { - public CredentialsValidator(final RestRequest request, BytesReference ref, final Settings opensearchSettings, + private final PasswordValidator passwordValidator; + + public CredentialsValidator(final RestRequest request, + final BytesReference ref, + final Settings opensearchSettings, Object... param) { super(request, ref, opensearchSettings, param); this.payloadMandatory = true; + this.passwordValidator = PasswordValidator.of(opensearchSettings); allowedKeys.put("hash", DataType.STRING); allowedKeys.put("password", DataType.STRING); } @@ -46,49 +49,29 @@ public boolean validate() { if (!super.validate()) { return false; } - - final String regex = this.opensearchSettings.get(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, null); - if ((request.method() == RestRequest.Method.PUT || request.method() == RestRequest.Method.PATCH) && this.content != null && this.content.length() > 1) { try { final Map contentAsMap = XContentHelper.convertToMap(this.content, false, XContentType.JSON).v2(); - String password = (String) contentAsMap.get("password"); + final String password = (String) contentAsMap.get("password"); if (password != null) { // Password is not allowed to be empty if present. if (password.isEmpty()) { this.errorType = ErrorType.INVALID_PASSWORD; return false; } - - if (!Strings.isNullOrEmpty(regex)) { - // Password can be null for an existing user. Regex will validate password if present - if (!Pattern.compile("^"+regex+"$").matcher(password).matches()) { - if(log.isDebugEnabled()) { - log.debug("Regex does not match password"); - } - this.errorType = ErrorType.INVALID_PASSWORD; - return false; - } - - final String username = Utils.coalesce(request.param("name"), hasParams() ? (String) param[0] : null); - final boolean isDebugEnabled = log.isDebugEnabled(); - - if (username == null || username.isEmpty()) { - if (isDebugEnabled) { - log.debug("Unable to validate username because no user is given"); - } - return false; - } - - if (username.toLowerCase().equals(password.toLowerCase())) { - if (isDebugEnabled) { - log.debug("Username must not match password"); - } - this.errorType = ErrorType.INVALID_PASSWORD; - return false; + final String username = Utils.coalesce(request.param("name"), hasParams() ? (String) param[0] : null); + if (Strings.isNullOrEmpty(username)) { + if (log.isDebugEnabled()) { + log.debug("Unable to validate username because no user is given"); } + return false; + } + final ErrorType passwordValidationResult = passwordValidator.validate(username, password); + if (passwordValidationResult != ErrorType.NONE) { + this.errorType = passwordValidationResult; + return false; } } } catch (NotXContentException e) { @@ -99,4 +82,5 @@ public boolean validate() { } return true; } + } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java new file mode 100644 index 0000000000..a59a4e6d40 --- /dev/null +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java @@ -0,0 +1,185 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.validation; + +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import com.google.common.collect.ImmutableList; +import com.nulabinc.zxcvbn.Strength; +import com.nulabinc.zxcvbn.Zxcvbn; +import com.nulabinc.zxcvbn.matchers.Match; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.common.Strings; +import org.opensearch.common.settings.Settings; +import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator.ErrorType; + +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX; + +public class PasswordValidator { + + private static final int MAX_LENGTH = 100; + + /** + * Checks a username similarity and a password + * names and passwords like: + * - some_user_name/456Some_uSer_Name_1234 + * - some_user_name/some_user_name_Ydfge + * - some_user_name/eman_resu_emos + * are similar + * "user_inputs" - is a default dictionary zxcvbn creates for checking similarity + */ + private final static Predicate USERNAME_SIMILARITY_CHECK = m -> + m.pattern == com.nulabinc.zxcvbn.Pattern.Dictionary && "user_inputs".equals(m.dictionaryName); + + private final Logger logger = LogManager.getLogger(this.getClass()); + + private final int minPasswordLength; + + private final Pattern passwordRegexpPattern; + + private final ScoreStrength scoreStrength; + + private final Zxcvbn zxcvbn; + + private PasswordValidator(final int minPasswordLength, + final Pattern passwordRegexpPattern, + final ScoreStrength scoreStrength) { + this.minPasswordLength = minPasswordLength; + this.passwordRegexpPattern = passwordRegexpPattern; + this.scoreStrength = scoreStrength; + this.zxcvbn = new Zxcvbn(); + } + + public static PasswordValidator of(final Settings settings) { + final String passwordRegex = settings.get(SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, null); + final ScoreStrength scoreStrength = ScoreStrength.fromConfiguration( + settings.get(SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, ScoreStrength.STRONG.name()) + ); + final int minPasswordLength = settings.getAsInt(SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, -1); + return new PasswordValidator( + minPasswordLength, + !Strings.isNullOrEmpty(passwordRegex) ? Pattern.compile(String.format("^%s$", passwordRegex)) : null, + scoreStrength); + } + + ErrorType validate(final String username, final String password) { + if (minPasswordLength > 0 && password.length() < minPasswordLength) { + logger.debug( + "Password is too short, the minimum required length is {}, but current length is {}", + minPasswordLength, + password.length() + ); + return ErrorType.INVALID_PASSWORD; + } + if (password.length() > MAX_LENGTH) { + logger.debug( + "Password is too long, the maximum required length is {}, but current length is {}", + MAX_LENGTH, + password.length() + ); + return ErrorType.INVALID_PASSWORD; + } + if (Objects.nonNull(passwordRegexpPattern) + && !passwordRegexpPattern.matcher(password).matches()) { + logger.debug("Regex does not match password"); + return ErrorType.INVALID_PASSWORD; + } + final Strength strength = zxcvbn.measure(password, ImmutableList.of(username)); + if (strength.getScore() < scoreStrength.score()) { + logger.debug( + "Password is weak the required score is {}, but current is {}", + scoreStrength, + ScoreStrength.fromScore(strength.getScore()) + ); + return ErrorType.WEAK_PASSWORD; + } + final boolean similar = strength.getSequence() + .stream() + .anyMatch(USERNAME_SIMILARITY_CHECK); + if (similar) { + logger.debug("Password is too similar to the user name {}", username); + return ErrorType.SIMILAR_PASSWORD; + } + return ErrorType.NONE; + } + + public enum ScoreStrength { + + // The weak score defines here only for debugging information + // and doesn't use as a configuration setting value. + WEAK(0, "too guessable: risky password"), + FAIR(1, "very guessable: protection from throttled online attacks"), + GOOD(2, "somewhat guessable: protection from unthrottled online attacks"), + STRONG(3, "safely unguessable: moderate protection from offline slow-hash scenario"), + VERY_STRONG(4, "very unguessable: strong protection from offline slow-hash scenario"); + + private final int score; + + private final String description; + + static final List CONFIGURATION_VALUES = ImmutableList.of(FAIR, STRONG, VERY_STRONG); + + static final String EXPECTED_CONFIGURATION_VALUES = + new StringJoiner(",") + .add(FAIR.name().toLowerCase(Locale.ROOT)) + .add(STRONG.name().toLowerCase(Locale.ROOT)) + .add(VERY_STRONG.name().toLowerCase(Locale.ROOT)) + .toString(); + + private ScoreStrength(final int score, final String description) { + this.score = score; + this.description = description; + } + + public static ScoreStrength fromScore(final int score) { + for (final ScoreStrength strength : values()) { + if (strength.score == score) + return strength; + } + throw new IllegalArgumentException("Unknown score " + score); + } + + public static ScoreStrength fromConfiguration(final String value) { + for (final ScoreStrength strength : CONFIGURATION_VALUES) { + if (strength.name().equalsIgnoreCase(value)) + return strength; + } + throw new IllegalArgumentException( + String.format( + "Setting [%s] cannot be used with the configured: %s. Expected one of [%s]", + SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, + value, + EXPECTED_CONFIGURATION_VALUES + ) + ); + } + + @Override + public String toString() { + return String.format("Password strength score %s. %s", score, description); + } + + public int score() { + return this.score; + } + + } +} diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index a58639bc0a..83281c9d0b 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -255,7 +255,9 @@ public enum RolesMappingResolution { public static final String SECURITY_RESTAPI_ENDPOINTS_DISABLED = "plugins.security.restapi.endpoints_disabled"; public static final String SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX = "plugins.security.restapi.password_validation_regex"; public static final String SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE = "plugins.security.restapi.password_validation_error_message"; - + public static final String SECURITY_RESTAPI_PASSWORD_MIN_LENGTH = "plugins.security.restapi.password_min_length"; + public static final String SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH = "plugins.security.restapi.password_score_based_validation_strength"; + // Illegal Opcodes from here on public static final String SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY = "plugins.security.unsupported.disable_rest_auth_initially"; public static final String SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY = "plugins.security.unsupported.disable_intertransport_auth_initially"; public static final String SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY = "plugins.security.unsupported.passive_intertransport_auth_initially"; diff --git a/src/test/java/org/opensearch/security/auditlog/compliance/RestApiComplianceAuditlogTest.java b/src/test/java/org/opensearch/security/auditlog/compliance/RestApiComplianceAuditlogTest.java index 0a90f2f396..37ef283ccb 100644 --- a/src/test/java/org/opensearch/security/auditlog/compliance/RestApiComplianceAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/compliance/RestApiComplianceAuditlogTest.java @@ -42,7 +42,7 @@ public void testRestApiRolesEnabled() throws Exception { setup(additionalSettings); TestAuditlogImpl.clear(); - String body = "{ \"password\":\"test\",\"backend_roles\":[\"role1\",\"role2\"] }"; + String body = "{ \"password\":\"some new password\",\"backend_roles\":[\"role1\",\"role2\"] }"; HttpResponse response = rh.executePutRequest("_opendistro/_security/api/internalusers/compuser?pretty", body, encodeBasicHeader("admin", "admin")); Thread.sleep(1500); System.out.println(TestAuditlogImpl.sb.toString()); @@ -71,7 +71,7 @@ public void testRestApiRolesDisabled() throws Exception { setup(additionalSettings); TestAuditlogImpl.clear(); - String body = "{ \"password\":\"test\",\"backend_roles\":[\"role1\",\"role2\"] }"; + String body = "{ \"password\":\"some new password\",\"backend_roles\":[\"role1\",\"role2\"] }"; rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; @@ -169,12 +169,13 @@ public void testRestApiNewUser() throws Exception { setup(additionalSettings); TestAuditlogImpl.clear(); - String body = "{ \"password\":\"test\",\"backend_roles\":[\"role1\",\"role2\"] }"; + String body = "{ \"password\":\"some new password\",\"backend_roles\":[\"role1\",\"role2\"] }"; System.out.println("exec"); - HttpResponse response = rh.executePutRequest("_opendistro/_security/api/internalusers/compuser?pretty", body, encodeBasicHeader("admin", "admin")); + HttpResponse response = rh.executePutRequest("_opendistro/_security/api/internalusers/compuser?pretty", + body, encodeBasicHeader("admin", "admin")); Thread.sleep(1500); System.out.println(TestAuditlogImpl.sb.toString()); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); Assert.assertTrue(TestAuditlogImpl.messages.size()+"", TestAuditlogImpl.messages.isEmpty()); } @@ -241,7 +242,7 @@ public void testBCryptHashRedaction() throws Exception { // create internal user and verify no BCrypt hash is present in audit logs TestAuditlogImpl.clear(); - rh.executePutRequest("/_opendistro/_security/api/internalusers/test", "{ \"password\":\"test\"}"); + rh.executePutRequest("/_opendistro/_security/api/internalusers/test", "{ \"password\":\"some new user password\"}"); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); Assert.assertFalse(AuditMessage.BCRYPT_HASH.matcher(TestAuditlogImpl.sb.toString()).matches()); } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java index a1cb20d99c..7b590f1d46 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java @@ -16,6 +16,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.stream.Stream; import com.fasterxml.jackson.core.JsonParseException; @@ -32,12 +33,15 @@ import org.opensearch.plugins.Plugin; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auditlog.AuditTestUtils; +import org.opensearch.security.dlic.rest.validation.PasswordValidator; import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.SingleClusterTest; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH; + public abstract class AbstractRestApiUnitTest extends SingleClusterTest { protected RestHelper rh = null; @@ -53,6 +57,7 @@ protected final void setup() throws Exception { Settings.Builder builder = Settings.builder(); builder.put("plugins.security.ssl.http.enabled", true) + .put(SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, PasswordValidator.ScoreStrength.FAIR.name()) .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("restapi/node-0-keystore.jks")) .put("plugins.security.ssl.http.truststore_filepath", @@ -89,6 +94,7 @@ protected final void setupWithRestRoles(Settings nodeOverride) throws Exception Settings.Builder builder = Settings.builder(); builder.put("plugins.security.ssl.http.enabled", true) + .put(SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, PasswordValidator.ScoreStrength.FAIR.name()) .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("restapi/node-0-keystore.jks")) .put("plugins.security.ssl.http.truststore_filepath", @@ -130,12 +136,19 @@ protected void deleteUser(String username) throws Exception { } protected void addUserWithPassword(String username, String password, int status) throws Exception { + addUserWithPassword(username, password, status, null); + } + + protected void addUserWithPassword(String username, String password, int status, String message) throws Exception { boolean sendAdminCertificate = rh.sendAdminCertificate; rh.sendAdminCertificate = true; HttpResponse response = rh.executePutRequest("/_opendistro/_security/api/internalusers/" + username, "{\"password\": \"" + password + "\"}", new Header[0]); Assert.assertEquals(status, response.getStatusCode()); rh.sendAdminCertificate = sendAdminCertificate; + if (Objects.nonNull(message)) { + Assert.assertTrue(response.getBody().contains(message)); + } } protected void addUserWithPassword(String username, String password, String[] roles, int status) throws Exception { @@ -220,7 +233,7 @@ protected String checkWriteAccess(int status, String username, String password, String payload = "{\"value\" : \"true\"}"; HttpResponse response = rh.executePutRequest(action, payload, encodeBasicHeader(username, password)); int returnedStatus = response.getStatusCode(); - Assert.assertEquals(status, returnedStatus); + Assert.assertEquals(response.getBody(), status, returnedStatus); return response.getBody(); } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java index 0b91aa35af..9f6bcb65c9 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java @@ -45,7 +45,7 @@ public void testGetAccount() throws Exception { // arrange setup(); final String testUser = "test-user"; - final String testPass = "test-pass"; + final String testPass = "some password for user"; addUserWithPassword(testUser, testPass, HttpStatus.SC_CREATED); // test - unauthorized access as credentials are missing. diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java index abb45df268..926ee23f28 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java @@ -53,9 +53,9 @@ public void testActionGroupsApi() throws Exception { setupStarfleetIndex(); // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + addUserWithPassword("picard", "picardpicardpicard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 0); rh.sendAdminCertificate = true; verifyGetForSuperAdmin(new Header[0]); rh.sendAdminCertificate = true; @@ -122,21 +122,21 @@ void verifyDeleteForSuperAdmin(final Header[] header, final boolean userAdminCer Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); rh.sendAdminCertificate = false; - checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 0); // put picard in captains role. Role opendistro_security_role_captains uses the CRUD_UT // action group // which uses READ_UT and WRITE action groups. We removed READ_UT, so only // WRITE is possible - addUserWithPassword("picard", "picard", new String[] { "captains" }, HttpStatus.SC_OK); - checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + addUserWithPassword("picard", "picardpicardpicard", new String[] { "captains" }, HttpStatus.SC_OK); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 0); + checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 0); // now remove also CRUD_UT groups, write also not possible anymore rh.sendAdminCertificate = true; response = rh.executeDeleteRequest(ENDPOINT+"/CRUD_UT", new Header[0]); rh.sendAdminCertificate = false; - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); - checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 0); + checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 0); } void verifyPutForSuperAdmin(final Header[] header, final boolean userAdminCert) throws Exception { @@ -161,8 +161,8 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean userAdminCert) rh.sendAdminCertificate = false; // write access allowed again, read forbidden, since READ_UT group is still missing - checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 0); // restore READ_UT action groups rh.sendAdminCertificate = userAdminCert; @@ -171,8 +171,8 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean userAdminCert) rh.sendAdminCertificate = false; // read/write allowed again - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 0); // -- PUT, new JSON format including readonly flag, disallowed in REST API rh.sendAdminCertificate = userAdminCert; @@ -370,9 +370,9 @@ public void testActionGroupsApiForRestAdmin() throws Exception { final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + addUserWithPassword("picard", "picardpicardpicard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 0); verifyGetForSuperAdmin(new Header[] {restApiAdminHeader}); verifyDeleteForSuperAdmin(new Header[]{restApiAdminHeader}, false); verifyPutForSuperAdmin(new Header[]{restApiAdminHeader}, false); @@ -388,9 +388,9 @@ public void testActionGroupsApiForActionGroupsRestApiAdmin() throws Exception { final Header restApiAdminActionGroupsHeader = encodeBasicHeader("rest_api_admin_actiongroups", "rest_api_admin_actiongroups"); // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + addUserWithPassword("picard", "picardpicardpicard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 0); verifyGetForSuperAdmin(new Header[] {restApiAdminActionGroupsHeader}); verifyDeleteForSuperAdmin(new Header[]{restApiAdminActionGroupsHeader}, false); verifyPutForSuperAdmin(new Header[]{restApiAdminActionGroupsHeader}, false); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java index 5a6d1f297c..a85566dc91 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java @@ -151,9 +151,9 @@ public void testRolesApi() throws Exception { setupStarfleetIndex(); // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + addUserWithPassword("picard", "picardpicardpicardpicard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); rh.sendAdminCertificate = true; verifyGetForSuperAdmin(new Header[0]); @@ -220,17 +220,17 @@ void verifyDeleteForSuperAdmin(final Header[] header, final boolean sendAdminCer Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); rh.sendAdminCertificate = false; // user has only role starfleet left, role has READ access only - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicardpicard", "sf", "_doc", 1); // ES7 only supports one doc type, but OpenSearch permission checks run first // So we also get a 403 FORBIDDEN when tring to add new document type - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); rh.sendAdminCertificate = sendAdminCert; // remove also starfleet role, nothing is allowed anymore response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); } void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) throws Exception { @@ -282,14 +282,14 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) FileHelper.loadFile("restapi/roles_starfleet.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); // now picard is only in opendistro_security_role_starfleet, which has write access to // all indices. We collapse all document types in ODFE7 so this permission in the // starfleet role grants all permissions: // _doc: // - 'indices:*' - checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); rh.sendAdminCertificate = sendAdminCert; @@ -298,19 +298,14 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) FileHelper.loadFile("restapi/roles_captains.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); rh.sendAdminCertificate = sendAdminCert; response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", FileHelper.loadFile("restapi/roles_complete_invalid.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); -// rh.sendAdminCertificate = sendAdminCert; -// response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", -// FileHelper.loadFile("restapi/roles_multiple.json"), header); -// Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", FileHelper.loadFile("restapi/roles_multiple_2.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); @@ -530,9 +525,9 @@ public void testRolesApiWithAllRestApiPermissions() throws Exception { setupStarfleetIndex(); // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + addUserWithPassword("picard", "picardpicardpicardpicard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); verifyGetForSuperAdmin(new Header[]{restApiAdminHeader}); verifyDeleteForSuperAdmin(new Header[]{restApiAdminHeader}, false); @@ -550,9 +545,9 @@ public void testRolesApiWithRestApiRolePermission() throws Exception { setupStarfleetIndex(); // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + addUserWithPassword("picard", "picardpicardpicardpicard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); verifyGetForSuperAdmin(new Header[]{restApiRolesHeader}); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java index 44e160e151..3bc647bf12 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java @@ -53,8 +53,8 @@ public void testRolesMappingApi() throws Exception { setupStarfleetIndex(); // add user picard, role captains initially maps to // opendistro_security_role_starfleet_captains and opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[] { "captains" }, HttpStatus.SC_CREATED); - checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + addUserWithPassword("picard", "picardpicardpicard", new String[] { "captains" }, HttpStatus.SC_CREATED); + checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picardpicardpicard", "sf", "_doc", 1); // TODO: only one doctype allowed for ES6 //checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); rh.sendAdminCertificate = true; @@ -65,35 +65,35 @@ public void testRolesMappingApi() throws Exception { verifyPutForSuperAdmin(new Header[0]); verifyPatchForSuperAdmin(new Header[0]); // mapping with several backend roles, one of the is captain - deleteAndputNewMapping(new Header[0],"rolesmapping_backendroles_captains_list.json", true); + deleteAndPutNewMapping(new Header[0],"rolesmapping_backendroles_captains_list.json", true); checkAllSfAllowed(); // mapping with one backend role, captain - deleteAndputNewMapping(new Header[0],"rolesmapping_backendroles_captains_single.json", true); + deleteAndPutNewMapping(new Header[0],"rolesmapping_backendroles_captains_single.json", true); checkAllSfAllowed(); // mapping with several users, one is picard - deleteAndputNewMapping(new Header[0],"rolesmapping_users_picard_list.json", true); + deleteAndPutNewMapping(new Header[0],"rolesmapping_users_picard_list.json", true); checkAllSfAllowed(); // just user picard - deleteAndputNewMapping(new Header[0],"rolesmapping_users_picard_single.json", true); + deleteAndPutNewMapping(new Header[0],"rolesmapping_users_picard_single.json", true); checkAllSfAllowed(); // hosts - deleteAndputNewMapping(new Header[0],"rolesmapping_hosts_list.json", true); + deleteAndPutNewMapping(new Header[0],"rolesmapping_hosts_list.json", true); checkAllSfAllowed(); // hosts - deleteAndputNewMapping(new Header[0],"rolesmapping_hosts_single.json", true); + deleteAndPutNewMapping(new Header[0],"rolesmapping_hosts_single.json", true); checkAllSfAllowed(); // full settings, access - deleteAndputNewMapping(new Header[0],"rolesmapping_all_access.json", true); + deleteAndPutNewMapping(new Header[0],"rolesmapping_all_access.json", true); checkAllSfAllowed(); // full settings, no access - deleteAndputNewMapping(new Header[0],"rolesmapping_all_noaccess.json", true); + deleteAndPutNewMapping(new Header[0],"rolesmapping_all_noaccess.json", true); checkAllSfForbidden(); } @@ -107,8 +107,8 @@ public void testRolesMappingApiWithFullPermissions() throws Exception { setupStarfleetIndex(); // add user picard, role captains initially maps to // opendistro_security_role_starfleet_captains and opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[] { "captains" }, HttpStatus.SC_CREATED); - checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + addUserWithPassword("picard", "picardpicardpicard", new String[] { "captains" }, HttpStatus.SC_CREATED); + checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picardpicardpicard", "sf", "_doc", 1); // TODO: only one doctype allowed for ES6 //checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); @@ -117,35 +117,35 @@ public void testRolesMappingApiWithFullPermissions() throws Exception { verifyPutForSuperAdmin(new Header[]{restApiAdminHeader}); verifyPatchForSuperAdmin(new Header[]{restApiAdminHeader}); // mapping with several backend roles, one of the is captain - deleteAndputNewMapping(new Header[]{restApiAdminHeader}, "rolesmapping_backendroles_captains_list.json", false); + deleteAndPutNewMapping(new Header[]{restApiAdminHeader}, "rolesmapping_backendroles_captains_list.json", false); checkAllSfAllowed(); // mapping with one backend role, captain - deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_backendroles_captains_single.json", true); + deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_backendroles_captains_single.json", true); checkAllSfAllowed(); // mapping with several users, one is picard - deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_users_picard_list.json", true); + deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_users_picard_list.json", true); checkAllSfAllowed(); // just user picard - deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_users_picard_single.json", true); + deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_users_picard_single.json", true); checkAllSfAllowed(); // hosts - deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_hosts_list.json", true); + deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_hosts_list.json", true); checkAllSfAllowed(); // hosts - deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_hosts_single.json", true); + deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_hosts_single.json", true); checkAllSfAllowed(); // full settings, access - deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_all_access.json", true); + deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_all_access.json", true); checkAllSfAllowed(); // full settings, no access - deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_all_noaccess.json", true); + deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_all_noaccess.json", true); checkAllSfForbidden(); } @@ -221,7 +221,7 @@ void verifyDeleteForSuperAdmin(final Header[] header, final boolean useAdminCert // now picard is only in opendistro_security_role_starfleet, which has write access to // public, but not to _doc - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 1); // TODO: only one doctype allowed for ES6 // checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 1); @@ -382,17 +382,17 @@ void verifyPatchForSuperAdmin(final Header[] header) throws Exception { private void checkAllSfAllowed() throws Exception { rh.sendAdminCertificate = false; - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 1); - checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 1); + checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 1); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 1); } private void checkAllSfForbidden() throws Exception { rh.sendAdminCertificate = false; - checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); + checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 1); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 1); } - private HttpResponse deleteAndputNewMapping(final Header[] header, final String fileName, final boolean useAdminCert) throws Exception { + private HttpResponse deleteAndPutNewMapping(final Header[] header, final String fileName, final boolean useAdminCert) throws Exception { rh.sendAdminCertificate = useAdminCert; HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", header); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index 60c2ccde6f..abcda9a69c 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -24,6 +24,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; +import org.opensearch.security.dlic.rest.validation.PasswordValidator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.file.FileHelper; @@ -89,15 +90,22 @@ public void testSecurityRoles() throws Exception { .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(USER_SETTING_SIZE, settings.size()); - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/newuser\", \"value\": {\"password\": \"newuser\", \"opendistro_security_roles\": [\"opendistro_security_all_access\"] } }]", new Header[0]); + Assert.assertEquals(133, settings.size()); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers", + "[{ \"op\": \"add\", \"path\": \"/newuser\", " + + "\"value\": {\"password\": \"fair password for the user\", " + + "\"opendistro_security_roles\": [\"opendistro_security_all_access\"] } }]", + new Header[0] + ); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/internalusers/newuser", new Header[0]); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"opendistro_security_roles\":[\"opendistro_security_all_access\"]")); - checkGeneralAccess(HttpStatus.SC_OK, "newuser", "newuser"); + checkGeneralAccess(HttpStatus.SC_OK, "newuser", "fair password for the user"); + } @Test @@ -108,7 +116,8 @@ public void testParallelPutRequests() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - HttpResponse[] responses = rh.executeMultipleAsyncPutRequest(10, ENDPOINT + "/internalusers/test1", "{\"password\":\"test1\"}"); + HttpResponse[] responses = rh.executeMultipleAsyncPutRequest(10, + ENDPOINT + "/internalusers/test1", "{\"password\":\"test1test1test1test1test1test1\"}"); boolean created = false; for (HttpResponse response : responses) { int sc = response.getStatusCode(); @@ -254,7 +263,7 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) // PATCH password rh.sendAdminCertificate = sendAdminCert; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/test", "[{ \"op\": \"add\", \"path\": \"/password\", \"value\": \"neu\" }]", restAdminHeader); + response = rh.executePatchRequest(ENDPOINT + "/internalusers/test", "[{ \"op\": \"add\", \"path\": \"/password\", \"value\": \"neu password 42\" }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/internalusers/test", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); @@ -292,7 +301,7 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) // PATCH rh.sendAdminCertificate = sendAdminCert; response = rh.executePatchRequest(ENDPOINT + "/internalusers", - "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": {\"password\": \"bla\", \"backend_roles\": [\"vulcan\"] } }]", restAdminHeader); + "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": {\"password\": \"bla bla bla password 42\", \"backend_roles\": [\"vulcan\"] } }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/internalusers/bulknew1", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); @@ -382,11 +391,11 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) // use password instead of hash rh.sendAdminCertificate = sendAdminCert; - addUserWithPassword("nagilum", "correctpassword", HttpStatus.SC_CREATED); + addUserWithPassword("nagilum", "correct password 42", HttpStatus.SC_CREATED); rh.sendAdminCertificate = false; checkGeneralAccess(HttpStatus.SC_UNAUTHORIZED, "nagilum", "wrongpassword"); - checkGeneralAccess(HttpStatus.SC_OK, "nagilum", "correctpassword"); + checkGeneralAccess(HttpStatus.SC_OK, "nagilum", "correct password 42"); deleteUser("nagilum"); @@ -490,24 +499,24 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E // use backendroles when creating user. User picard does not exist in // the internal user DB // and is also not assigned to any role by username - addUserWithPassword("picard", "picard", HttpStatus.SC_CREATED); + addUserWithPassword("picard", "picardpicardpicardpicardpicard", HttpStatus.SC_CREATED); // changed in ES5, you now need cluster:monitor/main which pucard does not have - checkGeneralAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard"); + checkGeneralAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicardpicardpicard"); // check read access to starfleet index and _doc type, must fail - checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicardpicardpicard", "sf", "_doc", 0); // overwrite user picard, and give him role "starfleet". - addUserWithPassword("picard", "picard", new String[]{"starfleet"}, HttpStatus.SC_OK); + addUserWithPassword("picard", "picardpicardpicardpicardpicard", new String[]{"starfleet"}, HttpStatus.SC_OK); - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); + checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicardpicard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicardpicardpicard", "sf", "_doc", 1); // overwrite user picard, and give him role "starfleet" plus "captains. Now // document can be created. - addUserWithPassword("picard", "picard", new String[]{"starfleet", "captains"}, HttpStatus.SC_OK); - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + addUserWithPassword("picard", "picardpicardpicardpicardpicard", new String[]{"starfleet", "captains"}, HttpStatus.SC_OK); + checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicardpicard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picardpicardpicardpicardpicard", "sf", "_doc", 1); rh.sendAdminCertificate = sendAdminCert; response = rh.executeGetRequest(ENDPOINT + "/internalusers/picard", header); @@ -520,8 +529,8 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E Assert.assertTrue(roles.contains("starfleet")); Assert.assertTrue(roles.contains("captains")); - addUserWithPassword("$1aAAAAAAAAC", "$1aAAAAAAAAC", HttpStatus.SC_CREATED); - addUserWithPassword("abc", "abc", HttpStatus.SC_CREATED); + addUserWithPassword("some_additional_user", "$1aAAAAAAAAC", HttpStatus.SC_CREATED); + addUserWithPassword("abc", "abcabcabcabc42", HttpStatus.SC_CREATED); // check tabs in json response = rh.executePutRequest(ENDPOINT + "/internalusers/userwithtabs", "\t{\"hash\": \t \"123\"\t} ", header); @@ -565,13 +574,15 @@ public void testUserApiWithRestInternalUsersAdminPermissions() throws Exception } @Test - public void testPasswordRules() throws Exception { + public void testRegExpPasswordRules() throws Exception { Settings nodeSettings = Settings.builder() .put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, "xxx") .put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, "(?=.*[A-Z])(?=.*[^a-zA-Z\\\\d])(?=.*[0-9])(?=.*[a-z]).{8,}") + .put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, + PasswordValidator.ScoreStrength.FAIR.name()) .build(); setup(nodeSettings); @@ -582,73 +593,137 @@ public void testPasswordRules() throws Exception { // initial configuration, 6 users HttpResponse response = rh .executeGetRequest("_plugins/_security/api/" + CType.INTERNALUSERS.toLCString()); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(USER_SETTING_SIZE, settings.size()); - addUserWithPassword("tooshoort", "", HttpStatus.SC_BAD_REQUEST); - addUserWithPassword("tooshoort", "123", HttpStatus.SC_BAD_REQUEST); - addUserWithPassword("tooshoort", "1234567", HttpStatus.SC_BAD_REQUEST); - addUserWithPassword("tooshoort", "1Aa%", HttpStatus.SC_BAD_REQUEST); - addUserWithPassword("no-nonnumeric", "123456789", HttpStatus.SC_BAD_REQUEST); - addUserWithPassword("no-uppercase", "a123456789", HttpStatus.SC_BAD_REQUEST); - addUserWithPassword("no-lowercase", "A123456789", HttpStatus.SC_BAD_REQUEST); - addUserWithPassword("ok1", "a%A123456789", HttpStatus.SC_CREATED); - addUserWithPassword("ok2", "$aA123456789", HttpStatus.SC_CREATED); - addUserWithPassword("ok3", "$Aa123456789", HttpStatus.SC_CREATED); - addUserWithPassword("ok4", "$1aAAAAAAAAA", HttpStatus.SC_CREATED); - addUserWithPassword("empty_password_no_hash", "", HttpStatus.SC_BAD_REQUEST); + verifyCouldNotCreatePasswords(HttpStatus.SC_BAD_REQUEST); + verifyCanCreatePasswords(); + verifySimilarity("xxx"); + addUserWithPasswordAndHash("empty_password", "", "$%^123", HttpStatus.SC_BAD_REQUEST); addUserWithPasswordAndHash("null_password", null, "$%^123", HttpStatus.SC_BAD_REQUEST); - response = rh.executePatchRequest(PLUGINS_PREFIX + "/api/internalusers", "[{ \"op\": \"add\", \"path\": \"/ok4\", \"value\": {\"password\": \"bla\", \"backend_roles\": [\"vulcan\"] } }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + response = rh.executeGetRequest(PLUGINS_PREFIX + "/api/internalusers/nothinghthere?pretty", new Header[0]); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + Assert.assertTrue(response.getBody().contains("NOT_FOUND")); + } + private void verifyCouldNotCreatePasswords(final int expectedStatus) throws Exception { + addUserWithPassword("tooshoort", "", expectedStatus); + addUserWithPassword("tooshoort", "123",expectedStatus); + addUserWithPassword("tooshoort", "1234567", expectedStatus); + addUserWithPassword("tooshoort", "1Aa%", expectedStatus); + addUserWithPassword("no-nonnumeric", "123456789", expectedStatus); + addUserWithPassword("no-uppercase", "a123456789", expectedStatus); + addUserWithPassword("no-lowercase", "A123456789", expectedStatus); + addUserWithPassword("empty_password_no_hash", "", expectedStatus); + HttpResponse response = rh.executePatchRequest( + PLUGINS_PREFIX + "/api/internalusers", + "[{ \"op\": \"add\", \"path\": \"/ok4\", \"value\": {\"password\": \"bla\", \"backend_roles\": [\"vulcan\"] } }]", + new Header[0] + ); + Assert.assertEquals(response.getBody(), expectedStatus, response.getStatusCode()); response = rh.executePatchRequest(PLUGINS_PREFIX + "/api/internalusers", "[{ \"op\": \"replace\", \"path\": \"/ok4\", \"value\": {\"password\": \"bla\", \"backend_roles\": [\"vulcan\"] } }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - - addUserWithPassword("ok4", "123", HttpStatus.SC_BAD_REQUEST); - - response = rh.executePatchRequest(PLUGINS_PREFIX + "/api/internalusers", "[{ \"op\": \"add\", \"path\": \"/ok4\", \"value\": {\"password\": \"$1aAAAAAAAAB\", \"backend_roles\": [\"vulcan\"] } }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - - addUserWithPassword("ok4", "$1aAAAAAAAAC", HttpStatus.SC_OK); + Assert.assertEquals(response.getBody(), expectedStatus, response.getStatusCode()); + addUserWithPassword("ok4", "123", expectedStatus); //its not allowed to use the username as password (case insensitive) response = rh.executePatchRequest(PLUGINS_PREFIX + "/api/internalusers", "[{ \"op\": \"add\", \"path\": \"/$1aAAAAAAAAB\", \"value\": {\"password\": \"$1aAAAAAAAAB\", \"backend_roles\": [\"vulcan\"] } }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - addUserWithPassword("$1aAAAAAAAAC", "$1aAAAAAAAAC", HttpStatus.SC_BAD_REQUEST); - addUserWithPassword("$1aAAAAAAAac", "$1aAAAAAAAAC", HttpStatus.SC_BAD_REQUEST); - addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%", "UTF-8"), "$1aAAAAAAAAC%", HttpStatus.SC_BAD_REQUEST); - addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%!=\"/\\;:test&~@^", "UTF-8").replace("+", "%2B"), "$1aAAAAAAAac%!=\\\"/\\\\;:test&~@^", HttpStatus.SC_BAD_REQUEST); - addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%!=\"/\\;: test&", "UTF-8"), "$1aAAAAAAAac%!=\\\"/\\\\;: test&123", HttpStatus.SC_BAD_REQUEST); - - response = rh.executeGetRequest(PLUGINS_PREFIX + "/api/internalusers/nothinghthere?pretty", new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("NOT_FOUND")); - + Assert.assertEquals(response.getBody(), expectedStatus, response.getStatusCode()); + addUserWithPassword("$1aAAAAAAAAC", "$1aAAAAAAAAC", expectedStatus); + addUserWithPassword("$1aAAAAAAAac", "$1aAAAAAAAAC", expectedStatus); + addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%", "UTF-8"), "$1aAAAAAAAAC%", expectedStatus); + addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%!=\"/\\;:test&~@^", "UTF-8").replace("+", "%2B"), "$1aAAAAAAAac%!=\\\"/\\\\;:test&~@^", expectedStatus); + addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%!=\"/\\;: test&", "UTF-8"), "$1aAAAAAAAac%!=\\\"/\\\\;: test&123", expectedStatus); String patchPayload = "[ " + "{ \"op\": \"add\", \"path\": \"/testuser1\", \"value\": { \"password\": \"$aA123456789\", \"backend_roles\": [\"testrole1\"] } }," + "{ \"op\": \"add\", \"path\": \"/testuser2\", \"value\": { \"password\": \"testpassword2\", \"backend_roles\": [\"testrole2\"] } }" + "]"; response = rh.executePatchRequest(PLUGINS_PREFIX + "/api/internalusers", patchPayload, new BasicHeader("Content-Type", "application/json")); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertEquals(expectedStatus, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("error")); Assert.assertTrue(response.getBody().contains("xxx")); response = rh.executePutRequest(PLUGINS_PREFIX + "/api/internalusers/ok1", "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{},\"password\":\"\"}", new Header[0]); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + Assert.assertEquals(expectedStatus, response.getStatusCode()); + + response = rh.executePutRequest(PLUGINS_PREFIX + "/api/internalusers/ok1", + "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{},\"password\":\"bla\"}", + new Header[0]); + Assert.assertEquals(expectedStatus, response.getStatusCode()); + } + private void verifyCanCreatePasswords() throws Exception { + addUserWithPassword("ok1", "a%A123456789", HttpStatus.SC_CREATED); + addUserWithPassword("ok2", "$aA123456789", HttpStatus.SC_CREATED); + addUserWithPassword("ok3", "$Aa123456789", HttpStatus.SC_CREATED); + addUserWithPassword("ok4", "$1aAAAAAAAAA", HttpStatus.SC_CREATED); + addUserWithPassword("ok4", "$1aAAAAAAAAC", HttpStatus.SC_OK); + HttpResponse response = rh.executePatchRequest( + PLUGINS_PREFIX + "/api/internalusers", + "[{ \"op\": \"add\", \"path\": \"/ok4\", \"value\": {\"password\": \"$1aAAAAAAAAB\", \"backend_roles\": [\"vulcan\"] } }]", + new Header[0] + ); + Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); response = rh.executePutRequest(PLUGINS_PREFIX + "/api/internalusers/ok1", "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{},\"password\":\"Admin_123\"}", new Header[0]); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executePutRequest(PLUGINS_PREFIX + "/api/internalusers/ok1", "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{}}", new Header[0]); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executePutRequest(PLUGINS_PREFIX + "/api/internalusers/ok1", "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{},\"password\":\"bla\"}", - new Header[0]); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + } + + + private void verifySimilarity(final String expectedMessage) throws Exception { + addUserWithPassword( + "some_user_name", "H3235,cc,some_User_Name", + HttpStatus.SC_BAD_REQUEST, + expectedMessage + ); + } + + @Test + public void testScoreBasedPasswordRules() throws Exception { + + Settings nodeSettings = + Settings.builder() + .put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, 9) + .build(); + + setup(nodeSettings); + + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendAdminCertificate = true; + + // initial configuration, 6 users + HttpResponse response = rh + .executeGetRequest("_plugins/_security/api/" + CType.INTERNALUSERS.toLCString()); + Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(133, settings.size()); + + addUserWithPassword( + "admin", "password89", + HttpStatus.SC_BAD_REQUEST, + AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD.getMessage() + ); + addUserWithPassword( + "admin", "A123456789", + HttpStatus.SC_BAD_REQUEST, + AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD.getMessage() + ); + + addUserWithPassword( + "admin", "pas", + HttpStatus.SC_BAD_REQUEST, + "Password does not match minimum criteria" + ); + + verifySimilarity(AbstractConfigurationValidator.ErrorType.SIMILAR_PASSWORD.getMessage()); + + addUserWithPassword("some_user_name", "ASSDsadwe324wadaasdadqwe", HttpStatus.SC_CREATED); } @Test @@ -669,13 +744,13 @@ public void testUserApiWithDots() throws Exception { addUserWithPassword(".my.dotuser0", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); - addUserWithPassword(".my.dot.user0", "12345678", + addUserWithPassword(".my.dot.user0", "12345678Sd", HttpStatus.SC_CREATED); addUserWithHash(".my.dotuser1", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); - addUserWithPassword(".my.dot.user2", "12345678", + addUserWithPassword(".my.dot.user2", "12345678Sd", HttpStatus.SC_CREATED); } @@ -697,7 +772,7 @@ public void testUserApiNoPasswordChange() throws Exception { response = rh.executePutRequest(ENDPOINT + "/internalusers/user1", "{\"hash\":\"$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m\",\"password\":\"\",\"backend_roles\":[\"admin\",\"rolea\"]}"); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - response = rh.executePutRequest(ENDPOINT + "/internalusers/user1", "{\"hash\":\"$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m\",\"password\":\"Admin_123\",\"backend_roles\":[\"admin\",\"rolea\"]}"); + response = rh.executePutRequest(ENDPOINT + "/internalusers/user1", "{\"hash\":\"$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m\",\"password\":\"Admin_123345Yq\",\"backend_roles\":[\"admin\",\"rolea\"]}"); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/internalusers/user1"); @@ -709,7 +784,7 @@ public void testUserApiNoPasswordChange() throws Exception { response = rh.executePutRequest(ENDPOINT + "/internalusers/user2", "{\"password\":\"\",\"backend_roles\":[\"admin\",\"rolex\"]}"); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - response = rh.executePutRequest(ENDPOINT + "/internalusers/user2", "{\"password\":\"Admin_123\",\"backend_roles\":[\"admin\",\"rolex\"]}"); + response = rh.executePutRequest(ENDPOINT + "/internalusers/user2", "{\"password\":\"Admin_123Qerty\",\"backend_roles\":[\"admin\",\"rolex\"]}"); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/internalusers/user2"); diff --git a/src/test/java/org/opensearch/security/dlic/rest/validation/PasswordValidatorTest.java b/src/test/java/org/opensearch/security/dlic/rest/validation/PasswordValidatorTest.java new file mode 100644 index 0000000000..b5d27827b8 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/validation/PasswordValidatorTest.java @@ -0,0 +1,197 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.validation; + +import java.util.List; + +import com.google.common.collect.ImmutableList; +import org.junit.Test; + +import org.opensearch.common.settings.Settings; + +import static org.junit.Assert.assertEquals; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX; + +public class PasswordValidatorTest { + + static final List WEAK_PASSWORDS = ImmutableList.of( + "q", "5", "&", "admin", "123456", "password" + ); + + static final List FAIR_PASSWORDS = ImmutableList.of( + "p@$$word@dmin", "qwertyuiop@[", + "zxcvbnm,./_", "asdfghjkl;:]", "20300101", + "pandapandapandapandapandapandapandapandapandaa", + "appleappleappleappleappleappleappleappleapplea", + "aelppaaelppaaelppaaelppaaelppaaelppaaelppaaelppa" + ); + + static final List GOOD_PASSWORDS = ImmutableList.of( + "xsw234rfvb", "yaq123edc", "cde345tgbn", "yaqwedcvb", + "Tr0ub4dour&3", "qwER43@!" + ); + + static final List STRONG_PASSWORDS = ImmutableList.of( + "YWert,H90", "Admincc,H90", "Hadmin,120" + ); + + static final List VERY_STRONG_PASSWORDS = ImmutableList.of( + "AeTq($%u-44c_j9NJB45a#2#JP7sH", "IB7~EOw!51gug+7s#+%A9P1O/w8f", + "1v_f%7JvS8w!_t398+ON-CObI#v0", "8lFmfc0!w)&iU9DM6~4_w)D)Y44J" + ); + + static final List SIMILAR_PASSWORDS = ImmutableList.of( + "some_user_name,H2344cc", "H3235,Some_User_Name,cc", + "H3235,cc,some_User_Name", "H3235,SOME_User_Name,cc", + "H3235,eman_resu_emos,cc" + ); + + public void verifyWeakPasswords(final PasswordValidator passwordValidator, + final AbstractConfigurationValidator.ErrorType expectedValidationResult) { + for (final String password : WEAK_PASSWORDS) + assertEquals( + password, + expectedValidationResult, + passwordValidator.validate("some_user_name", password) + ); + + } + + public void verifyFairPasswords(final PasswordValidator passwordValidator, + final AbstractConfigurationValidator.ErrorType expectedValidationResult) { + for (final String password : FAIR_PASSWORDS) + assertEquals( + password, + expectedValidationResult, + passwordValidator.validate("some_user_name", password) + ); + + } + + public void verifyGoodPasswords(final PasswordValidator passwordValidator, + final AbstractConfigurationValidator.ErrorType expectedValidationResult) { + for (final String password : GOOD_PASSWORDS) + assertEquals( + password, + expectedValidationResult, + passwordValidator.validate("some_user_name", password) + ); + + } + + public void verifyStrongPasswords(final PasswordValidator passwordValidator, + final AbstractConfigurationValidator.ErrorType expectedValidationResult) { + for (final String password : STRONG_PASSWORDS) + assertEquals( + password, + expectedValidationResult, + passwordValidator.validate("some_user_name", password) + ); + + } + + public void verifyVeryStrongPasswords(final PasswordValidator passwordValidator, + final AbstractConfigurationValidator.ErrorType expectedValidationResult) { + for (final String password : VERY_STRONG_PASSWORDS) + assertEquals( + password, + expectedValidationResult, + passwordValidator.validate("some_user_name", password) + ); + + } + + public void verifySimilarPasswords(final PasswordValidator passwordValidator) { + for (final String password : SIMILAR_PASSWORDS) + assertEquals( + password, + AbstractConfigurationValidator.ErrorType.SIMILAR_PASSWORD, + passwordValidator.validate("some_user_name", password) + ); + + } + + @Test + public void testRegExpBasedValidation() { + final PasswordValidator passwordValidator = + PasswordValidator.of( + Settings.builder() + .put( + SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, + "(?=.*[A-Z])(?=.*[^a-zA-Z\\\\d])(?=.*[0-9])(?=.*[a-z]).{8,}") + .build() + ); + verifyWeakPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD); + verifyFairPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD); + for (final String password : GOOD_PASSWORDS.subList(0, GOOD_PASSWORDS.size() - 2)) + assertEquals( + password, + AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD, + passwordValidator.validate("some_user_name", password) + ); + for (final String password: GOOD_PASSWORDS.subList(GOOD_PASSWORDS.size() - 2, GOOD_PASSWORDS.size())) + assertEquals( + password, + AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD, + passwordValidator.validate("some_user_name", password) + ); + verifyStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); + verifyVeryStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); + verifySimilarPasswords(passwordValidator); + } + + @Test + public void testMinLength() { + final PasswordValidator passwordValidator = + PasswordValidator.of( + Settings.builder() + .put(SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, 15) + .build() + ); + for (final String password: STRONG_PASSWORDS) { + assertEquals( + AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD, + passwordValidator.validate(password, "some_user_name") + ); + } + + } + + @Test + public void testScoreBasedValidation() { + PasswordValidator passwordValidator = PasswordValidator.of(Settings.EMPTY); + verifyWeakPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD); + verifyFairPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD); + verifyGoodPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD); + verifyStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); + verifyVeryStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); + verifySimilarPasswords(passwordValidator); + + passwordValidator = + PasswordValidator.of( + Settings.builder() + .put( + SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, + PasswordValidator.ScoreStrength.FAIR.name() + ).build()); + + verifyWeakPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD); + verifyFairPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); + verifyGoodPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); + verifyStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); + verifyVeryStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); + verifySimilarPasswords(passwordValidator); + } + +} From 33aebb96781a4988ae14fab2a5e53b17bb99ecf6 Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Tue, 23 May 2023 12:14:03 -0700 Subject: [PATCH 188/369] Fix the `import org.opensearch.core.common.Strings;` (#2781) Signed-off-by: Ryan Liang --- .../dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java | 2 +- .../dlic/auth/ldap/backend/LDAPAuthorizationBackend.java | 2 +- .../amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java | 2 +- .../org/opensearch/security/auditlog/impl/AuditMessage.java | 6 +++--- .../org/opensearch/security/auditlog/sink/WebhookSink.java | 2 +- .../opensearch/security/compliance/ComplianceConfig.java | 2 +- .../opensearch/security/dlic/rest/api/AccountApiAction.java | 2 +- .../security/dlic/rest/api/PatchableResourceApiAction.java | 2 +- .../security/dlic/rest/validation/CredentialsValidator.java | 2 +- .../security/dlic/rest/validation/PasswordValidator.java | 2 +- .../java/org/opensearch/security/filter/SecurityFilter.java | 2 +- .../security/http/HTTPClientCertAuthenticator.java | 2 +- .../opensearch/security/http/HTTPProxyAuthenticator.java | 2 +- .../security/http/proxy/HTTPExtendedProxyAuthenticator.java | 2 +- .../opensearch/security/privileges/PrivilegesEvaluator.java | 2 +- .../org/opensearch/security/securityconf/Migration.java | 2 +- .../opensearch/security/ssl/util/SSLCertificateHelper.java | 2 +- .../java/org/opensearch/security/support/Base64Helper.java | 2 +- 18 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java index 49b030a5ff..9978186f96 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java @@ -32,9 +32,9 @@ import org.opensearch.OpenSearchSecurityException; import org.opensearch.SpecialPermission; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.common.Strings; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; diff --git a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java index 1e7adeb488..68a8a3fba6 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java @@ -74,8 +74,8 @@ import org.opensearch.OpenSearchSecurityException; import org.opensearch.SpecialPermission; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.Strings; import org.opensearch.security.auth.AuthorizationBackend; import org.opensearch.security.ssl.util.SSLConfigConstants; import org.opensearch.security.support.PemKeyReader; diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java index 01d422ea6c..c140dbb6f9 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java @@ -49,8 +49,8 @@ import org.opensearch.OpenSearchSecurityException; import org.opensearch.SpecialPermission; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.Strings; import org.opensearch.security.auth.AuthorizationBackend; import org.opensearch.security.auth.Destroyable; import org.opensearch.security.support.WildcardMatcher; 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 21aee075c5..8931d44690 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java @@ -34,13 +34,13 @@ import org.opensearch.ExceptionsHelper; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.Strings; import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.collect.Tuple; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.common.xcontent.XContentType; import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.common.Strings; import org.opensearch.index.shard.ShardId; import org.opensearch.rest.RestRequest; import org.opensearch.security.auditlog.AuditLog.Operation; @@ -463,7 +463,7 @@ public String getDocId() { @Override public String toString() { try { - return Strings.toString(JsonXContent.contentBuilder().map(getAsMap())); + return org.opensearch.common.Strings.toString(JsonXContent.contentBuilder().map(getAsMap())); } catch (final IOException e) { throw ExceptionsHelper.convertToOpenSearchException(e); } @@ -471,7 +471,7 @@ public String toString() { public String toPrettyString() { try { - return Strings.toString(JsonXContent.contentBuilder().prettyPrint().map(getAsMap())); + return org.opensearch.common.Strings.toString(JsonXContent.contentBuilder().prettyPrint().map(getAsMap())); } catch (final IOException e) { throw ExceptionsHelper.convertToOpenSearchException(e); } diff --git a/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java b/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java index 3205eb1fd6..083df01bbd 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java @@ -43,8 +43,8 @@ import org.apache.hc.core5.ssl.SSLContextBuilder; import org.apache.hc.core5.ssl.TrustStrategy; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.Strings; import org.opensearch.security.auditlog.impl.AuditMessage; import org.opensearch.security.ssl.util.SSLConfigConstants; import org.opensearch.security.support.ConfigConstants; diff --git a/src/main/java/org/opensearch/security/compliance/ComplianceConfig.java b/src/main/java/org/opensearch/security/compliance/ComplianceConfig.java index e823330552..4b0f3079f2 100644 --- a/src/main/java/org/opensearch/security/compliance/ComplianceConfig.java +++ b/src/main/java/org/opensearch/security/compliance/ComplianceConfig.java @@ -54,8 +54,8 @@ import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.Strings; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auditlog.config.AuditConfig; import org.opensearch.security.support.ConfigConstants; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java index 5e4799d655..e5121f939d 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java @@ -23,11 +23,11 @@ import org.opensearch.action.index.IndexResponse; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.Strings; import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.common.Strings; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java index deb56a69c7..796a25fa9f 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java @@ -28,11 +28,11 @@ import org.opensearch.action.index.IndexResponse; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.Strings; import org.opensearch.common.bytes.BytesArray; import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.common.Strings; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestController; import org.opensearch.rest.RestRequest; diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java index a54f1947f0..ff1addc11e 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java @@ -13,12 +13,12 @@ import java.util.Map; -import org.opensearch.common.Strings; import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.compress.NotXContentException; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.common.Strings; import org.opensearch.rest.RestRequest; import org.opensearch.security.ssl.util.Utils; diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java index a59a4e6d40..f5ae9bee49 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java @@ -25,8 +25,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.Strings; import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator.ErrorType; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH; diff --git a/src/main/java/org/opensearch/security/filter/SecurityFilter.java b/src/main/java/org/opensearch/security/filter/SecurityFilter.java index a1c64a565f..7bdd5946d5 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityFilter.java @@ -65,10 +65,10 @@ import org.opensearch.action.support.ActionFilterChain; import org.opensearch.action.update.UpdateRequest; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.logging.LoggerMessageFormat; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.common.util.concurrent.ThreadContext.StoredContext; +import org.opensearch.core.common.logging.LoggerMessageFormat; import org.opensearch.index.reindex.DeleteByQueryRequest; import org.opensearch.index.reindex.UpdateByQueryRequest; import org.opensearch.rest.RestStatus; diff --git a/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java b/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java index 9b13af0ef2..51ff6304b1 100644 --- a/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java @@ -38,9 +38,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.common.Strings; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; import org.opensearch.security.auth.HTTPAuthenticator; diff --git a/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java b/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java index 2819cd24fd..28fb80e0db 100644 --- a/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java @@ -34,9 +34,9 @@ import org.apache.logging.log4j.Logger; import org.opensearch.OpenSearchSecurityException; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.common.Strings; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; import org.opensearch.security.auth.HTTPAuthenticator; diff --git a/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java b/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java index 87cd1ed1d4..d792158fea 100644 --- a/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java @@ -34,9 +34,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.common.Strings; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; import org.opensearch.security.http.HTTPProxyAuthenticator; diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 8f490d180d..36d53b2a9e 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -77,10 +77,10 @@ import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.common.Strings; import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.index.reindex.ReindexAction; import org.opensearch.security.auditlog.AuditLog; diff --git a/src/main/java/org/opensearch/security/securityconf/Migration.java b/src/main/java/org/opensearch/security/securityconf/Migration.java index 2cd9385a8f..3cb111f11c 100644 --- a/src/main/java/org/opensearch/security/securityconf/Migration.java +++ b/src/main/java/org/opensearch/security/securityconf/Migration.java @@ -32,8 +32,8 @@ import java.util.Map.Entry; import java.util.Set; -import org.opensearch.common.Strings; import org.opensearch.common.collect.Tuple; +import org.opensearch.core.common.Strings; import org.opensearch.security.auditlog.config.AuditConfig; import org.opensearch.security.securityconf.impl.AllowlistingSettings; import org.opensearch.security.securityconf.impl.CType; diff --git a/src/main/java/org/opensearch/security/ssl/util/SSLCertificateHelper.java b/src/main/java/org/opensearch/security/ssl/util/SSLCertificateHelper.java index cb693bff08..ff36cc40a8 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLCertificateHelper.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLCertificateHelper.java @@ -34,7 +34,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.common.Strings; +import org.opensearch.core.common.Strings; public class SSLCertificateHelper { diff --git a/src/main/java/org/opensearch/security/support/Base64Helper.java b/src/main/java/org/opensearch/security/support/Base64Helper.java index 0871823357..b20cadfc96 100644 --- a/src/main/java/org/opensearch/security/support/Base64Helper.java +++ b/src/main/java/org/opensearch/security/support/Base64Helper.java @@ -61,7 +61,7 @@ import org.opensearch.OpenSearchException; import org.opensearch.SpecialPermission; -import org.opensearch.common.Strings; +import org.opensearch.core.common.Strings; import org.opensearch.security.user.User; public class Base64Helper { From 1bb2ef14f59e8ef2bcee68e65a9e3ec08be988c5 Mon Sep 17 00:00:00 2001 From: Yaliang Wu Date: Thu, 25 May 2023 11:31:48 -0700 Subject: [PATCH 189/369] add ml model group system index (#2790) Signed-off-by: Yaliang Wu --- tools/install_demo_configuration.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/install_demo_configuration.sh b/tools/install_demo_configuration.sh index 6d6203124a..21174e6f63 100755 --- a/tools/install_demo_configuration.sh +++ b/tools/install_demo_configuration.sh @@ -383,7 +383,7 @@ echo "plugins.security.enable_snapshot_restore_privilege: true" | $SUDO_CMD tee echo "plugins.security.check_snapshot_restore_write_privileges: true" | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null echo 'plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null echo 'plugins.security.system_indices.enabled: true' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null -echo 'plugins.security.system_indices.indices: [".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".ql-datasources", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models"]' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null +echo 'plugins.security.system_indices.indices: [".plugins-ml-model-group", ".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".ql-datasources", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models"]' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null #network.host if $SUDO_CMD grep --quiet -i "^network.host" "$OPENSEARCH_CONF_FILE"; then From a580dfc6629ae5cc783d253076729270660044cd Mon Sep 17 00:00:00 2001 From: zhichao-aws Date: Fri, 26 May 2023 06:38:53 +0800 Subject: [PATCH 190/369] role.yml changes for lron feature (#2789) Signed-off-by: zhichao-aws --- config/roles.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/roles.yml b/config/roles.yml index e4eb3ac535..d03b47ab28 100644 --- a/config/roles.yml +++ b/config/roles.yml @@ -205,6 +205,8 @@ index_management_full_access: - "cluster:admin/opendistro/ism/*" - "cluster:admin/opendistro/rollup/*" - "cluster:admin/opendistro/transform/*" + - "cluster:admin/opensearch/controlcenter/lron/*" + - "cluster:admin/opensearch/notifications/channels/get" - "cluster:admin/opensearch/notifications/feature/publish" index_permissions: - index_patterns: From 9be79bd0281e1fcf9f221ddb8b2a8a3c133ce17a Mon Sep 17 00:00:00 2001 From: Thomas Farr Date: Wed, 31 May 2023 01:47:47 +1200 Subject: [PATCH 191/369] Remove dependency on javax.annotation implementation as OpenSearch now has one. (#2801) This resolves "JAR hell" issues when installing the plugin. Signed-off-by: Thomas Farr --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index ef187e290f..76a68ddfa0 100644 --- a/build.gradle +++ b/build.gradle @@ -346,7 +346,6 @@ task integrationTest(type: Test) { check.dependsOn integrationTest dependencies { - implementation 'jakarta.annotation:jakarta.annotation-api:1.3.5' implementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}" implementation "org.opensearch.client:opensearch-rest-high-level-client:${opensearch_version}" implementation "org.apache.httpcomponents.client5:httpclient5-cache:${versions.httpclient5}" From 4b386719db87b7d7bdc35191d7706f4a3944b2c9 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 30 May 2023 17:19:57 -0400 Subject: [PATCH 192/369] [FEATURE] usage of JWKS with JWT (w/o OpenID connect) (#2808) * [FEATURE] usage of JWKS with JWT (w/o OpenID connect) Signed-off-by: Sebastian Michalski * Change issuer to audience Signed-off-by: Craig Perkins * Switch to OCT_2 and use String instead of var Signed-off-by: Craig Perkins * Only check required issuer and required audience if set Signed-off-by: Craig Perkins * Fix test cases Signed-off-by: Craig Perkins * Remove unused import Signed-off-by: Craig Perkins --------- Signed-off-by: Sebastian Michalski Signed-off-by: Craig Perkins Co-authored-by: Sebastian Michalski --- .../jwt/AbstractHTTPJwtAuthenticator.java | 14 +- ...TTPJwtKeyByOpenIdConnectAuthenticator.java | 12 +- .../auth/http/jwt/keybyoidc/JwtVerifier.java | 24 ++- .../http/jwt/keybyoidc/KeySetRetriever.java | 29 +++- ...wtKeyByOpenIdConnectAuthenticatorTest.java | 149 ++++++++++++++++-- .../http/jwt/keybyoidc/MockIpdServer.java | 4 + ...wtKeyByOpenIdConnectAuthenticatorTest.java | 10 +- .../auth/http/jwt/keybyoidc/TestJwts.java | 30 +++- 8 files changed, 238 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java index 9978186f96..bef819effd 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java @@ -55,6 +55,8 @@ public abstract class AbstractHTTPJwtAuthenticator implements HTTPAuthenticator private final String jwtUrlParameter; private final String subjectKey; private final String rolesKey; + private final String requiredAudience; + private final String requiredIssuer; public static final int DEFAULT_CLOCK_SKEW_TOLERANCE_SECONDS = 30; private final int clockSkewToleranceSeconds ; @@ -66,10 +68,12 @@ public AbstractHTTPJwtAuthenticator(Settings settings, Path configPath) { rolesKey = settings.get("roles_key"); subjectKey = settings.get("subject_key"); clockSkewToleranceSeconds = settings.getAsInt("jwt_clock_skew_tolerance_seconds", DEFAULT_CLOCK_SKEW_TOLERANCE_SECONDS); + requiredAudience = settings.get("required_audience"); + requiredIssuer = settings.get("required_issuer"); try { this.keyProvider = this.initKeyProvider(settings, configPath); - jwtVerifier = new JwtVerifier(keyProvider, clockSkewToleranceSeconds ); + jwtVerifier = new JwtVerifier(keyProvider, clockSkewToleranceSeconds, requiredIssuer, requiredAudience); } catch (Exception e) { log.error("Error creating JWT authenticator. JWT authentication will not work", e); @@ -233,4 +237,12 @@ public boolean reRequestAuthentication(RestChannel channel, AuthCredentials auth return true; } + public String getRequiredAudience() { + return requiredAudience; + } + + public String getRequiredIssuer() { + return requiredIssuer; + } + } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticator.java index 0cede73911..b6738b725b 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticator.java @@ -32,9 +32,15 @@ protected KeyProvider initKeyProvider(Settings settings, Path configPath) throws int refreshRateLimitTimeWindowMs = settings.getAsInt("refresh_rate_limit_time_window_ms", 10000); int refreshRateLimitCount = settings.getAsInt("refresh_rate_limit_count", 10); - - KeySetRetriever keySetRetriever = new KeySetRetriever(settings.get("openid_connect_url"), - getSSLConfig(settings, configPath), settings.getAsBoolean("cache_jwks_endpoint", false)); + String jwksUri = settings.get("jwks_uri"); + + KeySetRetriever keySetRetriever; + if(jwksUri != null && !jwksUri.isBlank()) { + keySetRetriever = + new KeySetRetriever(getSSLConfig(settings, configPath), settings.getAsBoolean("cache_jwks_endpoint", false), jwksUri); + } else { + keySetRetriever = new KeySetRetriever(settings.get("openid_connect_url"), getSSLConfig(settings, configPath), settings.getAsBoolean("cache_jwks_endpoint", false)); + } keySetRetriever.setRequestTimeoutMs(idpRequestTimeoutMs); diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java index 99074ab233..a337224cdc 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java @@ -33,10 +33,14 @@ public class JwtVerifier { private final KeyProvider keyProvider; private final int clockSkewToleranceSeconds; - - public JwtVerifier(KeyProvider keyProvider, int clockSkewToleranceSeconds ) { + private final String requiredIssuer; + private final String requiredAudience; + + public JwtVerifier(KeyProvider keyProvider, int clockSkewToleranceSeconds, String requiredIssuer, String requiredAudience) { this.keyProvider = keyProvider; this.clockSkewToleranceSeconds = clockSkewToleranceSeconds; + this.requiredIssuer = requiredIssuer; + this.requiredAudience = requiredAudience; } public JwtToken getVerifiedJwtToken(String encodedJwt) throws BadCredentialsException { @@ -106,12 +110,26 @@ private JwsSignatureVerifier getInitializedSignatureVerifier(JsonWebKey key, Jwt } } - private void validateClaims(JwtToken jwt) throws BadCredentialsException, JwtException { + private void validateClaims(JwtToken jwt) throws JwtException { JwtClaims claims = jwt.getClaims(); if (claims != null) { JwtUtils.validateJwtExpiry(claims, clockSkewToleranceSeconds, false); JwtUtils.validateJwtNotBefore(claims, clockSkewToleranceSeconds, false); + validateRequiredAudienceAndIssuer(claims); + } + } + + private void validateRequiredAudienceAndIssuer(JwtClaims claims) { + String audience = claims.getAudience(); + String issuer = claims.getIssuer(); + + if (!Strings.isNullOrEmpty(requiredAudience) && !requiredAudience.equals(audience)) { + throw new JwtException("Invalid audience"); + } + + if (!Strings.isNullOrEmpty(requiredIssuer) && !requiredIssuer.equals(issuer)) { + throw new JwtException("Invalid issuer"); } } } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java index 50be122aec..f56c0dd90c 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.util.concurrent.TimeUnit; +import joptsimple.internal.Strings; import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys; import org.apache.cxf.rs.security.jose.jwk.JwkUtils; import org.apache.hc.client5.http.cache.HttpCacheContext; @@ -54,15 +55,20 @@ public class KeySetRetriever implements KeySetProvider { private int oidcCacheModuleResponses = 0; private long oidcRequests = 0; private long lastCacheStatusLog = 0; + private String jwksUri; KeySetRetriever(String openIdConnectEndpoint, SSLConfig sslConfig, boolean useCacheForOidConnectEndpoint) { this.openIdConnectEndpoint = openIdConnectEndpoint; this.sslConfig = sslConfig; - if (useCacheForOidConnectEndpoint) { - cacheConfig = CacheConfig.custom().setMaxCacheEntries(10).setMaxObjectSize(1024L * 1024L).build(); - oidcHttpCacheStorage = new BasicHttpCacheStorage(cacheConfig); - } + configureCache(useCacheForOidConnectEndpoint); + } + + KeySetRetriever(SSLConfig sslConfig, boolean useCacheForOidConnectEndpoint, String jwksUri) { + this.jwksUri = jwksUri; + this.sslConfig = sslConfig; + + configureCache(useCacheForOidConnectEndpoint); } public JsonWebKeys get() throws AuthenticatorUnavailableException { @@ -101,6 +107,14 @@ public JsonWebKeys get() throws AuthenticatorUnavailableException { String getJwksUri() throws AuthenticatorUnavailableException { + if (!Strings.isNullOrEmpty(jwksUri)) { + return jwksUri; + } + + if (Strings.isNullOrEmpty(openIdConnectEndpoint)) { + throw new AuthenticatorUnavailableException("Either openid_connect_url or jwks_uri must be configured for OIDC Authentication backend"); + } + try (CloseableHttpClient httpClient = createHttpClient(oidcHttpCacheStorage)) { HttpGet httpGet = new HttpGet(openIdConnectEndpoint); @@ -204,6 +218,13 @@ private CloseableHttpClient createHttpClient(HttpCacheStorage httpCacheStorage) return builder.build(); } + private void configureCache(boolean useCacheForOidConnectEndpoint) { + if (useCacheForOidConnectEndpoint) { + cacheConfig = CacheConfig.custom().setMaxCacheEntries(10).setMaxObjectSize(1024L * 1024L).build(); + oidcHttpCacheStorage = new BasicHttpCacheStorage(cacheConfig); + } + } + public int getOidcCacheHits() { return oidcCacheHits; } diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java index 6419e84891..3bcaba970f 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java @@ -18,6 +18,7 @@ import org.junit.BeforeClass; import org.junit.Test; +import org.opensearch.OpenSearchSecurityException; import org.opensearch.common.settings.Settings; import org.opensearch.security.user.AuthCredentials; import org.opensearch.security.util.FakeRestRequest; @@ -44,7 +45,11 @@ public static void tearDown() { @Test public void basicTest() { - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); @@ -55,12 +60,110 @@ public void basicTest() { Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(3, creds.getAttributes().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + } + + + @Test + public void jwksUriTest() { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( + ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()), null); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); + Assert.assertEquals(0, creds.getBackendRoles().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + } + + @Test + public void jwksMissingRequiredIssuerInClaimTest() { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( + ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_NO_ISSUER_OCT_1), new HashMap<>()), null); + + Assert.assertNull(creds); + } + + @Test + public void jwksNotMatchingRequiredIssuerInClaimTest() { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_issuer", "Wrong Issuer") + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( + ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()), null); + + Assert.assertNull(creds); + } + + @Test + public void jwksMissingRequiredAudienceInClaimTest() { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( + ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_NO_AUDIENCE_OCT_1), new HashMap<>()), null); + + Assert.assertNull(creds); + } + + @Test + public void jwksNotMatchingRequiredAudienceInClaimTest() { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_audience", "Wrong Audience") + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( + ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()), null); + + Assert.assertNull(creds); + } + + @Test + public void jwksUriMissingTest() { + var exception = Assert.assertThrows(Exception.class, () -> { + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(Settings.builder().build(), null); + jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap<>()), + null); + }); + + Assert.assertEquals("Authentication backend failed", exception.getMessage()); + Assert.assertEquals(OpenSearchSecurityException.class, exception.getClass()); } @Test public void testEscapeKid() { - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); @@ -71,12 +174,16 @@ public void testEscapeKid() { Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(3, creds.getAttributes().size()); + Assert.assertEquals(4, creds.getAttributes().size()); } @Test public void bearerTest() { - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); @@ -89,13 +196,17 @@ public void bearerTest() { Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(3, creds.getAttributes().size()); + Assert.assertEquals(4, creds.getAttributes().size()); } @Test public void testRoles() throws Exception { - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("roles_key", TestJwts.ROLES_CLAIM).build(); + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("roles_key", TestJwts.ROLES_CLAIM) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); @@ -126,6 +237,8 @@ public void testExpInSkew() throws Exception { Settings settings = Settings.builder() .put("openid_connect_url", mockIdpServer.getDiscoverUri()) .put("jwt_clock_skew_tolerance_seconds", "10") + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) .build(); HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); @@ -149,6 +262,8 @@ public void testNbf() throws Exception { Settings settings = Settings.builder() .put("openid_connect_url", mockIdpServer.getDiscoverUri()) .put("jwt_clock_skew_tolerance_seconds", "0") + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) .build(); HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); @@ -172,6 +287,8 @@ public void testNbfInSkew() throws Exception { Settings settings = Settings.builder() .put("openid_connect_url", mockIdpServer.getDiscoverUri()) .put("jwt_clock_skew_tolerance_seconds", "10") + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) .build(); HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); @@ -192,7 +309,11 @@ public void testNbfInSkew() throws Exception { @Test public void testRS256() throws Exception { - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); @@ -203,7 +324,7 @@ public void testRS256() throws Exception { Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(3, creds.getAttributes().size()); + Assert.assertEquals(4, creds.getAttributes().size()); } @Test @@ -221,7 +342,11 @@ public void testBadSignature() throws Exception { @Test public void testPeculiarJsonEscaping() { - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); @@ -233,7 +358,7 @@ public void testPeculiarJsonEscaping() { Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(3, creds.getAttributes().size()); + Assert.assertEquals(4, creds.getAttributes().size()); } } diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java index 21a9d239c3..35e51919cb 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java @@ -118,6 +118,10 @@ public String getDiscoverUri() { return uri + CTX_DISCOVER; } + public String getJwksUri() { + return uri + CTX_KEYS; + } + public int getPort() { return port; } diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest.java index 4400dd1cdc..4285ee07ae 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest.java @@ -40,7 +40,7 @@ public void basicTest() throws Exception { Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(3, creds.getAttributes().size()); + Assert.assertEquals(4, creds.getAttributes().size()); } finally { try { @@ -92,7 +92,7 @@ public void noAlgTest() throws Exception { Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(3, creds.getAttributes().size()); + Assert.assertEquals(4, creds.getAttributes().size()); } finally { try { mockIdpServer.close(); @@ -145,7 +145,7 @@ public void keyExchangeTest() throws Exception { Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(3, creds.getAttributes().size()); + Assert.assertEquals(4, creds.getAttributes().size()); creds = jwtAuth.extractCredentials( new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_2), @@ -170,7 +170,7 @@ public void keyExchangeTest() throws Exception { Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(3, creds.getAttributes().size()); + Assert.assertEquals(4, creds.getAttributes().size()); } finally { try { @@ -194,7 +194,7 @@ public void keyExchangeTest() throws Exception { Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(3, creds.getAttributes().size()); + Assert.assertEquals(4, creds.getAttributes().size()); } finally { try { diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java index 217e64926c..7d0e91561d 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java @@ -33,12 +33,25 @@ class TestJwts { static final String MCCOY_SUBJECT = "Leonard McCoy"; - static final JwtToken MC_COY = create(MCCOY_SUBJECT, TEST_AUDIENCE, ROLES_CLAIM, TEST_ROLES_STRING); + static final String TEST_ISSUER = "TestIssuer"; - static final JwtToken MC_COY_EXPIRED = create(MCCOY_SUBJECT, TEST_AUDIENCE, ROLES_CLAIM, TEST_ROLES_STRING, JwtConstants.CLAIM_EXPIRY, 10); + static final JwtToken MC_COY = create(MCCOY_SUBJECT, TEST_AUDIENCE, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); + + static final JwtToken MC_COY_2 = create(MCCOY_SUBJECT, TEST_AUDIENCE, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); + + static final JwtToken MC_COY_NO_AUDIENCE = create(MCCOY_SUBJECT, null, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); + + static final JwtToken MC_COY_NO_ISSUER = create(MCCOY_SUBJECT, TEST_AUDIENCE, null, ROLES_CLAIM, TEST_ROLES_STRING); + + static final JwtToken MC_COY_EXPIRED = create(MCCOY_SUBJECT, TEST_AUDIENCE, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING, JwtConstants.CLAIM_EXPIRY, 10); static final String MC_COY_SIGNED_OCT_1 = createSigned(MC_COY, TestJwk.OCT_1); + static final String MC_COY_SIGNED_OCT_2 = createSigned(MC_COY_2, TestJwk.OCT_2); + + static final String MC_COY_SIGNED_NO_AUDIENCE_OCT_1 = createSigned(MC_COY_NO_AUDIENCE, TestJwk.OCT_1); + static final String MC_COY_SIGNED_NO_ISSUER_OCT_1 = createSigned(MC_COY_NO_ISSUER, TestJwk.OCT_1); + static final String MC_COY_SIGNED_OCT_1_INVALID_KID = createSigned(MC_COY, TestJwk.FORWARD_SLASH_KID_OCT_1); static final String MC_COY_SIGNED_RSA_1 = createSigned(MC_COY, TestJwk.RSA_1); @@ -57,11 +70,16 @@ static class PeculiarEscaping { static final String MC_COY_SIGNED_RSA_1 = createSignedWithPeculiarEscaping(MC_COY, TestJwk.RSA_1); } - static JwtToken create(String subject, String audience, Object... moreClaims) { + static JwtToken create(String subject, String audience, String issuer, Object... moreClaims) { JwtClaims claims = new JwtClaims(); claims.setSubject(subject); - claims.setAudience(audience); + if (audience != null) { + claims.setAudience(audience); + } + if (issuer != null) { + claims.setIssuer(issuer); + } if (moreClaims != null) { for (int i = 0; i < moreClaims.length; i += 2) { @@ -108,8 +126,8 @@ static String createSignedWithPeculiarEscaping(JwtToken baseJwt, JsonWebKey jwk) static String createMcCoySignedOct1(long nbf, long exp) { JwtToken jwt_token = create( - MCCOY_SUBJECT, TEST_AUDIENCE, - ROLES_CLAIM, TEST_ROLES_STRING, + MCCOY_SUBJECT, TEST_AUDIENCE, + TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING, JwtConstants.CLAIM_NOT_BEFORE, nbf, JwtConstants.CLAIM_EXPIRY, exp); From 5f9c90b60abcda0953e663276225cc57d660a96f Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Wed, 31 May 2023 09:13:05 -0400 Subject: [PATCH 193/369] Update non java files (#2812) * Isolate spotless config changes Signed-off-by: Craig Perkins * update dlic/auth package style Signed-off-by: Stephen Crawford * Update formatterConfig.xml --------- Signed-off-by: Craig Perkins Signed-off-by: Stephen Crawford Co-authored-by: Craig Perkins --- .github/actions/create-bwc-build/action.yaml | 2 +- .../action.yml | 2 +- .github/workflows/delete_backport_branch.yml | 4 +- .github/workflows/plugin_install.yml | 2 +- CONTRIBUTING.md | 2 +- DEVELOPER_GUIDE.md | 6 +- DEVELOPING_WITH_DOCKER.md | 34 +- README.md | 12 +- TRIAGING.md | 4 +- build.gradle | 15 +- formatter/formatterConfig.xml | 362 ++++++++++++++++++ gradle/formatting.gradle | 36 ++ legacy/securityconfig_v6/action_groups.yml | 2 +- legacy/securityconfig_v6/config.yml | 36 +- legacy/securityconfig_v6/internal_users.yml | 4 +- legacy/securityconfig_v6/roles.yml | 14 +- legacy/securityconfig_v6/roles_mapping.yml | 4 +- .../resources/static_config/static_roles.yml | 9 +- src/test/resources/auditlog/data2.json | 2 +- src/test/resources/auditlog/data3.json | 4 +- .../configuration_wrong_endpoint_names.yml | 2 +- .../endpoints/routing/configuration_valid.yml | 4 +- .../configuration_wrong_endpoint_names.yml | 2 +- .../configuration_wrong_endpoint_types.yml | 2 +- .../auditlog/endpoints/routing/routing.yml | 2 +- .../sink/configuration_all_variants.yml | 4 +- .../sink/configuration_no_default.yml | 4 +- .../resources/config_auth_ratelimiting.yml | 2 +- src/test/resources/config_ldap.yml | 2 +- src/test/resources/data2.json | 2 +- src/test/resources/data3.json | 4 +- src/test/resources/dlsfls/doc1.json | 8 +- src/test/resources/dlsfls/flsquery.json | 10 +- src/test/resources/dlsfls/flsquery2.json | 4 +- .../resources/dlsfls/internal_users_tlq.yml | 2 +- .../dlsfls/masked_field_mapping.json | 6 +- src/test/resources/dlsfls/roles_tlq.yml | 2 +- src/test/resources/ldap/test1.yml | 4 +- .../securityconfig_v6/action_groups.yml | 2 +- .../legacy/securityconfig_v6/config.yml | 36 +- .../securityconfig_v6/internal_users.yml | 4 +- .../securityconfig_v6/migration/roles.yml | 10 +- .../legacy/securityconfig_v6/roles.yml | 14 +- .../securityconfig_v6/roles_mapping.yml | 4 +- .../multitenancy/config_basic_auth.yml | 2 +- src/test/resources/restapi/audit.yml | 2 +- .../resources/restapi/security_config.json | 22 +- .../restapi/users_key_not_quoted.json | 1 + src/test/resources/roles_invalidxcontent.yml | 2 +- 49 files changed, 553 insertions(+), 168 deletions(-) create mode 100644 formatter/formatterConfig.xml create mode 100644 gradle/formatting.gradle diff --git a/.github/actions/create-bwc-build/action.yaml b/.github/actions/create-bwc-build/action.yaml index b6ee3d5478..fcfa612a7d 100644 --- a/.github/actions/create-bwc-build/action.yaml +++ b/.github/actions/create-bwc-build/action.yaml @@ -5,7 +5,7 @@ inputs: plugin-branch: description: 'The branch of the plugin that should be built, e.g "2.2", "1.x"' required: true - + outputs: built-version: description: 'The version of OpenSearch that was associated with this branch' diff --git a/.github/actions/start-opensearch-with-one-plugin/action.yml b/.github/actions/start-opensearch-with-one-plugin/action.yml index b562851b0c..fa5681c422 100644 --- a/.github/actions/start-opensearch-with-one-plugin/action.yml +++ b/.github/actions/start-opensearch-with-one-plugin/action.yml @@ -70,7 +70,7 @@ runs: # Run any configuration scripts - name: Run Setup Script for Linux if: ${{ runner.os == 'Linux' && inputs.setup-script-name != '' }} - run: | + run: | echo "running linux setup" chmod +x ./${{ inputs.setup-script-name }}.sh ./${{ inputs.setup-script-name }}.sh diff --git a/.github/workflows/delete_backport_branch.yml b/.github/workflows/delete_backport_branch.yml index 35417b46b3..9964fe1ec8 100644 --- a/.github/workflows/delete_backport_branch.yml +++ b/.github/workflows/delete_backport_branch.yml @@ -1,9 +1,9 @@ name: Delete merged branch of the backport PRs -on: +on: pull_request: types: - closed - + jobs: delete-branch: runs-on: ubuntu-latest diff --git a/.github/workflows/plugin_install.yml b/.github/workflows/plugin_install.yml index 75289d560a..301c193e11 100644 --- a/.github/workflows/plugin_install.yml +++ b/.github/workflows/plugin_install.yml @@ -38,7 +38,7 @@ jobs: if: ${{ runner.os == 'Linux' }} run: | cat > setup.sh <<'EOF' - chmod +x ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/plugins/${{ env.PLUGIN_NAME }}/tools/install_demo_configuration.sh + chmod +x ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/plugins/${{ env.PLUGIN_NAME }}/tools/install_demo_configuration.sh /bin/bash -c "yes | ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/plugins/${{ env.PLUGIN_NAME }}/tools/install_demo_configuration.sh" EOF diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a7e7d89e9a..371de48a2e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ OpenSearch is a community project that is built and maintained by people just like **you**. [This document](https://github.com/opensearch-project/.github/blob/main/CONTRIBUTING.md) explains how you can contribute to this and related projects. -Visit the following link(s) for more information on specific practices: +Visit the following link(s) for more information on specific practices: - [Triaging](./TRIAGING.md) diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 5168d01a46..358b2eac14 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -48,9 +48,9 @@ The `curl localhost:9200` call should succeed again. Kill the server with `Ctrl+ >Worth noting:\ > The version of OpenSearch and the security plugin must match as there is an explicit version check at startup. This can be a bit confusing as, for example, at the time of writing this guide, the `main` branch of this security plugin builds version `3.0.0.0-SNAPSHOT` compatible with OpenSearch `3.0.0`. Check the expected compatible version in `build.gradle` file [here](https://github.com/opensearch-project/security/blob/main/build.gradle) and make sure you get the correct branch from OpenSearch when building that project. -> +> > The line to look for: `opensearch_version = System.getProperty("opensearch.version", "x")` -> +> > Alternatively, you can find the compatible version of OpenSearch by running in project root folder > ``` > ./gradlew properties -q | grep -E '^version:' | awk '{print $2}' @@ -164,7 +164,7 @@ Checkstyle enforces several rules within this codebase. Sometimes it will be nec *Execute Checkstyle* ``` -./gradlew checkstyleMain checkstyleTest +./gradlew checkstyleMain checkstyleTest ``` *Example violation* diff --git a/DEVELOPING_WITH_DOCKER.md b/DEVELOPING_WITH_DOCKER.md index a0ba045846..6616e11313 100644 --- a/DEVELOPING_WITH_DOCKER.md +++ b/DEVELOPING_WITH_DOCKER.md @@ -1,40 +1,40 @@ # Developing with Docker -Docker is a powerful tool that can be used to quickly spin up an OpenSearch cluster. When you follow the steps to run [OpenSearch with Docker](https://opensearch.org/docs/latest/install-and-configure/install-opensearch/docker/), you will find the Security Plugin already included in the basic distribution. +Docker is a powerful tool that can be used to quickly spin up an OpenSearch cluster. When you follow the steps to run [OpenSearch with Docker](https://opensearch.org/docs/latest/install-and-configure/install-opensearch/docker/), you will find the Security Plugin already included in the basic distribution. - [Developing with Docker](#developing-with-docker) - [Configuring Security](#configuring-security) - [Mounting Local Volumes](#mounting-local-volumes) - [Example docker-compose](#example-docker-compose) - -## Configuring Security -By default, the Docker installation of OpenSearch does not enable the Security plugin. In order to enable Security development, you will need set `DISABLE_SECURITY_PLUGIN=false`, as well as change `DISABLE_INSTALL_DEMO_CONFIG` and `DISABLE_SECURITY_DASHBOARDS_PLUGIN`. This will install the demo certificates, and allow you to develop with realistic Security configurations. An example of a completely configured docker-compose file is shown below. +## Configuring Security + +By default, the Docker installation of OpenSearch does not enable the Security plugin. In order to enable Security development, you will need set `DISABLE_SECURITY_PLUGIN=false`, as well as change `DISABLE_INSTALL_DEMO_CONFIG` and `DISABLE_SECURITY_DASHBOARDS_PLUGIN`. This will install the demo certificates, and allow you to develop with realistic Security configurations. An example of a completely configured docker-compose file is shown below. > Warning: You should never use the demo certificates for a production environment. Instead, you will need to follow the steps on [configuring security](https://opensearch.org/docs/latest/security/configuration/index/) before using the cluster for production. -### Mounting Local Volumes +### Mounting Local Volumes -In order to test development changes with an OpenSearch Docker-installation, you will need to mount the volumes in your docker-compose file. +In order to test development changes with an OpenSearch Docker-installation, you will need to mount the volumes in your docker-compose file. -To update your cluster to have local changes, follow these steps: +To update your cluster to have local changes, follow these steps: 1. First you will need to make changes in your local `opensearch-project/security` repository. For this example, assume your fork is cloned into a directory called `security`. -2. After you make changes to your cloned repository, you will need to run `./gradlew assemble`. This will create a `.jar` file you can mount into the Docker container. The file will be located at `./security/build/distributions/opensearch-security-.0-SNAPSHOT.jar`, where the `` field is simply the OpenSearch distribution. -3. You will then need to navigate to your `docker-compose.yml` file where you are running you OpenSearch cluster from. For this example, let us assume this is in another directory called `opensearch-docker`. -4. Modify the compose file, so that in the `volumes:` section of each node configuration (the default configuration will have `opensearch-node1` and `opensearch-node2`), you have a new line which reads `~/security/build/distributions/opensearch-security-.0-SNAPSHOT.jar:/usr/share/opensearch/plugins/opensearch-security/opensearch-security-.0.jar`. This line should be added to the volumes section of all nodes in the compose file. You will not need to add it to the `opensearch-dashboards` section. -5. You can now restart the Docker container by running `docker-compose down -v` and `docker-compose up`. Your changes will now be live in the OpenSearch cluster instance. +2. After you make changes to your cloned repository, you will need to run `./gradlew assemble`. This will create a `.jar` file you can mount into the Docker container. The file will be located at `./security/build/distributions/opensearch-security-.0-SNAPSHOT.jar`, where the `` field is simply the OpenSearch distribution. +3. You will then need to navigate to your `docker-compose.yml` file where you are running you OpenSearch cluster from. For this example, let us assume this is in another directory called `opensearch-docker`. +4. Modify the compose file, so that in the `volumes:` section of each node configuration (the default configuration will have `opensearch-node1` and `opensearch-node2`), you have a new line which reads `~/security/build/distributions/opensearch-security-.0-SNAPSHOT.jar:/usr/share/opensearch/plugins/opensearch-security/opensearch-security-.0.jar`. This line should be added to the volumes section of all nodes in the compose file. You will not need to add it to the `opensearch-dashboards` section. +5. You can now restart the Docker container by running `docker-compose down -v` and `docker-compose up`. Your changes will now be live in the OpenSearch cluster instance. -### Example docker-compose +### Example docker-compose -This is an example of a completely configured docker-compose file for a local installation of the 2.5.0 version of OpenSearch. +This is an example of a completely configured docker-compose file for a local installation of the 2.5.0 version of OpenSearch. ``` version: '3' services: opensearch-node1: - image: opensearchstaging/opensearch:2.5.0 # This is a image of the 2.5.0 distribution + image: opensearchstaging/opensearch:2.5.0 # This is a image of the 2.5.0 distribution environment: - cluster.name=opensearch-cluster - node.name=opensearch-node1 @@ -58,7 +58,7 @@ services: # - ./config/opensearch.yml:/usr/share/opensearch/config/opensearch.yml # These paths are relative to the location of the docker-compose file # - ./config/esnode.pem:/usr/share/opensearch/config/esnode.pem # - ./config/esnode-key.pem:/usr/share/opensearch/config/esnode-key.pem - # - ./config/root-ca.pem:/usr/share/opensearch/config/root-ca.pem + # - ./config/root-ca.pem:/usr/share/opensearch/config/root-ca.pem # - ./config/opensearch-security/audit.yml:/usr/share/opensearch/config/opensearch-security/audit.yml # - ./config/opensearch-security/tenants.yml:/usr/share/opensearch/config/opensearch-security/tenants.yml # - /OpenSearch-Snapshots:/mnt/snapshots # This is where your snapshots would be stored @@ -86,8 +86,8 @@ services: # - ./config/root-ca.pem:/usr/share/opensearch/config/root-ca.pem # - ./config/opensearch-security/audit.yml:/usr/share/opensearch/config/opensearch-security/audit.yml # - ./config/opensearch-security/tenants.yml:/usr/share/opensearch/config/opensearch-security/tenants.yml - # - /OpenSearch-Snapshots:/mnt/snapshots - # - /security/build/distributions/opensearch-security-2.5.0.0-SNAPSHOT.jar:/usr/share/opensearch/plugins/opensearch-security/opensearch-security-2.5.0.0.jar + # - /OpenSearch-Snapshots:/mnt/snapshots + # - /security/build/distributions/opensearch-security-2.5.0.0-SNAPSHOT.jar:/usr/share/opensearch/plugins/opensearch-security/opensearch-security-2.5.0.0.jar networks: - opensearch-net opensearch-dashboards: diff --git a/README.md b/README.md index b9ca3b80da..5c89f5d72d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -[![CI](https://github.com/opensearch-project/security/workflows/CI/badge.svg?branch=main)](https://github.com/opensearch-project/security/actions) [![](https://img.shields.io/github/issues/opensearch-project/security/untriaged?labelColor=red)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"untriaged") [![](https://img.shields.io/github/issues/opensearch-project/security/security%20vulnerability?labelColor=red)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"security%20vulnerability") [![](https://img.shields.io/github/issues/opensearch-project/security)](https://github.com/opensearch-project/security/issues) [![](https://img.shields.io/github/issues-pr/opensearch-project/security)](https://github.com/opensearch-project/security/pulls) +[![CI](https://github.com/opensearch-project/security/workflows/CI/badge.svg?branch=main)](https://github.com/opensearch-project/security/actions) [![](https://img.shields.io/github/issues/opensearch-project/security/untriaged?labelColor=red)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"untriaged") [![](https://img.shields.io/github/issues/opensearch-project/security/security%20vulnerability?labelColor=red)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"security%20vulnerability") [![](https://img.shields.io/github/issues/opensearch-project/security)](https://github.com/opensearch-project/security/issues) [![](https://img.shields.io/github/issues-pr/opensearch-project/security)](https://github.com/opensearch-project/security/pulls) [![](https://img.shields.io/codecov/c/gh/opensearch-project/security)](https://app.codecov.io/gh/opensearch-project/security) [![](https://img.shields.io/github/issues/opensearch-project/security/v2.4.0)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"v2.4.0") [![](https://img.shields.io/github/issues/opensearch-project/security/v3.0.0)](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3A"v3.0.0") [![Slack](https://img.shields.io/badge/Slack-4A154B?&logo=slack&logoColor=white)](https://opensearch.slack.com/archives/C051Y637FKK) -## Announcement: The Slack workspace is live! Please join the [conversation](https://opensearch.slack.com/archives/C051Y637FKK). +## Announcement: The Slack workspace is live! Please join the [conversation](https://opensearch.slack.com/archives/C051Y637FKK). @@ -37,7 +37,7 @@ OpenSearch Security is a plugin for OpenSearch that offers encryption, authentic * Full data in transit encryption * Node-to-node encryption * Certificate revocation lists -* Hot Certificate renewal +* Hot Certificate renewal ### Authentication * Internal user database @@ -60,7 +60,7 @@ OpenSearch Security is a plugin for OpenSearch that offers encryption, authentic * REST management API ### Audit/Compliance logging -* Audit logging +* Audit logging * Compliance logging for GDPR, HIPAA, PCI, SOX and ISO compliance ### OpenSearch Dashboards multi-tenancy @@ -126,7 +126,7 @@ sequenceDiagram participant OpenSearch participant SecurityPlugin participant Cluster as Plugin - + Client->>OpenSearch: Request OpenSearch->>SecurityPlugin: Request SecurityPlugin->>SecurityPlugin: Add Auth information to request context @@ -188,7 +188,7 @@ If you discover a potential security issue in this project we ask that you notif ## License -This code is licensed under the Apache 2.0 License. +This code is licensed under the Apache 2.0 License. ## Copyright diff --git a/TRIAGING.md b/TRIAGING.md index 2c4ea32fdf..bb61779a7c 100644 --- a/TRIAGING.md +++ b/TRIAGING.md @@ -20,7 +20,7 @@ If you have an issue you'd like to bring forth please consider getting a link to ### Is there an agenda for each week? -Meetings are lightly structured as follows: +Meetings are lightly structured as follows: 1. Announcements: If there are any announcements to be made they will happen at the start of the meeting. 2. Review of new issues: The meetings always start with reviewing all untriaged [issues](https://github.com/search?q=label%3Auntriaged+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=created&o=desc) for the security and security-dashboards repositories. @@ -53,7 +53,7 @@ There you can find answers to many common questions as well as speak with implem ### What if my issue is critical to OpenSearch operations, do I have to wait for the weekly meeting for it to be addressed? -All new issues for the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Auntriaged) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aissue+is%3Aopen+-label%3Atriaged) repo are reviewed daily to check for critical issues which require immediate triaging. If an issue relates to a severe concern for OpenSearch operation, it will be triaged by a maintainer mid-week. You can still come to discuss an issue at the following meeting even if it has already been triaged during the week. +All new issues for the [security](https://github.com/opensearch-project/security/issues?q=is%3Aissue+is%3Aopen+label%3Auntriaged) repo and [security-dashboards](https://github.com/opensearch-project/security-dashboards-plugin/issues?q=is%3Aissue+is%3Aopen+-label%3Atriaged) repo are reviewed daily to check for critical issues which require immediate triaging. If an issue relates to a severe concern for OpenSearch operation, it will be triaged by a maintainer mid-week. You can still come to discuss an issue at the following meeting even if it has already been triaged during the week. ### Is this where I should bring up potential security vulnerabilities? diff --git a/build.gradle b/build.gradle index 76a68ddfa0..bd2eba30ba 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,6 @@ */ -import com.diffplug.gradle.spotless.JavaExtension import org.opensearch.gradle.test.RestIntegTestTask buildscript { @@ -70,23 +69,11 @@ apply plugin: 'opensearch.opensearchplugin' apply plugin: 'opensearch.pluginzip' apply plugin: 'opensearch.rest-test' apply plugin: 'opensearch.testclusters' +apply from: 'gradle/formatting.gradle' licenseFile = rootProject.file('LICENSE.txt') noticeFile = rootProject.file('NOTICE.txt') -spotless { - java { - // note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports - importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') - targetExclude('src/integrationTest/**') - } - format("integrationTest", JavaExtension) { - target('src/integrationTest/java/**/*.java') - importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') - indentWithTabs(4) - } -} - spotbugs { includeFilter = file('spotbugs-include.xml') } diff --git a/formatter/formatterConfig.xml b/formatter/formatterConfig.xml new file mode 100644 index 0000000000..713e55274d --- /dev/null +++ b/formatter/formatterConfig.xml @@ -0,0 +1,362 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/formatting.gradle b/gradle/formatting.gradle new file mode 100644 index 0000000000..1851438039 --- /dev/null +++ b/gradle/formatting.gradle @@ -0,0 +1,36 @@ +allprojects { + project.apply plugin: "com.diffplug.spotless" + spotless { + java { + // Normally this isn't necessary, but we have Java sources in + // non-standard places + target '*/com/amazon/dlic/auth/**/*.java' + + removeUnusedImports() + eclipse().configFile rootProject.file('formatter/formatterConfig.xml') + trimTrailingWhitespace() + endWithNewline(); + + // note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports + importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') + + custom 'Refuse wildcard imports', { + // Wildcard imports can't be resolved; fail the build + if (it =~ /\s+import .*\*;/) { + throw new AssertionError("Do not use wildcard imports. 'spotlessApply' cannot resolve this issue.") + } + } + + // See DEVELOPER_GUIDE.md for details of when to enable this. + if (System.getProperty('spotless.paddedcell') != null) { + paddedCell() + } + } + format 'misc', { + target '*.md', '*.gradle', '**/*.json', '**/*.yaml', '**/*.yml', '**/*.svg' + + trimTrailingWhitespace() + endWithNewline() + } + } +} diff --git a/legacy/securityconfig_v6/action_groups.yml b/legacy/securityconfig_v6/action_groups.yml index 14c1b3082f..3faa4c5e31 100644 --- a/legacy/securityconfig_v6/action_groups.yml +++ b/legacy/securityconfig_v6/action_groups.yml @@ -128,7 +128,7 @@ CLUSTER_COMPOSITE_OPS: - "indices:admin/aliases*" - "indices:data/write/reindex" - CLUSTER_COMPOSITE_OPS_RO - + MANAGE_SNAPSHOTS: readonly: true permissions: diff --git a/legacy/securityconfig_v6/config.yml b/legacy/securityconfig_v6/config.yml index 15d5ee9973..d867a72200 100644 --- a/legacy/securityconfig_v6/config.yml +++ b/legacy/securityconfig_v6/config.yml @@ -1,14 +1,14 @@ # This is the main OpenSearch Security configuration file where authentication # and authorization is defined. -# +# # You need to configure at least one authentication domain in the authc of this file. -# An authentication domain is responsible for extracting the user credentials from -# the request and for validating them against an authentication backend like Active Directory for example. +# An authentication domain is responsible for extracting the user credentials from +# the request and for validating them against an authentication backend like Active Directory for example. # -# If more than one authentication domain is configured the first one which succeeds wins. +# If more than one authentication domain is configured the first one which succeeds wins. # If all authentication domains fail then the request is unauthenticated. # In this case an exception is thrown and/or the HTTP status is set to 401. -# +# # After authentication authorization (authz) will be applied. There can be zero or more authorizers which collect # the roles from a given backend for the authenticated user. # @@ -21,18 +21,18 @@ # For HTTP it is possible to allow anonymous authentication. If that is the case then the HTTP authenticators try to # find user credentials in the HTTP request. If credentials are found then the user gets regularly authenticated. # If none can be found the user will be authenticated as an "anonymous" user. This user has always the username "opendistro_security_anonymous" -# and one role named "opendistro_security_anonymous_backendrole". +# and one role named "opendistro_security_anonymous_backendrole". # If you enable anonymous authentication all HTTP authenticators will not challenge. -# +# # # Note: If you define more than one HTTP authenticators make sure to put non-challenging authenticators like "proxy" or "clientcert" -# first and the challenging one last. +# first and the challenging one last. # Because it's not possible to challenge a client with two different authentication methods (for example # Kerberos and Basic) only one can have the challenge flag set to true. You can cope with this situation # by using pre-authentication, e.g. sending a HTTP Basic authentication header in the request. # # Default value of the challenge flag is true. -# +# # # HTTP # basic (challenging) @@ -78,7 +78,7 @@ opendistro_security: ###### and here https://tools.ietf.org/html/rfc7239 ###### and https://tomcat.apache.org/tomcat-8.0-doc/config/valve.html#Remote_IP_Valve authc: - kerberos_auth_domain: + kerberos_auth_domain: http_enabled: false transport_enabled: false order: 6 @@ -92,7 +92,7 @@ opendistro_security: strip_realm_from_principal: true authentication_backend: type: noop - basic_internal_auth_domain: + basic_internal_auth_domain: http_enabled: true transport_enabled: true order: 4 @@ -164,11 +164,11 @@ opendistro_security: password: null userbase: 'ou=people,dc=example,dc=com' # Filter to search for users (currently in the whole subtree beneath userbase) - # {0} is substituted with the username + # {0} is substituted with the username usersearch: '(sAMAccountName={0})' # Use this attribute from the user as username (if not set then DN is used) username_attribute: null - authz: + authz: roles_from_myldap: http_enabled: false transport_enabled: false @@ -191,8 +191,8 @@ opendistro_security: rolebase: 'ou=groups,dc=example,dc=com' # Filter to search for roles (currently in the whole subtree beneath rolebase) # {0} is substituted with the DN of the user - # {1} is substituted with the username - # {2} is substituted with an attribute value from user's directory entry, of the authenticated user. Use userroleattribute to specify the name of the attribute + # {1} is substituted with the username + # {2} is substituted with an attribute value from user's directory entry, of the authenticated user. Use userroleattribute to specify the name of the attribute rolesearch: '(member={0})' # Specify the name of the attribute which value should be substituted with {2} above userroleattribute: null @@ -206,12 +206,12 @@ opendistro_security: resolve_nested_roles: true userbase: 'ou=people,dc=example,dc=com' # Filter to search for users (currently in the whole subtree beneath userbase) - # {0} is substituted with the username + # {0} is substituted with the username usersearch: '(uid={0})' # Skip users matching a user name, a wildcard or a regex pattern - #skip_users: + #skip_users: # - 'cn=Michael Jackson,ou*people,o=TEST' - # - '/\S*/' + # - '/\S*/' roles_from_another_ldap: http_enabled: false transport_enabled: false diff --git a/legacy/securityconfig_v6/internal_users.yml b/legacy/securityconfig_v6/internal_users.yml index 19c5eff661..c7d177787d 100644 --- a/legacy/securityconfig_v6/internal_users.yml +++ b/legacy/securityconfig_v6/internal_users.yml @@ -19,13 +19,13 @@ logstash: roles: - logstash -#password is: kibanaserver +#password is: kibanaserver kibanaserver: readonly: true hash: $2a$12$4AcgAt3xwOWadA5s5blL6ev39OXDNhmOesEoo33eZtrq2N0YrU3H. #password is: kibanaro -kibanaro: +kibanaro: hash: $2a$12$JJSXNfTowz7Uu5ttXfeYpeYE0arACvcwlPBStB1F.MI7f0U9Z4DGC roles: - kibanauser diff --git a/legacy/securityconfig_v6/roles.yml b/legacy/securityconfig_v6/roles.yml index 68a5bd6f98..c546b85393 100644 --- a/legacy/securityconfig_v6/roles.yml +++ b/legacy/securityconfig_v6/roles.yml @@ -3,7 +3,7 @@ # - '' # indices: # '': -# '': +# '': # - '' # _dls_: '' # _fls_: @@ -15,9 +15,9 @@ # and a type. If a request is executed against all indices (or all types) then the asterix ('*') is needed. # Every role a user has will be examined if it allows the action against an index (or type). At least one role must match # for the request to be successful. If no role match then the request will be denied. Currently a match must happen within -# one single role - that means that permissions can not span multiple roles. +# one single role - that means that permissions can not span multiple roles. -# For , and simple wildcards and regular expressions are possible. +# For , and simple wildcards and regular expressions are possible. # A asterix (*) will match any character sequence (or an empty sequence) # A question mark (?) will match any single character (but NOT empty character) # Example: '*my*index' will match 'my_first_index' as well as 'myindex' but not 'myindex1' @@ -27,7 +27,7 @@ # '//' # Example: '/\S*/' will match any non whitespace characters -# Important: +# Important: # Index, alias or type names can not contain dots (.) in the or expression. # Reason is that we currently parse the config file into a OpenSearch settings object which cannot cope with dots in keys. # Workaround: Just configure something like '?kibana' instead of '.kibana' or 'my?index' instead of 'my.index' @@ -59,7 +59,7 @@ opendistro_security_readall: '*': - READ -# Read all and monitor, but no write permissions +# Read all and monitor, but no write permissions opendistro_security_readall_and_monitor: cluster: - CLUSTER_MONITOR @@ -99,7 +99,7 @@ opendistro_security_kibana_user: - INDICES_ALL '?management-beats': '*': - - INDICES_ALL + - INDICES_ALL '*': '*': - indices:data/read/field_caps* @@ -135,7 +135,7 @@ opendistro_security_kibana_server: - "indices:admin/aliases*" # For logstash and beats -opendistro_security_logstash: +opendistro_security_logstash: cluster: - CLUSTER_MONITOR - CLUSTER_COMPOSITE_OPS diff --git a/legacy/securityconfig_v6/roles_mapping.yml b/legacy/securityconfig_v6/roles_mapping.yml index b3263eb234..588ba13f6e 100644 --- a/legacy/securityconfig_v6/roles_mapping.yml +++ b/legacy/securityconfig_v6/roles_mapping.yml @@ -9,12 +9,12 @@ opendistro_security_all_access: opendistro_security_logstash: backendroles: - logstash - + opendistro_security_kibana_server: readonly: true users: - kibanaserver - + opendistro_security_kibana_user: backendroles: - kibanauser diff --git a/src/main/resources/static_config/static_roles.yml b/src/main/resources/static_config/static_roles.yml index 417e4f0ab7..dc80662d0e 100644 --- a/src/main/resources/static_config/static_roles.yml +++ b/src/main/resources/static_config/static_roles.yml @@ -62,7 +62,7 @@ own_index: allowed_actions: - "indices_all" - + manage_snapshots: reserved: true hidden: false @@ -76,7 +76,7 @@ manage_snapshots: allowed_actions: - "indices:data/write/index" - "indices:admin/create" - + kibana_server: reserved: true hidden: false @@ -141,7 +141,7 @@ logstash: allowed_actions: - "crud" - "create_index" - + readall_and_monitor: reserved: true hidden: false @@ -155,7 +155,7 @@ readall_and_monitor: - "*" allowed_actions: - "read" - + readall: reserved: true hidden: false @@ -168,4 +168,3 @@ readall: - "*" allowed_actions: - "read" - diff --git a/src/test/resources/auditlog/data2.json b/src/test/resources/auditlog/data2.json index 1d37b6abc2..cc4748ed82 100644 --- a/src/test/resources/auditlog/data2.json +++ b/src/test/resources/auditlog/data2.json @@ -1,6 +1,6 @@ { "text": "text question value", "joinfield": { - "name": "question" + "name": "question" } } diff --git a/src/test/resources/auditlog/data3.json b/src/test/resources/auditlog/data3.json index 05c678b114..6846fe0f68 100644 --- a/src/test/resources/auditlog/data3.json +++ b/src/test/resources/auditlog/data3.json @@ -1,7 +1,7 @@ { "text": "text answer value", "joinfield": { - "name": "answer", - "parent": "1" + "name": "answer", + "parent": "1" } } diff --git a/src/test/resources/auditlog/endpoints/configuration_wrong_endpoint_names.yml b/src/test/resources/auditlog/endpoints/configuration_wrong_endpoint_names.yml index 24fb5dcae5..dee8c95641 100644 --- a/src/test/resources/auditlog/endpoints/configuration_wrong_endpoint_names.yml +++ b/src/test/resources/auditlog/endpoints/configuration_wrong_endpoint_names.yml @@ -7,7 +7,7 @@ plugins.security: type: external_opensearch config: http_endpoints: ['localhost:9200','localhost:9201','localhost:9202'] - index: auditlog + index: auditlog username: auditloguser password: auditlogpassword enable_ssl: false diff --git a/src/test/resources/auditlog/endpoints/routing/configuration_valid.yml b/src/test/resources/auditlog/endpoints/routing/configuration_valid.yml index 75df18aac7..046e4d6ee5 100644 --- a/src/test/resources/auditlog/endpoints/routing/configuration_valid.yml +++ b/src/test/resources/auditlog/endpoints/routing/configuration_valid.yml @@ -3,7 +3,7 @@ plugins.security: type: external_opensearch config: http_endpoints: ['localhost:9200','localhost:9201','localhost:9202'] - index: auditlog + index: auditlog username: auditloguser password: auditlogpassword enable_ssl: false @@ -16,7 +16,7 @@ plugins.security: type: external_opensearch config: http_endpoints: ['localhost:9200','localhost:9201','localhost:9202'] - index: auditlog + index: auditlog username: auditloguser password: auditlogpassword enable_ssl: false diff --git a/src/test/resources/auditlog/endpoints/routing/configuration_wrong_endpoint_names.yml b/src/test/resources/auditlog/endpoints/routing/configuration_wrong_endpoint_names.yml index 2361b6049b..2b96265492 100644 --- a/src/test/resources/auditlog/endpoints/routing/configuration_wrong_endpoint_names.yml +++ b/src/test/resources/auditlog/endpoints/routing/configuration_wrong_endpoint_names.yml @@ -8,7 +8,7 @@ plugins.security: type: external_opensearch config: http_endpoints: ['localhost:9200','localhost:9201','localhost:9202'] - index: auditlog + index: auditlog username: auditloguser password: auditlogpassword enable_ssl: false diff --git a/src/test/resources/auditlog/endpoints/routing/configuration_wrong_endpoint_types.yml b/src/test/resources/auditlog/endpoints/routing/configuration_wrong_endpoint_types.yml index 6b6929b2c7..c59adc4ee1 100644 --- a/src/test/resources/auditlog/endpoints/routing/configuration_wrong_endpoint_types.yml +++ b/src/test/resources/auditlog/endpoints/routing/configuration_wrong_endpoint_types.yml @@ -8,7 +8,7 @@ plugins.security: type: external_opensearch config: http_endpoints: ['localhost:9200','localhost:9201','localhost:9202'] - index: auditlog + index: auditlog username: auditloguser password: auditlogpassword enable_ssl: false diff --git a/src/test/resources/auditlog/endpoints/routing/routing.yml b/src/test/resources/auditlog/endpoints/routing/routing.yml index ac6596424e..4135b800e2 100644 --- a/src/test/resources/auditlog/endpoints/routing/routing.yml +++ b/src/test/resources/auditlog/endpoints/routing/routing.yml @@ -19,4 +19,4 @@ plugins.security: - endpoint3 COMPLIANCE_DOC_WRITE: endpoints: - - default + - default diff --git a/src/test/resources/auditlog/endpoints/sink/configuration_all_variants.yml b/src/test/resources/auditlog/endpoints/sink/configuration_all_variants.yml index e441322c17..f1c8620e88 100644 --- a/src/test/resources/auditlog/endpoints/sink/configuration_all_variants.yml +++ b/src/test/resources/auditlog/endpoints/sink/configuration_all_variants.yml @@ -17,10 +17,10 @@ plugins.security: type: external_opensearch something: key: value - endpoint6: + endpoint6: something: key: value - endpoint7: + endpoint7: config: key: value endpoint8: diff --git a/src/test/resources/auditlog/endpoints/sink/configuration_no_default.yml b/src/test/resources/auditlog/endpoints/sink/configuration_no_default.yml index f81b37dde2..013410ea77 100644 --- a/src/test/resources/auditlog/endpoints/sink/configuration_no_default.yml +++ b/src/test/resources/auditlog/endpoints/sink/configuration_no_default.yml @@ -16,10 +16,10 @@ plugins.security: type: external_opensearch something: key: value - endpoint6: + endpoint6: something: key: value - endpoint7: + endpoint7: config: key: value endpoint8: diff --git a/src/test/resources/config_auth_ratelimiting.yml b/src/test/resources/config_auth_ratelimiting.yml index 3675afc33e..a55ad0b5d3 100644 --- a/src/test/resources/config_auth_ratelimiting.yml +++ b/src/test/resources/config_auth_ratelimiting.yml @@ -23,5 +23,5 @@ config: allowed_tries: 10 internal_authentication_backend_limiting: type: username - authentication_backend: intern + authentication_backend: intern allowed_tries: 3 diff --git a/src/test/resources/config_ldap.yml b/src/test/resources/config_ldap.yml index c2aebf869b..5dc0895d8d 100644 --- a/src/test/resources/config_ldap.yml +++ b/src/test/resources/config_ldap.yml @@ -7,7 +7,7 @@ plugins.security: remoteIpHeader: "x-forwarded-for" proxiesHeader: "x-forwarded-by" trustedProxies: "proxy1|proxy2" - authenticator: + authenticator: type: org.opensearch.security.http.HTTPBasicAuthenticator authcz: authentication_domain_basic_internal: diff --git a/src/test/resources/data2.json b/src/test/resources/data2.json index 1d37b6abc2..cc4748ed82 100644 --- a/src/test/resources/data2.json +++ b/src/test/resources/data2.json @@ -1,6 +1,6 @@ { "text": "text question value", "joinfield": { - "name": "question" + "name": "question" } } diff --git a/src/test/resources/data3.json b/src/test/resources/data3.json index 05c678b114..6846fe0f68 100644 --- a/src/test/resources/data3.json +++ b/src/test/resources/data3.json @@ -1,7 +1,7 @@ { "text": "text answer value", "joinfield": { - "name": "answer", - "parent": "1" + "name": "answer", + "parent": "1" } } diff --git a/src/test/resources/dlsfls/doc1.json b/src/test/resources/dlsfls/doc1.json index 6067eedc9a..7dccde9b93 100644 --- a/src/test/resources/dlsfls/doc1.json +++ b/src/test/resources/dlsfls/doc1.json @@ -1,5 +1,5 @@ { - + "customer": { "name": "", "type": "normal", @@ -9,8 +9,8 @@ "street": "street1", "zip": "12345", "city": "mycity" - - } + + } }, "secret": "a secret value", @@ -34,7 +34,7 @@ "boolfield5": true, "boolfield6": false, "nullfield": null, - + "@timestamp": "", "timestamp": "" diff --git a/src/test/resources/dlsfls/flsquery.json b/src/test/resources/dlsfls/flsquery.json index 9418e8721b..19d3897971 100644 --- a/src/test/resources/dlsfls/flsquery.json +++ b/src/test/resources/dlsfls/flsquery.json @@ -34,9 +34,9 @@ } }, "query":{ - - - + + + "bool":{ "must":[ { @@ -53,8 +53,8 @@ ] } - - + + }, "stored_fields":[ "*", diff --git a/src/test/resources/dlsfls/flsquery2.json b/src/test/resources/dlsfls/flsquery2.json index a5ad1da20e..26fce316db 100644 --- a/src/test/resources/dlsfls/flsquery2.json +++ b/src/test/resources/dlsfls/flsquery2.json @@ -38,8 +38,8 @@ "match_all":{ - - + + } }, "stored_fields":[ diff --git a/src/test/resources/dlsfls/internal_users_tlq.yml b/src/test/resources/dlsfls/internal_users_tlq.yml index 5bbec586f0..dff0a67633 100644 --- a/src/test/resources/dlsfls/internal_users_tlq.yml +++ b/src/test/resources/dlsfls/internal_users_tlq.yml @@ -2,7 +2,7 @@ _meta: type: "internalusers" config_version: 2 - + tlq_1337: hash: "$2y$12$SP9z.rBgEHTlueKkiqSK/OxqB2PLJN/eRoNJ8WOPoHWIpirvbFAAy" # "password" backend_roles: ["os_dls_tlq_lookup"] diff --git a/src/test/resources/dlsfls/masked_field_mapping.json b/src/test/resources/dlsfls/masked_field_mapping.json index f3629282fd..56863f546c 100644 --- a/src/test/resources/dlsfls/masked_field_mapping.json +++ b/src/test/resources/dlsfls/masked_field_mapping.json @@ -1,5 +1,5 @@ { - + "_doc": { "properties": { @@ -142,6 +142,6 @@ } } } - - + + } diff --git a/src/test/resources/dlsfls/roles_tlq.yml b/src/test/resources/dlsfls/roles_tlq.yml index c2d08ca948..1420a7a965 100644 --- a/src/test/resources/dlsfls/roles_tlq.yml +++ b/src/test/resources/dlsfls/roles_tlq.yml @@ -2,7 +2,7 @@ _meta: type: "roles" config_version: 2 - + os_dls_tlq_lookup: cluster_permissions: - "*" diff --git a/src/test/resources/ldap/test1.yml b/src/test/resources/ldap/test1.yml index e1e396ba01..e0ad96ceea 100644 --- a/src/test/resources/ldap/test1.yml +++ b/src/test/resources/ldap/test1.yml @@ -1,8 +1,8 @@ ---- +--- enable_ssl: true enable_ssl_client_auth: false enable_start_tls: false -#hosts: +#hosts: # - "localhost:${ldapport}" path.home: "." pemcert_content: | diff --git a/src/test/resources/legacy/securityconfig_v6/action_groups.yml b/src/test/resources/legacy/securityconfig_v6/action_groups.yml index 5acbe1aea8..ac564e7421 100644 --- a/src/test/resources/legacy/securityconfig_v6/action_groups.yml +++ b/src/test/resources/legacy/securityconfig_v6/action_groups.yml @@ -128,7 +128,7 @@ OPENDISTRO_SECURITY_CLUSTER_COMPOSITE_OPS: - "indices:admin/aliases*" - "indices:data/write/reindex" - CLUSTER_COMPOSITE_OPS_RO - + OPENDISTRO_SECURITY_MANAGE_SNAPSHOTS: readonly: true permissions: diff --git a/src/test/resources/legacy/securityconfig_v6/config.yml b/src/test/resources/legacy/securityconfig_v6/config.yml index 031be9bb15..19b1fd76cd 100644 --- a/src/test/resources/legacy/securityconfig_v6/config.yml +++ b/src/test/resources/legacy/securityconfig_v6/config.yml @@ -1,14 +1,14 @@ # This is the main OpenSearch Security configuration file where authentication # and authorization is defined. -# +# # You need to configure at least one authentication domain in the authc of this file. -# An authentication domain is responsible for extracting the user credentials from -# the request and for validating them against an authentication backend like Active Directory for example. +# An authentication domain is responsible for extracting the user credentials from +# the request and for validating them against an authentication backend like Active Directory for example. # -# If more than one authentication domain is configured the first one which succeeds wins. +# If more than one authentication domain is configured the first one which succeeds wins. # If all authentication domains fail then the request is unauthenticated. # In this case an exception is thrown and/or the HTTP status is set to 401. -# +# # After authentication authorization (authz) will be applied. There can be zero or more authorizers which collect # the roles from a given backend for the authenticated user. # @@ -21,18 +21,18 @@ # For HTTP it is possible to allow anonymous authentication. If that is the case then the HTTP authenticators try to # find user credentials in the HTTP request. If credentials are found then the user gets regularly authenticated. # If none can be found the user will be authenticated as an "anonymous" user. This user has always the username "opendistro_security_anonymous" -# and one role named "opendistro_security_anonymous_backendrole". +# and one role named "opendistro_security_anonymous_backendrole". # If you enable anonymous authentication all HTTP authenticators will not challenge. -# +# # # Note: If you define more than one HTTP authenticators make sure to put non-challenging authenticators like "proxy" or "clientcert" -# first and the challenging one last. +# first and the challenging one last. # Because it's not possible to challenge a client with two different authentication methods (for example # Kerberos and Basic) only one can have the challenge flag set to true. You can cope with this situation # by using pre-authentication, e.g. sending a HTTP Basic authentication header in the request. # # Default value of the challenge flag is true. -# +# # # HTTP # basic (challenging) @@ -77,7 +77,7 @@ opendistro_security: ###### and here https://tools.ietf.org/html/rfc7239 ###### and https://tomcat.apache.org/tomcat-8.0-doc/config/valve.html#Remote_IP_Valve authc: - kerberos_auth_domain: + kerberos_auth_domain: http_enabled: false transport_enabled: false order: 6 @@ -91,7 +91,7 @@ opendistro_security: strip_realm_from_principal: true authentication_backend: type: noop - basic_internal_auth_domain: + basic_internal_auth_domain: http_enabled: true transport_enabled: true order: 4 @@ -163,11 +163,11 @@ opendistro_security: password: null userbase: 'ou=people,dc=example,dc=com' # Filter to search for users (currently in the whole subtree beneath userbase) - # {0} is substituted with the username + # {0} is substituted with the username usersearch: '(sAMAccountName={0})' # Use this attribute from the user as username (if not set then DN is used) username_attribute: null - authz: + authz: roles_from_myldap: http_enabled: false transport_enabled: false @@ -190,8 +190,8 @@ opendistro_security: rolebase: 'ou=groups,dc=example,dc=com' # Filter to search for roles (currently in the whole subtree beneath rolebase) # {0} is substituted with the DN of the user - # {1} is substituted with the username - # {2} is substituted with an attribute value from user's directory entry, of the authenticated user. Use userroleattribute to specify the name of the attribute + # {1} is substituted with the username + # {2} is substituted with an attribute value from user's directory entry, of the authenticated user. Use userroleattribute to specify the name of the attribute rolesearch: '(member={0})' # Specify the name of the attribute which value should be substituted with {2} above userroleattribute: null @@ -205,12 +205,12 @@ opendistro_security: resolve_nested_roles: true userbase: 'ou=people,dc=example,dc=com' # Filter to search for users (currently in the whole subtree beneath userbase) - # {0} is substituted with the username + # {0} is substituted with the username usersearch: '(uid={0})' # Skip users matching a user name, a wildcard or a regex pattern - #skip_users: + #skip_users: # - 'cn=Michael Jackson,ou*people,o=TEST' - # - '/\S*/' + # - '/\S*/' roles_from_another_ldap: enabled: false authorization_backend: diff --git a/src/test/resources/legacy/securityconfig_v6/internal_users.yml b/src/test/resources/legacy/securityconfig_v6/internal_users.yml index d8d5b0d3a4..64b6295bbe 100644 --- a/src/test/resources/legacy/securityconfig_v6/internal_users.yml +++ b/src/test/resources/legacy/securityconfig_v6/internal_users.yml @@ -19,13 +19,13 @@ opendistro_security_logstash: roles: - logstash -#password is: kibanaserver +#password is: kibanaserver kibanaserver: readonly: true hash: $2a$12$4AcgAt3xwOWadA5s5blL6ev39OXDNhmOesEoo33eZtrq2N0YrU3H. #password is: kibanaro -kibanaro: +kibanaro: hash: $2a$12$JJSXNfTowz7Uu5ttXfeYpeYE0arACvcwlPBStB1F.MI7f0U9Z4DGC roles: - kibanauser diff --git a/src/test/resources/legacy/securityconfig_v6/migration/roles.yml b/src/test/resources/legacy/securityconfig_v6/migration/roles.yml index 0163e4a7bb..52f9dd60a4 100644 --- a/src/test/resources/legacy/securityconfig_v6/migration/roles.yml +++ b/src/test/resources/legacy/securityconfig_v6/migration/roles.yml @@ -3,7 +3,7 @@ # - '' # indices: # '': -# '': +# '': # - '' # _dls_: '' # _fls_: @@ -15,9 +15,9 @@ # and a type. If a request is executed against all indices (or all types) then the asterix ('*') is needed. # Every role a user has will be examined if it allows the action against an index (or type). At least one role must match # for the request to be successful. If no role match then the request will be denied. Currently a match must happen within -# one single role - that means that permissions can not span multiple roles. +# one single role - that means that permissions can not span multiple roles. -# For , and simple wildcards and regular expressions are possible. +# For , and simple wildcards and regular expressions are possible. # A asterix (*) will match any character sequence (or an empty sequence) # A question mark (?) will match any single character (but NOT empty character) # Example: '*my*index' will match 'my_first_index' as well as 'myindex' but not 'myindex1' @@ -27,7 +27,7 @@ # '//' # Example: '/\S*/' will match any non whitespace characters -# Important: +# Important: # Index, alias or type names can not contain dots (.) in the or expression. # Reason is that we currently parse the config file into a opensearch settings object which cannot cope with dots in keys. # Workaround: Just configure something like '?kibana' instead of '.kibana' or 'my?index' instead of 'my.index' @@ -54,7 +54,7 @@ opendistro_security_readall: '*': - READ -# Read all and monitor, but no write permissions +# Read all and monitor, but no write permissions opendistro_security_readall_and_monitor: cluster: - CLUSTER_MONITOR diff --git a/src/test/resources/legacy/securityconfig_v6/roles.yml b/src/test/resources/legacy/securityconfig_v6/roles.yml index d755d67dbe..65f02a7106 100644 --- a/src/test/resources/legacy/securityconfig_v6/roles.yml +++ b/src/test/resources/legacy/securityconfig_v6/roles.yml @@ -3,7 +3,7 @@ # - '' # indices: # '': -# '': +# '': # - '' # _dls_: '' # _fls_: @@ -15,9 +15,9 @@ # and a type. If a request is executed against all indices (or all types) then the asterix ('*') is needed. # Every role a user has will be examined if it allows the action against an index (or type). At least one role must match # for the request to be successful. If no role match then the request will be denied. Currently a match must happen within -# one single role - that means that permissions can not span multiple roles. +# one single role - that means that permissions can not span multiple roles. -# For , and simple wildcards and regular expressions are possible. +# For , and simple wildcards and regular expressions are possible. # A asterix (*) will match any character sequence (or an empty sequence) # A question mark (?) will match any single character (but NOT empty character) # Example: '*my*index' will match 'my_first_index' as well as 'myindex' but not 'myindex1' @@ -27,7 +27,7 @@ # '//' # Example: '/\S*/' will match any non whitespace characters -# Important: +# Important: # Index, alias or type names can not contain dots (.) in the or expression. # Reason is that we currently parse the config file into a opensearch settings object which cannot cope with dots in keys. # Workaround: Just configure something like '?kibana' instead of '.kibana' or 'my?index' instead of 'my.index' @@ -57,7 +57,7 @@ opendistro_security_readall: '*': - READ -# Read all and monitor, but no write permissions +# Read all and monitor, but no write permissions opendistro_security_readall_and_monitor: cluster: - CLUSTER_MONITOR @@ -97,7 +97,7 @@ opendistro_security_kibana_user: - INDICES_ALL '?management-beats': '*': - - INDICES_ALL + - INDICES_ALL '*': '*': - indices:data/read/field_caps* @@ -133,7 +133,7 @@ opendistro_security_kibana_server: - "indices:admin/aliases*" # For logstash and beats -opendistro_security_logstash: +opendistro_security_logstash: cluster: - CLUSTER_MONITOR - CLUSTER_COMPOSITE_OPS diff --git a/src/test/resources/legacy/securityconfig_v6/roles_mapping.yml b/src/test/resources/legacy/securityconfig_v6/roles_mapping.yml index b3263eb234..588ba13f6e 100644 --- a/src/test/resources/legacy/securityconfig_v6/roles_mapping.yml +++ b/src/test/resources/legacy/securityconfig_v6/roles_mapping.yml @@ -9,12 +9,12 @@ opendistro_security_all_access: opendistro_security_logstash: backendroles: - logstash - + opendistro_security_kibana_server: readonly: true users: - kibanaserver - + opendistro_security_kibana_user: backendroles: - kibanauser diff --git a/src/test/resources/multitenancy/config_basic_auth.yml b/src/test/resources/multitenancy/config_basic_auth.yml index f60caab0b7..72f73f8305 100644 --- a/src/test/resources/multitenancy/config_basic_auth.yml +++ b/src/test/resources/multitenancy/config_basic_auth.yml @@ -22,7 +22,7 @@ config: internalProxies: ".*" remoteIpHeader: "x-forwarded-for" authc: - basic_internal_auth_domain: + basic_internal_auth_domain: http_enabled: true transport_enabled: true order: 0 diff --git a/src/test/resources/restapi/audit.yml b/src/test/resources/restapi/audit.yml index 06eb47a56d..796c92f828 100644 --- a/src/test/resources/restapi/audit.yml +++ b/src/test/resources/restapi/audit.yml @@ -33,7 +33,7 @@ config: # configs internal_config: true external_config: false - + # compliance read read_metadata_only: false read_watched_fields: {} diff --git a/src/test/resources/restapi/security_config.json b/src/test/resources/restapi/security_config.json index e8acc0a22a..e5c09050cc 100644 --- a/src/test/resources/restapi/security_config.json +++ b/src/test/resources/restapi/security_config.json @@ -1,5 +1,5 @@ { - + "dynamic":{ "filtered_alias_mode":"warn", "disable_rest_auth": false, @@ -29,13 +29,13 @@ "challenge":true, "type":"kerberos", "config":{ - + } }, "authentication_backend":{ "type":"noop", "config":{ - + } }, "description":"Migrated from v6" @@ -48,13 +48,13 @@ "challenge":true, "type":"clientcert", "config":{ - + } }, "authentication_backend":{ "type":"noop", "config":{ - + } }, "description":"Migrated from v6" @@ -74,7 +74,7 @@ "authentication_backend":{ "type":"noop", "config":{ - + } }, "description":"Migrated from v6" @@ -87,13 +87,13 @@ "challenge":true, "type":"basic", "config":{ - + } }, "authentication_backend":{ "type":"intern", "config":{ - + } }, "description":"Migrated from v6" @@ -106,7 +106,7 @@ "authorization_backend":{ "type":"xxx", "config":{ - + } }, "description":"Migrated from v6" @@ -127,12 +127,12 @@ } }, "auth_failure_listeners":{ - + }, "do_not_fail_on_forbidden":false, "multi_rolespan_enabled":false, "hosts_resolver_mode":"ip-only", "do_not_fail_on_forbidden_empty":false } - + } diff --git a/src/test/resources/restapi/users_key_not_quoted.json b/src/test/resources/restapi/users_key_not_quoted.json index e69de29bb2..8b13789179 100644 --- a/src/test/resources/restapi/users_key_not_quoted.json +++ b/src/test/resources/restapi/users_key_not_quoted.json @@ -0,0 +1 @@ + diff --git a/src/test/resources/roles_invalidxcontent.yml b/src/test/resources/roles_invalidxcontent.yml index 8d805273cf..f67c09a823 100644 --- a/src/test/resources/roles_invalidxcontent.yml +++ b/src/test/resources/roles_invalidxcontent.yml @@ -5,6 +5,6 @@ opendistro_security_public: indices: indices: '.notexistingindexcvnjl9809991' - '*': + '*': - ALL invalid yml From 30d960cc8f6cb25b437504862b3104b43075881e Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Thu, 1 Jun 2023 00:26:12 -0700 Subject: [PATCH 194/369] Add release notes for 2.8.0 (#2827) Signed-off-by: Ryan Liang --- ...ensearch-security.release-notes-2.8.0.0.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-2.8.0.0.md diff --git a/release-notes/opensearch-security.release-notes-2.8.0.0.md b/release-notes/opensearch-security.release-notes-2.8.0.0.md new file mode 100644 index 0000000000..32d33d83f7 --- /dev/null +++ b/release-notes/opensearch-security.release-notes-2.8.0.0.md @@ -0,0 +1,30 @@ +## 2023-06-06 Version 2.8.0.0 + +Compatible with OpenSearch 2.8.0 + +### Features + +* Identify extension Transport requests and permit handshake and extension registration actions ([#2599](https://github.com/opensearch-project/security/pull/2599)) +* Use ExtensionsManager.lookupExtensionSettingsById when verifying extension unique id ([#2749](https://github.com/opensearch-project/security/pull/2749)) +* Generate auth tokens for service accounts ([#2716](https://github.com/opensearch-project/security/pull/2716)) +* Security User Refactor ([#2594](https://github.com/opensearch-project/security/pull/2594)) +* Add score based password verification ([#2557](https://github.com/opensearch-project/security/pull/2557)) +* Usage of JWKS with JWT (w/o OpenID connect) ([#2808](https://github.com/opensearch-project/security/pull/2808)) + +### Bug Fixes + +* `deserializeSafeFromHeader` uses `context.getHeader(headerName)` instead of `context.getHeaders()` ([#2768](https://github.com/opensearch-project/security/pull/2768)) +* Fix multitency config update ([#2758](https://github.com/opensearch-project/security/pull/2758)) + +### Enhancements + +* Add default roles for SQL plugin: PPL and cross-cluster search ([#2729](https://github.com/opensearch-project/security/pull/2729)) +* Update security-analytics roles to add correlation engine apis ([#2732](https://github.com/opensearch-project/security/pull/2732)) +* Changes in role.yml for long-running operation notification feature in Index-Management repo ([#2789](https://github.com/opensearch-project/security/pull/2789)) +* Rest admin permissions ([#2411](https://github.com/opensearch-project/security/pull/2411)) +* Separate config option to enable restapi: permissions ([#2605](https://github.com/opensearch-project/security/pull/2605)) + +### Maintenance + +* Update to Gradle 8.1.1 ([#2738](https://github.com/opensearch-project/security/pull/2738)) +* Upgrade spring-core from 5.3.26 to 5.3.27 ([#2717](https://github.com/opensearch-project/security/pull/2717)) From 76a5d7fa4904119c7e69329e490ad8a09f61061a Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Thu, 1 Jun 2023 10:03:13 -0400 Subject: [PATCH 195/369] Updates the style of all java files under the */dlic/util/ dir. (#2823) * rebase Signed-off-by: Stephen Crawford * Update java style under **/auth Signed-off-by: Stephen Crawford * Update Util dir Signed-off-by: Stephen Crawford * readd formatting Signed-off-by: Stephen Crawford --------- Signed-off-by: Stephen Crawford --- build.gradle | 54 +- .../SecurityBackwardsCompatibilityIT.java | 8 +- gradle/formatting.gradle | 129 ++- .../jwt/AbstractHTTPJwtAuthenticator.java | 24 +- .../auth/http/jwt/HTTPJwtAuthenticator.java | 113 ++- .../AuthenticatorUnavailableException.java | 33 +- .../keybyoidc/BadCredentialsException.java | 33 +- ...TTPJwtKeyByOpenIdConnectAuthenticator.java | 92 +- .../auth/http/jwt/keybyoidc/JwtVerifier.java | 177 ++-- .../auth/http/jwt/keybyoidc/KeyProvider.java | 5 +- .../http/jwt/keybyoidc/KeySetProvider.java | 2 +- .../http/jwt/keybyoidc/KeySetRetriever.java | 343 +++---- .../jwt/keybyoidc/SelfRefreshingKeySet.java | 587 ++++++------ .../json/OpenIdProviderConfiguration.java | 16 +- .../kerberos/HTTPSpnegoAuthenticator.java | 106 +- .../auth/http/kerberos/util/JaasKrbUtil.java | 344 +++---- .../auth/http/kerberos/util/KrbConstants.java | 45 +- .../http/saml/AuthTokenProcessorHandler.java | 96 +- .../auth/http/saml/HTTPSamlAuthenticator.java | 70 +- .../auth/http/saml/Saml2SettingsProvider.java | 81 +- .../http/saml/SamlHTTPMetadataResolver.java | 3 +- .../com/amazon/dlic/auth/ldap/LdapUser.java | 22 +- .../backend/LDAPAuthenticationBackend.java | 115 ++- .../backend/LDAPAuthorizationBackend.java | 364 ++++--- .../dlic/auth/ldap/util/ConfigConstants.java | 9 +- .../dlic/auth/ldap/util/LdapHelper.java | 28 +- .../com/amazon/dlic/auth/ldap/util/Utils.java | 12 +- .../ldap2/LDAPAuthenticationBackend2.java | 37 +- .../auth/ldap2/LDAPAuthorizationBackend2.java | 132 ++- .../ldap2/LDAPConnectionFactoryFactory.java | 74 +- .../dlic/auth/ldap2/LDAPUserSearcher.java | 63 +- .../util/SettingsBasedSSLConfigurator.java | 233 +++-- .../util/SettingsBasedSSLConfiguratorV4.java | 233 +++-- .../security/DefaultObjectMapper.java | 6 +- .../ConfigUpdateNodeResponse.java | 10 +- .../TransportConfigUpdateAction.java | 8 +- .../action/whoami/TransportWhoAmIAction.java | 6 +- .../action/whoami/WhoAmIRequestBuilder.java | 2 +- .../action/whoami/WhoAmIResponse.java | 8 +- .../security/auditlog/AuditLog.java | 2 +- .../security/auditlog/config/AuditConfig.java | 2 +- .../security/auth/AuthFailureListener.java | 6 +- .../security/auth/AuthenticationBackend.java | 14 +- .../security/auth/AuthorizationBackend.java | 4 +- .../security/auth/BackendRegistry.java | 12 +- .../security/auth/HTTPAuthenticator.java | 16 +- .../auth/blocking/ClientBlockRegistry.java | 6 +- .../HeapBasedClientBlockRegistry.java | 6 +- .../auth/limiting/AbstractRateLimiter.java | 6 +- .../limiting/AddressBasedRateLimiter.java | 6 +- .../limiting/UserNameBasedRateLimiter.java | 6 +- .../security/configuration/AdminDNs.java | 30 +- .../configuration/ClusterInfoHolder.java | 6 +- .../security/configuration/CompatConfig.java | 6 +- .../configuration/ConfigCallback.java | 2 +- ...onfigUpdateAlreadyInProgressException.java | 6 +- .../configuration/DlsFlsRequestValve.java | 4 +- .../configuration/DlsFlsValveImpl.java | 6 +- .../configuration/DlsQueryParser.java | 8 +- .../configuration/EmptyFilterLeafReader.java | 2 +- .../configuration/InvalidConfigException.java | 2 +- .../StaticResourceException.java | 6 +- .../security/filter/SecurityFilter.java | 22 +- .../security/filter/SecurityRestFilter.java | 12 +- .../security/http/HTTPBasicAuthenticator.java | 8 +- .../http/HTTPClientCertAuthenticator.java | 18 +- .../security/http/HTTPProxyAuthenticator.java | 6 +- .../security/http/RemoteIpDetector.java | 20 +- .../http/SecurityHttpServerTransport.java | 2 +- .../SecurityNonSslHttpServerTransport.java | 2 +- .../opensearch/security/http/XFFResolver.java | 14 +- .../proxy/HTTPExtendedProxyAuthenticator.java | 4 +- .../security/httpclient/HttpClient.java | 2 +- .../privileges/DocumentAllowList.java | 2 +- .../privileges/PrivilegesEvaluator.java | 10 +- .../PrivilegesEvaluatorResponse.java | 12 +- .../privileges/PrivilegesInterceptor.java | 4 +- .../SecurityIndexAccessEvaluator.java | 6 +- .../privileges/SnapshotRestoreEvaluator.java | 18 +- .../privileges/TermsAggregationEvaluator.java | 14 +- .../resolver/IndexResolverReplacer.java | 12 +- .../security/rest/DashboardsInfoAction.java | 8 +- .../security/rest/SecurityHealthAction.java | 14 +- .../security/rest/SecurityInfoAction.java | 14 +- .../security/rest/TenantInfoAction.java | 24 +- .../security/securityconf/ConfigModelV6.java | 72 +- .../security/securityconf/ConfigModelV7.java | 80 +- .../securityconf/DynamicConfigFactory.java | 44 +- .../securityconf/DynamicConfigModel.java | 18 +- .../securityconf/DynamicConfigModelV6.java | 58 +- .../securityconf/DynamicConfigModelV7.java | 54 +- .../securityconf/EvaluatedDlsFlsConfig.java | 4 +- .../security/securityconf/Hideable.java | 2 +- .../security/securityconf/Initializable.java | 2 +- .../securityconf/InternalUsersModel.java | 2 +- .../security/securityconf/Migration.java | 48 +- .../securityconf/StaticDefinable.java | 2 +- .../security/securityconf/impl/Meta.java | 16 +- .../impl/SecurityDynamicConfiguration.java | 60 +- .../securityconf/impl/v6/ActionGroupsV6.java | 8 +- .../securityconf/impl/v6/ConfigV6.java | 64 +- .../securityconf/impl/v6/InternalUserV6.java | 14 +- .../securityconf/impl/v6/RoleMappingsV6.java | 2 +- .../security/securityconf/impl/v6/RoleV6.java | 10 +- .../securityconf/impl/v7/ActionGroupsV7.java | 18 +- .../securityconf/impl/v7/ConfigV7.java | 112 +-- .../securityconf/impl/v7/InternalUserV7.java | 16 +- .../securityconf/impl/v7/RoleMappingsV7.java | 2 +- .../security/securityconf/impl/v7/RoleV7.java | 54 +- .../securityconf/impl/v7/TenantV7.java | 10 +- .../ssl/ExternalSecurityKeyStore.java | 30 +- .../ssl/OpenSearchSecuritySSLPlugin.java | 76 +- .../security/ssl/SecurityKeyStore.java | 8 +- .../security/ssl/SslExceptionHandler.java | 12 +- .../SecuritySSLNettyHttpServerTransport.java | 12 +- .../ssl/http/netty/ValidatingDispatcher.java | 14 +- .../ssl/rest/SecuritySSLInfoAction.java | 14 +- .../transport/DefaultPrincipalExtractor.java | 16 +- .../ssl/transport/PrincipalExtractor.java | 14 +- .../transport/SecuritySSLNettyTransport.java | 22 +- .../transport/SecuritySSLRequestHandler.java | 34 +- .../SecuritySSLTransportInterceptor.java | 12 +- .../ssl/util/CertificateValidator.java | 68 +- .../security/ssl/util/ExceptionUtils.java | 20 +- .../ssl/util/SSLCertificateHelper.java | 34 +- .../security/ssl/util/SSLConfigConstants.java | 52 +- .../security/ssl/util/SSLRequestHelper.java | 50 +- .../opensearch/security/ssl/util/Utils.java | 12 +- .../security/support/ConfigConstants.java | 24 +- .../security/support/HTTPHelper.java | 14 +- .../security/support/HeaderHelper.java | 6 +- .../opensearch/security/support/MapUtils.java | 6 +- .../security/support/ModuleInfo.java | 18 +- .../security/support/PemKeyReader.java | 86 +- .../support/ReflectiveAttributeAccessors.java | 4 +- .../security/support/SecurityJsonNode.java | 30 +- .../security/support/SecurityUtils.java | 20 +- .../support/SnapshotRestoreHelper.java | 18 +- .../security/support/SourceFieldsContext.java | 2 +- .../security/support/WildcardMatcher.java | 2 +- .../org/opensearch/security/tools/Hasher.java | 4 +- .../opensearch/security/tools/Migrater.java | 32 +- .../security/tools/SecurityAdmin.java | 202 ++-- .../DefaultInterClusterRequestEvaluator.java | 10 +- .../InterClusterRequestEvaluator.java | 8 +- .../transport/SecurityInterceptor.java | 8 +- .../transport/SecurityRequestHandler.java | 2 +- .../security/user/CustomAttributesAware.java | 2 +- .../org/opensearch/security/user/User.java | 34 +- .../ratetracking/HeapBasedRateTracker.java | 8 +- .../util/ratetracking/RateTracker.java | 6 +- .../ratetracking/SingleTryRateTracker.java | 6 +- .../http/jwt/HTTPJwtAuthenticatorTest.java | 149 +-- .../auth/http/jwt/keybyoidc/CxfTestTools.java | 6 +- ...wtKeyByOpenIdConnectAuthenticatorTest.java | 699 +++++++------- .../jwt/keybyoidc/KeySetRetrieverTest.java | 32 +- .../http/jwt/keybyoidc/MockIpdServer.java | 250 ++--- .../keybyoidc/SelfRefreshingKeySetTest.java | 135 ++- ...wtKeyByOpenIdConnectAuthenticatorTest.java | 361 ++++--- .../dlic/auth/http/jwt/keybyoidc/TestJwk.java | 152 +-- .../auth/http/jwt/keybyoidc/TestJwts.java | 157 +-- .../http/saml/HTTPSamlAuthenticatorTest.java | 471 +++++---- .../auth/http/saml/MockSamlIdpServer.java | 188 ++-- .../dlic/auth/ldap/LdapBackendIntegTest.java | 15 +- .../dlic/auth/ldap/LdapBackendTest.java | 904 +++++++++--------- .../auth/ldap/LdapBackendTestClientCert.java | 303 +++--- .../ldap/LdapBackendTestNewStyleConfig.java | 714 ++++++++------ .../com/amazon/dlic/auth/ldap/UtilsTest.java | 37 +- .../auth/ldap/srv/EmbeddedLDAPServer.java | 1 - .../amazon/dlic/auth/ldap/srv/LdapServer.java | 37 +- .../auth/ldap2/LdapBackendIntegTest2.java | 15 +- .../ldap2/LdapBackendTestClientCert2.java | 303 +++--- .../ldap2/LdapBackendTestNewStyleConfig2.java | 873 ++++++++++------- .../ldap2/LdapBackendTestOldStyleConfig2.java | 878 +++++++++-------- .../org/opensearch/node/PluginAwareNode.java | 4 +- .../opensearch/security/AggregationTests.java | 24 +- ...waysFalseInterClusterRequestEvaluator.java | 4 +- .../org/opensearch/security/ConfigTests.java | 40 +- .../org/opensearch/security/HealthTests.java | 12 +- .../security/HttpIntegrationTests.java | 220 ++--- .../security/IndexIntegrationTests.java | 236 ++--- .../InitializationIntegrationTests.java | 38 +- .../opensearch/security/IntegrationTests.java | 140 +-- .../opensearch/security/ResolveAPITests.java | 2 +- .../security/RolesInjectorIntegTest.java | 2 +- .../security/RolesValidationIntegTest.java | 2 +- .../security/SecurityAdminTests.java | 152 +-- .../security/SlowIntegrationTests.java | 40 +- .../security/SystemIntegratorsTests.java | 80 +- .../org/opensearch/security/TaskTests.java | 10 +- .../org/opensearch/security/UtilTests.java | 20 +- .../compliance/ComplianceAuditlogTest.java | 2 +- .../integration/BasicAuditlogTest.java | 4 +- .../HeapBasedClientBlockRegistryTest.java | 22 +- .../limiting/AddressBasedRateLimiterTest.java | 6 +- .../limiting/HeapBasedRateTrackerTest.java | 64 +- .../UserNameBasedRateLimiterTest.java | 6 +- .../ccstest/CrossClusterSearchTests.java | 8 +- .../security/ccstest/RemoteReindexTests.java | 36 +- .../dlic/dlsfls/AbstractDlsFlsTest.java | 12 +- .../dlic/dlsfls/CCReplicationTest.java | 2 +- .../security/dlic/dlsfls/DlsDateMathTest.java | 10 +- .../dlic/dlsfls/DlsTermLookupQueryTest.java | 4 +- .../dlic/dlsfls/FlsIndexingTests.java | 4 +- .../dlic/rest/api/AccountApiTest.java | 4 +- .../dlic/rest/api/ActionGroupsApiTest.java | 2 +- .../dlic/rest/api/AuditApiActionTest.java | 2 +- .../rest/api/DashboardsInfoActionTest.java | 2 +- .../dlic/rest/api/FlushCacheApiTest.java | 2 +- .../rest/api/GetConfigurationApiTest.java | 2 +- .../dlic/rest/api/IndexMissingTest.java | 2 +- .../dlic/rest/api/NodesDnApiTest.java | 2 +- .../dlic/rest/api/RoleBasedAccessTest.java | 2 +- .../dlic/rest/api/RolesMappingApiTest.java | 2 +- .../dlic/rest/api/SecurityApiAccessTest.java | 2 +- .../dlic/rest/api/SecurityConfigApiTest.java | 4 +- .../rest/api/SecurityHealthActionTest.java | 2 +- .../dlic/rest/api/SecurityInfoActionTest.java | 2 +- .../dlic/rest/api/TenantInfoActionTest.java | 4 +- .../dlic/rest/api/WhitelistApiTest.java | 2 +- .../security/filter/SecurityFilterTest.java | 2 +- .../HTTPExtendedProxyAuthenticatorTest.java | 24 +- .../test/TenancyMultitenancyEnabledTests.java | 4 +- .../SecurityIndexAccessEvaluatorTest.java | 6 +- .../impl/v7/IndexPatternTests.java | 10 +- .../setting/DeprecatedSettingsTest.java | 2 +- .../ssl/CertificateValidatorTest.java | 50 +- .../opensearch/security/ssl/OpenSSLTest.java | 24 +- .../security/ssl/TestPrincipalExtractor.java | 14 +- .../test/AbstractSecurityUnitTest.java | 4 +- .../helper/cluster/ClusterConfiguration.java | 28 +- .../test/helper/cluster/ClusterHelper.java | 4 +- .../security/test/helper/file/FileHelper.java | 12 +- .../security/test/helper/rest/RestHelper.java | 36 +- .../helper/rules/SecurityTestWatcher.java | 2 +- .../test/plugin/UserInjectorPlugin.java | 16 +- 236 files changed, 7670 insertions(+), 6451 deletions(-) diff --git a/build.gradle b/build.gradle index bd2eba30ba..94f62ab567 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,7 @@ */ +import com.diffplug.gradle.spotless.JavaExtension import org.opensearch.gradle.test.RestIntegTestTask buildscript { @@ -51,7 +52,7 @@ plugins { id 'idea' id 'jacoco' id 'maven-publish' - id 'com.diffplug.spotless' version '6.18.0' + id 'com.diffplug.spotless' version '6.19.0' id 'checkstyle' id 'com.netflix.nebula.ospackage' version "11.1.0" id "org.gradle.test-retry" version "1.5.2" @@ -69,7 +70,56 @@ apply plugin: 'opensearch.opensearchplugin' apply plugin: 'opensearch.pluginzip' apply plugin: 'opensearch.rest-test' apply plugin: 'opensearch.testclusters' -apply from: 'gradle/formatting.gradle' +//apply from: 'gradle/formatting.gradle' + +spotless { + java { + // Normally this isn't necessary, but we have Java sources in + // non-standard places + target '**/com/amazon/dlic/**/*.java' + + removeUnusedImports() + eclipse().configFile rootProject.file('formatter/formatterConfig.xml') + trimTrailingWhitespace() + endWithNewline(); + + // note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports + importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') + + custom 'Refuse wildcard imports', { + // Wildcard imports can't be resolved; fail the build + if (it =~ /\s+import .*\*;/) { + throw new AssertionError("Do not use wildcard imports. 'spotlessApply' cannot resolve this issue.") + } + } + + // See DEVELOPER_GUIDE.md for details of when to enable this. + if (System.getProperty('spotless.paddedcell') != null) { + paddedCell() + } + } + format 'misc', { + target '*.md', '*.gradle', '**/*.json', '**/*.yaml', '**/*.yml', '**/*.svg' + + trimTrailingWhitespace() + endWithNewline() + } + format('javaFoo', JavaExtension) { + + importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') + target '**/*.java' + targetExclude '**/com/amazon/dlic/**/*.java' + targetExclude('src/integrationTest/**') + + trimTrailingWhitespace() + endWithNewline(); + } + format("integrationTest", JavaExtension) { + target('src/integrationTest/java/**/*.java') + importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') + indentWithTabs(4) + } +} licenseFile = rootProject.file('LICENSE.txt') noticeFile = rootProject.file('NOTICE.txt') diff --git a/bwc-test/src/test/java/SecurityBackwardsCompatibilityIT.java b/bwc-test/src/test/java/SecurityBackwardsCompatibilityIT.java index 1afc1b88d5..d3c3658245 100644 --- a/bwc-test/src/test/java/SecurityBackwardsCompatibilityIT.java +++ b/bwc-test/src/test/java/SecurityBackwardsCompatibilityIT.java @@ -12,17 +12,17 @@ import java.util.Set; import java.util.stream.Collectors; -import org.junit.Assume; +import com.google.common.collect.ImmutableMap; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; + +import org.opensearch.Version; import org.opensearch.client.Response; import org.opensearch.common.settings.Settings; import org.opensearch.rest.RestStatus; import org.opensearch.test.rest.OpenSearchRestTestCase; -import org.opensearch.Version; -import com.google.common.collect.ImmutableMap; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItem; diff --git a/gradle/formatting.gradle b/gradle/formatting.gradle index 1851438039..40ae51afb1 100644 --- a/gradle/formatting.gradle +++ b/gradle/formatting.gradle @@ -1,36 +1,95 @@ -allprojects { - project.apply plugin: "com.diffplug.spotless" - spotless { - java { - // Normally this isn't necessary, but we have Java sources in - // non-standard places - target '*/com/amazon/dlic/auth/**/*.java' - - removeUnusedImports() - eclipse().configFile rootProject.file('formatter/formatterConfig.xml') - trimTrailingWhitespace() - endWithNewline(); - - // note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports - importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') - - custom 'Refuse wildcard imports', { - // Wildcard imports can't be resolved; fail the build - if (it =~ /\s+import .*\*;/) { - throw new AssertionError("Do not use wildcard imports. 'spotlessApply' cannot resolve this issue.") - } - } - - // See DEVELOPER_GUIDE.md for details of when to enable this. - if (System.getProperty('spotless.paddedcell') != null) { - paddedCell() - } - } - format 'misc', { - target '*.md', '*.gradle', '**/*.json', '**/*.yaml', '**/*.yml', '**/*.svg' - - trimTrailingWhitespace() - endWithNewline() - } - } +/* +* 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. +*/ + +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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. + */ + +import org.opensearch.gradle.BuildPlugin + +/* + * This script plugin configures formatting for Java source using Spotless + * for Gradle. Since the act of formatting existing source can interfere + * with developers' workflows, we don't automatically format all code + * (yet). Instead, we maintain a list of projects that are excluded from + * formatting, until we reach a point where we can comfortably format them + * in one go without too much disruption. + * + * Any new sub-projects must not be added to the exclusions list! + * + * To perform a reformat, run: + * + * ./gradlew spotlessApply + * + * To check the current format, run: + * + * ./gradlew spotlessJavaCheck + * + * This is also carried out by the `precommit` task. + * + * For more about Spotless, see: + * + * https://github.com/diffplug/spotless/tree/master/plugin-gradle + */ + +org.opensearch.gradle.BuildPlugin { + plugins.withType(BuildPlugin).whenPluginAdded { + project.apply plugin: "com.diffplug.spotless" + + spotless { + java { + // Normally this isn't necessary, but we have Java sources in + // non-standard places + target '**/*.java' + + removeUnusedImports() + eclipse().configFile rootProject.file('buildSrc/formatterConfig.xml') + trimTrailingWhitespace() + endWithNewline() + + custom 'Refuse wildcard imports', { + // Wildcard imports can't be resolved; fail the build + if (it =~ /\s+import .*\*;/) { + throw new AssertionError("Do not use wildcard imports. 'spotlessApply' cannot resolve this issue.") + } + } + + // See DEVELOPER_GUIDE.md for details of when to enable this. + if (System.getProperty('spotless.paddedcell') != null) { + paddedCell() + } + } + format 'misc', { + target '*.md', '*.gradle', '**/*.yaml', '**/*.yml', '**/*.svg' + + trimTrailingWhitespace() + endWithNewline() + } + } + + precommit.dependsOn 'spotlessJavaCheck' + } } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java index bef819effd..ffe9db81f2 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java @@ -59,7 +59,7 @@ public abstract class AbstractHTTPJwtAuthenticator implements HTTPAuthenticator private final String requiredIssuer; public static final int DEFAULT_CLOCK_SKEW_TOLERANCE_SECONDS = 30; - private final int clockSkewToleranceSeconds ; + private final int clockSkewToleranceSeconds; public AbstractHTTPJwtAuthenticator(Settings settings, Path configPath) { jwtUrlParameter = settings.get("jwt_url_parameter"); @@ -83,8 +83,7 @@ public AbstractHTTPJwtAuthenticator(Settings settings, Path configPath) { @Override @SuppressWarnings("removal") - public AuthCredentials extractCredentials(RestRequest request, ThreadContext context) - throws OpenSearchSecurityException { + public AuthCredentials extractCredentials(RestRequest request, ThreadContext context) throws OpenSearchSecurityException { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { @@ -186,8 +185,11 @@ public String extractSubject(JwtClaims claims) { // warning if (!(subjectObject instanceof String)) { log.warn( - "Expected type String for roles in the JWT for subject_key {}, but value was '{}' ({}). Will convert this value to String.", - subjectKey, subjectObject, subjectObject.getClass()); + "Expected type String for roles in the JWT for subject_key {}, but value was '{}' ({}). Will convert this value to String.", + subjectKey, + subjectObject, + subjectObject.getClass() + ); subject = String.valueOf(subjectObject); } else { subject = (String) subjectObject; @@ -207,8 +209,9 @@ public String[] extractRoles(JwtClaims claims) { if (rolesObject == null) { log.warn( - "Failed to get roles from JWT claims with roles_key '{}'. Check if this key is correct and available in the JWT payload.", - rolesKey); + "Failed to get roles from JWT claims with roles_key '{}'. Check if this key is correct and available in the JWT payload.", + rolesKey + ); return new String[0]; } @@ -218,8 +221,11 @@ public String[] extractRoles(JwtClaims claims) { // String but issue a warning if (!(rolesObject instanceof String) && !(rolesObject instanceof Collection)) { log.warn( - "Expected type String or Collection for roles in the JWT for roles_key {}, but value was '{}' ({}). Will convert this value to String.", - rolesKey, rolesObject, rolesObject.getClass()); + "Expected type String or Collection for roles in the JWT for roles_key {}, but value was '{}' ({}). Will convert this value to String.", + rolesKey, + rolesObject, + rolesObject.getClass() + ); } else if (rolesObject instanceof Collection) { roles = ((Collection) rolesObject).toArray(new String[0]); } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticator.java index 16cc71ffbd..3468bb89af 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticator.java @@ -68,7 +68,7 @@ public HTTPJwtAuthenticator(final Settings settings, final Path configPath) { try { String signingKey = settings.get("signing_key"); - if(signingKey == null || signingKey.length() == 0) { + if (signingKey == null || signingKey.length() == 0) { log.error("signingKey must not be null or empty. JWT authentication will not work"); } else { @@ -90,7 +90,7 @@ public HTTPJwtAuthenticator(final Settings settings, final Path configPath) { log.debug("No public ECDSA key, try other algos ({})", e.toString()); } - if(key != null) { + if (key != null) { _jwtParser = Jwts.parser().setSigningKey(key); } else { _jwtParser = Jwts.parser().setSigningKey(decoded); @@ -121,7 +121,6 @@ public HTTPJwtAuthenticator(final Settings settings, final Path configPath) { jwtParser = _jwtParser; } - @Override @SuppressWarnings("removal") public AuthCredentials extractCredentials(RestRequest request, ThreadContext context) throws OpenSearchSecurityException { @@ -152,25 +151,29 @@ private AuthCredentials extractCredentials0(final RestRequest request) { jwtToken = null; } - if((jwtToken == null || jwtToken.isEmpty()) && jwtUrlParameter != null) { + if ((jwtToken == null || jwtToken.isEmpty()) && jwtUrlParameter != null) { jwtToken = request.param(jwtUrlParameter); } else { - //just consume to avoid "contains unrecognized parameter" + // just consume to avoid "contains unrecognized parameter" request.param(jwtUrlParameter); } if (jwtToken == null || jwtToken.length() == 0) { - if(log.isDebugEnabled()) { - log.debug("No JWT token found in '{}' {} header", jwtUrlParameter==null?jwtHeaderName:jwtUrlParameter, jwtUrlParameter==null?"header":"url parameter"); + if (log.isDebugEnabled()) { + log.debug( + "No JWT token found in '{}' {} header", + jwtUrlParameter == null ? jwtHeaderName : jwtUrlParameter, + jwtUrlParameter == null ? "header" : "url parameter" + ); } return null; } final int index; - if((index = jwtToken.toLowerCase().indexOf(BEARER)) > -1) { //detect Bearer - jwtToken = jwtToken.substring(index+BEARER.length()); + if ((index = jwtToken.toLowerCase().indexOf(BEARER)) > -1) { // detect Bearer + jwtToken = jwtToken.substring(index + BEARER.length()); } else { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("No Bearer scheme found in header"); } } @@ -181,16 +184,16 @@ private AuthCredentials extractCredentials0(final RestRequest request) { final String subject = extractSubject(claims, request); if (subject == null) { - log.error("No subject found in JWT token"); - return null; + log.error("No subject found in JWT token"); + return null; } final String[] roles = extractRoles(claims, request); final AuthCredentials ac = new AuthCredentials(subject, roles).markComplete(); - for(Entry claim: claims.entrySet()) { - ac.addAttribute("attr.jwt."+claim.getKey(), String.valueOf(claim.getValue())); + for (Entry claim : claims.entrySet()) { + ac.addAttribute("attr.jwt." + claim.getKey(), String.valueOf(claim.getValue())); } return ac; @@ -199,7 +202,7 @@ private AuthCredentials extractCredentials0(final RestRequest request) { log.error("Cannot authenticate user with JWT because of ", e); return null; } catch (Exception e) { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("Invalid or expired JWT token.", e); } return null; @@ -208,7 +211,7 @@ private AuthCredentials extractCredentials0(final RestRequest request) { @Override public boolean reRequestAuthentication(final RestChannel channel, AuthCredentials creds) { - final BytesRestResponse wwwAuthenticateResponse = new BytesRestResponse(RestStatus.UNAUTHORIZED,""); + final BytesRestResponse wwwAuthenticateResponse = new BytesRestResponse(RestStatus.UNAUTHORIZED, ""); wwwAuthenticateResponse.addHeader("WWW-Authenticate", "Bearer realm=\"OpenSearch Security\""); channel.sendResponse(wwwAuthenticateResponse); return true; @@ -221,16 +224,21 @@ public String getType() { protected String extractSubject(final Claims claims, final RestRequest request) { String subject = claims.getSubject(); - if(subjectKey != null) { - // try to get roles from claims, first as Object to avoid having to catch the ExpectedTypeException + if (subjectKey != null) { + // try to get roles from claims, first as Object to avoid having to catch the ExpectedTypeException Object subjectObject = claims.get(subjectKey, Object.class); - if(subjectObject == null) { + if (subjectObject == null) { log.warn("Failed to get subject from JWT claims, check if subject_key '{}' is correct.", subjectKey); return null; } - // We expect a String. If we find something else, convert to String but issue a warning - if(!(subjectObject instanceof String)) { - log.warn("Expected type String for roles in the JWT for subject_key {}, but value was '{}' ({}). Will convert this value to String.", subjectKey, subjectObject, subjectObject.getClass()); + // We expect a String. If we find something else, convert to String but issue a warning + if (!(subjectObject instanceof String)) { + log.warn( + "Expected type String for roles in the JWT for subject_key {}, but value was '{}' ({}). Will convert this value to String.", + subjectKey, + subjectObject, + subjectObject.getClass() + ); } subject = String.valueOf(subjectObject); } @@ -239,34 +247,43 @@ protected String extractSubject(final Claims claims, final RestRequest request) @SuppressWarnings("unchecked") protected String[] extractRoles(final Claims claims, final RestRequest request) { - // no roles key specified - if(rolesKey == null) { - return new String[0]; - } - // try to get roles from claims, first as Object to avoid having to catch the ExpectedTypeException - final Object rolesObject = claims.get(rolesKey, Object.class); - if(rolesObject == null) { - log.warn("Failed to get roles from JWT claims with roles_key '{}'. Check if this key is correct and available in the JWT payload.", rolesKey); - return new String[0]; - } - - String[] roles = String.valueOf(rolesObject).split(","); - - // We expect a String or Collection. If we find something else, convert to String but issue a warning - if (!(rolesObject instanceof String) && !(rolesObject instanceof Collection)) { - log.warn("Expected type String or Collection for roles in the JWT for roles_key {}, but value was '{}' ({}). Will convert this value to String.", rolesKey, rolesObject, rolesObject.getClass()); - } else if (rolesObject instanceof Collection) { - roles = ((Collection) rolesObject).toArray(new String[0]); - } - - for (int i = 0; i < roles.length; i++) { - roles[i] = roles[i].trim(); - } - - return roles; + // no roles key specified + if (rolesKey == null) { + return new String[0]; + } + // try to get roles from claims, first as Object to avoid having to catch the ExpectedTypeException + final Object rolesObject = claims.get(rolesKey, Object.class); + if (rolesObject == null) { + log.warn( + "Failed to get roles from JWT claims with roles_key '{}'. Check if this key is correct and available in the JWT payload.", + rolesKey + ); + return new String[0]; + } + + String[] roles = String.valueOf(rolesObject).split(","); + + // We expect a String or Collection. If we find something else, convert to String but issue a warning + if (!(rolesObject instanceof String) && !(rolesObject instanceof Collection)) { + log.warn( + "Expected type String or Collection for roles in the JWT for roles_key {}, but value was '{}' ({}). Will convert this value to String.", + rolesKey, + rolesObject, + rolesObject.getClass() + ); + } else if (rolesObject instanceof Collection) { + roles = ((Collection) rolesObject).toArray(new String[0]); + } + + for (int i = 0; i < roles.length; i++) { + roles[i] = roles[i].trim(); + } + + return roles; } - private static PublicKey getPublicKey(final byte[] keyBytes, final String algo) throws NoSuchAlgorithmException, InvalidKeySpecException { + private static PublicKey getPublicKey(final byte[] keyBytes, final String algo) throws NoSuchAlgorithmException, + InvalidKeySpecException { X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance(algo); return kf.generatePublic(spec); diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/AuthenticatorUnavailableException.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/AuthenticatorUnavailableException.java index d9aa1aebb6..b17663b429 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/AuthenticatorUnavailableException.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/AuthenticatorUnavailableException.java @@ -12,27 +12,26 @@ package com.amazon.dlic.auth.http.jwt.keybyoidc; public class AuthenticatorUnavailableException extends RuntimeException { - private static final long serialVersionUID = -7007025852090301416L; + private static final long serialVersionUID = -7007025852090301416L; - public AuthenticatorUnavailableException() { - super(); - } + public AuthenticatorUnavailableException() { + super(); + } - public AuthenticatorUnavailableException(String message, Throwable cause, boolean enableSuppression, - boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } + public AuthenticatorUnavailableException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } - public AuthenticatorUnavailableException(String message, Throwable cause) { - super(message, cause); - } + public AuthenticatorUnavailableException(String message, Throwable cause) { + super(message, cause); + } - public AuthenticatorUnavailableException(String message) { - super(message); - } + public AuthenticatorUnavailableException(String message) { + super(message); + } - public AuthenticatorUnavailableException(Throwable cause) { - super(cause); - } + public AuthenticatorUnavailableException(Throwable cause) { + super(cause); + } } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/BadCredentialsException.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/BadCredentialsException.java index 12b9195c0e..0d705f98cf 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/BadCredentialsException.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/BadCredentialsException.java @@ -13,26 +13,25 @@ public class BadCredentialsException extends Exception { - private static final long serialVersionUID = 9092575587366580869L; + private static final long serialVersionUID = 9092575587366580869L; - public BadCredentialsException() { - super(); - } + public BadCredentialsException() { + super(); + } - public BadCredentialsException(String message, Throwable cause, boolean enableSuppression, - boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } + public BadCredentialsException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } - public BadCredentialsException(String message, Throwable cause) { - super(message, cause); - } + public BadCredentialsException(String message, Throwable cause) { + super(message, cause); + } - public BadCredentialsException(String message) { - super(message); - } + public BadCredentialsException(String message) { + super(message); + } - public BadCredentialsException(Throwable cause) { - super(cause); - } + public BadCredentialsException(Throwable cause) { + super(cause); + } } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticator.java index b6738b725b..808abfc5ea 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticator.java @@ -20,48 +20,54 @@ public class HTTPJwtKeyByOpenIdConnectAuthenticator extends AbstractHTTPJwtAuthenticator { - //private final static Logger log = LogManager.getLogger(HTTPJwtKeyByOpenIdConnectAuthenticator.class); - - public HTTPJwtKeyByOpenIdConnectAuthenticator(Settings settings, Path configPath) { - super(settings, configPath); - } - - protected KeyProvider initKeyProvider(Settings settings, Path configPath) throws Exception { - int idpRequestTimeoutMs = settings.getAsInt("idp_request_timeout_ms", 5000); - int idpQueuedThreadTimeoutMs = settings.getAsInt("idp_queued_thread_timeout_ms", 2500); - - int refreshRateLimitTimeWindowMs = settings.getAsInt("refresh_rate_limit_time_window_ms", 10000); - int refreshRateLimitCount = settings.getAsInt("refresh_rate_limit_count", 10); - String jwksUri = settings.get("jwks_uri"); - - KeySetRetriever keySetRetriever; - if(jwksUri != null && !jwksUri.isBlank()) { - keySetRetriever = - new KeySetRetriever(getSSLConfig(settings, configPath), settings.getAsBoolean("cache_jwks_endpoint", false), jwksUri); - } else { - keySetRetriever = new KeySetRetriever(settings.get("openid_connect_url"), getSSLConfig(settings, configPath), settings.getAsBoolean("cache_jwks_endpoint", false)); - } - - keySetRetriever.setRequestTimeoutMs(idpRequestTimeoutMs); - - SelfRefreshingKeySet selfRefreshingKeySet = new SelfRefreshingKeySet(keySetRetriever); - - selfRefreshingKeySet.setRequestTimeoutMs(idpRequestTimeoutMs); - selfRefreshingKeySet.setQueuedThreadTimeoutMs(idpQueuedThreadTimeoutMs); - selfRefreshingKeySet.setRefreshRateLimitTimeWindowMs(refreshRateLimitTimeWindowMs); - selfRefreshingKeySet.setRefreshRateLimitCount(refreshRateLimitCount); - - return selfRefreshingKeySet; - } - - private static SettingsBasedSSLConfigurator.SSLConfig getSSLConfig(Settings settings, Path configPath) - throws Exception { - return new SettingsBasedSSLConfigurator(settings, configPath, "openid_connect_idp").buildSSLConfig(); - } - - @Override - public String getType() { - return "jwt-key-by-oidc"; - } + // private final static Logger log = LogManager.getLogger(HTTPJwtKeyByOpenIdConnectAuthenticator.class); + + public HTTPJwtKeyByOpenIdConnectAuthenticator(Settings settings, Path configPath) { + super(settings, configPath); + } + + protected KeyProvider initKeyProvider(Settings settings, Path configPath) throws Exception { + int idpRequestTimeoutMs = settings.getAsInt("idp_request_timeout_ms", 5000); + int idpQueuedThreadTimeoutMs = settings.getAsInt("idp_queued_thread_timeout_ms", 2500); + + int refreshRateLimitTimeWindowMs = settings.getAsInt("refresh_rate_limit_time_window_ms", 10000); + int refreshRateLimitCount = settings.getAsInt("refresh_rate_limit_count", 10); + String jwksUri = settings.get("jwks_uri"); + + KeySetRetriever keySetRetriever; + if (jwksUri != null && !jwksUri.isBlank()) { + keySetRetriever = new KeySetRetriever( + getSSLConfig(settings, configPath), + settings.getAsBoolean("cache_jwks_endpoint", false), + jwksUri + ); + } else { + keySetRetriever = new KeySetRetriever( + settings.get("openid_connect_url"), + getSSLConfig(settings, configPath), + settings.getAsBoolean("cache_jwks_endpoint", false) + ); + } + + keySetRetriever.setRequestTimeoutMs(idpRequestTimeoutMs); + + SelfRefreshingKeySet selfRefreshingKeySet = new SelfRefreshingKeySet(keySetRetriever); + + selfRefreshingKeySet.setRequestTimeoutMs(idpRequestTimeoutMs); + selfRefreshingKeySet.setQueuedThreadTimeoutMs(idpQueuedThreadTimeoutMs); + selfRefreshingKeySet.setRefreshRateLimitTimeWindowMs(refreshRateLimitTimeWindowMs); + selfRefreshingKeySet.setRefreshRateLimitCount(refreshRateLimitCount); + + return selfRefreshingKeySet; + } + + private static SettingsBasedSSLConfigurator.SSLConfig getSSLConfig(Settings settings, Path configPath) throws Exception { + return new SettingsBasedSSLConfigurator(settings, configPath, "openid_connect_idp").buildSSLConfig(); + } + + @Override + public String getType() { + return "jwt-key-by-oidc"; + } } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java index a337224cdc..5893d623a7 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java @@ -29,107 +29,106 @@ public class JwtVerifier { - private final static Logger log = LogManager.getLogger(JwtVerifier.class); - - private final KeyProvider keyProvider; - private final int clockSkewToleranceSeconds; - private final String requiredIssuer; - private final String requiredAudience; - - public JwtVerifier(KeyProvider keyProvider, int clockSkewToleranceSeconds, String requiredIssuer, String requiredAudience) { - this.keyProvider = keyProvider; - this.clockSkewToleranceSeconds = clockSkewToleranceSeconds; - this.requiredIssuer = requiredIssuer; - this.requiredAudience = requiredAudience; - } - - public JwtToken getVerifiedJwtToken(String encodedJwt) throws BadCredentialsException { - try { - JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(encodedJwt); - JwtToken jwt = jwtConsumer.getJwtToken(); - - String escapedKid = jwt.getJwsHeaders().getKeyId(); - String kid = escapedKid; - if (!Strings.isNullOrEmpty(kid)) { - kid = StringEscapeUtils.unescapeJava(escapedKid); - } - JsonWebKey key = keyProvider.getKey(kid); - - // Algorithm is not mandatory for the key material, so we set it to the same as the JWT - if (key.getAlgorithm() == null && key.getPublicKeyUse() == PublicKeyUse.SIGN && key.getKeyType() == KeyType.RSA) - { - key.setAlgorithm(jwt.getJwsHeaders().getAlgorithm()); - } - - JwsSignatureVerifier signatureVerifier = getInitializedSignatureVerifier(key, jwt); - - - boolean signatureValid = jwtConsumer.verifySignatureWith(signatureVerifier); - - if (!signatureValid && Strings.isNullOrEmpty(kid)) { - key = keyProvider.getKeyAfterRefresh(null); - signatureVerifier = getInitializedSignatureVerifier(key, jwt); - signatureValid = jwtConsumer.verifySignatureWith(signatureVerifier); - } - - if (!signatureValid) { - throw new BadCredentialsException("Invalid JWT signature"); - } - - validateClaims(jwt); - - return jwt; - } catch (JwtException e) { - throw new BadCredentialsException(e.getMessage(), e); - } - } + private final static Logger log = LogManager.getLogger(JwtVerifier.class); + + private final KeyProvider keyProvider; + private final int clockSkewToleranceSeconds; + private final String requiredIssuer; + private final String requiredAudience; + + public JwtVerifier(KeyProvider keyProvider, int clockSkewToleranceSeconds, String requiredIssuer, String requiredAudience) { + this.keyProvider = keyProvider; + this.clockSkewToleranceSeconds = clockSkewToleranceSeconds; + this.requiredIssuer = requiredIssuer; + this.requiredAudience = requiredAudience; + } + + public JwtToken getVerifiedJwtToken(String encodedJwt) throws BadCredentialsException { + try { + JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(encodedJwt); + JwtToken jwt = jwtConsumer.getJwtToken(); + + String escapedKid = jwt.getJwsHeaders().getKeyId(); + String kid = escapedKid; + if (!Strings.isNullOrEmpty(kid)) { + kid = StringEscapeUtils.unescapeJava(escapedKid); + } + JsonWebKey key = keyProvider.getKey(kid); + + // Algorithm is not mandatory for the key material, so we set it to the same as the JWT + if (key.getAlgorithm() == null && key.getPublicKeyUse() == PublicKeyUse.SIGN && key.getKeyType() == KeyType.RSA) { + key.setAlgorithm(jwt.getJwsHeaders().getAlgorithm()); + } + + JwsSignatureVerifier signatureVerifier = getInitializedSignatureVerifier(key, jwt); + + boolean signatureValid = jwtConsumer.verifySignatureWith(signatureVerifier); + + if (!signatureValid && Strings.isNullOrEmpty(kid)) { + key = keyProvider.getKeyAfterRefresh(null); + signatureVerifier = getInitializedSignatureVerifier(key, jwt); + signatureValid = jwtConsumer.verifySignatureWith(signatureVerifier); + } + + if (!signatureValid) { + throw new BadCredentialsException("Invalid JWT signature"); + } + + validateClaims(jwt); + + return jwt; + } catch (JwtException e) { + throw new BadCredentialsException(e.getMessage(), e); + } + } private void validateSignatureAlgorithm(JsonWebKey key, JwtToken jwt) throws BadCredentialsException { if (Strings.isNullOrEmpty(key.getAlgorithm())) { return; } - SignatureAlgorithm keyAlgorithm =SignatureAlgorithm.getAlgorithm(key.getAlgorithm()); + SignatureAlgorithm keyAlgorithm = SignatureAlgorithm.getAlgorithm(key.getAlgorithm()); SignatureAlgorithm tokenAlgorithm = SignatureAlgorithm.getAlgorithm(jwt.getJwsHeaders().getAlgorithm()); if (!keyAlgorithm.equals(tokenAlgorithm)) { - throw new BadCredentialsException("Algorithm of JWT does not match algorithm of JWK (" + keyAlgorithm + " != " + tokenAlgorithm + ")"); + throw new BadCredentialsException( + "Algorithm of JWT does not match algorithm of JWK (" + keyAlgorithm + " != " + tokenAlgorithm + ")" + ); } } + private JwsSignatureVerifier getInitializedSignatureVerifier(JsonWebKey key, JwtToken jwt) throws BadCredentialsException, + JwtException { - private JwsSignatureVerifier getInitializedSignatureVerifier(JsonWebKey key, JwtToken jwt) - throws BadCredentialsException, JwtException { - - validateSignatureAlgorithm(key, jwt); + validateSignatureAlgorithm(key, jwt); JwsSignatureVerifier result = JwsUtils.getSignatureVerifier(key, jwt.getJwsHeaders().getSignatureAlgorithm()); - if (result == null) { - throw new BadCredentialsException("Cannot verify JWT"); - } else { - return result; - } - } - - private void validateClaims(JwtToken jwt) throws JwtException { - JwtClaims claims = jwt.getClaims(); - - if (claims != null) { - JwtUtils.validateJwtExpiry(claims, clockSkewToleranceSeconds, false); - JwtUtils.validateJwtNotBefore(claims, clockSkewToleranceSeconds, false); - validateRequiredAudienceAndIssuer(claims); - } - } - - private void validateRequiredAudienceAndIssuer(JwtClaims claims) { - String audience = claims.getAudience(); - String issuer = claims.getIssuer(); - - if (!Strings.isNullOrEmpty(requiredAudience) && !requiredAudience.equals(audience)) { - throw new JwtException("Invalid audience"); - } - - if (!Strings.isNullOrEmpty(requiredIssuer) && !requiredIssuer.equals(issuer)) { - throw new JwtException("Invalid issuer"); - } - } + if (result == null) { + throw new BadCredentialsException("Cannot verify JWT"); + } else { + return result; + } + } + + private void validateClaims(JwtToken jwt) throws JwtException { + JwtClaims claims = jwt.getClaims(); + + if (claims != null) { + JwtUtils.validateJwtExpiry(claims, clockSkewToleranceSeconds, false); + JwtUtils.validateJwtNotBefore(claims, clockSkewToleranceSeconds, false); + validateRequiredAudienceAndIssuer(claims); + } + } + + private void validateRequiredAudienceAndIssuer(JwtClaims claims) { + String audience = claims.getAudience(); + String issuer = claims.getIssuer(); + + if (!Strings.isNullOrEmpty(requiredAudience) && !requiredAudience.equals(audience)) { + throw new JwtException("Invalid audience"); + } + + if (!Strings.isNullOrEmpty(requiredIssuer) && !requiredIssuer.equals(issuer)) { + throw new JwtException("Invalid issuer"); + } + } } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeyProvider.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeyProvider.java index 5eff7cb213..a0e76c918f 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeyProvider.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeyProvider.java @@ -14,6 +14,7 @@ import org.apache.cxf.rs.security.jose.jwk.JsonWebKey; public interface KeyProvider { - public JsonWebKey getKey(String kid) throws AuthenticatorUnavailableException, BadCredentialsException; - public JsonWebKey getKeyAfterRefresh(String kid) throws AuthenticatorUnavailableException, BadCredentialsException; + public JsonWebKey getKey(String kid) throws AuthenticatorUnavailableException, BadCredentialsException; + + public JsonWebKey getKeyAfterRefresh(String kid) throws AuthenticatorUnavailableException, BadCredentialsException; } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetProvider.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetProvider.java index edbe39f020..53ea0237db 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetProvider.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetProvider.java @@ -15,5 +15,5 @@ @FunctionalInterface public interface KeySetProvider { - JsonWebKeys get() throws AuthenticatorUnavailableException; + JsonWebKeys get() throws AuthenticatorUnavailableException; } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java index f56c0dd90c..9ef50a4404 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java @@ -39,205 +39,218 @@ import org.opensearch.security.DefaultObjectMapper; - public class KeySetRetriever implements KeySetProvider { - private final static Logger log = LogManager.getLogger(KeySetRetriever.class); - private static final long CACHE_STATUS_LOG_INTERVAL_MS = 60L * 60L * 1000L; - - private String openIdConnectEndpoint; - private SSLConfig sslConfig; - private int requestTimeoutMs = 10000; - private CacheConfig cacheConfig; - private HttpCacheStorage oidcHttpCacheStorage; - private int oidcCacheHits = 0; - private int oidcCacheMisses = 0; - private int oidcCacheHitsValidated = 0; - private int oidcCacheModuleResponses = 0; - private long oidcRequests = 0; - private long lastCacheStatusLog = 0; - private String jwksUri; - - KeySetRetriever(String openIdConnectEndpoint, SSLConfig sslConfig, boolean useCacheForOidConnectEndpoint) { - this.openIdConnectEndpoint = openIdConnectEndpoint; - this.sslConfig = sslConfig; - - configureCache(useCacheForOidConnectEndpoint); - } - - KeySetRetriever(SSLConfig sslConfig, boolean useCacheForOidConnectEndpoint, String jwksUri) { - this.jwksUri = jwksUri; - this.sslConfig = sslConfig; - - configureCache(useCacheForOidConnectEndpoint); - } - - public JsonWebKeys get() throws AuthenticatorUnavailableException { - String uri = getJwksUri(); - - try (CloseableHttpClient httpClient = createHttpClient(null)) { - - HttpGet httpGet = new HttpGet(uri); - - RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(getRequestTimeoutMs(), TimeUnit.MILLISECONDS) - .setConnectTimeout(getRequestTimeoutMs(), TimeUnit.MILLISECONDS).build(); - - httpGet.setConfig(requestConfig); - - try (CloseableHttpResponse response = httpClient.execute(httpGet)) { - if (response.getCode() < 200 || response.getCode() >= 300) { - throw new AuthenticatorUnavailableException("Error while getting " + uri + ": " + response.getReasonPhrase()); - } - - HttpEntity httpEntity = response.getEntity(); - - if (httpEntity == null) { - throw new AuthenticatorUnavailableException( - "Error while getting " + uri + ": Empty response entity"); - } + private final static Logger log = LogManager.getLogger(KeySetRetriever.class); + private static final long CACHE_STATUS_LOG_INTERVAL_MS = 60L * 60L * 1000L; - JsonWebKeys keySet = JwkUtils.readJwkSet(httpEntity.getContent()); + private String openIdConnectEndpoint; + private SSLConfig sslConfig; + private int requestTimeoutMs = 10000; + private CacheConfig cacheConfig; + private HttpCacheStorage oidcHttpCacheStorage; + private int oidcCacheHits = 0; + private int oidcCacheMisses = 0; + private int oidcCacheHitsValidated = 0; + private int oidcCacheModuleResponses = 0; + private long oidcRequests = 0; + private long lastCacheStatusLog = 0; + private String jwksUri; - return keySet; - } - } catch (IOException e) { - throw new AuthenticatorUnavailableException("Error while getting " + uri + ": " + e, e); - } + KeySetRetriever(String openIdConnectEndpoint, SSLConfig sslConfig, boolean useCacheForOidConnectEndpoint) { + this.openIdConnectEndpoint = openIdConnectEndpoint; + this.sslConfig = sslConfig; - } + configureCache(useCacheForOidConnectEndpoint); + } - String getJwksUri() throws AuthenticatorUnavailableException { + KeySetRetriever(SSLConfig sslConfig, boolean useCacheForOidConnectEndpoint, String jwksUri) { + this.jwksUri = jwksUri; + this.sslConfig = sslConfig; - if (!Strings.isNullOrEmpty(jwksUri)) { - return jwksUri; - } + configureCache(useCacheForOidConnectEndpoint); + } - if (Strings.isNullOrEmpty(openIdConnectEndpoint)) { - throw new AuthenticatorUnavailableException("Either openid_connect_url or jwks_uri must be configured for OIDC Authentication backend"); - } + public JsonWebKeys get() throws AuthenticatorUnavailableException { + String uri = getJwksUri(); - try (CloseableHttpClient httpClient = createHttpClient(oidcHttpCacheStorage)) { + try (CloseableHttpClient httpClient = createHttpClient(null)) { - HttpGet httpGet = new HttpGet(openIdConnectEndpoint); + HttpGet httpGet = new HttpGet(uri); - RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(getRequestTimeoutMs(), TimeUnit.MILLISECONDS) - .setConnectTimeout(getRequestTimeoutMs(), TimeUnit.MILLISECONDS).build(); + RequestConfig requestConfig = RequestConfig.custom() + .setConnectionRequestTimeout(getRequestTimeoutMs(), TimeUnit.MILLISECONDS) + .setConnectTimeout(getRequestTimeoutMs(), TimeUnit.MILLISECONDS) + .build(); - httpGet.setConfig(requestConfig); + httpGet.setConfig(requestConfig); - HttpCacheContext httpContext = null; + try (CloseableHttpResponse response = httpClient.execute(httpGet)) { + if (response.getCode() < 200 || response.getCode() >= 300) { + throw new AuthenticatorUnavailableException("Error while getting " + uri + ": " + response.getReasonPhrase()); + } - if (oidcHttpCacheStorage != null) { - httpContext = new HttpCacheContext(); - } + HttpEntity httpEntity = response.getEntity(); - try (CloseableHttpResponse response = httpClient.execute(httpGet, httpContext)) { - if (httpContext != null) { - logCacheResponseStatus(httpContext); - } + if (httpEntity == null) { + throw new AuthenticatorUnavailableException("Error while getting " + uri + ": Empty response entity"); + } - if (response.getCode() < 200 || response.getCode() >= 300) { - throw new AuthenticatorUnavailableException( - "Error while getting " + openIdConnectEndpoint + ": " + response.getReasonPhrase()); - } + JsonWebKeys keySet = JwkUtils.readJwkSet(httpEntity.getContent()); - HttpEntity httpEntity = response.getEntity(); + return keySet; + } + } catch (IOException e) { + throw new AuthenticatorUnavailableException("Error while getting " + uri + ": " + e, e); + } - if (httpEntity == null) { - throw new AuthenticatorUnavailableException( - "Error while getting " + openIdConnectEndpoint + ": Empty response entity"); - } + } + + String getJwksUri() throws AuthenticatorUnavailableException { - OpenIdProviderConfiguration parsedEntity = DefaultObjectMapper.objectMapper.readValue(httpEntity.getContent(), - OpenIdProviderConfiguration.class); + if (!Strings.isNullOrEmpty(jwksUri)) { + return jwksUri; + } - return parsedEntity.getJwksUri(); + if (Strings.isNullOrEmpty(openIdConnectEndpoint)) { + throw new AuthenticatorUnavailableException( + "Either openid_connect_url or jwks_uri must be configured for OIDC Authentication backend" + ); + } + + try (CloseableHttpClient httpClient = createHttpClient(oidcHttpCacheStorage)) { - } + HttpGet httpGet = new HttpGet(openIdConnectEndpoint); - } catch (IOException e) { - throw new AuthenticatorUnavailableException("Error while getting " + openIdConnectEndpoint + ": " + e, e); - } + RequestConfig requestConfig = RequestConfig.custom() + .setConnectionRequestTimeout(getRequestTimeoutMs(), TimeUnit.MILLISECONDS) + .setConnectTimeout(getRequestTimeoutMs(), TimeUnit.MILLISECONDS) + .build(); - } + httpGet.setConfig(requestConfig); - public int getRequestTimeoutMs() { - return requestTimeoutMs; - } + HttpCacheContext httpContext = null; - public void setRequestTimeoutMs(int httpTimeoutMs) { - this.requestTimeoutMs = httpTimeoutMs; - } + if (oidcHttpCacheStorage != null) { + httpContext = new HttpCacheContext(); + } - private void logCacheResponseStatus(HttpCacheContext httpContext) { - this.oidcRequests++; + try (CloseableHttpResponse response = httpClient.execute(httpGet, httpContext)) { + if (httpContext != null) { + logCacheResponseStatus(httpContext); + } - switch (httpContext.getCacheResponseStatus()) { - case CACHE_HIT: - this.oidcCacheHits++; - break; - case CACHE_MODULE_RESPONSE: - this.oidcCacheModuleResponses++; - break; - case CACHE_MISS: - this.oidcCacheMisses++; - break; - case VALIDATED: - this.oidcCacheHitsValidated++; - break; - } + if (response.getCode() < 200 || response.getCode() >= 300) { + throw new AuthenticatorUnavailableException( + "Error while getting " + openIdConnectEndpoint + ": " + response.getReasonPhrase() + ); + } + + HttpEntity httpEntity = response.getEntity(); - long now = System.currentTimeMillis(); + if (httpEntity == null) { + throw new AuthenticatorUnavailableException("Error while getting " + openIdConnectEndpoint + ": Empty response entity"); + } - if (this.oidcRequests >= 2 && now - lastCacheStatusLog > CACHE_STATUS_LOG_INTERVAL_MS) { - log.info("Cache status for KeySetRetriever:\noidcCacheHits: {}\noidcCacheHitsValidated: {}" - + "\noidcCacheModuleResponses: {}" + "\noidcCacheMisses: {}", oidcCacheHits, oidcCacheHitsValidated, oidcCacheModuleResponses, oidcCacheMisses); - lastCacheStatusLog = now; - } + OpenIdProviderConfiguration parsedEntity = DefaultObjectMapper.objectMapper.readValue( + httpEntity.getContent(), + OpenIdProviderConfiguration.class + ); - } + return parsedEntity.getJwksUri(); - private CloseableHttpClient createHttpClient(HttpCacheStorage httpCacheStorage) { - HttpClientBuilder builder; + } - if (httpCacheStorage != null) { - builder = CachingHttpClients.custom().setCacheConfig(cacheConfig).setHttpCacheStorage(httpCacheStorage); - } else { - builder = HttpClients.custom(); - } + } catch (IOException e) { + throw new AuthenticatorUnavailableException("Error while getting " + openIdConnectEndpoint + ": " + e, e); + } - builder.useSystemProperties(); + } - if (sslConfig != null) { - final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()) - .build(); + public int getRequestTimeoutMs() { + return requestTimeoutMs; + } - builder.setConnectionManager(cm); - } + public void setRequestTimeoutMs(int httpTimeoutMs) { + this.requestTimeoutMs = httpTimeoutMs; + } - return builder.build(); - } + private void logCacheResponseStatus(HttpCacheContext httpContext) { + this.oidcRequests++; - private void configureCache(boolean useCacheForOidConnectEndpoint) { - if (useCacheForOidConnectEndpoint) { - cacheConfig = CacheConfig.custom().setMaxCacheEntries(10).setMaxObjectSize(1024L * 1024L).build(); - oidcHttpCacheStorage = new BasicHttpCacheStorage(cacheConfig); - } - } - - public int getOidcCacheHits() { - return oidcCacheHits; - } - - public int getOidcCacheMisses() { - return oidcCacheMisses; - } - - public int getOidcCacheHitsValidated() { - return oidcCacheHitsValidated; - } - - public int getOidcCacheModuleResponses() { - return oidcCacheModuleResponses; - } + switch (httpContext.getCacheResponseStatus()) { + case CACHE_HIT: + this.oidcCacheHits++; + break; + case CACHE_MODULE_RESPONSE: + this.oidcCacheModuleResponses++; + break; + case CACHE_MISS: + this.oidcCacheMisses++; + break; + case VALIDATED: + this.oidcCacheHitsValidated++; + break; + } + + long now = System.currentTimeMillis(); + + if (this.oidcRequests >= 2 && now - lastCacheStatusLog > CACHE_STATUS_LOG_INTERVAL_MS) { + log.info( + "Cache status for KeySetRetriever:\noidcCacheHits: {}\noidcCacheHitsValidated: {}" + + "\noidcCacheModuleResponses: {}" + + "\noidcCacheMisses: {}", + oidcCacheHits, + oidcCacheHitsValidated, + oidcCacheModuleResponses, + oidcCacheMisses + ); + lastCacheStatusLog = now; + } + + } + + private CloseableHttpClient createHttpClient(HttpCacheStorage httpCacheStorage) { + HttpClientBuilder builder; + + if (httpCacheStorage != null) { + builder = CachingHttpClients.custom().setCacheConfig(cacheConfig).setHttpCacheStorage(httpCacheStorage); + } else { + builder = HttpClients.custom(); + } + + builder.useSystemProperties(); + + if (sslConfig != null) { + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()) + .build(); + + builder.setConnectionManager(cm); + } + + return builder.build(); + } + + private void configureCache(boolean useCacheForOidConnectEndpoint) { + if (useCacheForOidConnectEndpoint) { + cacheConfig = CacheConfig.custom().setMaxCacheEntries(10).setMaxObjectSize(1024L * 1024L).build(); + oidcHttpCacheStorage = new BasicHttpCacheStorage(cacheConfig); + } + } + + public int getOidcCacheHits() { + return oidcCacheHits; + } + + public int getOidcCacheMisses() { + return oidcCacheMisses; + } + + public int getOidcCacheHitsValidated() { + return oidcCacheHitsValidated; + } + + public int getOidcCacheModuleResponses() { + return oidcCacheModuleResponses; + } } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SelfRefreshingKeySet.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SelfRefreshingKeySet.java index 7e3cec8246..fe410b171c 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SelfRefreshingKeySet.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SelfRefreshingKeySet.java @@ -25,295 +25,300 @@ import org.apache.logging.log4j.Logger; public class SelfRefreshingKeySet implements KeyProvider { - private static final Logger log = LogManager.getLogger(SelfRefreshingKeySet.class); - - private final KeySetProvider keySetProvider; - private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 10, 1000, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue()); - private volatile JsonWebKeys jsonWebKeys = new JsonWebKeys(); - private boolean refreshInProgress = false; - private long refreshCount = 0; - private long queuedGetCount = 0; - private long recentRefreshCount = 0; - private long refreshTime = 0; - private Throwable lastRefreshFailure = null; - private int requestTimeoutMs = 5000; - private int queuedThreadTimeoutMs = 2500; - private int refreshRateLimitTimeWindowMs = 10000; - private int refreshRateLimitCount = 10; - - public SelfRefreshingKeySet(KeySetProvider refreshFunction) { - this.keySetProvider = refreshFunction; - } - - public JsonWebKey getKey(String kid) throws AuthenticatorUnavailableException, BadCredentialsException { - if (Strings.isNullOrEmpty(kid)) { - return getKeyWithoutKeyId(); - } else { - return getKeyWithKeyId(kid); - } - } - - public synchronized JsonWebKey getKeyAfterRefresh(String kid) - throws AuthenticatorUnavailableException, BadCredentialsException { - JsonWebKey result = getKeyAfterRefreshInternal(kid); - - if (result != null) { - return result; - } else if (jsonWebKeys.getKeys().size() == 0) { - throw new AuthenticatorUnavailableException("No JWK are available from IdP"); - } else { - throw new BadCredentialsException("JWT did not contain KID which is required if IdP provides multiple JWK"); - } - } - - private synchronized JsonWebKey getKeyAfterRefreshInternal(String kid) throws AuthenticatorUnavailableException { - if (refreshInProgress) { - return waitForRefreshToFinish(kid); - } else { - return performRefresh(kid); - } - } - - private JsonWebKey getKeyWithoutKeyId() throws AuthenticatorUnavailableException, BadCredentialsException { - List keys = jsonWebKeys.getKeys(); - - if (keys == null || keys.size() == 0) { - JsonWebKey result = getKeyWithRefresh(null); - - if (result != null) { - return result; - } else { - throw new AuthenticatorUnavailableException("No JWK are available from IdP"); - } - } else if (keys.size() == 1) { - return keys.get(0); - } else { - JsonWebKey result = getKeyWithRefresh(null); - - if (result != null) { - return result; - } else { - throw new BadCredentialsException( - "JWT did not contain KID which is required if IdP provides multiple JWK"); - } - } - } - - private JsonWebKey getKeyWithKeyId(String kid) throws AuthenticatorUnavailableException, BadCredentialsException { - JsonWebKey result = jsonWebKeys.getKey(kid); - - if (result != null) { - return result; - } - - result = getKeyWithRefresh(kid); - - if (result == null) { - throw new BadCredentialsException("Unknown kid " + kid); - } - - return result; - } - - private synchronized JsonWebKey getKeyWithRefresh(String kid) throws AuthenticatorUnavailableException { - - // Always re-check within synchronized to handle any races - - JsonWebKey result = getKeySimple(kid); - - if (result != null) { - return result; - } - - return getKeyAfterRefreshInternal(kid); - } - - private JsonWebKey getKeySimple(String kid) { - if (Strings.isNullOrEmpty(kid)) { - List keys = jsonWebKeys.getKeys(); - - if (keys != null && keys.size() == 1) { - return keys.get(0); - } else { - return null; - } - - } else { - return jsonWebKeys.getKey(kid); - } - } - - private synchronized JsonWebKey waitForRefreshToFinish(String kid) { - queuedGetCount++; - long currentRefreshCount = refreshCount; - - try { - wait(queuedThreadTimeoutMs); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.debug(e.toString()); - } - - // Just be optimistic and re-check the key - - JsonWebKey result = getKeySimple(kid); - - if (result != null) { - return result; - } - - if (refreshInProgress && currentRefreshCount == refreshCount) { - // The wait() call returned due to the timeout. - throw new AuthenticatorUnavailableException("Authentication backend timed out"); - } else if (lastRefreshFailure != null) { - throw new AuthenticatorUnavailableException("Authentication backend failed", lastRefreshFailure); - } else { - // Refresh was successful, but we did not get a matching key - return null; - } - } - - private synchronized JsonWebKey performRefresh(String kid) { - if (log.isDebugEnabled()) { - log.debug("performRefresh({})", kid); - } - - final boolean recentRefresh; - - if (System.currentTimeMillis() - refreshTime < refreshRateLimitTimeWindowMs) { - recentRefreshCount++; - recentRefresh = true; - - if (recentRefreshCount > refreshRateLimitCount) { - throw new AuthenticatorUnavailableException("Too many unknown kids recently: " + recentRefreshCount); - } - } else { - recentRefresh = false; - } - - refreshInProgress = true; - refreshCount++; - - log.info("Performing refresh {}", refreshCount); - - long currentRefreshCount = refreshCount; - - try { - - Future future = threadPoolExecutor.submit(new Runnable() { - - @Override - public void run() { - try { - JsonWebKeys newKeys = keySetProvider.get(); - - if (newKeys == null) { - throw new RuntimeException("Refresh function " + keySetProvider + " yielded null"); - } - - log.info("KeySetProvider finished"); - - synchronized (SelfRefreshingKeySet.this) { - jsonWebKeys = newKeys; - refreshInProgress = false; - lastRefreshFailure = null; - SelfRefreshingKeySet.this.notifyAll(); - } - } catch (Throwable e) { - synchronized (SelfRefreshingKeySet.this) { - lastRefreshFailure = e; - refreshInProgress = false; - SelfRefreshingKeySet.this.notifyAll(); - } - log.warn("KeySetProvider threw error", e); - } finally { - if (!recentRefresh) { - recentRefreshCount = 0; - refreshTime = System.currentTimeMillis(); - } - } - - } - }); - - try { - wait(requestTimeoutMs); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.debug(e.toString()); - } - - JsonWebKey result = getKeySimple(kid); - - if (result != null) { - return result; - } - - if (refreshInProgress && currentRefreshCount == refreshCount) { - if (!future.isDone()) { - future.cancel(true); - } - - lastRefreshFailure = new AuthenticatorUnavailableException("Authentication backend timed out"); - - throw new AuthenticatorUnavailableException("Authentication backend timed out"); - } - - if (lastRefreshFailure != null) { - throw new AuthenticatorUnavailableException("Authentication backend failed", lastRefreshFailure); - } - - return null; - - } catch (RejectedExecutionException e) { - throw new AuthenticatorUnavailableException("Did not try to call authentication backend because of " - + threadPoolExecutor.getActiveCount() + " pending threads", e); - } finally { - if (refreshInProgress && currentRefreshCount == refreshCount) { - refreshInProgress = false; - notifyAll(); - } - } - } - - public int getRequestTimeoutMs() { - return requestTimeoutMs; - } - - public void setRequestTimeoutMs(int requestTimeoutMs) { - this.requestTimeoutMs = requestTimeoutMs; - } - - public int getQueuedThreadTimeoutMs() { - return queuedThreadTimeoutMs; - } - - public void setQueuedThreadTimeoutMs(int queuedThreadTimeoutMs) { - this.queuedThreadTimeoutMs = queuedThreadTimeoutMs; - } - - public long getRefreshCount() { - return refreshCount; - } - - public long getQueuedGetCount() { - return queuedGetCount; - } - - public int getRefreshRateLimitTimeWindowMs() { - return refreshRateLimitTimeWindowMs; - } - - public void setRefreshRateLimitTimeWindowMs(int refreshRateLimitTimeWindowMs) { - this.refreshRateLimitTimeWindowMs = refreshRateLimitTimeWindowMs; - } - - public int getRefreshRateLimitCount() { - return refreshRateLimitCount; - } - - public void setRefreshRateLimitCount(int refreshRateLimitCount) { - this.refreshRateLimitCount = refreshRateLimitCount; - } + private static final Logger log = LogManager.getLogger(SelfRefreshingKeySet.class); + + private final KeySetProvider keySetProvider; + private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + 1, + 10, + 1000, + TimeUnit.MILLISECONDS, + new LinkedBlockingQueue() + ); + private volatile JsonWebKeys jsonWebKeys = new JsonWebKeys(); + private boolean refreshInProgress = false; + private long refreshCount = 0; + private long queuedGetCount = 0; + private long recentRefreshCount = 0; + private long refreshTime = 0; + private Throwable lastRefreshFailure = null; + private int requestTimeoutMs = 5000; + private int queuedThreadTimeoutMs = 2500; + private int refreshRateLimitTimeWindowMs = 10000; + private int refreshRateLimitCount = 10; + + public SelfRefreshingKeySet(KeySetProvider refreshFunction) { + this.keySetProvider = refreshFunction; + } + + public JsonWebKey getKey(String kid) throws AuthenticatorUnavailableException, BadCredentialsException { + if (Strings.isNullOrEmpty(kid)) { + return getKeyWithoutKeyId(); + } else { + return getKeyWithKeyId(kid); + } + } + + public synchronized JsonWebKey getKeyAfterRefresh(String kid) throws AuthenticatorUnavailableException, BadCredentialsException { + JsonWebKey result = getKeyAfterRefreshInternal(kid); + + if (result != null) { + return result; + } else if (jsonWebKeys.getKeys().size() == 0) { + throw new AuthenticatorUnavailableException("No JWK are available from IdP"); + } else { + throw new BadCredentialsException("JWT did not contain KID which is required if IdP provides multiple JWK"); + } + } + + private synchronized JsonWebKey getKeyAfterRefreshInternal(String kid) throws AuthenticatorUnavailableException { + if (refreshInProgress) { + return waitForRefreshToFinish(kid); + } else { + return performRefresh(kid); + } + } + + private JsonWebKey getKeyWithoutKeyId() throws AuthenticatorUnavailableException, BadCredentialsException { + List keys = jsonWebKeys.getKeys(); + + if (keys == null || keys.size() == 0) { + JsonWebKey result = getKeyWithRefresh(null); + + if (result != null) { + return result; + } else { + throw new AuthenticatorUnavailableException("No JWK are available from IdP"); + } + } else if (keys.size() == 1) { + return keys.get(0); + } else { + JsonWebKey result = getKeyWithRefresh(null); + + if (result != null) { + return result; + } else { + throw new BadCredentialsException("JWT did not contain KID which is required if IdP provides multiple JWK"); + } + } + } + + private JsonWebKey getKeyWithKeyId(String kid) throws AuthenticatorUnavailableException, BadCredentialsException { + JsonWebKey result = jsonWebKeys.getKey(kid); + + if (result != null) { + return result; + } + + result = getKeyWithRefresh(kid); + + if (result == null) { + throw new BadCredentialsException("Unknown kid " + kid); + } + + return result; + } + + private synchronized JsonWebKey getKeyWithRefresh(String kid) throws AuthenticatorUnavailableException { + + // Always re-check within synchronized to handle any races + + JsonWebKey result = getKeySimple(kid); + + if (result != null) { + return result; + } + + return getKeyAfterRefreshInternal(kid); + } + + private JsonWebKey getKeySimple(String kid) { + if (Strings.isNullOrEmpty(kid)) { + List keys = jsonWebKeys.getKeys(); + + if (keys != null && keys.size() == 1) { + return keys.get(0); + } else { + return null; + } + + } else { + return jsonWebKeys.getKey(kid); + } + } + + private synchronized JsonWebKey waitForRefreshToFinish(String kid) { + queuedGetCount++; + long currentRefreshCount = refreshCount; + + try { + wait(queuedThreadTimeoutMs); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.debug(e.toString()); + } + + // Just be optimistic and re-check the key + + JsonWebKey result = getKeySimple(kid); + + if (result != null) { + return result; + } + + if (refreshInProgress && currentRefreshCount == refreshCount) { + // The wait() call returned due to the timeout. + throw new AuthenticatorUnavailableException("Authentication backend timed out"); + } else if (lastRefreshFailure != null) { + throw new AuthenticatorUnavailableException("Authentication backend failed", lastRefreshFailure); + } else { + // Refresh was successful, but we did not get a matching key + return null; + } + } + + private synchronized JsonWebKey performRefresh(String kid) { + if (log.isDebugEnabled()) { + log.debug("performRefresh({})", kid); + } + + final boolean recentRefresh; + + if (System.currentTimeMillis() - refreshTime < refreshRateLimitTimeWindowMs) { + recentRefreshCount++; + recentRefresh = true; + + if (recentRefreshCount > refreshRateLimitCount) { + throw new AuthenticatorUnavailableException("Too many unknown kids recently: " + recentRefreshCount); + } + } else { + recentRefresh = false; + } + + refreshInProgress = true; + refreshCount++; + + log.info("Performing refresh {}", refreshCount); + + long currentRefreshCount = refreshCount; + + try { + + Future future = threadPoolExecutor.submit(new Runnable() { + + @Override + public void run() { + try { + JsonWebKeys newKeys = keySetProvider.get(); + + if (newKeys == null) { + throw new RuntimeException("Refresh function " + keySetProvider + " yielded null"); + } + + log.info("KeySetProvider finished"); + + synchronized (SelfRefreshingKeySet.this) { + jsonWebKeys = newKeys; + refreshInProgress = false; + lastRefreshFailure = null; + SelfRefreshingKeySet.this.notifyAll(); + } + } catch (Throwable e) { + synchronized (SelfRefreshingKeySet.this) { + lastRefreshFailure = e; + refreshInProgress = false; + SelfRefreshingKeySet.this.notifyAll(); + } + log.warn("KeySetProvider threw error", e); + } finally { + if (!recentRefresh) { + recentRefreshCount = 0; + refreshTime = System.currentTimeMillis(); + } + } + + } + }); + + try { + wait(requestTimeoutMs); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.debug(e.toString()); + } + + JsonWebKey result = getKeySimple(kid); + + if (result != null) { + return result; + } + + if (refreshInProgress && currentRefreshCount == refreshCount) { + if (!future.isDone()) { + future.cancel(true); + } + + lastRefreshFailure = new AuthenticatorUnavailableException("Authentication backend timed out"); + + throw new AuthenticatorUnavailableException("Authentication backend timed out"); + } + + if (lastRefreshFailure != null) { + throw new AuthenticatorUnavailableException("Authentication backend failed", lastRefreshFailure); + } + + return null; + + } catch (RejectedExecutionException e) { + throw new AuthenticatorUnavailableException( + "Did not try to call authentication backend because of " + threadPoolExecutor.getActiveCount() + " pending threads", + e + ); + } finally { + if (refreshInProgress && currentRefreshCount == refreshCount) { + refreshInProgress = false; + notifyAll(); + } + } + } + + public int getRequestTimeoutMs() { + return requestTimeoutMs; + } + + public void setRequestTimeoutMs(int requestTimeoutMs) { + this.requestTimeoutMs = requestTimeoutMs; + } + + public int getQueuedThreadTimeoutMs() { + return queuedThreadTimeoutMs; + } + + public void setQueuedThreadTimeoutMs(int queuedThreadTimeoutMs) { + this.queuedThreadTimeoutMs = queuedThreadTimeoutMs; + } + + public long getRefreshCount() { + return refreshCount; + } + + public long getQueuedGetCount() { + return queuedGetCount; + } + + public int getRefreshRateLimitTimeWindowMs() { + return refreshRateLimitTimeWindowMs; + } + + public void setRefreshRateLimitTimeWindowMs(int refreshRateLimitTimeWindowMs) { + this.refreshRateLimitTimeWindowMs = refreshRateLimitTimeWindowMs; + } + + public int getRefreshRateLimitCount() { + return refreshRateLimitCount; + } + + public void setRefreshRateLimitCount(int refreshRateLimitCount) { + this.refreshRateLimitCount = refreshRateLimitCount; + } } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/oidc/json/OpenIdProviderConfiguration.java b/src/main/java/com/amazon/dlic/auth/http/jwt/oidc/json/OpenIdProviderConfiguration.java index 58a4310a2d..3bcfb796b0 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/oidc/json/OpenIdProviderConfiguration.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/oidc/json/OpenIdProviderConfiguration.java @@ -17,15 +17,15 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class OpenIdProviderConfiguration { - @JsonProperty("jwks_uri") - private String jwksUri; + @JsonProperty("jwks_uri") + private String jwksUri; - public String getJwksUri() { - return jwksUri; - } + public String getJwksUri() { + return jwksUri; + } - public void setJwksUri(String jwksUri) { - this.jwksUri = jwksUri; - } + public void setJwksUri(String jwksUri) { + this.jwksUri = jwksUri; + } } diff --git a/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java index 15aff90f1a..d8e11960d6 100644 --- a/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java @@ -58,7 +58,7 @@ public class HTTPSpnegoAuthenticator implements HTTPAuthenticator { private static final String EMPTY_STRING = ""; - private static final Oid[] KRB_OIDS = new Oid[] {KrbConstants.SPNEGO, KrbConstants.KRB5MECH}; + private static final Oid[] KRB_OIDS = new Oid[] { KrbConstants.SPNEGO, KrbConstants.KRB5MECH }; protected final Logger log = LogManager.getLogger(this.getClass()); @@ -98,17 +98,17 @@ public Void run() { } } catch (Throwable e) { log.error("Unable to enable krb_debug due to ", e); - System.err.println("Unable to enable krb_debug due to "+ExceptionsHelper.stackTrace(e)); - System.out.println("Unable to enable krb_debug due to "+ExceptionsHelper.stackTrace(e)); + System.err.println("Unable to enable krb_debug due to " + ExceptionsHelper.stackTrace(e)); + System.out.println("Unable to enable krb_debug due to " + ExceptionsHelper.stackTrace(e)); } System.setProperty(KrbConstants.USE_SUBJECT_CREDS_ONLY_PROP, "false"); String krb5Path = krb5PathSetting; - if(!Strings.isNullOrEmpty(krb5Path)) { + if (!Strings.isNullOrEmpty(krb5Path)) { - if(Paths.get(krb5Path).isAbsolute()) { + if (Paths.get(krb5Path).isAbsolute()) { log.debug("krb5_filepath: {}", krb5Path); System.setProperty(KrbConstants.KRB5_CONF_PROP, krb5Path); } else { @@ -118,29 +118,36 @@ public Void run() { System.setProperty(KrbConstants.KRB5_CONF_PROP, krb5Path); } else { - if(Strings.isNullOrEmpty(System.getProperty(KrbConstants.KRB5_CONF_PROP))) { + if (Strings.isNullOrEmpty(System.getProperty(KrbConstants.KRB5_CONF_PROP))) { System.setProperty(KrbConstants.KRB5_CONF_PROP, "/etc/krb5.conf"); log.debug("krb5_filepath (was not set or configured, set to default): /etc/krb5.conf"); } } stripRealmFromPrincipalName = settings.getAsBoolean("strip_realm_from_principal", true); - acceptorPrincipal = new HashSet<>(settings.getAsList("plugins.security.kerberos.acceptor_principal", Collections.emptyList())); + acceptorPrincipal = new HashSet<>( + settings.getAsList("plugins.security.kerberos.acceptor_principal", Collections.emptyList()) + ); final String _acceptorKeyTabPath = settings.get("plugins.security.kerberos.acceptor_keytab_filepath"); - if(acceptorPrincipal == null || acceptorPrincipal.size() == 0) { + if (acceptorPrincipal == null || acceptorPrincipal.size() == 0) { log.error("acceptor_principal must not be null or empty. Kerberos authentication will not work"); acceptorPrincipal = null; } - if(_acceptorKeyTabPath == null || _acceptorKeyTabPath.length() == 0) { - log.error("plugins.security.kerberos.acceptor_keytab_filepath must not be null or empty. Kerberos authentication will not work"); + if (_acceptorKeyTabPath == null || _acceptorKeyTabPath.length() == 0) { + log.error( + "plugins.security.kerberos.acceptor_keytab_filepath must not be null or empty. Kerberos authentication will not work" + ); acceptorKeyTabPath = null; } else { acceptorKeyTabPath = configDir.resolve(settings.get("plugins.security.kerberos.acceptor_keytab_filepath")); - if(!Files.exists(acceptorKeyTabPath)) { - log.error("Unable to read keytab from {} - Maybe the file does not exist or is not readable. Kerberos authentication will not work", acceptorKeyTabPath); + if (!Files.exists(acceptorKeyTabPath)) { + log.error( + "Unable to read keytab from {} - Maybe the file does not exist or is not readable. Kerberos authentication will not work", + acceptorKeyTabPath + ); acceptorKeyTabPath = null; } } @@ -155,7 +162,9 @@ public Void run() { } catch (Throwable e) { log.error("Cannot construct HTTPSpnegoAuthenticator due to {}", e.getMessage(), e); - log.error("Please make sure you configured 'plugins.security.kerberos.acceptor_keytab_filepath' realtive to the ES config/ dir!"); + log.error( + "Please make sure you configured 'plugins.security.kerberos.acceptor_keytab_filepath' realtive to the ES config/ dir!" + ); throw e; } @@ -252,11 +261,13 @@ public GSSCredential run() throws GSSException { return new AuthCredentials("_incomplete_", (Object) outToken); } - final String username = ((SimpleUserPrincipal) principal).getName(); - if(username == null || username.length() == 0) { - log.error("Got empty or null user from kerberos. Normally this means that you acceptor principal {} does not match the server hostname", acceptorPrincipal); + if (username == null || username.length() == 0) { + log.error( + "Got empty or null user from kerberos. Normally this means that you acceptor principal {} does not match the server hostname", + acceptorPrincipal + ); } return new AuthCredentials(username, (Object) outToken).markComplete(); @@ -272,19 +283,22 @@ public GSSCredential run() throws GSSException { @Override public boolean reRequestAuthentication(final RestChannel channel, AuthCredentials creds) { - final BytesRestResponse wwwAuthenticateResponse; - XContentBuilder response = getNegotiateResponseBody(); + final BytesRestResponse wwwAuthenticateResponse; + XContentBuilder response = getNegotiateResponseBody(); - if (response != null) { - wwwAuthenticateResponse = new BytesRestResponse(RestStatus.UNAUTHORIZED, response); + if (response != null) { + wwwAuthenticateResponse = new BytesRestResponse(RestStatus.UNAUTHORIZED, response); } else { - wwwAuthenticateResponse = new BytesRestResponse(RestStatus.UNAUTHORIZED, EMPTY_STRING); + wwwAuthenticateResponse = new BytesRestResponse(RestStatus.UNAUTHORIZED, EMPTY_STRING); } - if(creds == null || creds.getNativeCredentials() == null) { + if (creds == null || creds.getNativeCredentials() == null) { wwwAuthenticateResponse.addHeader("WWW-Authenticate", "Negotiate"); } else { - wwwAuthenticateResponse.addHeader("WWW-Authenticate", "Negotiate "+Base64.getEncoder().encodeToString((byte[]) creds.getNativeCredentials())); + wwwAuthenticateResponse.addHeader( + "WWW-Authenticate", + "Negotiate " + Base64.getEncoder().encodeToString((byte[]) creds.getNativeCredentials()) + ); } channel.sendResponse(wwwAuthenticateResponse); return true; @@ -298,7 +312,7 @@ public String getType() { /** * This class gets a gss credential via a privileged action. */ - //borrowed from Apache Tomcat 8 http://svn.apache.org/repos/asf/tomcat/tc8.0.x/trunk/ + // borrowed from Apache Tomcat 8 http://svn.apache.org/repos/asf/tomcat/tc8.0.x/trunk/ private static class AcceptAction implements PrivilegedExceptionAction { GSSContext gssContext; @@ -316,7 +330,7 @@ public byte[] run() throws GSSException { } } - //borrowed from Apache Tomcat 8 http://svn.apache.org/repos/asf/tomcat/tc8.0.x/trunk/ + // borrowed from Apache Tomcat 8 http://svn.apache.org/repos/asf/tomcat/tc8.0.x/trunk/ private static class AuthenticateAction implements PrivilegedAction { private final Logger logger; @@ -336,7 +350,7 @@ public Principal run() { } } - //borrowed from Apache Tomcat 8 http://svn.apache.org/repos/asf/tomcat/tc8.0.x/trunk/ + // borrowed from Apache Tomcat 8 http://svn.apache.org/repos/asf/tomcat/tc8.0.x/trunk/ private static String getUsernameFromGSSContext(final GSSContext gssContext, final boolean strip, final Logger logger) { if (gssContext.isEstablished()) { GSSName gssName = null; @@ -359,26 +373,26 @@ private static String getUsernameFromGSSContext(final GSSContext gssContext, fin return null; } - private XContentBuilder getNegotiateResponseBody() { - try { - XContentBuilder negotiateResponseBody = XContentFactory.jsonBuilder(); - negotiateResponseBody.startObject(); - negotiateResponseBody.field("error"); - negotiateResponseBody.startObject(); - negotiateResponseBody.field("header"); - negotiateResponseBody.startObject(); - negotiateResponseBody.field("WWW-Authenticate", "Negotiate"); - negotiateResponseBody.endObject(); - negotiateResponseBody.endObject(); - negotiateResponseBody.endObject(); - return negotiateResponseBody; - } catch (Exception ex) { - log.error("Can't construct response body", ex); - return null; - } - } - - private static String stripRealmName(String name, boolean strip){ + private XContentBuilder getNegotiateResponseBody() { + try { + XContentBuilder negotiateResponseBody = XContentFactory.jsonBuilder(); + negotiateResponseBody.startObject(); + negotiateResponseBody.field("error"); + negotiateResponseBody.startObject(); + negotiateResponseBody.field("header"); + negotiateResponseBody.startObject(); + negotiateResponseBody.field("WWW-Authenticate", "Negotiate"); + negotiateResponseBody.endObject(); + negotiateResponseBody.endObject(); + negotiateResponseBody.endObject(); + return negotiateResponseBody; + } catch (Exception ex) { + log.error("Can't construct response body", ex); + return null; + } + } + + private static String stripRealmName(String name, boolean strip) { if (strip && name != null) { final int i = name.indexOf('@'); if (i > 0) { diff --git a/src/main/java/com/amazon/dlic/auth/http/kerberos/util/JaasKrbUtil.java b/src/main/java/com/amazon/dlic/auth/http/kerberos/util/JaasKrbUtil.java index 6574728da4..619c780027 100644 --- a/src/main/java/com/amazon/dlic/auth/http/kerberos/util/JaasKrbUtil.java +++ b/src/main/java/com/amazon/dlic/auth/http/kerberos/util/JaasKrbUtil.java @@ -38,177 +38,177 @@ */ public final class JaasKrbUtil { - private static boolean debug = false; - - private JaasKrbUtil() { - } - - public static void setDebug(final boolean debug) { - JaasKrbUtil.debug = debug; - } - - public static Subject loginUsingPassword(final String principal, final String password) throws LoginException { - final Set principals = new HashSet(); - principals.add(new KerberosPrincipal(principal)); - - final Subject subject = new Subject(false, principals, new HashSet(), new HashSet()); - - final Configuration conf = usePassword(principal); - final String confName = "PasswordConf"; - final CallbackHandler callback = new KrbCallbackHandler(principal, password); - final LoginContext loginContext = new LoginContext(confName, subject, callback, conf); - loginContext.login(); - return loginContext.getSubject(); - } - - public static Subject loginUsingTicketCache(final String principal, final Path cachePath) throws LoginException { - final Set principals = new HashSet(); - principals.add(new KerberosPrincipal(principal)); - - final Subject subject = new Subject(false, principals, new HashSet(), new HashSet()); - - final Configuration conf = useTicketCache(principal, cachePath); - final String confName = "TicketCacheConf"; - final LoginContext loginContext = new LoginContext(confName, subject, null, conf); - loginContext.login(); - return loginContext.getSubject(); - } - - public static Subject loginUsingKeytab(final Set principalAsStrings, final Path keytabPath, final boolean initiator) throws LoginException { - final Set principals = new HashSet(); - - for(String p: principalAsStrings) { - principals.add(new KerberosPrincipal(p)); - } - - - final Subject subject = new Subject(false, principals, new HashSet(), new HashSet()); - - final Configuration conf = useKeytab("*", keytabPath, initiator); - final String confName = "KeytabConf"; - final LoginContext loginContext = new LoginContext(confName, subject, null, conf); - loginContext.login(); - return loginContext.getSubject(); - } - - public static Configuration usePassword(final String principal) { - return new PasswordJaasConf(principal); - } - - public static Configuration useTicketCache(final String principal, final Path credentialPath) { - return new TicketCacheJaasConf(principal, credentialPath); - } - - public static Configuration useKeytab(final String principal, final Path keytabPath, final boolean initiator) { - return new KeytabJaasConf(principal, keytabPath, initiator); - } - - private static String getKrb5LoginModuleName() { - return System.getProperty("java.vendor").contains("IBM") ? "com.ibm.security.auth.module.Krb5LoginModule" - : "com.sun.security.auth.module.Krb5LoginModule"; - } - - static class KeytabJaasConf extends Configuration { - private final String principal; - private final Path keytabPath; - private final boolean initiator; - - public KeytabJaasConf(final String principal, final Path keytab, final boolean initiator) { - this.principal = principal; - this.keytabPath = keytab; - this.initiator = initiator; - } - - @Override - public AppConfigurationEntry[] getAppConfigurationEntry(final String name) { - final Map options = new HashMap(); - options.put("keyTab", keytabPath.toAbsolutePath().toString()); - options.put("principal", principal); - options.put("useKeyTab", "true"); - options.put("storeKey", "true"); - options.put("doNotPrompt", "true"); - options.put("renewTGT", "false"); - options.put("refreshKrb5Config", "true"); - options.put("isInitiator", String.valueOf(initiator)); - options.put("debug", String.valueOf(debug)); - - return new AppConfigurationEntry[] { new AppConfigurationEntry(getKrb5LoginModuleName(), - AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options) }; - } - } - - static class TicketCacheJaasConf extends Configuration { - private final String principal; - private final Path clientCredentialPath; - - public TicketCacheJaasConf(final String principal, final Path clientCredentialPath) { - this.principal = principal; - this.clientCredentialPath = clientCredentialPath; - } - - @Override - public AppConfigurationEntry[] getAppConfigurationEntry(final String name) { - final Map options = new HashMap(); - options.put("principal", principal); - options.put("storeKey", "false"); - options.put("doNotPrompt", "false"); - options.put("useTicketCache", "true"); - options.put("renewTGT", "true"); - options.put("refreshKrb5Config", "true"); - options.put("isInitiator", "true"); - options.put("ticketCache", clientCredentialPath.toAbsolutePath().toString()); - options.put("debug", String.valueOf(debug)); - - return new AppConfigurationEntry[] { new AppConfigurationEntry(getKrb5LoginModuleName(), - AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options) }; - } - } - - static class PasswordJaasConf extends Configuration { - private final String principal; - - public PasswordJaasConf(final String principal) { - this.principal = principal; - } - - @Override - public AppConfigurationEntry[] getAppConfigurationEntry(final String name) { - final Map options = new HashMap<>(); - options.put("principal", principal); - options.put("storeKey", "true"); - options.put("useTicketCache", "true"); - options.put("useKeyTab", "false"); - options.put("renewTGT", "true"); - options.put("refreshKrb5Config", "true"); - options.put("isInitiator", "true"); - options.put("debug", String.valueOf(debug)); - - return new AppConfigurationEntry[] { new AppConfigurationEntry(getKrb5LoginModuleName(), - AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options) }; - } - } - - public static class KrbCallbackHandler implements CallbackHandler { - private final String principal; - private final String password; - - public KrbCallbackHandler(final String principal, final String password) { - this.principal = principal; - this.password = password; - } - - @Override - public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException { - for (int i = 0; i < callbacks.length; i++) { - if (callbacks[i] instanceof PasswordCallback) { - final PasswordCallback pc = (PasswordCallback) callbacks[i]; - if (pc.getPrompt().contains(principal)) { - pc.setPassword(password.toCharArray()); - break; - } - } - } - } - } + private static boolean debug = false; + + private JaasKrbUtil() {} + + public static void setDebug(final boolean debug) { + JaasKrbUtil.debug = debug; + } + + public static Subject loginUsingPassword(final String principal, final String password) throws LoginException { + final Set principals = new HashSet(); + principals.add(new KerberosPrincipal(principal)); + + final Subject subject = new Subject(false, principals, new HashSet(), new HashSet()); + + final Configuration conf = usePassword(principal); + final String confName = "PasswordConf"; + final CallbackHandler callback = new KrbCallbackHandler(principal, password); + final LoginContext loginContext = new LoginContext(confName, subject, callback, conf); + loginContext.login(); + return loginContext.getSubject(); + } + + public static Subject loginUsingTicketCache(final String principal, final Path cachePath) throws LoginException { + final Set principals = new HashSet(); + principals.add(new KerberosPrincipal(principal)); + + final Subject subject = new Subject(false, principals, new HashSet(), new HashSet()); + + final Configuration conf = useTicketCache(principal, cachePath); + final String confName = "TicketCacheConf"; + final LoginContext loginContext = new LoginContext(confName, subject, null, conf); + loginContext.login(); + return loginContext.getSubject(); + } + + public static Subject loginUsingKeytab(final Set principalAsStrings, final Path keytabPath, final boolean initiator) + throws LoginException { + final Set principals = new HashSet(); + + for (String p : principalAsStrings) { + principals.add(new KerberosPrincipal(p)); + } + + final Subject subject = new Subject(false, principals, new HashSet(), new HashSet()); + + final Configuration conf = useKeytab("*", keytabPath, initiator); + final String confName = "KeytabConf"; + final LoginContext loginContext = new LoginContext(confName, subject, null, conf); + loginContext.login(); + return loginContext.getSubject(); + } + + public static Configuration usePassword(final String principal) { + return new PasswordJaasConf(principal); + } + + public static Configuration useTicketCache(final String principal, final Path credentialPath) { + return new TicketCacheJaasConf(principal, credentialPath); + } + + public static Configuration useKeytab(final String principal, final Path keytabPath, final boolean initiator) { + return new KeytabJaasConf(principal, keytabPath, initiator); + } + + private static String getKrb5LoginModuleName() { + return System.getProperty("java.vendor").contains("IBM") + ? "com.ibm.security.auth.module.Krb5LoginModule" + : "com.sun.security.auth.module.Krb5LoginModule"; + } + + static class KeytabJaasConf extends Configuration { + private final String principal; + private final Path keytabPath; + private final boolean initiator; + + public KeytabJaasConf(final String principal, final Path keytab, final boolean initiator) { + this.principal = principal; + this.keytabPath = keytab; + this.initiator = initiator; + } + + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(final String name) { + final Map options = new HashMap(); + options.put("keyTab", keytabPath.toAbsolutePath().toString()); + options.put("principal", principal); + options.put("useKeyTab", "true"); + options.put("storeKey", "true"); + options.put("doNotPrompt", "true"); + options.put("renewTGT", "false"); + options.put("refreshKrb5Config", "true"); + options.put("isInitiator", String.valueOf(initiator)); + options.put("debug", String.valueOf(debug)); + + return new AppConfigurationEntry[] { + new AppConfigurationEntry(getKrb5LoginModuleName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options) }; + } + } + + static class TicketCacheJaasConf extends Configuration { + private final String principal; + private final Path clientCredentialPath; + + public TicketCacheJaasConf(final String principal, final Path clientCredentialPath) { + this.principal = principal; + this.clientCredentialPath = clientCredentialPath; + } + + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(final String name) { + final Map options = new HashMap(); + options.put("principal", principal); + options.put("storeKey", "false"); + options.put("doNotPrompt", "false"); + options.put("useTicketCache", "true"); + options.put("renewTGT", "true"); + options.put("refreshKrb5Config", "true"); + options.put("isInitiator", "true"); + options.put("ticketCache", clientCredentialPath.toAbsolutePath().toString()); + options.put("debug", String.valueOf(debug)); + + return new AppConfigurationEntry[] { + new AppConfigurationEntry(getKrb5LoginModuleName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options) }; + } + } + + static class PasswordJaasConf extends Configuration { + private final String principal; + + public PasswordJaasConf(final String principal) { + this.principal = principal; + } + + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(final String name) { + final Map options = new HashMap<>(); + options.put("principal", principal); + options.put("storeKey", "true"); + options.put("useTicketCache", "true"); + options.put("useKeyTab", "false"); + options.put("renewTGT", "true"); + options.put("refreshKrb5Config", "true"); + options.put("isInitiator", "true"); + options.put("debug", String.valueOf(debug)); + + return new AppConfigurationEntry[] { + new AppConfigurationEntry(getKrb5LoginModuleName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options) }; + } + } + + public static class KrbCallbackHandler implements CallbackHandler { + private final String principal; + private final String password; + + public KrbCallbackHandler(final String principal, final String password) { + this.principal = principal; + this.password = password; + } + + @Override + public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof PasswordCallback) { + final PasswordCallback pc = (PasswordCallback) callbacks[i]; + if (pc.getPrompt().contains(principal)) { + pc.setPassword(password.toCharArray()); + break; + } + } + } + } + } } diff --git a/src/main/java/com/amazon/dlic/auth/http/kerberos/util/KrbConstants.java b/src/main/java/com/amazon/dlic/auth/http/kerberos/util/KrbConstants.java index 801ca78e27..a8a57633dc 100644 --- a/src/main/java/com/amazon/dlic/auth/http/kerberos/util/KrbConstants.java +++ b/src/main/java/com/amazon/dlic/auth/http/kerberos/util/KrbConstants.java @@ -16,28 +16,27 @@ public final class KrbConstants { - static { - Oid spnegoTmp = null; - Oid krbTmp = null; - try { - spnegoTmp = new Oid("1.3.6.1.5.5.2"); - krbTmp = new Oid("1.2.840.113554.1.2.2"); - } catch (final GSSException e) { - - } - SPNEGO = spnegoTmp; - KRB5MECH = krbTmp; - } - - public static final Oid SPNEGO; - public static final Oid KRB5MECH; - public static final String KRB5_CONF_PROP = "java.security.krb5.conf"; - public static final String JAAS_LOGIN_CONF_PROP = "java.security.auth.login.config"; - public static final String USE_SUBJECT_CREDS_ONLY_PROP = "javax.security.auth.useSubjectCredsOnly"; - public static final String NEGOTIATE = "Negotiate"; - public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; - - private KrbConstants() { - } + static { + Oid spnegoTmp = null; + Oid krbTmp = null; + try { + spnegoTmp = new Oid("1.3.6.1.5.5.2"); + krbTmp = new Oid("1.2.840.113554.1.2.2"); + } catch (final GSSException e) { + + } + SPNEGO = spnegoTmp; + KRB5MECH = krbTmp; + } + + public static final Oid SPNEGO; + public static final Oid KRB5MECH; + public static final String KRB5_CONF_PROP = "java.security.krb5.conf"; + public static final String JAAS_LOGIN_CONF_PROP = "java.security.auth.login.config"; + public static final String USE_SUBJECT_CREDS_ONLY_PROP = "javax.security.auth.useSubjectCredsOnly"; + public static final String NEGOTIATE = "Negotiate"; + public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + + private KrbConstants() {} } 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 4ab5673adb..1c49d10b2e 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 @@ -83,8 +83,7 @@ class AuthTokenProcessorHandler { private JsonMapObjectReaderWriter jsonMapReaderWriter = new JsonMapObjectReaderWriter(); private Pattern samlRolesSeparatorPattern; - AuthTokenProcessorHandler(Settings settings, Settings jwtSettings, Saml2SettingsProvider saml2SettingsProvider) - throws Exception { + AuthTokenProcessorHandler(Settings settings, Settings jwtSettings, Saml2SettingsProvider saml2SettingsProvider) throws Exception { this.saml2SettingsProvider = saml2SettingsProvider; this.jwtRolesKey = jwtSettings.get("roles_key", "roles"); @@ -133,8 +132,8 @@ boolean handle(RestRequest restRequest, RestChannel restChannel) throws Exceptio return AccessController.doPrivileged(new PrivilegedExceptionAction() { @Override - public Boolean run() throws XPathExpressionException, SamlConfigException, IOException, - ParserConfigurationException, SAXException, SettingsException { + public Boolean run() throws XPathExpressionException, SamlConfigException, IOException, ParserConfigurationException, + SAXException, SettingsException { return handleLowLevel(restRequest, restChannel); } }); @@ -147,17 +146,23 @@ public Boolean run() throws XPathExpressionException, SamlConfigException, IOExc } } - private AuthTokenProcessorAction.Response handleImpl(RestRequest restRequest, RestChannel restChannel, - String samlResponseBase64, String samlRequestId, String acsEndpoint, Saml2Settings saml2Settings) - throws XPathExpressionException, ParserConfigurationException, SAXException, IOException, - SettingsException { + private AuthTokenProcessorAction.Response handleImpl( + RestRequest restRequest, + RestChannel restChannel, + String samlResponseBase64, + String samlRequestId, + String acsEndpoint, + Saml2Settings saml2Settings + ) throws XPathExpressionException, ParserConfigurationException, SAXException, IOException, SettingsException { if (token_log.isDebugEnabled()) { try { - token_log.debug("SAMLResponse for {}\n{}", samlRequestId, new String(Util.base64decoder(samlResponseBase64), StandardCharsets.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{}", - samlRequestId, samlResponseBase64, e); + token_log.warn("SAMLResponse for {} cannot be decoded from base64\n{}", samlRequestId, samlResponseBase64, e); } } @@ -185,20 +190,23 @@ private AuthTokenProcessorAction.Response handleImpl(RestRequest restRequest, Re } } - private boolean handleLowLevel(RestRequest restRequest, RestChannel restChannel) throws SamlConfigException, - IOException, XPathExpressionException, ParserConfigurationException, SAXException, SettingsException { + private boolean handleLowLevel(RestRequest restRequest, RestChannel restChannel) throws SamlConfigException, IOException, + XPathExpressionException, ParserConfigurationException, SAXException, SettingsException { try { if (restRequest.getXContentType() != XContentType.JSON) { throw new OpenSearchSecurityException( - "/_opendistro/_security/api/authtoken expects content with type application/json", - RestStatus.UNSUPPORTED_MEDIA_TYPE); + "/_opendistro/_security/api/authtoken expects content with type application/json", + RestStatus.UNSUPPORTED_MEDIA_TYPE + ); } if (restRequest.method() != Method.POST) { - throw new OpenSearchSecurityException("/_opendistro/_security/api/authtoken expects POST requests", - RestStatus.METHOD_NOT_ALLOWED); + throw new OpenSearchSecurityException( + "/_opendistro/_security/api/authtoken expects POST requests", + RestStatus.METHOD_NOT_ALLOWED + ); } Saml2Settings saml2Settings = this.saml2SettingsProvider.getCached(); @@ -214,24 +222,28 @@ private boolean handleLowLevel(RestRequest restRequest, RestChannel restChannel) if (((ObjectNode) jsonRoot).get("SAMLResponse") == null) { log.warn("SAMLResponse is missing from request "); - throw new OpenSearchSecurityException("SAMLResponse is missing from request", - RestStatus.BAD_REQUEST); + throw new OpenSearchSecurityException("SAMLResponse is missing from request", RestStatus.BAD_REQUEST); } String samlResponseBase64 = ((ObjectNode) jsonRoot).get("SAMLResponse").asText(); String samlRequestId = ((ObjectNode) jsonRoot).get("RequestId") != null - ? ((ObjectNode) jsonRoot).get("RequestId").textValue() - : null; + ? ((ObjectNode) jsonRoot).get("RequestId").textValue() + : null; String acsEndpoint = saml2Settings.getSpAssertionConsumerServiceUrl().toString(); - if (((ObjectNode) jsonRoot).get("acsEndpoint") != null - && ((ObjectNode) jsonRoot).get("acsEndpoint").textValue() != null) { + if (((ObjectNode) jsonRoot).get("acsEndpoint") != null && ((ObjectNode) jsonRoot).get("acsEndpoint").textValue() != null) { acsEndpoint = getAbsoluteAcsEndpoint(((ObjectNode) jsonRoot).get("acsEndpoint").textValue()); } - AuthTokenProcessorAction.Response responseBody = this.handleImpl(restRequest, restChannel, - samlResponseBase64, samlRequestId, acsEndpoint, saml2Settings); + AuthTokenProcessorAction.Response responseBody = this.handleImpl( + restRequest, + restChannel, + samlResponseBase64, + samlRequestId, + acsEndpoint, + saml2Settings + ); if (responseBody == null) { return false; @@ -239,16 +251,14 @@ private boolean handleLowLevel(RestRequest restRequest, RestChannel restChannel) String responseBodyString = DefaultObjectMapper.objectMapper.writeValueAsString(responseBody); - BytesRestResponse authenticateResponse = new BytesRestResponse(RestStatus.OK, "application/json", - responseBodyString); + BytesRestResponse authenticateResponse = new BytesRestResponse(RestStatus.OK, "application/json", responseBodyString); restChannel.sendResponse(authenticateResponse); return true; } catch (JsonProcessingException e) { log.warn("Error while parsing JSON for /_opendistro/_security/api/authtoken", e); - BytesRestResponse authenticateResponse = new BytesRestResponse(RestStatus.BAD_REQUEST, - "JSON could not be parsed"); + BytesRestResponse authenticateResponse = new BytesRestResponse(RestStatus.BAD_REQUEST, "JSON could not be parsed"); restChannel.sendResponse(authenticateResponse); return true; } @@ -274,7 +284,8 @@ JsonWebKey createJwkFromSettings(Settings settings, Settings jwtSettings) throws if (jwkSettings.isEmpty()) { throw new Exception( - "Settings for key exchange missing. Please specify at least the option exchange_key with a shared secret."); + "Settings for key exchange missing. Please specify at least the option exchange_key with a shared secret." + ); } JsonWebKey jwk = new JsonWebKey(); @@ -319,8 +330,14 @@ private String createJwt(SamlResponse samlResponse) throws Exception { String encodedJwt = this.jwtProducer.processJwt(jwt); if (token_log.isDebugEnabled()) { - token_log.debug("Created JWT: " + encodedJwt + "\n" + jsonMapReaderWriter.toJson(jwt.getJwsHeaders()) + "\n" - + JwtUtils.claimsToJson(jwt.getClaims())); + token_log.debug( + "Created JWT: " + + encodedJwt + + "\n" + + jsonMapReaderWriter.toJson(jwt.getJwsHeaders()) + + "\n" + + JwtUtils.claimsToJson(jwt.getClaims()) + ); } return encodedJwt; @@ -335,8 +352,7 @@ private long getJwtExpiration(SamlResponse samlResponse) throws Exception { if (sessionNotOnOrAfter != null) { return sessionNotOnOrAfter.getMillis() / 1000 + this.expiryOffset; } else { - throw new Exception( - "Error while determining JWT expiration time: SamlResponse did not contain sessionNotOnOrAfter value"); + throw new Exception("Error while determining JWT expiration time: SamlResponse did not contain sessionNotOnOrAfter value"); } } else { // AUTO @@ -419,9 +435,9 @@ private String[] extractRoles(SamlResponse samlResponse) throws XPathExpressionE private List splitRoles(List values) { return values.stream() - .flatMap(v -> samlRolesSeparatorPattern.splitAsStream(v)) - .filter(r -> !Strings.isNullOrEmpty(r)) - .collect(Collectors.toList()); + .flatMap(v -> samlRolesSeparatorPattern.splitAsStream(v)) + .filter(r -> !Strings.isNullOrEmpty(r)) + .collect(Collectors.toList()); } private String getAbsoluteAcsEndpoint(String acsEndpoint) { @@ -440,7 +456,9 @@ private String getAbsoluteAcsEndpoint(String acsEndpoint) { } private enum ExpiryBaseValue { - AUTO, NOW, SESSION + AUTO, + NOW, + SESSION } public JsonWebKey getSigningKey() { diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticator.java index 7015017fc7..d3068b852a 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticator.java @@ -68,7 +68,6 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; - public class HTTPSamlAuthenticator implements HTTPAuthenticator, Destroyable { protected final static Logger log = LogManager.getLogger(HTTPSamlAuthenticator.class); @@ -78,9 +77,8 @@ public class HTTPSamlAuthenticator implements HTTPAuthenticator, Destroyable { private static final String API_AUTHTOKEN_SUFFIX = "api/authtoken"; private static final String AUTHINFO_SUFFIX = "authinfo"; - private static final String REGEX_PATH_PREFIX = "/(" + LEGACY_OPENDISTRO_PREFIX + "|" + PLUGINS_PREFIX + ")/" +"(.*)"; - private static final Pattern PATTERN_PATH_PREFIX = Pattern.compile(REGEX_PATH_PREFIX); - + private static final String REGEX_PATH_PREFIX = "/(" + LEGACY_OPENDISTRO_PREFIX + "|" + PLUGINS_PREFIX + ")/" + "(.*)"; + private static final Pattern PATTERN_PATH_PREFIX = Pattern.compile(REGEX_PATH_PREFIX); private static boolean openSamlInitialized = false; @@ -132,13 +130,15 @@ public HTTPSamlAuthenticator(final Settings settings, final Path configPath) { try { this.saml2SettingsProvider.getCached(); } catch (Exception e) { - log.debug("Exception while initializing Saml2SettingsProvider. Possibly, the IdP is unreachable right now. This is recoverable by a meta data refresh.", e); + log.debug( + "Exception while initializing Saml2SettingsProvider. Possibly, the IdP is unreachable right now. This is recoverable by a meta data refresh.", + e + ); } this.jwtSettings = this.createJwtAuthenticatorSettings(settings); - this.authTokenProcessorHandler = new AuthTokenProcessorHandler(settings, jwtSettings, - this.saml2SettingsProvider); + this.authTokenProcessorHandler = new AuthTokenProcessorHandler(settings, jwtSettings, this.saml2SettingsProvider); this.httpJwtAuthenticator = new HTTPJwtAuthenticator(this.jwtSettings, configPath); @@ -149,8 +149,7 @@ public HTTPSamlAuthenticator(final Settings settings, final Path configPath) { } @Override - public AuthCredentials extractCredentials(RestRequest restRequest, ThreadContext threadContext) - throws OpenSearchSecurityException { + public AuthCredentials extractCredentials(RestRequest restRequest, ThreadContext threadContext) throws OpenSearchSecurityException { Matcher matcher = PATTERN_PATH_PREFIX.matcher(restRequest.path()); final String suffix = matcher.matches() ? matcher.group(2) : null; if (API_AUTHTOKEN_SUFFIX.equals(suffix)) { @@ -177,8 +176,7 @@ public boolean reRequestAuthentication(RestChannel restChannel, AuthCredentials RestRequest restRequest = restChannel.request(); Matcher matcher = PATTERN_PATH_PREFIX.matcher(restRequest.path()); final String suffix = matcher.matches() ? matcher.group(2) : null; - if (API_AUTHTOKEN_SUFFIX.equals(suffix) - && this.authTokenProcessorHandler.handle(restRequest, restChannel)){ + if (API_AUTHTOKEN_SUFFIX.equals(suffix) && this.authTokenProcessorHandler.handle(restRequest, restChannel)) { return true; } @@ -200,9 +198,12 @@ private String getWwwAuthenticateHeader(Saml2Settings saml2Settings) throws Exce AuthnRequest authnRequest = this.buildAuthnRequest(saml2Settings); return "X-Security-IdP realm=\"OpenSearch Security\" location=\"" - + StringEscapeUtils.escapeJava(getSamlRequestRedirectBindingLocation(IdpEndpointType.SSO, saml2Settings, - authnRequest.getEncodedAuthnRequest(true))) - + "\" requestId=\"" + StringEscapeUtils.escapeJava(authnRequest.getId()) + "\""; + + StringEscapeUtils.escapeJava( + getSamlRequestRedirectBindingLocation(IdpEndpointType.SSO, saml2Settings, authnRequest.getEncodedAuthnRequest(true)) + ) + + "\" requestId=\"" + + StringEscapeUtils.escapeJava(authnRequest.getId()) + + "\""; } private AuthnRequest buildAuthnRequest(Saml2Settings saml2Settings) { @@ -221,12 +222,16 @@ private AuthnRequest buildAuthnRequest(Saml2Settings saml2Settings) { private PrivateKey getSpSignaturePrivateKey(Settings settings, Path configPath) throws Exception { try { - PrivateKey result = PemKeyReader.loadKeyFromStream(settings.get("sp.signature_private_key_password"), - PemKeyReader.resolveStream("sp.signature_private_key", settings)); + PrivateKey result = PemKeyReader.loadKeyFromStream( + settings.get("sp.signature_private_key_password"), + PemKeyReader.resolveStream("sp.signature_private_key", settings) + ); if (result == null) { - result = PemKeyReader.loadKeyFromFile(settings.get("sp.signature_private_key_password"), - PemKeyReader.resolve("sp.signature_private_key_filepath", settings, configPath, false)); + result = PemKeyReader.loadKeyFromFile( + settings.get("sp.signature_private_key_password"), + PemKeyReader.resolve("sp.signature_private_key_filepath", settings, configPath, false) + ); } return result; @@ -297,8 +302,7 @@ public Void run() throws InitializationException { } @SuppressWarnings("removal") - private MetadataResolver createMetadataResolver(final Settings settings, final Path configPath) - throws Exception { + private MetadataResolver createMetadataResolver(final Settings settings, final Path configPath) throws Exception { final AbstractMetadataResolver metadataResolver; final String idpMetadataUrl = settings.get(IDP_METADATA_URL); @@ -311,7 +315,9 @@ private MetadataResolver createMetadataResolver(final Settings settings, final P } else if (idpMetadataBody != null) { metadataResolver = new DOMMetadataResolver(getMetadataDOM(idpMetadataBody)); } else { - throw new Exception(String.format("One of %s, %s or %s must be configured", IDP_METADATA_URL, IDP_METADATA_FILE, IDP_METADATA_CONTENT)); + throw new Exception( + String.format("One of %s, %s or %s must be configured", IDP_METADATA_URL, IDP_METADATA_FILE, IDP_METADATA_CONTENT) + ); } metadataResolver.setId(HTTPSamlAuthenticator.class.getName() + "_" + (++resolverIdCounter)); @@ -378,14 +384,12 @@ String buildLogoutUrl(AuthCredentials authCredentials) { String nameIdClaim = this.subjectKey == null ? "sub" : "saml_ni"; String nameId = authCredentials.getAttributes().get("attr.jwt." + nameIdClaim); - String nameIdFormat = SamlNameIdFormat - .getByShortName(authCredentials.getAttributes().get("attr.jwt.saml_nif")).getUri(); + String nameIdFormat = SamlNameIdFormat.getByShortName(authCredentials.getAttributes().get("attr.jwt.saml_nif")).getUri(); String sessionIndex = authCredentials.getAttributes().get("attr.jwt.saml_si"); LogoutRequest logoutRequest = new LogoutRequest(saml2Settings, null, nameId, sessionIndex, nameIdFormat); - return getSamlRequestRedirectBindingLocation(IdpEndpointType.SLO, saml2Settings, - logoutRequest.getEncodedLogoutRequest(true)); + return getSamlRequestRedirectBindingLocation(IdpEndpointType.SLO, saml2Settings, logoutRequest.getEncodedLogoutRequest(true)); } catch (Exception e) { log.error("Error while creating logout URL. Logout will be not available", e); @@ -398,8 +402,8 @@ private void initLogoutUrl(RestRequest restRequest, ThreadContext threadContext, threadContext.putTransient(ConfigConstants.SSO_LOGOUT_URL, buildLogoutUrl(authCredentials)); } - private String getSamlRequestRedirectBindingLocation(IdpEndpointType idpEndpointType, Saml2Settings saml2Settings, - String samlRequest) throws Exception { + private String getSamlRequestRedirectBindingLocation(IdpEndpointType idpEndpointType, Saml2Settings saml2Settings, String samlRequest) + throws Exception { URL idpUrl = getIdpUrl(idpEndpointType, saml2Settings); @@ -417,8 +421,7 @@ private String getSamlRequestQueryString(String samlRequest) throws Exception { return "SAMLRequest=" + Util.urlEncoder(samlRequest); } - String queryString = "SAMLRequest=" + Util.urlEncoder(samlRequest) + "&SigAlg=" - + Util.urlEncoder(this.spSignatureAlgorithm); + String queryString = "SAMLRequest=" + Util.urlEncoder(samlRequest) + "&SigAlg=" + Util.urlEncoder(this.spSignatureAlgorithm); String signature = getSamlRequestQueryStringSignature(queryString); @@ -429,8 +432,7 @@ private String getSamlRequestQueryString(String samlRequest) throws Exception { private String getSamlRequestQueryStringSignature(String samlRequestQueryString) throws Exception { try { - return Util.base64encoder( - Util.sign(samlRequestQueryString, this.spSignaturePrivateKey, this.spSignatureAlgorithm)); + return Util.base64encoder(Util.sign(samlRequestQueryString, this.spSignaturePrivateKey, this.spSignatureAlgorithm)); } catch (Exception e) { throw new Exception("Error while signing SAML request", e); } @@ -462,8 +464,7 @@ protected KeyProvider initKeyProvider(Settings settings, Path configPath) throws return new KeyProvider() { @Override - public JsonWebKey getKeyAfterRefresh(String kid) - throws AuthenticatorUnavailableException, BadCredentialsException { + public JsonWebKey getKeyAfterRefresh(String kid) throws AuthenticatorUnavailableException, BadCredentialsException { return authTokenProcessorHandler.getSigningKey(); } @@ -477,6 +478,7 @@ public JsonWebKey getKey(String kid) throws AuthenticatorUnavailableException, B } private enum IdpEndpointType { - SSO, SLO + SSO, + SLO } } diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/Saml2SettingsProvider.java b/src/main/java/com/amazon/dlic/auth/http/saml/Saml2SettingsProvider.java index 5274569502..881c9c3553 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/Saml2SettingsProvider.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/Saml2SettingsProvider.java @@ -68,19 +68,23 @@ Saml2Settings get() throws SamlConfigException { try { HashMap configProperties = new HashMap<>(); - EntityDescriptor entityDescriptor = this.metadataResolver - .resolveSingle(new CriteriaSet(new EntityIdCriterion(this.idpEntityId))); + EntityDescriptor entityDescriptor = this.metadataResolver.resolveSingle( + new CriteriaSet(new EntityIdCriterion(this.idpEntityId)) + ); if (entityDescriptor == null) { throw new SamlConfigException("Could not find entity descriptor for " + this.idpEntityId); } - IDPSSODescriptor idpSsoDescriptor = entityDescriptor - .getIDPSSODescriptor("urn:oasis:names:tc:SAML:2.0:protocol"); + IDPSSODescriptor idpSsoDescriptor = entityDescriptor.getIDPSSODescriptor("urn:oasis:names:tc:SAML:2.0:protocol"); if (idpSsoDescriptor == null) { - throw new SamlConfigException("Could not find IDPSSODescriptor supporting SAML 2.0 in " - + this.idpEntityId + "; role descriptors: " + entityDescriptor.getRoleDescriptors()); + throw new SamlConfigException( + "Could not find IDPSSODescriptor supporting SAML 2.0 in " + + this.idpEntityId + + "; role descriptors: " + + entityDescriptor.getRoleDescriptors() + ); } initIdpEndpoints(idpSsoDescriptor, configProperties); @@ -121,8 +125,7 @@ Saml2Settings getCached() throws SamlConfigException { private boolean isUpdateRequired() { RefreshableMetadataResolver refreshableMetadataResolver = (RefreshableMetadataResolver) this.metadataResolver; - if (this.cachedSaml2Settings == null || this.metadataUpdateTime == null - || refreshableMetadataResolver.getLastUpdate() == null) { + if (this.cachedSaml2Settings == null || this.metadataUpdateTime == null || refreshableMetadataResolver.getLastUpdate() == null) { return true; } @@ -140,35 +143,39 @@ private void initMisc(HashMap configProperties) { } private void initSpEndpoints(HashMap configProperties) { - configProperties.put(SettingsBuilder.SP_ASSERTION_CONSUMER_SERVICE_URL_PROPERTY_KEY, - this.buildAssertionConsumerEndpoint(this.opensearchSettings.get("kibana_url"))); - configProperties.put(SettingsBuilder.SP_ASSERTION_CONSUMER_SERVICE_BINDING_PROPERTY_KEY, - "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); + configProperties.put( + SettingsBuilder.SP_ASSERTION_CONSUMER_SERVICE_URL_PROPERTY_KEY, + this.buildAssertionConsumerEndpoint(this.opensearchSettings.get("kibana_url")) + ); + configProperties.put( + SettingsBuilder.SP_ASSERTION_CONSUMER_SERVICE_BINDING_PROPERTY_KEY, + "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + ); configProperties.put(SettingsBuilder.SP_ENTITYID_PROPERTY_KEY, this.opensearchSettings.get("sp.entity_id")); } - private void initIdpEndpoints(IDPSSODescriptor idpSsoDescriptor, HashMap configProperties) - throws SamlConfigException { - SingleSignOnService singleSignOnService = this.findSingleSignOnService(idpSsoDescriptor, - "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"); + private void initIdpEndpoints(IDPSSODescriptor idpSsoDescriptor, HashMap configProperties) throws SamlConfigException { + SingleSignOnService singleSignOnService = this.findSingleSignOnService( + idpSsoDescriptor, + "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + ); - configProperties.put(SettingsBuilder.IDP_SINGLE_SIGN_ON_SERVICE_URL_PROPERTY_KEY, - singleSignOnService.getLocation()); - configProperties.put(SettingsBuilder.IDP_SINGLE_SIGN_ON_SERVICE_BINDING_PROPERTY_KEY, - singleSignOnService.getBinding()); + configProperties.put(SettingsBuilder.IDP_SINGLE_SIGN_ON_SERVICE_URL_PROPERTY_KEY, singleSignOnService.getLocation()); + configProperties.put(SettingsBuilder.IDP_SINGLE_SIGN_ON_SERVICE_BINDING_PROPERTY_KEY, singleSignOnService.getBinding()); configProperties.put(SettingsBuilder.IDP_ENTITYID_PROPERTY_KEY, this.opensearchSettings.get("idp.entity_id")); - SingleLogoutService singleLogoutService = this.findSingleLogoutService(idpSsoDescriptor, - "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"); + SingleLogoutService singleLogoutService = this.findSingleLogoutService( + idpSsoDescriptor, + "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + ); if (singleLogoutService != null) { - configProperties.put(SettingsBuilder.IDP_SINGLE_LOGOUT_SERVICE_URL_PROPERTY_KEY, - singleLogoutService.getLocation()); - configProperties.put(SettingsBuilder.IDP_SINGLE_LOGOUT_SERVICE_BINDING_PROPERTY_KEY, - singleLogoutService.getBinding()); + configProperties.put(SettingsBuilder.IDP_SINGLE_LOGOUT_SERVICE_URL_PROPERTY_KEY, singleLogoutService.getLocation()); + configProperties.put(SettingsBuilder.IDP_SINGLE_LOGOUT_SERVICE_BINDING_PROPERTY_KEY, singleLogoutService.getBinding()); } else { log.warn( - "The IdP does not provide a Single Logout Service. In order to ensure that users have to re-enter their password after logging out, OpenSearch Security will issue all SAML authentication requests with a mandatory password input (ForceAuthn=true)"); + "The IdP does not provide a Single Logout Service. In order to ensure that users have to re-enter their password after logging out, OpenSearch Security will issue all SAML authentication requests with a mandatory password input (ForceAuthn=true)" + ); } } @@ -176,32 +183,32 @@ private void initIdpCerts(IDPSSODescriptor idpSsoDescriptor, HashMap extractLdapAttributes(String originalUsername, final LdapEntry userEntry, - int customAttrMaxValueLen, WildcardMatcher allowlistedCustomLdapAttrMatcher) { + + public static Map extractLdapAttributes( + String originalUsername, + final LdapEntry userEntry, + int customAttrMaxValueLen, + WildcardMatcher allowlistedCustomLdapAttrMatcher + ) { Map attributes = new HashMap<>(); attributes.put("ldap.original.username", originalUsername); attributes.put("ldap.dn", userEntry.getDn()); diff --git a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java index 1ce545e8da..96cd7a40c9 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthenticationBackend.java @@ -67,13 +67,16 @@ public LDAPAuthenticationBackend(final Settings settings, final Path configPath) this.settings = settings; this.configPath = configPath; this.userBaseSettings = getUserBaseSettings(settings); - this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); + this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())) + .toArray(new String[0]); this.shouldFollowReferrals = settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT); customAttrMaxValueLen = settings.getAsInt(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 36); checkForDeprecatedSetting(settings, ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, ConfigConstants.LDAP_CUSTOM_ATTR_ALLOWLIST); - final List customAttrAllowList = settings.getAsList(ConfigConstants.LDAP_CUSTOM_ATTR_ALLOWLIST, settings.getAsList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, - Collections.singletonList("*"))); + final List customAttrAllowList = settings.getAsList( + ConfigConstants.LDAP_CUSTOM_ATTR_ALLOWLIST, + settings.getAsList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, Collections.singletonList("*")) + ); allowlistedCustomLdapAttrMatcher = WildcardMatcher.from(customAttrAllowList); } @@ -81,7 +84,7 @@ public LDAPAuthenticationBackend(final Settings settings, final Path configPath) public User authenticate(final AuthCredentials credentials) throws OpenSearchSecurityException { Connection ldapConnection = null; - final String user =credentials.getUsername(); + final String user = credentials.getUsername(); byte[] password = credentials.getPassword(); try { @@ -98,11 +101,12 @@ public User authenticate(final AuthCredentials credentials) throws OpenSearchSec // makes guessing if a user exists or not harder when looking on the // authentication delay time if (entry == null && settings.getAsBoolean(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, false)) { - String fakeLognDn = settings.get(ConfigConstants.LDAP_FAKE_LOGIN_DN, - "CN=faketomakebindfail,DC=" + UUID.randomUUID().toString()); + String fakeLognDn = settings.get( + ConfigConstants.LDAP_FAKE_LOGIN_DN, + "CN=faketomakebindfail,DC=" + UUID.randomUUID().toString() + ); entry = new LdapEntry(fakeLognDn); - password = settings.get(ConfigConstants.LDAP_FAKE_LOGIN_PASSWORD, "fakeLoginPwd123") - .getBytes(StandardCharsets.UTF_8); + password = settings.get(ConfigConstants.LDAP_FAKE_LOGIN_PASSWORD, "fakeLoginPwd123").getBytes(StandardCharsets.UTF_8); } else if (entry == null) { throw new OpenSearchSecurityException("No user " + user + " found"); } @@ -166,15 +170,24 @@ public boolean exists(final User user) { try { ldapConnection = LDAPAuthorizationBackend.getConnection(settings, configPath); - LdapEntry userEntry = exists(userName, ldapConnection, settings, userBaseSettings, this.returnAttributes, this.shouldFollowReferrals); + LdapEntry userEntry = exists( + userName, + ldapConnection, + settings, + userBaseSettings, + this.returnAttributes, + this.shouldFollowReferrals + ); boolean exists = userEntry != null; - - if(exists) { - user.addAttributes(LdapUser.extractLdapAttributes(userName, userEntry, customAttrMaxValueLen, allowlistedCustomLdapAttrMatcher)); + + if (exists) { + user.addAttributes( + LdapUser.extractLdapAttributes(userName, userEntry, customAttrMaxValueLen, allowlistedCustomLdapAttrMatcher) + ); } - + return exists; - + } catch (final Exception e) { log.warn("User {} does not exist due to ", userName, e); return false; @@ -184,33 +197,39 @@ public boolean exists(final User user) { } static List> getUserBaseSettings(Settings settings) { - Map userBaseSettingsMap = new HashMap<>( - settings.getGroups(ConfigConstants.LDAP_AUTHCZ_USERS)); + Map userBaseSettingsMap = new HashMap<>(settings.getGroups(ConfigConstants.LDAP_AUTHCZ_USERS)); if (!userBaseSettingsMap.isEmpty()) { if (settings.hasValue(ConfigConstants.LDAP_AUTHC_USERBASE)) { throw new RuntimeException( - "Both old-style and new-style configuration defined for LDAP authentication backend: " - + settings); + "Both old-style and new-style configuration defined for LDAP authentication backend: " + settings + ); } return Utils.getOrderedBaseSettings(userBaseSettingsMap); } else { Settings.Builder settingsBuilder = Settings.builder(); - settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_BASE, - settings.get(ConfigConstants.LDAP_AUTHC_USERBASE, DEFAULT_USERBASE)); - settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_SEARCH, - settings.get(ConfigConstants.LDAP_AUTHC_USERSEARCH, DEFAULT_USERSEARCH_PATTERN)); + settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_BASE, settings.get(ConfigConstants.LDAP_AUTHC_USERBASE, DEFAULT_USERBASE)); + settingsBuilder.put( + ConfigConstants.LDAP_AUTHCZ_SEARCH, + settings.get(ConfigConstants.LDAP_AUTHC_USERSEARCH, DEFAULT_USERSEARCH_PATTERN) + ); return Collections.singletonList(Pair.of("_legacyConfig", settingsBuilder.build())); } } - static LdapEntry exists(final String user, Connection ldapConnection, Settings settings, - List> userBaseSettings, String[] returnAttributes, final boolean shouldFollowReferrals) throws Exception { + static LdapEntry exists( + final String user, + Connection ldapConnection, + Settings settings, + List> userBaseSettings, + String[] returnAttributes, + final boolean shouldFollowReferrals + ) throws Exception { if (settings.getAsBoolean(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, false) - || settings.getAsBoolean(ConfigConstants.LDAP_SEARCH_ALL_BASES, false) - || settings.hasValue(ConfigConstants.LDAP_AUTHC_USERBASE)) { + || settings.getAsBoolean(ConfigConstants.LDAP_SEARCH_ALL_BASES, false) + || settings.hasValue(ConfigConstants.LDAP_AUTHC_USERBASE)) { return existsSearchingAllBases(user, ldapConnection, userBaseSettings, returnAttributes, shouldFollowReferrals); } else { return existsSearchingUntilFirstHit(user, ldapConnection, userBaseSettings, returnAttributes, shouldFollowReferrals); @@ -218,8 +237,13 @@ static LdapEntry exists(final String user, Connection ldapConnection, Settings s } - private static LdapEntry existsSearchingUntilFirstHit(final String user, Connection ldapConnection, - List> userBaseSettings, final String[] returnAttributes, final boolean shouldFollowReferrals) throws Exception { + private static LdapEntry existsSearchingUntilFirstHit( + final String user, + Connection ldapConnection, + List> userBaseSettings, + final String[] returnAttributes, + final boolean shouldFollowReferrals + ) throws Exception { final String username = user; final boolean isDebugEnabled = log.isDebugEnabled(); @@ -230,11 +254,14 @@ private static LdapEntry existsSearchingUntilFirstHit(final String user, Connect f.setFilter(baseSettings.get(ConfigConstants.LDAP_AUTHCZ_SEARCH, DEFAULT_USERSEARCH_PATTERN)); f.setParameter(ZERO_PLACEHOLDER, username); - List result = LdapHelper.search(ldapConnection, - baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), - f, - SearchScope.SUBTREE, - returnAttributes, shouldFollowReferrals); + List result = LdapHelper.search( + ldapConnection, + baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), + f, + SearchScope.SUBTREE, + returnAttributes, + shouldFollowReferrals + ); if (isDebugEnabled) { log.debug("Results for LDAP search for {} in base {} is {}", user, entry.getKey(), result); @@ -248,8 +275,13 @@ private static LdapEntry existsSearchingUntilFirstHit(final String user, Connect return null; } - private static LdapEntry existsSearchingAllBases(final String user, Connection ldapConnection, - List> userBaseSettings, final String[] returnAttributes, final boolean shouldFollowReferrals) throws Exception { + private static LdapEntry existsSearchingAllBases( + final String user, + Connection ldapConnection, + List> userBaseSettings, + final String[] returnAttributes, + final boolean shouldFollowReferrals + ) throws Exception { final String username = user; Set result = new HashSet<>(); @@ -261,11 +293,14 @@ private static LdapEntry existsSearchingAllBases(final String user, Connection l f.setFilter(baseSettings.get(ConfigConstants.LDAP_AUTHCZ_SEARCH, DEFAULT_USERSEARCH_PATTERN)); f.setParameter(ZERO_PLACEHOLDER, username); - List foundEntries = LdapHelper.search(ldapConnection, - baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), - f, - SearchScope.SUBTREE, - returnAttributes, shouldFollowReferrals); + List foundEntries = LdapHelper.search( + ldapConnection, + baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), + f, + SearchScope.SUBTREE, + returnAttributes, + shouldFollowReferrals + ); if (isDebugEnabled) { log.debug("Results for LDAP search for " + user + " in base " + entry.getKey() + ":\n" + result); diff --git a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java index 68a8a3fba6..ac3fd8b32f 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/backend/LDAPAuthorizationBackend.java @@ -89,7 +89,8 @@ public class LDAPAuthorizationBackend implements AuthorizationBackend { private static final AtomicInteger CONNECTION_COUNTER = new AtomicInteger(); - private static final String COM_SUN_JNDI_LDAP_OBJECT_DISABLE_ENDPOINT_IDENTIFICATION = "com.sun.jndi.ldap.object.disableEndpointIdentification"; + private static final String COM_SUN_JNDI_LDAP_OBJECT_DISABLE_ENDPOINT_IDENTIFICATION = + "com.sun.jndi.ldap.object.disableEndpointIdentification"; private static final List DEFAULT_TLS_PROTOCOLS = Arrays.asList("TLSv1.2", "TLSv1.1"); static final int ONE_PLACEHOLDER = 1; static final int TWO_PLACEHOLDER = 2; @@ -112,12 +113,14 @@ public class LDAPAuthorizationBackend implements AuthorizationBackend { public LDAPAuthorizationBackend(final Settings settings, final Path configPath) { this.settings = settings; this.skipUsersMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS)); - this.nestedRoleMatcher = settings.getAsBoolean(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) ? - WildcardMatcher.from(settings.getAsList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER)) : null; + this.nestedRoleMatcher = settings.getAsBoolean(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) + ? WildcardMatcher.from(settings.getAsList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER)) + : null; this.configPath = configPath; this.roleBaseSettings = getRoleSearchSettings(settings); this.userBaseSettings = LDAPAuthenticationBackend.getUserBaseSettings(settings); - this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); + this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())) + .toArray(new String[0]); this.shouldFollowReferrals = settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT); } @@ -198,10 +201,8 @@ private static List> convertOldStyleSettingsToNewSty Settings.Builder settingsBuilder = Settings.builder(); - settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_BASE, - settings.get(ConfigConstants.LDAP_AUTHZ_ROLEBASE, DEFAULT_ROLEBASE)); - settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_SEARCH, - settings.get(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, DEFAULT_ROLESEARCH)); + settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_BASE, settings.get(ConfigConstants.LDAP_AUTHZ_ROLEBASE, DEFAULT_ROLEBASE)); + settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_SEARCH, settings.get(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, DEFAULT_ROLESEARCH)); result.put("convertedOldStyleSettings", settingsBuilder.build()); @@ -209,9 +210,13 @@ private static List> convertOldStyleSettingsToNewSty } @SuppressWarnings("removal") - private static void checkConnection0(final ConnectionConfig connectionConfig, String bindDn, byte[] password, final ClassLoader cl, - final boolean needRestore) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, - FileNotFoundException, IOException, LdapException { + private static void checkConnection0( + final ConnectionConfig connectionConfig, + String bindDn, + byte[] password, + final ClassLoader cl, + final boolean needRestore + ) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, LdapException { Connection connection = null; @@ -251,13 +256,15 @@ public Void run() throws Exception { } } - private static Connection getConnection0(final Settings settings, final Path configPath, final ClassLoader cl, - final boolean needRestore) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, - FileNotFoundException, IOException, LdapException { + private static Connection getConnection0( + final Settings settings, + final Path configPath, + final ClassLoader cl, + final boolean needRestore + ) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, LdapException { final boolean enableSSL = settings.getAsBoolean(ConfigConstants.LDAPS_ENABLE_SSL, false); - final List ldapHosts = settings.getAsList(ConfigConstants.LDAP_HOSTS, - Collections.singletonList("localhost")); + final List ldapHosts = settings.getAsList(ConfigConstants.LDAP_HOSTS, Collections.singletonList("localhost")); Connection connection = null; Exception lastException = null; @@ -295,16 +302,17 @@ private static Connection getConnection0(final Settings settings, final Path con final String password = settings.get(ConfigConstants.LDAP_PASSWORD, null); if (isDebugEnabled) { - log.debug("bindDn {}, password {}", bindDn, - password != null && password.length() > 0 ? "****" : ""); + log.debug("bindDn {}, password {}", bindDn, password != null && password.length() > 0 ? "****" : ""); } if (bindDn != null && (password == null || password.length() == 0)) { log.error("No password given for bind_dn {}. Will try to authenticate anonymously to ldap", bindDn); } - final boolean enableClientAuth = settings.getAsBoolean(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, - ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH_DEFAULT); + final boolean enableClientAuth = settings.getAsBoolean( + ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, + ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH_DEFAULT + ); if (isDebugEnabled) { if (enableClientAuth && bindDn == null) { @@ -313,7 +321,8 @@ private static Connection getConnection0(final Settings settings, final Path con log.debug("Will perform anonymous bind because no bind dn is given"); } else if (enableClientAuth && bindDn != null) { log.debug( - "Will perform simple bind with bind dn because to bind dn is given and overrides client cert authentication"); + "Will perform simple bind with bind dn because to bind dn is given and overrides client cert authentication" + ); } else if (!enableClientAuth && bindDn != null) { log.debug("Will perform simple bind with bind dn"); } @@ -358,7 +367,7 @@ private static Connection getConnection0(final Settings settings, final Path con } if (connection == null || !connection.isOpen()) { - Utils.unbindAndCloseSilently(connection); //just in case + Utils.unbindAndCloseSilently(connection); // just in case if (needRestore) { restoreClassLoader0(cl); } @@ -367,8 +376,9 @@ private static Connection getConnection0(final Settings settings, final Path con throw new LdapException("Unable to connect to any of those ldap servers " + ldapHosts); } else { throw new LdapException( - "Unable to connect to any of those ldap servers " + ldapHosts + " due to " + lastException, - lastException); + "Unable to connect to any of those ldap servers " + ldapHosts + " due to " + lastException, + lastException + ); } } @@ -398,29 +408,29 @@ public Response reopen() throws LdapException { @Override public Response open(BindRequest request) throws LdapException { - + try { - if(isDebugEnabled && delegate != null && delegate.isOpen()) { + if (isDebugEnabled && delegate != null && delegate.isOpen()) { log.debug("Opened a connection, total count is now {}", CONNECTION_COUNTER.incrementAndGet()); } } catch (Throwable e) { - //ignore + // ignore } - + return delegate.open(request); } @Override public Response open() throws LdapException { - + try { - if(isDebugEnabled && delegate != null && delegate.isOpen()) { + if (isDebugEnabled && delegate != null && delegate.isOpen()) { log.debug("Opened a connection, total count is now {}", CONNECTION_COUNTER.incrementAndGet()); } } catch (Throwable e) { - //ignore + // ignore } - + return delegate.open(); } @@ -441,15 +451,15 @@ public ConnectionConfig getConnectionConfig() { @Override public void close(RequestControl[] controls) { - + try { - if(isDebugEnabled && delegate != null && delegate.isOpen()) { + if (isDebugEnabled && delegate != null && delegate.isOpen()) { log.debug("Closed a connection, total count is now {}", CONNECTION_COUNTER.decrementAndGet()); } } catch (Throwable e) { - //ignore + // ignore } - + try { delegate.close(controls); } finally { @@ -459,15 +469,15 @@ public void close(RequestControl[] controls) { @Override public void close() { - + try { - if(isDebugEnabled && delegate != null && delegate.isOpen()) { + if (isDebugEnabled && delegate != null && delegate.isOpen()) { log.debug("Closed a connection, total count is now {}", CONNECTION_COUNTER.decrementAndGet()); } } catch (Throwable e) { - //ignore + // ignore } - + try { delegate.close(); } finally { @@ -498,8 +508,7 @@ public Void run() throws Exception { } } - private static void configureSSL(final ConnectionConfig config, final Settings settings, - final Path configPath) throws Exception { + private static void configureSSL(final ConnectionConfig config, final Settings settings, final Path configPath) throws Exception { final boolean isDebugEnabled = log.isDebugEnabled(); final boolean enableSSL = settings.getAsBoolean(ConfigConstants.LDAPS_ENABLE_SSL, false); @@ -507,13 +516,15 @@ private static void configureSSL(final ConnectionConfig config, final Settings s if (enableSSL || enableStartTLS) { - final boolean enableClientAuth = settings.getAsBoolean(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, - ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH_DEFAULT); + final boolean enableClientAuth = settings.getAsBoolean( + ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, + ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH_DEFAULT + ); final boolean trustAll = settings.getAsBoolean(ConfigConstants.LDAPS_TRUST_ALL, false); - final boolean verifyHostnames = !trustAll && settings.getAsBoolean(ConfigConstants.LDAPS_VERIFY_HOSTNAMES, - ConfigConstants.LDAPS_VERIFY_HOSTNAMES_DEFAULT); + final boolean verifyHostnames = !trustAll + && settings.getAsBoolean(ConfigConstants.LDAPS_VERIFY_HOSTNAMES, ConfigConstants.LDAPS_VERIFY_HOSTNAMES_DEFAULT); if (isDebugEnabled) { log.debug("verifyHostname {}:", verifyHostnames); @@ -525,64 +536,74 @@ private static void configureSSL(final ConnectionConfig config, final Settings s } final boolean pem = settings.get(ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, null) != null - || settings.get(ConfigConstants.LDAPS_PEMTRUSTEDCAS_CONTENT, null) != null; + || settings.get(ConfigConstants.LDAPS_PEMTRUSTEDCAS_CONTENT, null) != null; final SslConfig sslConfig = new SslConfig(); CredentialConfig cc; if (pem) { X509Certificate[] trustCertificates = PemKeyReader.loadCertificatesFromStream( - PemKeyReader.resolveStream(ConfigConstants.LDAPS_PEMTRUSTEDCAS_CONTENT, settings)); + PemKeyReader.resolveStream(ConfigConstants.LDAPS_PEMTRUSTEDCAS_CONTENT, settings) + ); if (trustCertificates == null) { - trustCertificates = PemKeyReader.loadCertificatesFromFile(PemKeyReader - .resolve(ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, settings, configPath, !trustAll)); + trustCertificates = PemKeyReader.loadCertificatesFromFile( + PemKeyReader.resolve(ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, settings, configPath, !trustAll) + ); } // for client authentication X509Certificate authenticationCertificate = PemKeyReader.loadCertificateFromStream( - PemKeyReader.resolveStream(ConfigConstants.LDAPS_PEMCERT_CONTENT, settings)); + PemKeyReader.resolveStream(ConfigConstants.LDAPS_PEMCERT_CONTENT, settings) + ); if (authenticationCertificate == null) { - authenticationCertificate = PemKeyReader.loadCertificateFromFile(PemKeyReader - .resolve(ConfigConstants.LDAPS_PEMCERT_FILEPATH, settings, configPath, enableClientAuth)); + authenticationCertificate = PemKeyReader.loadCertificateFromFile( + PemKeyReader.resolve(ConfigConstants.LDAPS_PEMCERT_FILEPATH, settings, configPath, enableClientAuth) + ); } PrivateKey authenticationKey = PemKeyReader.loadKeyFromStream( - settings.get(ConfigConstants.LDAPS_PEMKEY_PASSWORD), - PemKeyReader.resolveStream(ConfigConstants.LDAPS_PEMKEY_CONTENT, settings)); + settings.get(ConfigConstants.LDAPS_PEMKEY_PASSWORD), + PemKeyReader.resolveStream(ConfigConstants.LDAPS_PEMKEY_CONTENT, settings) + ); if (authenticationKey == null) { - authenticationKey = PemKeyReader - .loadKeyFromFile(settings.get(ConfigConstants.LDAPS_PEMKEY_PASSWORD), PemKeyReader.resolve( - ConfigConstants.LDAPS_PEMKEY_FILEPATH, settings, configPath, enableClientAuth)); + authenticationKey = PemKeyReader.loadKeyFromFile( + settings.get(ConfigConstants.LDAPS_PEMKEY_PASSWORD), + PemKeyReader.resolve(ConfigConstants.LDAPS_PEMKEY_FILEPATH, settings, configPath, enableClientAuth) + ); } - cc = CredentialConfigFactory.createX509CredentialConfig(trustCertificates, authenticationCertificate, - authenticationKey); + cc = CredentialConfigFactory.createX509CredentialConfig(trustCertificates, authenticationCertificate, authenticationKey); if (isDebugEnabled) { - log.debug("Use PEM to secure communication with LDAP server (client auth is {})", - authenticationKey != null); + log.debug("Use PEM to secure communication with LDAP server (client auth is {})", authenticationKey != null); } } else { final KeyStore trustStore = PemKeyReader.loadKeyStore( - PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, - configPath, !trustAll), - SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); + PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, configPath, !trustAll), + SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE) + ); final List trustStoreAliases = settings.getAsList(ConfigConstants.LDAPS_JKS_TRUST_ALIAS, null); // for client authentication final KeyStore keyStore = PemKeyReader.loadKeyStore( - PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, settings, - configPath, enableClientAuth), - SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, - SSLConfigConstants.DEFAULT_STORE_PASSWORD), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE)); - final String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD - .getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD); + PemKeyReader.resolve( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + settings, + configPath, + enableClientAuth + ), + SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE) + ); + final String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting( + settings, + SSLConfigConstants.DEFAULT_STORE_PASSWORD + ); final String keyStoreAlias = settings.get(ConfigConstants.LDAPS_JKS_CERT_ALIAS, null); final String[] keyStoreAliases = keyStoreAlias == null ? null : new String[] { keyStoreAlias }; @@ -592,14 +613,17 @@ private static void configureSSL(final ConnectionConfig config, final Settings s } if (isDebugEnabled) { - log.debug("Use Trust-/Keystore to secure communication with LDAP server (client auth is {})", - keyStore != null); + log.debug("Use Trust-/Keystore to secure communication with LDAP server (client auth is {})", keyStore != null); log.debug("trustStoreAliases: {}, keyStoreAlias: {}", trustStoreAliases, keyStoreAlias); } - cc = CredentialConfigFactory.createKeyStoreCredentialConfig(trustStore, - trustStoreAliases == null ? null : trustStoreAliases.toArray(new String[0]), keyStore, - keyStorePassword, keyStoreAliases); + cc = CredentialConfigFactory.createKeyStoreCredentialConfig( + trustStore, + trustStoreAliases == null ? null : trustStoreAliases.toArray(new String[0]), + keyStore, + keyStorePassword, + keyStoreAliases + ); } @@ -614,9 +638,13 @@ private static void configureSSL(final ConnectionConfig config, final Settings s final String deiProp = System.getProperty(COM_SUN_JNDI_LDAP_OBJECT_DISABLE_ENDPOINT_IDENTIFICATION); if (deiProp == null || !Boolean.parseBoolean(deiProp)) { - log.warn("In order to disable host name verification for LDAP connections (verify_hostnames: true), " - + "you also need to set set the system property "+COM_SUN_JNDI_LDAP_OBJECT_DISABLE_ENDPOINT_IDENTIFICATION+" to true when starting the JVM running OpenSearch. " - + "This applies for all Java versions released since July 2018."); + log.warn( + "In order to disable host name verification for LDAP connections (verify_hostnames: true), " + + "you also need to set set the system property " + + COM_SUN_JNDI_LDAP_OBJECT_DISABLE_ENDPOINT_IDENTIFICATION + + " to true when starting the JVM running OpenSearch. " + + "This applies for all Java versions released since July 2018." + ); // See: // https://www.oracle.com/technetwork/java/javase/8u181-relnotes-4479407.html // https://www.oracle.com/technetwork/java/javase/10-0-2-relnotes-4477557.html @@ -627,10 +655,8 @@ private static void configureSSL(final ConnectionConfig config, final Settings s } - final List enabledCipherSuites = settings.getAsList(ConfigConstants.LDAPS_ENABLED_SSL_CIPHERS, - Collections.emptyList()); - final List enabledProtocols = settings.getAsList(ConfigConstants.LDAPS_ENABLED_SSL_PROTOCOLS, - DEFAULT_TLS_PROTOCOLS); + final List enabledCipherSuites = settings.getAsList(ConfigConstants.LDAPS_ENABLED_SSL_CIPHERS, Collections.emptyList()); + final List enabledProtocols = settings.getAsList(ConfigConstants.LDAPS_ENABLED_SSL_PROTOCOLS, DEFAULT_TLS_PROTOCOLS); if (!enabledCipherSuites.isEmpty()) { sslConfig.setEnabledCipherSuites(enabledCipherSuites.toArray(new String[0])); @@ -654,14 +680,12 @@ private static void configureSSL(final ConnectionConfig config, final Settings s config.setResponseTimeout(Duration.ofMillis(responseTimeout < 0L ? 0L : responseTimeout)); if (isDebugEnabled) { - log.debug("Connect timeout: " + config.getConnectTimeout() + "/ResponseTimeout: " - + config.getResponseTimeout()); + log.debug("Connect timeout: " + config.getConnectTimeout() + "/ResponseTimeout: " + config.getResponseTimeout()); } } @Override - public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) - throws OpenSearchSecurityException { + public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) throws OpenSearchSecurityException { if (user == null) { return; @@ -673,7 +697,7 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) String dn = null; final boolean isDebugEnabled = log.isDebugEnabled(); - if (isDebugEnabled){ + if (isDebugEnabled) { log.debug("DBGTRACE (2): username: {} -> {}", user.getName(), Arrays.toString(user.getName().getBytes(StandardCharsets.UTF_8))); } @@ -686,11 +710,14 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) originalUserName = user.getName(); } - if (isDebugEnabled){ - log.debug("DBGTRACE (3): authenticatedUser: {} -> {}", authenticatedUser, Arrays.toString(authenticatedUser.getBytes(StandardCharsets.UTF_8))); + if (isDebugEnabled) { + log.debug( + "DBGTRACE (3): authenticatedUser: {} -> {}", + authenticatedUser, + Arrays.toString(authenticatedUser.getBytes(StandardCharsets.UTF_8)) + ); } - final boolean rolesearchEnabled = settings.getAsBoolean(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true); if (isDebugEnabled) { @@ -727,8 +754,13 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) log.trace("{} is a valid DN", authenticatedUser); } - if (isDebugEnabled){ - log.debug("DBGTRACE (4): authenticatedUser="+authenticatedUser+" -> "+Arrays.toString(authenticatedUser.getBytes(StandardCharsets.UTF_8))); + if (isDebugEnabled) { + log.debug( + "DBGTRACE (4): authenticatedUser=" + + authenticatedUser + + " -> " + + Arrays.toString(authenticatedUser.getBytes(StandardCharsets.UTF_8)) + ); } entry = LdapHelper.lookup(connection, authenticatedUser, this.returnAttributes, this.shouldFollowReferrals); @@ -739,10 +771,21 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) } else { - if (isDebugEnabled) - log.debug("DBGTRACE (5): authenticatedUser="+user.getName()+" -> "+Arrays.toString(user.getName().getBytes(StandardCharsets.UTF_8))); - - entry = LDAPAuthenticationBackend.exists(user.getName(), connection, settings, userBaseSettings, this.returnAttributes, this.shouldFollowReferrals); + if (isDebugEnabled) log.debug( + "DBGTRACE (5): authenticatedUser=" + + user.getName() + + " -> " + + Arrays.toString(user.getName().getBytes(StandardCharsets.UTF_8)) + ); + + entry = LDAPAuthenticationBackend.exists( + user.getName(), + connection, + settings, + userBaseSettings, + this.returnAttributes, + this.shouldFollowReferrals + ); if (isTraceEnabled) { log.trace("{} is not a valid DN and was resolved to {}", authenticatedUser, entry); @@ -759,8 +802,8 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) log.trace("User found with DN {}", dn); } - if (isDebugEnabled){ - log.debug("DBGTRACE (6): dn"+dn+" -> "+Arrays.toString(dn.getBytes(StandardCharsets.UTF_8))); + if (isDebugEnabled) { + log.debug("DBGTRACE (6): dn" + dn + " -> " + Arrays.toString(dn.getBytes(StandardCharsets.UTF_8))); } } @@ -784,8 +827,8 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) final Collection userRoles = entry.getAttribute(roleName).getStringValues(); for (final String possibleRoleDN : userRoles) { - if (isDebugEnabled){ - log.debug("DBGTRACE (7): possibleRoleDN"+possibleRoleDN); + if (isDebugEnabled) { + log.debug("DBGTRACE (7): possibleRoleDN" + possibleRoleDN); } if (isValidDn(possibleRoleDN)) { @@ -838,8 +881,8 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) if (rolesearchEnabled) { String escapedDn = dn; - if (isDebugEnabled){ - log.debug("DBGTRACE (8): escapedDn"+escapedDn); + if (isDebugEnabled) { + log.debug("DBGTRACE (8): escapedDn" + escapedDn); } for (Map.Entry roleSearchSettingsEntry : roleBaseSettings) { @@ -849,16 +892,24 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) f.setFilter(roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_SEARCH, DEFAULT_ROLESEARCH)); f.setParameter(LDAPAuthenticationBackend.ZERO_PLACEHOLDER, escapedDn); f.setParameter(ONE_PLACEHOLDER, originalUserName); - f.setParameter(TWO_PLACEHOLDER, - userRoleAttributeValue == null ? TWO_PLACEHOLDER : userRoleAttributeValue); + f.setParameter(TWO_PLACEHOLDER, userRoleAttributeValue == null ? TWO_PLACEHOLDER : userRoleAttributeValue); - List rolesResult = LdapHelper.search(connection, - roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), - f, - SearchScope.SUBTREE, this.returnAttributes, this.shouldFollowReferrals); + List rolesResult = LdapHelper.search( + connection, + roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), + f, + SearchScope.SUBTREE, + this.returnAttributes, + this.shouldFollowReferrals + ); if (isTraceEnabled) { - log.trace("Results for LDAP group search for {} in base {}:\n{}", escapedDn, roleSearchSettingsEntry.getKey(), rolesResult); + log.trace( + "Results for LDAP group search for {} in base {}:\n{}", + escapedDn, + roleSearchSettingsEntry.getKey(), + rolesResult + ); } if (rolesResult != null && !rolesResult.isEmpty()) { @@ -886,17 +937,21 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) final Set nestedReturn = new HashSet<>(ldapRoles); for (final LdapName roleLdapName : ldapRoles) { - Set> nameRoleSearchBaseKeys = resultRoleSearchBaseKeys - .get(roleLdapName); + Set> nameRoleSearchBaseKeys = resultRoleSearchBaseKeys.get(roleLdapName); if (nameRoleSearchBaseKeys == null) { - log.error("Could not find roleSearchBaseKeys for " + roleLdapName + "; existing: " - + resultRoleSearchBaseKeys); + log.error("Could not find roleSearchBaseKeys for " + roleLdapName + "; existing: " + resultRoleSearchBaseKeys); continue; } - final Set nestedRoles = resolveNestedRoles(roleLdapName, connection, userRoleNames, 0, - rolesearchEnabled, nameRoleSearchBaseKeys); + final Set nestedRoles = resolveNestedRoles( + roleLdapName, + connection, + userRoleNames, + 0, + rolesearchEnabled, + nameRoleSearchBaseKeys + ); if (isTraceEnabled) { log.trace("{} nested roles for {}", nestedRoles.size(), roleLdapName); @@ -953,10 +1008,14 @@ public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) } - protected Set resolveNestedRoles(final LdapName roleDn, final Connection ldapConnection, - String userRoleName, int depth, final boolean rolesearchEnabled, - Set> roleSearchBaseSettingsSet) - throws OpenSearchSecurityException, LdapException { + protected Set resolveNestedRoles( + final LdapName roleDn, + final Connection ldapConnection, + String userRoleName, + int depth, + final boolean rolesearchEnabled, + Set> roleSearchBaseSettingsSet + ) throws OpenSearchSecurityException, LdapException { if (nestedRoleMatcher.test(roleDn.toString())) { @@ -980,8 +1039,8 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti for (final String possibleRoleDN : userRoles) { - if (isDebugEnabled){ - log.debug("DBGTRACE (10): possibleRoleDN"+possibleRoleDN); + if (isDebugEnabled) { + log.debug("DBGTRACE (10): possibleRoleDN" + possibleRoleDN); } if (isValidDn(possibleRoleDN)) { @@ -1008,13 +1067,11 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti if (rolesearchEnabled) { String escapedDn = roleDn.toString(); - if (isDebugEnabled){ + if (isDebugEnabled) { log.debug("DBGTRACE (10): escapedDn {}", escapedDn); } - - for (Map.Entry roleSearchBaseSettingsEntry : Utils - .getOrderedBaseSettings(roleSearchBaseSettingsSet)) { + for (Map.Entry roleSearchBaseSettingsEntry : Utils.getOrderedBaseSettings(roleSearchBaseSettingsSet)) { Settings roleSearchSettings = roleSearchBaseSettingsEntry.getValue(); SearchFilter f = new SearchFilter(); @@ -1022,14 +1079,22 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti f.setParameter(LDAPAuthenticationBackend.ZERO_PLACEHOLDER, escapedDn); f.setParameter(ONE_PLACEHOLDER, escapedDn); - List foundEntries = LdapHelper.search(ldapConnection, - roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), - f, - SearchScope.SUBTREE, - this.returnAttributes, this.shouldFollowReferrals); + List foundEntries = LdapHelper.search( + ldapConnection, + roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), + f, + SearchScope.SUBTREE, + this.returnAttributes, + this.shouldFollowReferrals + ); if (isTraceEnabled) { - log.trace("Results for LDAP group search for {} in base {}:\n{}", escapedDn, roleSearchBaseSettingsEntry.getKey(), foundEntries); + log.trace( + "Results for LDAP group search for {} in base {}:\n{}", + escapedDn, + roleSearchBaseSettingsEntry.getKey(), + foundEntries + ); } if (foundEntries != null) { @@ -1048,8 +1113,7 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti int maxDepth = ConfigConstants.LDAP_AUTHZ_MAX_NESTED_DEPTH_DEFAULT; try { - maxDepth = settings.getAsInt(ConfigConstants.LDAP_AUTHZ_MAX_NESTED_DEPTH, - ConfigConstants.LDAP_AUTHZ_MAX_NESTED_DEPTH_DEFAULT); + maxDepth = settings.getAsInt(ConfigConstants.LDAP_AUTHZ_MAX_NESTED_DEPTH, ConfigConstants.LDAP_AUTHZ_MAX_NESTED_DEPTH_DEFAULT); } catch (Exception e) { log.error(ConfigConstants.LDAP_AUTHZ_MAX_NESTED_DEPTH + " is not parseable: " + e, e); } @@ -1059,13 +1123,18 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti Set> nameRoleSearchBaseKeys = resultRoleSearchBaseKeys.get(nm); if (nameRoleSearchBaseKeys == null) { - log.error( - "Could not find roleSearchBaseKeys for " + nm + "; existing: " + resultRoleSearchBaseKeys); + log.error("Could not find roleSearchBaseKeys for " + nm + "; existing: " + resultRoleSearchBaseKeys); continue; } - final Set in = resolveNestedRoles(nm, ldapConnection, userRoleName, depth, rolesearchEnabled, - nameRoleSearchBaseKeys); + final Set in = resolveNestedRoles( + nm, + ldapConnection, + userRoleName, + depth, + rolesearchEnabled, + nameRoleSearchBaseKeys + ); result.addAll(in); } } @@ -1099,16 +1168,21 @@ private String getRoleFromEntry(final Connection ldapConnection, final LdapName return null; } - if("dn".equalsIgnoreCase(role)) { + if ("dn".equalsIgnoreCase(role)) { return ldapName.toString(); } try { - final LdapEntry roleEntry = LdapHelper.lookup(ldapConnection, ldapName.toString(), this.returnAttributes, this.shouldFollowReferrals); - - if(roleEntry != null) { + final LdapEntry roleEntry = LdapHelper.lookup( + ldapConnection, + ldapName.toString(), + this.returnAttributes, + this.shouldFollowReferrals + ); + + if (roleEntry != null) { final LdapAttribute roleAttribute = roleEntry.getAttribute(role); - if(roleAttribute != null) { + if (roleAttribute != null) { return Utils.getSingleStringValue(roleAttribute); } } diff --git a/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java b/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java index bdd9f39a8e..4854f80332 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/util/ConfigConstants.java @@ -14,7 +14,7 @@ public final class ConfigConstants { public static final String LDAP_AUTHC_USERBASE = "userbase"; - public static final String LDAP_AUTHC_USERNAME_ATTRIBUTE = "username_attribute";//multi-value + public static final String LDAP_AUTHC_USERNAME_ATTRIBUTE = "username_attribute";// multi-value public static final String LDAP_AUTHC_USERSEARCH = "usersearch"; public static final String LDAP_AUTHCZ_USERS = "users"; @@ -22,13 +22,12 @@ public final class ConfigConstants { public static final String LDAP_AUTHCZ_BASE = "base"; public static final String LDAP_AUTHCZ_SEARCH = "search"; - public static final String LDAP_AUTHZ_RESOLVE_NESTED_ROLES = "resolve_nested_roles"; public static final String LDAP_AUTHZ_ROLEBASE = "rolebase"; - public static final String LDAP_AUTHZ_ROLENAME = "rolename";//multi-value + public static final String LDAP_AUTHZ_ROLENAME = "rolename";// multi-value public static final String LDAP_AUTHZ_ROLESEARCH = "rolesearch"; - public static final String LDAP_AUTHZ_USERROLEATTRIBUTE = "userroleattribute";//multi-value - public static final String LDAP_AUTHZ_USERROLENAME = "userrolename";//multi-value + public static final String LDAP_AUTHZ_USERROLEATTRIBUTE = "userroleattribute";// multi-value + public static final String LDAP_AUTHZ_USERROLENAME = "userrolename";// multi-value public static final String LDAP_AUTHZ_SKIP_USERS = "skip_users"; public static final String LDAP_AUTHZ_ROLESEARCH_ENABLED = "rolesearch_enabled"; public static final String LDAP_AUTHZ_NESTEDROLEFILTER = "nested_role_filter"; diff --git a/src/main/java/com/amazon/dlic/auth/ldap/util/LdapHelper.java b/src/main/java/com/amazon/dlic/auth/ldap/util/LdapHelper.java index db737ca5c5..f06c7d59d7 100644 --- a/src/main/java/com/amazon/dlic/auth/ldap/util/LdapHelper.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/util/LdapHelper.java @@ -38,9 +38,16 @@ public class LdapHelper { private static SearchFilter ALL = new SearchFilter("(objectClass=*)"); + @SuppressWarnings("removal") - public static List search(final Connection conn, final String unescapedDn, SearchFilter filter, - final SearchScope searchScope, final String[] returnAttributes, boolean shouldFollowReferrals) throws LdapException { + public static List search( + final Connection conn, + final String unescapedDn, + SearchFilter filter, + final SearchScope searchScope, + final String[] returnAttributes, + boolean shouldFollowReferrals + ) throws LdapException { final SecurityManager sm = System.getSecurityManager(); @@ -61,7 +68,7 @@ public List run() throws Exception { final SearchOperation search = new SearchOperation(conn); if (shouldFollowReferrals) { - // referrals will be followed to build the response + // referrals will be followed to build the response request.setReferralHandler(new SearchReferralHandler()); } @@ -80,12 +87,17 @@ public List run() throws Exception { } else { throw new RuntimeException(e); } - }catch (InvalidNameException e) { + } catch (InvalidNameException e) { throw new RuntimeException(e); } } - public static LdapEntry lookup(final Connection conn, final String unescapedDn, final String[] returnAttributes, boolean shouldFollowReferrals) throws LdapException { + public static LdapEntry lookup( + final Connection conn, + final String unescapedDn, + final String[] returnAttributes, + boolean shouldFollowReferrals + ) throws LdapException { final List entries = search(conn, unescapedDn, ALL, SearchScope.OBJECT, returnAttributes, shouldFollowReferrals); @@ -99,15 +111,15 @@ public static LdapEntry lookup(final Connection conn, final String unescapedDn, private static String escapeDn(String dn) throws InvalidNameException { final LdapName dnName = new LdapName(dn); final List escaped = new ArrayList<>(dnName.size()); - for(Rdn rdn: dnName.getRdns()) { + for (Rdn rdn : dnName.getRdns()) { escaped.add(new Rdn(rdn.getType(), escapeForwardSlash(rdn.getValue()))); } return new LdapName(escaped).toString(); } private static Object escapeForwardSlash(Object input) { - if(input != null && input instanceof String) { - return ((String)input).replace("/", "\\2f"); + if (input != null && input instanceof String) { + return ((String) input).replace("/", "\\2f"); } else { return input; } diff --git a/src/main/java/com/amazon/dlic/auth/ldap/util/Utils.java b/src/main/java/com/amazon/dlic/auth/ldap/util/Utils.java index f10452f410..743705eee5 100644 --- a/src/main/java/com/amazon/dlic/auth/ldap/util/Utils.java +++ b/src/main/java/com/amazon/dlic/auth/ldap/util/Utils.java @@ -83,8 +83,10 @@ private static void sortBaseSettings(List> list) { @Override public int compare(Map.Entry o1, Map.Entry o2) { - int attributeOrder = Integer.compare(o1.getValue().getAsInt("order", Integer.MAX_VALUE), - o2.getValue().getAsInt("order", Integer.MAX_VALUE)); + int attributeOrder = Integer.compare( + o1.getValue().getAsInt("order", Integer.MAX_VALUE), + o2.getValue().getAsInt("order", Integer.MAX_VALUE) + ); if (attributeOrder != 0) { return attributeOrder; @@ -96,12 +98,12 @@ public int compare(Map.Entry o1, Map.Entry o } public static String getSingleStringValue(LdapAttribute attribute) { - if(attribute == null) { + if (attribute == null) { return null; } - if(attribute.size() > 1) { - if(log.isDebugEnabled()) { + if (attribute.size() > 1) { + if (log.isDebugEnabled()) { log.debug("Multiple values found for {} ({})", attribute.getName(), attribute); } } diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthenticationBackend2.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthenticationBackend2.java index f64f3fc0b4..74184de0eb 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthenticationBackend2.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthenticationBackend2.java @@ -65,8 +65,7 @@ public class LDAPAuthenticationBackend2 implements AuthenticationBackend, Destro public LDAPAuthenticationBackend2(final Settings settings, final Path configPath) throws SSLConfigException { this.settings = settings; - LDAPConnectionFactoryFactory ldapConnectionFactoryFactory = new LDAPConnectionFactoryFactory(settings, - configPath); + LDAPConnectionFactoryFactory ldapConnectionFactoryFactory = new LDAPConnectionFactoryFactory(settings, configPath); this.connectionPool = ldapConnectionFactoryFactory.createConnectionPool(); this.connectionFactory = ldapConnectionFactoryFactory.createConnectionFactory(this.connectionPool); @@ -78,11 +77,13 @@ public LDAPAuthenticationBackend2(final Settings settings, final Path configPath } this.userSearcher = new LDAPUserSearcher(settings); - this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); + this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())) + .toArray(new String[0]); this.shouldFollowReferrals = settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT); customAttrMaxValueLen = settings.getAsInt(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 36); - whitelistedCustomLdapAttrMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, - Collections.singletonList("*"))); + whitelistedCustomLdapAttrMatcher = WildcardMatcher.from( + settings.getAsList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, Collections.singletonList("*")) + ); } @Override @@ -112,7 +113,6 @@ public User run() throws Exception { } } - private User authenticate0(final AuthCredentials credentials) throws OpenSearchSecurityException { Connection ldapConnection = null; @@ -130,11 +130,12 @@ private User authenticate0(final AuthCredentials credentials) throws OpenSearchS // makes guessing if a user exists or not harder when looking on the // authentication delay time if (entry == null && settings.getAsBoolean(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, false)) { - String fakeLognDn = settings.get(ConfigConstants.LDAP_FAKE_LOGIN_DN, - "CN=faketomakebindfail,DC=" + UUID.randomUUID().toString()); + String fakeLognDn = settings.get( + ConfigConstants.LDAP_FAKE_LOGIN_DN, + "CN=faketomakebindfail,DC=" + UUID.randomUUID().toString() + ); entry = new LdapEntry(fakeLognDn); - password = settings.get(ConfigConstants.LDAP_FAKE_LOGIN_PASSWORD, "fakeLoginPwd123") - .getBytes(StandardCharsets.UTF_8); + password = settings.get(ConfigConstants.LDAP_FAKE_LOGIN_PASSWORD, "fakeLoginPwd123").getBytes(StandardCharsets.UTF_8); } else if (entry == null) { throw new OpenSearchSecurityException("No user " + user + " found"); } @@ -195,7 +196,6 @@ public boolean exists(final User user) { sm.checkPermission(new SpecialPermission()); } - return AccessController.doPrivileged(new PrivilegedAction() { @Override public Boolean run() { @@ -217,13 +217,15 @@ private boolean exists0(final User user) { ldapConnection = this.connectionFactory.getConnection(); ldapConnection.open(); LdapEntry userEntry = this.userSearcher.exists(ldapConnection, userName, this.returnAttributes, this.shouldFollowReferrals); - + boolean exists = userEntry != null; - - if(exists) { - user.addAttributes(LdapUser.extractLdapAttributes(userName, userEntry, customAttrMaxValueLen, whitelistedCustomLdapAttrMatcher)); + + if (exists) { + user.addAttributes( + LdapUser.extractLdapAttributes(userName, userEntry, customAttrMaxValueLen, whitelistedCustomLdapAttrMatcher) + ); } - + return exists; } catch (final Exception e) { log.warn("User {} does not exist due to exception", userName, e); @@ -234,8 +236,7 @@ private boolean exists0(final User user) { } @SuppressWarnings("removal") - private void authenticateByLdapServer(final Connection connection, final String dn, byte[] password) - throws LdapException { + private void authenticateByLdapServer(final Connection connection, final String dn, byte[] password) throws LdapException { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java index c140dbb6f9..d8d27de7da 100755 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPAuthorizationBackend2.java @@ -81,17 +81,18 @@ public class LDAPAuthorizationBackend2 implements AuthorizationBackend, Destroya public LDAPAuthorizationBackend2(final Settings settings, final Path configPath) throws SSLConfigException { this.settings = settings; this.skipUsersMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS)); - this.nestedRoleMatcher = settings.getAsBoolean(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) ? - WildcardMatcher.from(settings.getAsList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER)) : null; + this.nestedRoleMatcher = settings.getAsBoolean(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) + ? WildcardMatcher.from(settings.getAsList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER)) + : null; this.roleBaseSettings = getRoleSearchSettings(settings); - LDAPConnectionFactoryFactory ldapConnectionFactoryFactory = new LDAPConnectionFactoryFactory(settings, - configPath); + LDAPConnectionFactoryFactory ldapConnectionFactoryFactory = new LDAPConnectionFactoryFactory(settings, configPath); this.connectionPool = ldapConnectionFactoryFactory.createConnectionPool(); this.connectionFactory = ldapConnectionFactoryFactory.createConnectionFactory(this.connectionPool); this.userSearcher = new LDAPUserSearcher(settings); - this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())).toArray(new String[0]); + this.returnAttributes = settings.getAsList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, Arrays.asList(ReturnAttributes.ALL.value())) + .toArray(new String[0]); this.shouldFollowReferrals = settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT); } @@ -112,10 +113,8 @@ private static List> convertOldStyleSettingsToNewSty Settings.Builder settingsBuilder = Settings.builder(); - settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_BASE, - settings.get(ConfigConstants.LDAP_AUTHZ_ROLEBASE, DEFAULT_ROLEBASE)); - settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_SEARCH, - settings.get(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, DEFAULT_ROLESEARCH)); + settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_BASE, settings.get(ConfigConstants.LDAP_AUTHZ_ROLEBASE, DEFAULT_ROLEBASE)); + settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_SEARCH, settings.get(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, DEFAULT_ROLESEARCH)); result.put("convertedOldStyleSettings", settingsBuilder.build()); @@ -124,8 +123,7 @@ private static List> convertOldStyleSettingsToNewSty @SuppressWarnings("removal") @Override - public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) - throws OpenSearchSecurityException { + public void fillRoles(final User user, final AuthCredentials optionalAuthCreds) throws OpenSearchSecurityException { final SecurityManager sm = System.getSecurityManager(); @@ -152,8 +150,7 @@ public Void run() throws Exception { } } - private void fillRoles0(final User user, final AuthCredentials optionalAuthCreds) - throws OpenSearchSecurityException { + private void fillRoles0(final User user, final AuthCredentials optionalAuthCreds) throws OpenSearchSecurityException { if (user == null) { return; @@ -170,7 +167,7 @@ private void fillRoles0(final User user, final AuthCredentials optionalAuthCreds authenticatedUser = entry.getDn(); originalUserName = ((LdapUser) user).getOriginalUsername(); } else { - authenticatedUser =user.getName(); + authenticatedUser = user.getName(); originalUserName = user.getName(); } @@ -309,17 +306,24 @@ private void fillRoles0(final User user, final AuthCredentials optionalAuthCreds f.setFilter(roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_SEARCH, DEFAULT_ROLESEARCH)); f.setParameter(ZERO_PLACEHOLDER, escapedDn); f.setParameter(ONE_PLACEHOLDER, originalUserName); - f.setParameter(TWO_PLACEHOLDER, - userRoleAttributeValue == null ? TWO_PLACEHOLDER : userRoleAttributeValue); + f.setParameter(TWO_PLACEHOLDER, userRoleAttributeValue == null ? TWO_PLACEHOLDER : userRoleAttributeValue); - List rolesResult = LdapHelper.search(connection, - roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), - f, - SearchScope.SUBTREE, - this.returnAttributes, this.shouldFollowReferrals); + List rolesResult = LdapHelper.search( + connection, + roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), + f, + SearchScope.SUBTREE, + this.returnAttributes, + this.shouldFollowReferrals + ); if (isTraceEnabled) { - log.trace("Results for LDAP group search for {} in base {}:\n{}", escapedDn, roleSearchSettingsEntry.getKey(), rolesResult); + log.trace( + "Results for LDAP group search for {} in base {}:\n{}", + escapedDn, + roleSearchSettingsEntry.getKey(), + rolesResult + ); } if (rolesResult != null && !rolesResult.isEmpty()) { @@ -347,17 +351,21 @@ private void fillRoles0(final User user, final AuthCredentials optionalAuthCreds final Set nestedReturn = new HashSet<>(ldapRoles); for (final LdapName roleLdapName : ldapRoles) { - Set> nameRoleSearchBaseKeys = resultRoleSearchBaseKeys - .get(roleLdapName); + Set> nameRoleSearchBaseKeys = resultRoleSearchBaseKeys.get(roleLdapName); if (nameRoleSearchBaseKeys == null) { - log.error("Could not find roleSearchBaseKeys for {}; existing: {}", - roleLdapName, resultRoleSearchBaseKeys); + log.error("Could not find roleSearchBaseKeys for {}; existing: {}", roleLdapName, resultRoleSearchBaseKeys); continue; } - final Set nestedRoles = resolveNestedRoles(roleLdapName, connection, userRoleNames, 0, - rolesearchEnabled, nameRoleSearchBaseKeys); + final Set nestedRoles = resolveNestedRoles( + roleLdapName, + connection, + userRoleNames, + 0, + rolesearchEnabled, + nameRoleSearchBaseKeys + ); if (isTraceEnabled) { log.trace("{} nested roles for {}", nestedRoles.size(), roleLdapName); @@ -412,10 +420,14 @@ private void fillRoles0(final User user, final AuthCredentials optionalAuthCreds } - protected Set resolveNestedRoles(final LdapName roleDn, final Connection ldapConnection, - String userRoleName, int depth, final boolean rolesearchEnabled, - Set> roleSearchBaseSettingsSet) - throws OpenSearchSecurityException, LdapException { + protected Set resolveNestedRoles( + final LdapName roleDn, + final Connection ldapConnection, + String userRoleName, + int depth, + final boolean rolesearchEnabled, + Set> roleSearchBaseSettingsSet + ) throws OpenSearchSecurityException, LdapException { final boolean isTraceEnabled = log.isTraceEnabled(); if (nestedRoleMatcher.test(roleDn.toString())) { @@ -461,8 +473,7 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti if (rolesearchEnabled) { String escapedDn = roleDn.toString(); - for (Map.Entry roleSearchBaseSettingsEntry : Utils - .getOrderedBaseSettings(roleSearchBaseSettingsSet)) { + for (Map.Entry roleSearchBaseSettingsEntry : Utils.getOrderedBaseSettings(roleSearchBaseSettingsSet)) { Settings roleSearchSettings = roleSearchBaseSettingsEntry.getValue(); SearchFilter f = new SearchFilter(); @@ -470,14 +481,22 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti f.setParameter(ZERO_PLACEHOLDER, escapedDn); f.setParameter(ONE_PLACEHOLDER, escapedDn); - List foundEntries = LdapHelper.search(ldapConnection, - roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), - f, - SearchScope.SUBTREE, - this.returnAttributes, this.shouldFollowReferrals); + List foundEntries = LdapHelper.search( + ldapConnection, + roleSearchSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_ROLEBASE), + f, + SearchScope.SUBTREE, + this.returnAttributes, + this.shouldFollowReferrals + ); if (isTraceEnabled) { - log.trace("Results for LDAP group search for {} in base {}:\n{}", escapedDn, roleSearchBaseSettingsEntry.getKey(), foundEntries); + log.trace( + "Results for LDAP group search for {} in base {}:\n{}", + escapedDn, + roleSearchBaseSettingsEntry.getKey(), + foundEntries + ); } if (foundEntries != null) { @@ -496,8 +515,7 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti int maxDepth = ConfigConstants.LDAP_AUTHZ_MAX_NESTED_DEPTH_DEFAULT; try { - maxDepth = settings.getAsInt(ConfigConstants.LDAP_AUTHZ_MAX_NESTED_DEPTH, - ConfigConstants.LDAP_AUTHZ_MAX_NESTED_DEPTH_DEFAULT); + maxDepth = settings.getAsInt(ConfigConstants.LDAP_AUTHZ_MAX_NESTED_DEPTH, ConfigConstants.LDAP_AUTHZ_MAX_NESTED_DEPTH_DEFAULT); } catch (Exception e) { log.error(ConfigConstants.LDAP_AUTHZ_MAX_NESTED_DEPTH + " is not parseable: ", e); } @@ -507,13 +525,18 @@ protected Set resolveNestedRoles(final LdapName roleDn, final Connecti Set> nameRoleSearchBaseKeys = resultRoleSearchBaseKeys.get(nm); if (nameRoleSearchBaseKeys == null) { - log.error( - "Could not find roleSearchBaseKeys for {}; existing: {}", nm, resultRoleSearchBaseKeys); + log.error("Could not find roleSearchBaseKeys for {}; existing: {}", nm, resultRoleSearchBaseKeys); continue; } - final Set in = resolveNestedRoles(nm, ldapConnection, userRoleName, depth, rolesearchEnabled, - nameRoleSearchBaseKeys); + final Set in = resolveNestedRoles( + nm, + ldapConnection, + userRoleName, + depth, + rolesearchEnabled, + nameRoleSearchBaseKeys + ); result.addAll(in); } } @@ -547,21 +570,26 @@ private String getRoleFromEntry(final Connection ldapConnection, final LdapName return null; } - if("dn".equalsIgnoreCase(role)) { + if ("dn".equalsIgnoreCase(role)) { return ldapName.toString(); } try { - final LdapEntry roleEntry = LdapHelper.lookup(ldapConnection, ldapName.toString(), this.returnAttributes, this.shouldFollowReferrals); - - if(roleEntry != null) { + final LdapEntry roleEntry = LdapHelper.lookup( + ldapConnection, + ldapName.toString(), + this.returnAttributes, + this.shouldFollowReferrals + ); + + if (roleEntry != null) { final LdapAttribute roleAttribute = roleEntry.getAttribute(role); - if(roleAttribute != null) { + if (roleAttribute != null) { return Utils.getSingleStringValue(roleAttribute); } } } catch (LdapException e) { - log.error("Unable to handle role {} because of ",ldapName, e); + log.error("Unable to handle role {} because of ", ldapName, e); } return null; diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPConnectionFactoryFactory.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPConnectionFactoryFactory.java index f711e41982..877c4160da 100644 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPConnectionFactoryFactory.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPConnectionFactoryFactory.java @@ -132,10 +132,22 @@ public ConnectionPool createConnectionPool() { checkForDeprecatedSetting(settings, ConfigConstants.LDAP_LEGACY_POOL_PRUNING_PERIOD, ConfigConstants.LDAP_POOL_PRUNING_PERIOD); checkForDeprecatedSetting(settings, ConfigConstants.LDAP_LEGACY_POOL_IDLE_TIME, ConfigConstants.LDAP_POOL_IDLE_TIME); - - result.setPruneStrategy(new IdlePruneStrategy( - Duration.ofMinutes(this.settings.getAsLong(ConfigConstants.LDAP_POOL_PRUNING_PERIOD, this.settings.getAsLong(ConfigConstants.LDAP_LEGACY_POOL_PRUNING_PERIOD, 5l))), - Duration.ofMinutes(this.settings.getAsLong(ConfigConstants.LDAP_POOL_IDLE_TIME, this.settings.getAsLong(ConfigConstants.LDAP_LEGACY_POOL_IDLE_TIME, 10l)))) + + result.setPruneStrategy( + new IdlePruneStrategy( + Duration.ofMinutes( + this.settings.getAsLong( + ConfigConstants.LDAP_POOL_PRUNING_PERIOD, + this.settings.getAsLong(ConfigConstants.LDAP_LEGACY_POOL_PRUNING_PERIOD, 5l) + ) + ), + Duration.ofMinutes( + this.settings.getAsLong( + ConfigConstants.LDAP_POOL_IDLE_TIME, + this.settings.getAsLong(ConfigConstants.LDAP_LEGACY_POOL_IDLE_TIME, 10l) + ) + ) + ) ); result.initialize(); @@ -186,8 +198,10 @@ private ConnectionInitializer getConnectionInitializer() { log.error("No password given for bind_dn {}. Will try to authenticate anonymously to ldap", bindDn); } - boolean enableClientAuth = settings.getAsBoolean(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, - ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH_DEFAULT); + boolean enableClientAuth = settings.getAsBoolean( + ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, + ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH_DEFAULT + ); if (bindDn != null && password != null) { log.debug("Will perform simple bind with bind dn"); @@ -195,8 +209,7 @@ private ConnectionInitializer getConnectionInitializer() { result.setBindCredential(new Credential(password)); if (enableClientAuth) { - log.warn( - "Will perform simple bind with bind dn because to bind dn is given and overrides client cert authentication"); + log.warn("Will perform simple bind with bind dn because to bind dn is given and overrides client cert authentication"); } } else if (enableClientAuth) { log.debug("Will perform External SASL bind because client cert authentication is enabled"); @@ -210,12 +223,12 @@ private ConnectionInitializer getConnectionInitializer() { private ConnectionStrategy getConnectionStrategy() { switch (this.settings.get(ConfigConstants.LDAP_CONNECTION_STRATEGY, "active_passive").toLowerCase()) { - case "round_robin": - return new RoundRobinConnectionStrategy(); - case "random": - return new RandomConnectionStrategy(); - default: - return new ActivePassiveConnectionStrategy(); + case "round_robin": + return new RoundRobinConnectionStrategy(); + case "random": + return new RandomConnectionStrategy(); + default: + return new ActivePassiveConnectionStrategy(); } } @@ -228,14 +241,19 @@ private Validator getConnectionValidator() { Validator result = null; if ("compare".equalsIgnoreCase(validationStrategy)) { - result = new CompareValidator(new CompareRequest(this.settings.get("validation.compare.dn", ""), - new LdapAttribute(this.settings.get("validation.compare.attribute", "objectClass"), - this.settings.get("validation.compare.value", "top")))); + result = new CompareValidator( + new CompareRequest( + this.settings.get("validation.compare.dn", ""), + new LdapAttribute( + this.settings.get("validation.compare.attribute", "objectClass"), + this.settings.get("validation.compare.value", "top") + ) + ) + ); } else { SearchRequest searchRequest = new SearchRequest(); searchRequest.setBaseDn(this.settings.get("validation.search.base_dn", "")); - searchRequest.setSearchFilter( - new SearchFilter(this.settings.get("validation.search.filter", "(objectClass=*)"))); + searchRequest.setSearchFilter(new SearchFilter(this.settings.get("validation.search.filter", "(objectClass=*)"))); searchRequest.setReturnAttributes(ReturnAttributes.NONE.value()); searchRequest.setSearchScope(SearchScope.OBJECT); searchRequest.setSizeLimit(1); @@ -250,8 +268,7 @@ private String getLdapUrlString() { // It's a bit weird that we create from structured data a plain string which is // later parsed again by ldaptive. But that's the way the API wants it to be. - List ldapHosts = this.settings.getAsList(ConfigConstants.LDAP_HOSTS, - Collections.singletonList("localhost")); + List ldapHosts = this.settings.getAsList(ConfigConstants.LDAP_HOSTS, Collections.singletonList("localhost")); boolean enableSSL = settings.getAsBoolean(ConfigConstants.LDAPS_ENABLE_SSL, false); StringBuilder result = new StringBuilder(); @@ -282,9 +299,12 @@ private void configureSSL(ConnectionConfig config) { SslConfig ldaptiveSslConfig = new SslConfig(); CredentialConfig cc = CredentialConfigFactory.createKeyStoreCredentialConfig( - this.sslConfig.getEffectiveTruststore(), this.sslConfig.getEffectiveTruststoreAliasesArray(), - this.sslConfig.getEffectiveKeystore(), this.sslConfig.getEffectiveKeyPasswordString(), - this.sslConfig.getEffectiveKeyAliasesArray()); + this.sslConfig.getEffectiveTruststore(), + this.sslConfig.getEffectiveTruststoreAliasesArray(), + this.sslConfig.getEffectiveKeystore(), + this.sslConfig.getEffectiveKeyPasswordString(), + this.sslConfig.getEffectiveKeyAliasesArray() + ); ldaptiveSslConfig.setCredentialConfig(cc); @@ -292,9 +312,11 @@ private void configureSSL(ConnectionConfig config) { ldaptiveSslConfig.setHostnameVerifier(new AllowAnyHostnameVerifier()); if (!Boolean.parseBoolean(System.getProperty("com.sun.jndi.ldap.object.disableEndpointIdentification"))) { - log.warn("In order to disable host name verification for LDAP connections (verify_hostnames: true), " + log.warn( + "In order to disable host name verification for LDAP connections (verify_hostnames: true), " + "you also need to set set the system property com.sun.jndi.ldap.object.disableEndpointIdentification to true when starting the JVM running OpenSearch. " - + "This applies for all Java versions released since July 2018."); + + "This applies for all Java versions released since July 2018." + ); // See: // https://www.oracle.com/technetwork/java/javase/8u181-relnotes-4479407.html // https://www.oracle.com/technetwork/java/javase/10-0-2-relnotes-4477557.html diff --git a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPUserSearcher.java b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPUserSearcher.java index 81da81647f..966555daff 100644 --- a/src/main/java/com/amazon/dlic/auth/ldap2/LDAPUserSearcher.java +++ b/src/main/java/com/amazon/dlic/auth/ldap2/LDAPUserSearcher.java @@ -48,33 +48,34 @@ public LDAPUserSearcher(Settings settings) { } static List> getUserBaseSettings(Settings settings) { - Map userBaseSettingsMap = new HashMap<>( - settings.getGroups(ConfigConstants.LDAP_AUTHCZ_USERS)); + Map userBaseSettingsMap = new HashMap<>(settings.getGroups(ConfigConstants.LDAP_AUTHCZ_USERS)); if (!userBaseSettingsMap.isEmpty()) { if (settings.hasValue(ConfigConstants.LDAP_AUTHC_USERBASE)) { throw new RuntimeException( - "Both old-style and new-style configuration defined for LDAP authentication backend: " - + settings); + "Both old-style and new-style configuration defined for LDAP authentication backend: " + settings + ); } return Utils.getOrderedBaseSettings(userBaseSettingsMap); } else { Settings.Builder settingsBuilder = Settings.builder(); - settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_BASE, - settings.get(ConfigConstants.LDAP_AUTHC_USERBASE, DEFAULT_USERBASE)); - settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_SEARCH, - settings.get(ConfigConstants.LDAP_AUTHC_USERSEARCH, DEFAULT_USERSEARCH_PATTERN)); + settingsBuilder.put(ConfigConstants.LDAP_AUTHCZ_BASE, settings.get(ConfigConstants.LDAP_AUTHC_USERBASE, DEFAULT_USERBASE)); + settingsBuilder.put( + ConfigConstants.LDAP_AUTHCZ_SEARCH, + settings.get(ConfigConstants.LDAP_AUTHC_USERSEARCH, DEFAULT_USERSEARCH_PATTERN) + ); return Collections.singletonList(Pair.of("_legacyConfig", settingsBuilder.build())); } } - LdapEntry exists(Connection ldapConnection, String user, final String[] returnAttributes, final boolean shouldFollowReferrals) throws Exception { + LdapEntry exists(Connection ldapConnection, String user, final String[] returnAttributes, final boolean shouldFollowReferrals) + throws Exception { if (settings.getAsBoolean(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, false) - || settings.getAsBoolean(ConfigConstants.LDAP_SEARCH_ALL_BASES, false) - || settings.hasValue(ConfigConstants.LDAP_AUTHC_USERBASE)) { + || settings.getAsBoolean(ConfigConstants.LDAP_SEARCH_ALL_BASES, false) + || settings.hasValue(ConfigConstants.LDAP_AUTHC_USERBASE)) { return existsSearchingAllBases(ldapConnection, user, returnAttributes, shouldFollowReferrals); } else { return existsSearchingUntilFirstHit(ldapConnection, user, returnAttributes, shouldFollowReferrals); @@ -82,7 +83,12 @@ LdapEntry exists(Connection ldapConnection, String user, final String[] returnAt } - private LdapEntry existsSearchingUntilFirstHit(Connection ldapConnection, String user, final String[] returnAttributes, final boolean shouldFollowReferrals) throws Exception { + private LdapEntry existsSearchingUntilFirstHit( + Connection ldapConnection, + String user, + final String[] returnAttributes, + final boolean shouldFollowReferrals + ) throws Exception { final String username = user; final boolean isDebugEnabled = log.isDebugEnabled(); for (Map.Entry entry : userBaseSettings) { @@ -92,11 +98,14 @@ private LdapEntry existsSearchingUntilFirstHit(Connection ldapConnection, String f.setFilter(baseSettings.get(ConfigConstants.LDAP_AUTHCZ_SEARCH, DEFAULT_USERSEARCH_PATTERN)); f.setParameter(ZERO_PLACEHOLDER, username); - List result = LdapHelper.search(ldapConnection, - baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), - f, - SearchScope.SUBTREE, - returnAttributes, shouldFollowReferrals); + List result = LdapHelper.search( + ldapConnection, + baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), + f, + SearchScope.SUBTREE, + returnAttributes, + shouldFollowReferrals + ); if (isDebugEnabled) { log.debug("Results for LDAP search for {} in base {}:\n{}", user, entry.getKey(), result); @@ -110,7 +119,12 @@ private LdapEntry existsSearchingUntilFirstHit(Connection ldapConnection, String return null; } - private LdapEntry existsSearchingAllBases(Connection ldapConnection, String user, final String[] returnAttributes, final boolean shouldFollowReferrals) throws Exception { + private LdapEntry existsSearchingAllBases( + Connection ldapConnection, + String user, + final String[] returnAttributes, + final boolean shouldFollowReferrals + ) throws Exception { final String username = user; Set result = new HashSet<>(); final boolean isDebugEnabled = log.isDebugEnabled(); @@ -121,11 +135,14 @@ private LdapEntry existsSearchingAllBases(Connection ldapConnection, String user f.setFilter(baseSettings.get(ConfigConstants.LDAP_AUTHCZ_SEARCH, DEFAULT_USERSEARCH_PATTERN)); f.setParameter(ZERO_PLACEHOLDER, username); - List foundEntries = LdapHelper.search(ldapConnection, - baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), - f, - SearchScope.SUBTREE, - returnAttributes, shouldFollowReferrals); + List foundEntries = LdapHelper.search( + ldapConnection, + baseSettings.get(ConfigConstants.LDAP_AUTHCZ_BASE, DEFAULT_USERBASE), + f, + SearchScope.SUBTREE, + returnAttributes, + shouldFollowReferrals + ); if (isDebugEnabled) { log.debug("Results for LDAP search for {} in base {}:\n{}", user, entry.getKey(), result); diff --git a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfigurator.java b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfigurator.java index ea99625e6b..ed42117a04 100644 --- a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfigurator.java +++ b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfigurator.java @@ -95,8 +95,7 @@ public class SettingsBasedSSLConfigurator { private String effectiveKeyAlias; private List effectiveTruststoreAliases; - public SettingsBasedSSLConfigurator(Settings settings, Path configPath, String settingsKeyPrefix, - String clientName) { + public SettingsBasedSSLConfigurator(Settings settings, Path configPath, String settingsKeyPrefix, String clientName) { this.settings = settings; this.configPath = configPath; this.settingsKeyPrefix = normalizeSettingsKeyPrefix(settingsKeyPrefix); @@ -136,10 +135,20 @@ public SSLConfig buildSSLConfig() throws SSLConfigException { return null; } - return new SSLConfig(sslContext, getSupportedProtocols(), getSupportedCipherSuites(), getHostnameVerifier(), - isHostnameVerificationEnabled(), isTrustAllEnabled(), isStartTlsEnabled(), this.effectiveTruststore, - this.effectiveTruststoreAliases, this.effectiveKeystore, this.effectiveKeyPassword, - this.effectiveKeyAlias); + return new SSLConfig( + sslContext, + getSupportedProtocols(), + getSupportedCipherSuites(), + getHostnameVerifier(), + isHostnameVerificationEnabled(), + isTrustAllEnabled(), + isStartTlsEnabled(), + this.effectiveTruststore, + this.effectiveTruststoreAliases, + this.effectiveKeystore, + this.effectiveKeyPassword, + this.effectiveKeyAlias + ); } private boolean isHostnameVerificationEnabled() { @@ -181,7 +190,7 @@ private void configureWithSettings() throws SSLConfigException, NoSuchAlgorithmE this.enableSslClientAuth = getSettingAsBoolean(ENABLE_SSL_CLIENT_AUTH, false); if (settings.get(settingsKeyPrefix + PEMTRUSTEDCAS_FILEPATH, null) != null - || settings.get(settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT, null) != null) { + || settings.get(settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT, null) != null) { initFromPem(); } else { initFromKeyStore(); @@ -194,22 +203,21 @@ private void configureWithSettings() throws SSLConfigException, NoSuchAlgorithmE if (enableSslClientAuth) { if (effectiveKeystore != null) { try { - sslContextBuilder.loadKeyMaterial(effectiveKeystore, effectiveKeyPassword, - new PrivateKeyStrategy() { - - @Override - public String chooseAlias(Map aliases, SSLParameters sslParameters) { - if (aliases == null || aliases.isEmpty()) { - return effectiveKeyAlias; - } - - if (effectiveKeyAlias == null || effectiveKeyAlias.isEmpty()) { - return aliases.keySet().iterator().next(); - } - - return effectiveKeyAlias; - } - }); + sslContextBuilder.loadKeyMaterial(effectiveKeystore, effectiveKeyPassword, new PrivateKeyStrategy() { + + @Override + public String chooseAlias(Map aliases, SSLParameters sslParameters) { + if (aliases == null || aliases.isEmpty()) { + return effectiveKeyAlias; + } + + if (effectiveKeyAlias == null || effectiveKeyAlias.isEmpty()) { + return aliases.keySet().iterator().next(); + } + + return effectiveKeyAlias; + } + }); } catch (UnrecoverableKeyException e) { throw new RuntimeException(e); } @@ -223,22 +231,25 @@ private void initFromPem() throws SSLConfigException { try { trustCertificates = PemKeyReader.loadCertificatesFromStream( - PemKeyReader.resolveStream(settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT, settings)); + PemKeyReader.resolveStream(settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT, settings) + ); } catch (Exception e) { throw new SSLConfigException( - "Error loading PEM from " + settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT + " for " + this.clientName, - e); + "Error loading PEM from " + settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT + " for " + this.clientName, + e + ); } if (trustCertificates == null) { - String path = PemKeyReader.resolve(settingsKeyPrefix + PEMTRUSTEDCAS_FILEPATH, settings, configPath, - !isTrustAllEnabled()); + String path = PemKeyReader.resolve(settingsKeyPrefix + PEMTRUSTEDCAS_FILEPATH, settings, configPath, !isTrustAllEnabled()); try { trustCertificates = PemKeyReader.loadCertificatesFromFile(path); } catch (Exception e) { - throw new SSLConfigException("Error loading PEM from " + path + " (" + settingsKeyPrefix - + PEMTRUSTEDCAS_FILEPATH + ") for " + this.clientName, e); + throw new SSLConfigException( + "Error loading PEM from " + path + " (" + settingsKeyPrefix + PEMTRUSTEDCAS_FILEPATH + ") for " + this.clientName, + e + ); } } @@ -247,21 +258,22 @@ private void initFromPem() throws SSLConfigException { try { authenticationCertificate = PemKeyReader.loadCertificatesFromStream( - PemKeyReader.resolveStream(settingsKeyPrefix + PEMCERT_CONTENT, settings)); + PemKeyReader.resolveStream(settingsKeyPrefix + PEMCERT_CONTENT, settings) + ); } catch (Exception e) { - throw new SSLConfigException( - "Error loading PEM from " + settingsKeyPrefix + PEMCERT_CONTENT + " for " + this.clientName, e); + throw new SSLConfigException("Error loading PEM from " + settingsKeyPrefix + PEMCERT_CONTENT + " for " + this.clientName, e); } if (authenticationCertificate == null) { - String path = PemKeyReader.resolve(settingsKeyPrefix + PEMCERT_FILEPATH, settings, configPath, - enableSslClientAuth); + String path = PemKeyReader.resolve(settingsKeyPrefix + PEMCERT_FILEPATH, settings, configPath, enableSslClientAuth); try { authenticationCertificate = PemKeyReader.loadCertificatesFromFile(path); } catch (Exception e) { - throw new SSLConfigException("Error loading PEM from " + path + " (" + settingsKeyPrefix - + PEMCERT_FILEPATH + ") for " + this.clientName, e); + throw new SSLConfigException( + "Error loading PEM from " + path + " (" + settingsKeyPrefix + PEMCERT_FILEPATH + ") for " + this.clientName, + e + ); } } @@ -269,22 +281,24 @@ private void initFromPem() throws SSLConfigException { PrivateKey authenticationKey; try { - authenticationKey = PemKeyReader.loadKeyFromStream(getSetting(PEMKEY_PASSWORD), - PemKeyReader.resolveStream(settingsKeyPrefix + PEMKEY_CONTENT, settings)); + authenticationKey = PemKeyReader.loadKeyFromStream( + getSetting(PEMKEY_PASSWORD), + PemKeyReader.resolveStream(settingsKeyPrefix + PEMKEY_CONTENT, settings) + ); } catch (Exception e) { - throw new SSLConfigException( - "Error loading PEM from " + settingsKeyPrefix + PEMKEY_CONTENT + " for " + this.clientName, e); + throw new SSLConfigException("Error loading PEM from " + settingsKeyPrefix + PEMKEY_CONTENT + " for " + this.clientName, e); } if (authenticationKey == null) { - String path = PemKeyReader.resolve(settingsKeyPrefix + PEMKEY_FILEPATH, settings, configPath, - enableSslClientAuth); + String path = PemKeyReader.resolve(settingsKeyPrefix + PEMKEY_FILEPATH, settings, configPath, enableSslClientAuth); try { authenticationKey = PemKeyReader.loadKeyFromFile(getSetting(PEMKEY_PASSWORD), path); } catch (Exception e) { - throw new SSLConfigException("Error loading PEM from " + path + " (" + settingsKeyPrefix - + PEMKEY_FILEPATH + ") for " + this.clientName, e); + throw new SSLConfigException( + "Error loading PEM from " + path + " (" + settingsKeyPrefix + PEMKEY_FILEPATH + ") for " + this.clientName, + e + ); } } @@ -292,8 +306,12 @@ private void initFromPem() throws SSLConfigException { effectiveKeyPassword = PemKeyReader.randomChars(12); effectiveKeyAlias = "al"; effectiveTruststore = PemKeyReader.toTruststore(effectiveKeyAlias, trustCertificates); - effectiveKeystore = PemKeyReader.toKeystore(effectiveKeyAlias, effectiveKeyPassword, - authenticationCertificate, authenticationKey); + effectiveKeystore = PemKeyReader.toKeystore( + effectiveKeyAlias, + effectiveKeyPassword, + authenticationCertificate, + authenticationKey + ); } catch (Exception e) { throw new SSLConfigException("Error initializing SSLConfig for " + this.clientName, e); } @@ -306,13 +324,20 @@ private void initFromKeyStore() throws SSLConfigException { try { trustStore = PemKeyReader.loadKeyStore( - PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, - configPath, !isTrustAllEnabled()), - SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); + PemKeyReader.resolve( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + settings, + configPath, + !isTrustAllEnabled() + ), + SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE) + ); } catch (Exception e) { - throw new SSLConfigException("Error loading trust store from " - + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH), e); + throw new SSLConfigException( + "Error loading trust store from " + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH), + e + ); } effectiveTruststoreAliases = getSettingAsList(CA_ALIAS, null); @@ -321,20 +346,24 @@ private void initFromKeyStore() throws SSLConfigException { try { keyStore = PemKeyReader.loadKeyStore( - PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, settings, - configPath, enableSslClientAuth), - SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, - SSLConfigConstants.DEFAULT_STORE_PASSWORD), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE)); + PemKeyReader.resolve( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + settings, + configPath, + enableSslClientAuth + ), + SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE) + ); } catch (Exception e) { - throw new SSLConfigException("Error loading key store from " - + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH), e); + throw new SSLConfigException( + "Error loading key store from " + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH), + e + ); } - String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, - SSLConfigConstants.DEFAULT_STORE_PASSWORD); - effectiveKeyPassword = keyStorePassword == null || keyStorePassword.isEmpty() ? null - : keyStorePassword.toCharArray(); + String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD); + effectiveKeyPassword = keyStorePassword == null || keyStorePassword.isEmpty() ? null : keyStorePassword.toCharArray(); effectiveKeyAlias = getSetting(CERT_ALIAS); if (enableSslClientAuth && effectiveKeyAlias == null) { @@ -393,10 +422,20 @@ public static class SSLConfig { private final char[] effectiveKeyPassword; private final String effectiveKeyAlias; - public SSLConfig(SSLContext sslContext, String[] supportedProtocols, String[] supportedCipherSuites, - HostnameVerifier hostnameVerifier, boolean hostnameVerificationEnabled, boolean trustAll, - boolean startTlsEnabled, KeyStore effectiveTruststore, List effectiveTruststoreAliases, - KeyStore effectiveKeystore, char[] effectiveKeyPassword, String effectiveKeyAlias) { + public SSLConfig( + SSLContext sslContext, + String[] supportedProtocols, + String[] supportedCipherSuites, + HostnameVerifier hostnameVerifier, + boolean hostnameVerificationEnabled, + boolean trustAll, + boolean startTlsEnabled, + KeyStore effectiveTruststore, + List effectiveTruststoreAliases, + KeyStore effectiveKeystore, + char[] effectiveKeyPassword, + String effectiveKeyAlias + ) { this.sslContext = sslContext; this.supportedProtocols = supportedProtocols; this.supportedCipherSuites = supportedCipherSuites; @@ -432,8 +471,7 @@ public HostnameVerifier getHostnameVerifier() { } public SSLConnectionSocketFactory toSSLConnectionSocketFactory() { - return new SSLConnectionSocketFactory(sslContext, supportedProtocols, supportedCipherSuites, - hostnameVerifier); + return new SSLConnectionSocketFactory(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier); } public boolean isStartTlsEnabled() { @@ -490,12 +528,29 @@ public String[] getEffectiveKeyAliasesArray() { @Override public String toString() { - return "SSLConfig [sslContext=" + sslContext + ", supportedProtocols=" + Arrays.toString(supportedProtocols) - + ", supportedCipherSuites=" + Arrays.toString(supportedCipherSuites) + ", hostnameVerifier=" - + hostnameVerifier + ", startTlsEnabled=" + startTlsEnabled + ", hostnameVerificationEnabled=" - + hostnameVerificationEnabled + ", trustAll=" + trustAll + ", effectiveTruststore=" - + effectiveTruststore + ", effectiveTruststoreAliases=" + effectiveTruststoreAliases - + ", effectiveKeystore=" + effectiveKeystore + ", effectiveKeyAlias=" + effectiveKeyAlias + "]"; + return "SSLConfig [sslContext=" + + sslContext + + ", supportedProtocols=" + + Arrays.toString(supportedProtocols) + + ", supportedCipherSuites=" + + Arrays.toString(supportedCipherSuites) + + ", hostnameVerifier=" + + hostnameVerifier + + ", startTlsEnabled=" + + startTlsEnabled + + ", hostnameVerificationEnabled=" + + hostnameVerificationEnabled + + ", trustAll=" + + trustAll + + ", effectiveTruststore=" + + effectiveTruststore + + ", effectiveTruststoreAliases=" + + effectiveTruststoreAliases + + ", effectiveKeystore=" + + effectiveKeystore + + ", effectiveKeyAlias=" + + effectiveKeyAlias + + "]"; } public boolean isTrustAllEnabled() { @@ -511,8 +566,7 @@ public SSLConfigException() { super(); } - public SSLConfigException(String message, Throwable cause, boolean enableSuppression, - boolean writableStackTrace) { + public SSLConfigException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } @@ -532,23 +586,26 @@ public SSLConfigException(Throwable cause) { private static class OverlyTrustfulSSLContextBuilder extends SSLContextBuilder { @Override - protected void initSSLContext(SSLContext sslContext, Collection keyManagers, - Collection trustManagers, SecureRandom secureRandom) throws KeyManagementException { - sslContext.init(!keyManagers.isEmpty() ? keyManagers.toArray(new KeyManager[keyManagers.size()]) : null, - new TrustManager[] { new OverlyTrustfulTrustManager() }, secureRandom); + protected void initSSLContext( + SSLContext sslContext, + Collection keyManagers, + Collection trustManagers, + SecureRandom secureRandom + ) throws KeyManagementException { + sslContext.init( + !keyManagers.isEmpty() ? keyManagers.toArray(new KeyManager[keyManagers.size()]) : null, + new TrustManager[] { new OverlyTrustfulTrustManager() }, + secureRandom + ); } } private static class OverlyTrustfulTrustManager implements X509TrustManager { @Override - public void checkClientTrusted(final X509Certificate[] chain, final String authType) - throws CertificateException { - } + public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {} @Override - public void checkServerTrusted(final X509Certificate[] chain, final String authType) - throws CertificateException { - } + public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {} @Override public X509Certificate[] getAcceptedIssuers() { diff --git a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java index 013d8b70d7..c2de5d95a2 100644 --- a/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java +++ b/src/main/java/com/amazon/dlic/util/SettingsBasedSSLConfiguratorV4.java @@ -96,8 +96,7 @@ public class SettingsBasedSSLConfiguratorV4 { private String effectiveKeyAlias; private List effectiveTruststoreAliases; - public SettingsBasedSSLConfiguratorV4(Settings settings, Path configPath, String settingsKeyPrefix, - String clientName) { + public SettingsBasedSSLConfiguratorV4(Settings settings, Path configPath, String settingsKeyPrefix, String clientName) { this.settings = settings; this.configPath = configPath; this.settingsKeyPrefix = normalizeSettingsKeyPrefix(settingsKeyPrefix); @@ -137,10 +136,20 @@ public SSLConfig buildSSLConfig() throws SSLConfigException { return null; } - return new SSLConfig(sslContext, getSupportedProtocols(), getSupportedCipherSuites(), getHostnameVerifier(), - isHostnameVerificationEnabled(), isTrustAllEnabled(), isStartTlsEnabled(), this.effectiveTruststore, - this.effectiveTruststoreAliases, this.effectiveKeystore, this.effectiveKeyPassword, - this.effectiveKeyAlias); + return new SSLConfig( + sslContext, + getSupportedProtocols(), + getSupportedCipherSuites(), + getHostnameVerifier(), + isHostnameVerificationEnabled(), + isTrustAllEnabled(), + isStartTlsEnabled(), + this.effectiveTruststore, + this.effectiveTruststoreAliases, + this.effectiveKeystore, + this.effectiveKeyPassword, + this.effectiveKeyAlias + ); } private boolean isHostnameVerificationEnabled() { @@ -182,7 +191,7 @@ private void configureWithSettings() throws SSLConfigException, NoSuchAlgorithmE this.enableSslClientAuth = getSettingAsBoolean(ENABLE_SSL_CLIENT_AUTH, false); if (settings.get(settingsKeyPrefix + PEMTRUSTEDCAS_FILEPATH, null) != null - || settings.get(settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT, null) != null) { + || settings.get(settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT, null) != null) { initFromPem(); } else { initFromKeyStore(); @@ -195,22 +204,21 @@ private void configureWithSettings() throws SSLConfigException, NoSuchAlgorithmE if (enableSslClientAuth) { if (effectiveKeystore != null) { try { - sslContextBuilder.loadKeyMaterial(effectiveKeystore, effectiveKeyPassword, - new PrivateKeyStrategy() { - - @Override - public String chooseAlias(Map aliases, Socket socket) { - if (aliases == null || aliases.isEmpty()) { - return effectiveKeyAlias; - } - - if (effectiveKeyAlias == null || effectiveKeyAlias.isEmpty()) { - return aliases.keySet().iterator().next(); - } - - return effectiveKeyAlias; - } - }); + sslContextBuilder.loadKeyMaterial(effectiveKeystore, effectiveKeyPassword, new PrivateKeyStrategy() { + + @Override + public String chooseAlias(Map aliases, Socket socket) { + if (aliases == null || aliases.isEmpty()) { + return effectiveKeyAlias; + } + + if (effectiveKeyAlias == null || effectiveKeyAlias.isEmpty()) { + return aliases.keySet().iterator().next(); + } + + return effectiveKeyAlias; + } + }); } catch (UnrecoverableKeyException e) { throw new RuntimeException(e); } @@ -224,22 +232,25 @@ private void initFromPem() throws SSLConfigException { try { trustCertificates = PemKeyReader.loadCertificatesFromStream( - PemKeyReader.resolveStream(settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT, settings)); + PemKeyReader.resolveStream(settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT, settings) + ); } catch (Exception e) { throw new SSLConfigException( - "Error loading PEM from " + settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT + " for " + this.clientName, - e); + "Error loading PEM from " + settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT + " for " + this.clientName, + e + ); } if (trustCertificates == null) { - String path = PemKeyReader.resolve(settingsKeyPrefix + PEMTRUSTEDCAS_FILEPATH, settings, configPath, - !isTrustAllEnabled()); + String path = PemKeyReader.resolve(settingsKeyPrefix + PEMTRUSTEDCAS_FILEPATH, settings, configPath, !isTrustAllEnabled()); try { trustCertificates = PemKeyReader.loadCertificatesFromFile(path); } catch (Exception e) { - throw new SSLConfigException("Error loading PEM from " + path + " (" + settingsKeyPrefix - + PEMTRUSTEDCAS_FILEPATH + ") for " + this.clientName, e); + throw new SSLConfigException( + "Error loading PEM from " + path + " (" + settingsKeyPrefix + PEMTRUSTEDCAS_FILEPATH + ") for " + this.clientName, + e + ); } } @@ -248,21 +259,22 @@ private void initFromPem() throws SSLConfigException { try { authenticationCertificate = PemKeyReader.loadCertificatesFromStream( - PemKeyReader.resolveStream(settingsKeyPrefix + PEMCERT_CONTENT, settings)); + PemKeyReader.resolveStream(settingsKeyPrefix + PEMCERT_CONTENT, settings) + ); } catch (Exception e) { - throw new SSLConfigException( - "Error loading PEM from " + settingsKeyPrefix + PEMCERT_CONTENT + " for " + this.clientName, e); + throw new SSLConfigException("Error loading PEM from " + settingsKeyPrefix + PEMCERT_CONTENT + " for " + this.clientName, e); } if (authenticationCertificate == null) { - String path = PemKeyReader.resolve(settingsKeyPrefix + PEMCERT_FILEPATH, settings, configPath, - enableSslClientAuth); + String path = PemKeyReader.resolve(settingsKeyPrefix + PEMCERT_FILEPATH, settings, configPath, enableSslClientAuth); try { authenticationCertificate = PemKeyReader.loadCertificatesFromFile(path); } catch (Exception e) { - throw new SSLConfigException("Error loading PEM from " + path + " (" + settingsKeyPrefix - + PEMCERT_FILEPATH + ") for " + this.clientName, e); + throw new SSLConfigException( + "Error loading PEM from " + path + " (" + settingsKeyPrefix + PEMCERT_FILEPATH + ") for " + this.clientName, + e + ); } } @@ -270,22 +282,24 @@ private void initFromPem() throws SSLConfigException { PrivateKey authenticationKey; try { - authenticationKey = PemKeyReader.loadKeyFromStream(getSetting(PEMKEY_PASSWORD), - PemKeyReader.resolveStream(settingsKeyPrefix + PEMKEY_CONTENT, settings)); + authenticationKey = PemKeyReader.loadKeyFromStream( + getSetting(PEMKEY_PASSWORD), + PemKeyReader.resolveStream(settingsKeyPrefix + PEMKEY_CONTENT, settings) + ); } catch (Exception e) { - throw new SSLConfigException( - "Error loading PEM from " + settingsKeyPrefix + PEMKEY_CONTENT + " for " + this.clientName, e); + throw new SSLConfigException("Error loading PEM from " + settingsKeyPrefix + PEMKEY_CONTENT + " for " + this.clientName, e); } if (authenticationKey == null) { - String path = PemKeyReader.resolve(settingsKeyPrefix + PEMKEY_FILEPATH, settings, configPath, - enableSslClientAuth); + String path = PemKeyReader.resolve(settingsKeyPrefix + PEMKEY_FILEPATH, settings, configPath, enableSslClientAuth); try { authenticationKey = PemKeyReader.loadKeyFromFile(getSetting(PEMKEY_PASSWORD), path); } catch (Exception e) { - throw new SSLConfigException("Error loading PEM from " + path + " (" + settingsKeyPrefix - + PEMKEY_FILEPATH + ") for " + this.clientName, e); + throw new SSLConfigException( + "Error loading PEM from " + path + " (" + settingsKeyPrefix + PEMKEY_FILEPATH + ") for " + this.clientName, + e + ); } } @@ -293,8 +307,12 @@ private void initFromPem() throws SSLConfigException { effectiveKeyPassword = PemKeyReader.randomChars(12); effectiveKeyAlias = "al"; effectiveTruststore = PemKeyReader.toTruststore(effectiveKeyAlias, trustCertificates); - effectiveKeystore = PemKeyReader.toKeystore(effectiveKeyAlias, effectiveKeyPassword, - authenticationCertificate, authenticationKey); + effectiveKeystore = PemKeyReader.toKeystore( + effectiveKeyAlias, + effectiveKeyPassword, + authenticationCertificate, + authenticationKey + ); } catch (Exception e) { throw new SSLConfigException("Error initializing SSLConfig for " + this.clientName, e); } @@ -307,13 +325,20 @@ private void initFromKeyStore() throws SSLConfigException { try { trustStore = PemKeyReader.loadKeyStore( - PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, - configPath, !isTrustAllEnabled()), - SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); + PemKeyReader.resolve( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + settings, + configPath, + !isTrustAllEnabled() + ), + SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE) + ); } catch (Exception e) { - throw new SSLConfigException("Error loading trust store from " - + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH), e); + throw new SSLConfigException( + "Error loading trust store from " + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH), + e + ); } effectiveTruststoreAliases = getSettingAsList(CA_ALIAS, null); @@ -322,20 +347,24 @@ private void initFromKeyStore() throws SSLConfigException { try { keyStore = PemKeyReader.loadKeyStore( - PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, settings, - configPath, enableSslClientAuth), - SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, - SSLConfigConstants.DEFAULT_STORE_PASSWORD), - settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE)); + PemKeyReader.resolve( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + settings, + configPath, + enableSslClientAuth + ), + SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE) + ); } catch (Exception e) { - throw new SSLConfigException("Error loading key store from " - + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH), e); + throw new SSLConfigException( + "Error loading key store from " + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH), + e + ); } - String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD - .getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD); - effectiveKeyPassword = keyStorePassword == null || keyStorePassword.isEmpty() ? null - : keyStorePassword.toCharArray(); + String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD); + effectiveKeyPassword = keyStorePassword == null || keyStorePassword.isEmpty() ? null : keyStorePassword.toCharArray(); effectiveKeyAlias = getSetting(CERT_ALIAS); if (enableSslClientAuth && effectiveKeyAlias == null) { @@ -394,10 +423,20 @@ public static class SSLConfig { private final char[] effectiveKeyPassword; private final String effectiveKeyAlias; - public SSLConfig(SSLContext sslContext, String[] supportedProtocols, String[] supportedCipherSuites, - HostnameVerifier hostnameVerifier, boolean hostnameVerificationEnabled, boolean trustAll, - boolean startTlsEnabled, KeyStore effectiveTruststore, List effectiveTruststoreAliases, - KeyStore effectiveKeystore, char[] effectiveKeyPassword, String effectiveKeyAlias) { + public SSLConfig( + SSLContext sslContext, + String[] supportedProtocols, + String[] supportedCipherSuites, + HostnameVerifier hostnameVerifier, + boolean hostnameVerificationEnabled, + boolean trustAll, + boolean startTlsEnabled, + KeyStore effectiveTruststore, + List effectiveTruststoreAliases, + KeyStore effectiveKeystore, + char[] effectiveKeyPassword, + String effectiveKeyAlias + ) { this.sslContext = sslContext; this.supportedProtocols = supportedProtocols; this.supportedCipherSuites = supportedCipherSuites; @@ -437,8 +476,7 @@ public SSLIOSessionStrategy toSSLIOSessionStrategy() { } public SSLConnectionSocketFactory toSSLConnectionSocketFactory() { - return new SSLConnectionSocketFactory(sslContext, supportedProtocols, supportedCipherSuites, - hostnameVerifier); + return new SSLConnectionSocketFactory(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier); } public boolean isStartTlsEnabled() { @@ -495,12 +533,29 @@ public String[] getEffectiveKeyAliasesArray() { @Override public String toString() { - return "SSLConfig [sslContext=" + sslContext + ", supportedProtocols=" + Arrays.toString(supportedProtocols) - + ", supportedCipherSuites=" + Arrays.toString(supportedCipherSuites) + ", hostnameVerifier=" - + hostnameVerifier + ", startTlsEnabled=" + startTlsEnabled + ", hostnameVerificationEnabled=" - + hostnameVerificationEnabled + ", trustAll=" + trustAll + ", effectiveTruststore=" - + effectiveTruststore + ", effectiveTruststoreAliases=" + effectiveTruststoreAliases - + ", effectiveKeystore=" + effectiveKeystore + ", effectiveKeyAlias=" + effectiveKeyAlias + "]"; + return "SSLConfig [sslContext=" + + sslContext + + ", supportedProtocols=" + + Arrays.toString(supportedProtocols) + + ", supportedCipherSuites=" + + Arrays.toString(supportedCipherSuites) + + ", hostnameVerifier=" + + hostnameVerifier + + ", startTlsEnabled=" + + startTlsEnabled + + ", hostnameVerificationEnabled=" + + hostnameVerificationEnabled + + ", trustAll=" + + trustAll + + ", effectiveTruststore=" + + effectiveTruststore + + ", effectiveTruststoreAliases=" + + effectiveTruststoreAliases + + ", effectiveKeystore=" + + effectiveKeystore + + ", effectiveKeyAlias=" + + effectiveKeyAlias + + "]"; } public boolean isTrustAllEnabled() { @@ -516,8 +571,7 @@ public SSLConfigException() { super(); } - public SSLConfigException(String message, Throwable cause, boolean enableSuppression, - boolean writableStackTrace) { + public SSLConfigException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } @@ -537,23 +591,26 @@ public SSLConfigException(Throwable cause) { private static class OverlyTrustfulSSLContextBuilder extends SSLContextBuilder { @Override - protected void initSSLContext(SSLContext sslContext, Collection keyManagers, - Collection trustManagers, SecureRandom secureRandom) throws KeyManagementException { - sslContext.init(!keyManagers.isEmpty() ? keyManagers.toArray(new KeyManager[keyManagers.size()]) : null, - new TrustManager[] { new OverlyTrustfulTrustManager() }, secureRandom); + protected void initSSLContext( + SSLContext sslContext, + Collection keyManagers, + Collection trustManagers, + SecureRandom secureRandom + ) throws KeyManagementException { + sslContext.init( + !keyManagers.isEmpty() ? keyManagers.toArray(new KeyManager[keyManagers.size()]) : null, + new TrustManager[] { new OverlyTrustfulTrustManager() }, + secureRandom + ); } } private static class OverlyTrustfulTrustManager implements X509TrustManager { @Override - public void checkClientTrusted(final X509Certificate[] chain, final String authType) - throws CertificateException { - } + public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {} @Override - public void checkServerTrusted(final X509Certificate[] chain, final String authType) - throws CertificateException { - } + public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {} @Override public X509Certificate[] getAcceptedIssuers() { diff --git a/src/main/java/org/opensearch/security/DefaultObjectMapper.java b/src/main/java/org/opensearch/security/DefaultObjectMapper.java index 7c3168c68a..774af04bfa 100644 --- a/src/main/java/org/opensearch/security/DefaultObjectMapper.java +++ b/src/main/java/org/opensearch/security/DefaultObjectMapper.java @@ -54,7 +54,7 @@ public class DefaultObjectMapper { public static final ObjectMapper objectMapper = new ObjectMapper(); public final static ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()); private static final ObjectMapper defaulOmittingObjectMapper = new ObjectMapper(); - + static { objectMapper.setSerializationInclusion(Include.NON_NULL); //objectMapper.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); @@ -116,7 +116,7 @@ public T run() throws Exception { throw (IOException) e.getCause(); } } - + @SuppressWarnings("removal") public static T readValue(String string, Class clazz) throws IOException { @@ -137,7 +137,7 @@ public T run() throws Exception { throw (IOException) e.getCause(); } } - + @SuppressWarnings("removal") public static JsonNode readTree(String string) throws IOException { diff --git a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java index 29c5b6c9a1..0e6944e9c4 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java @@ -37,10 +37,10 @@ import org.opensearch.core.xcontent.XContentBuilder; public class ConfigUpdateNodeResponse extends BaseNodeResponse implements ToXContentObject { - + private String[] updatedConfigTypes; private String message; - + public ConfigUpdateNodeResponse(StreamInput in) throws IOException { super(in); this.updatedConfigTypes = in.readStringArray(); @@ -52,11 +52,11 @@ public ConfigUpdateNodeResponse(final DiscoveryNode node, String[] updatedConfig this.updatedConfigTypes = updatedConfigTypes; this.message = message; } - + public static ConfigUpdateNodeResponse readNodeResponse(StreamInput in) throws IOException { return new ConfigUpdateNodeResponse(in); } - + public String[] getUpdatedConfigTypes() { return updatedConfigTypes==null?null:Arrays.copyOf(updatedConfigTypes, updatedConfigTypes.length); } @@ -64,7 +64,7 @@ public String[] getUpdatedConfigTypes() { public String getMessage() { return message; } - + @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); diff --git a/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java b/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java index 42879a8b6c..c5c60cf8a6 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java +++ b/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java @@ -57,12 +57,12 @@ public class TransportConfigUpdateAction private final Provider backendRegistry; private final ConfigurationRepository configurationRepository; private DynamicConfigFactory dynamicConfigFactory; - + @Inject public TransportConfigUpdateAction(final Settings settings, final ThreadPool threadPool, final ClusterService clusterService, final TransportService transportService, final ConfigurationRepository configurationRepository, final ActionFilters actionFilters, - Provider backendRegistry, DynamicConfigFactory dynamicConfigFactory) { + Provider backendRegistry, DynamicConfigFactory dynamicConfigFactory) { super(ConfigUpdateAction.NAME, threadPool, clusterService, transportService, actionFilters, ConfigUpdateRequest::new, TransportConfigUpdateAction.NodeConfigUpdateRequest::new, ThreadPool.Names.MANAGEMENT, ConfigUpdateNodeResponse.class); @@ -96,14 +96,14 @@ public void writeTo(final StreamOutput out) throws IOException { protected ConfigUpdateNodeResponse newNodeResponse(StreamInput in) throws IOException { return new ConfigUpdateNodeResponse(in); } - + @Override protected ConfigUpdateResponse newResponse(ConfigUpdateRequest request, List responses, List failures) { return new ConfigUpdateResponse(this.clusterService.getClusterName(), responses, failures); } - + @Override protected ConfigUpdateNodeResponse nodeOperation(final NodeConfigUpdateRequest request) { configurationRepository.reloadConfiguration(CType.fromStringValues((request.request.getConfigTypes()))); diff --git a/src/main/java/org/opensearch/security/action/whoami/TransportWhoAmIAction.java b/src/main/java/org/opensearch/security/action/whoami/TransportWhoAmIAction.java index 897843ca92..901800f0a2 100644 --- a/src/main/java/org/opensearch/security/action/whoami/TransportWhoAmIAction.java +++ b/src/main/java/org/opensearch/security/action/whoami/TransportWhoAmIAction.java @@ -65,10 +65,10 @@ protected void doExecute(Task task, WhoAmIRequest request, ActionListener { +ActionRequestBuilder { public WhoAmIRequestBuilder(final ClusterAdminClient client) throws IOException { this(client, WhoAmIAction.INSTANCE); } diff --git a/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java b/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java index 876079dced..2b25344f76 100644 --- a/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java +++ b/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java @@ -37,12 +37,12 @@ import org.opensearch.core.xcontent.XContentBuilder; public class WhoAmIResponse extends ActionResponse implements ToXContent { - + private String dn; private boolean isAdmin; private boolean isAuthenticated; private boolean isNodeCertificateRequest; - + public WhoAmIResponse(String dn, boolean isAdmin, boolean isAuthenticated, boolean isNodeCertificateRequest) { this.dn = dn; this.isAdmin = isAdmin; @@ -93,8 +93,8 @@ public boolean isNodeCertificateRequest() { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - - builder.startObject("whoami"); + + builder.startObject("whoami"); builder.field("dn", dn); builder.field("is_admin", isAdmin); builder.field("is_authenticated", isAuthenticated); diff --git a/src/main/java/org/opensearch/security/auditlog/AuditLog.java b/src/main/java/org/opensearch/security/auditlog/AuditLog.java index a35bfaa209..128944e387 100644 --- a/src/main/java/org/opensearch/security/auditlog/AuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/AuditLog.java @@ -74,7 +74,7 @@ public interface AuditLog extends Closeable { // set config void setConfig(AuditConfig auditConfig); - + public enum Origin { REST, TRANSPORT, LOCAL } diff --git a/src/main/java/org/opensearch/security/auditlog/config/AuditConfig.java b/src/main/java/org/opensearch/security/auditlog/config/AuditConfig.java index 5fb11102a8..1152abe89e 100644 --- a/src/main/java/org/opensearch/security/auditlog/config/AuditConfig.java +++ b/src/main/java/org/opensearch/security/auditlog/config/AuditConfig.java @@ -275,7 +275,7 @@ static Set fromSettingStringSet(final Settings settings, FilterEntries f final boolean foundDefault = stringSetOfKey.stream().anyMatch(defaultDetectorValue::equals); if (!foundDefault) { - return stringSetOfKey; + return stringSetOfKey; } // Fallback to the legacy keyname diff --git a/src/main/java/org/opensearch/security/auth/AuthFailureListener.java b/src/main/java/org/opensearch/security/auth/AuthFailureListener.java index 6495c8e2c9..b835078aa3 100644 --- a/src/main/java/org/opensearch/security/auth/AuthFailureListener.java +++ b/src/main/java/org/opensearch/security/auth/AuthFailureListener.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.auth; diff --git a/src/main/java/org/opensearch/security/auth/AuthenticationBackend.java b/src/main/java/org/opensearch/security/auth/AuthenticationBackend.java index 5762cd371a..0296f56a4a 100644 --- a/src/main/java/org/opensearch/security/auth/AuthenticationBackend.java +++ b/src/main/java/org/opensearch/security/auth/AuthenticationBackend.java @@ -43,13 +43,13 @@ * Instead catch all exceptions and log a appropriate error message. A logger can be instantiated like: *

* {@code private final Logger log = LogManager.getLogger(this.getClass());} - * + * *

*/ public interface AuthenticationBackend { /** - * The type (name) of the authenticator. Only for logging. + * The type (name) of the authenticator. Only for logging. * @return the type */ String getType(); @@ -58,20 +58,20 @@ public interface AuthenticationBackend { * Validate credentials and return an authenticated user (or throw an OpenSearchSecurityException) *

* Results of this method are normally cached so that we not need to query the backend for every authentication attempt. - *

+ *

* @param The credentials to be validated, never null * @return the authenticated User, never null * @throws OpenSearchSecurityException in case an authentication failure * (when credentials are incorrect, the user does not exist or the backend is not reachable) */ User authenticate(AuthCredentials credentials) throws OpenSearchSecurityException; - + /** - * + * * Lookup for a specific user in the authentication backend - * + * * @param user The user for which the authentication backend should be queried. If the authentication backend supports - * user attributes in combination with impersonation the attributes needs to be added to user by calling {@code user.addAttributes()} + * user attributes in combination with impersonation the attributes needs to be added to user by calling {@code user.addAttributes()} * @return true if the user exists in the authentication backend, false otherwise. Before return call {@code user.addAttributes()} as explained above. */ boolean exists(User user); diff --git a/src/main/java/org/opensearch/security/auth/AuthorizationBackend.java b/src/main/java/org/opensearch/security/auth/AuthorizationBackend.java index 83bf131a62..3aea412e69 100644 --- a/src/main/java/org/opensearch/security/auth/AuthorizationBackend.java +++ b/src/main/java/org/opensearch/security/auth/AuthorizationBackend.java @@ -49,7 +49,7 @@ public interface AuthorizationBackend { /** - * The type (name) of the authorizer. Only for logging. + * The type (name) of the authorizer. Only for logging. * @return the type */ String getType(); @@ -61,7 +61,7 @@ public interface AuthorizationBackend { *

* @param user The authenticated user to populate with backend roles, never null * @param credentials Credentials to authenticate to the authorization backend, maybe null. - * This parameter is for future usage, currently always empty credentials are passed! + * This parameter is for future usage, currently always empty credentials are passed! * @throws OpenSearchSecurityException in case when the authorization backend cannot be reached * or the {@code credentials} are insufficient to authenticate to the authorization backend. */ diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index 635811a7ae..51e93978bd 100644 --- a/src/main/java/org/opensearch/security/auth/BackendRegistry.java +++ b/src/main/java/org/opensearch/security/auth/BackendRegistry.java @@ -181,7 +181,7 @@ public boolean authenticate(final RestRequest request, final RestChannel channel if (isDebugEnabled) { log.debug("Rejecting REST request because of blocked address: {}", request.getHttpChannel().getRemoteAddress()); } - + channel.sendResponse(new BytesRestResponse(RestStatus.UNAUTHORIZED, "Authentication finally failed")); return false; @@ -200,14 +200,14 @@ public boolean authenticate(final RestRequest request, final RestChannel channel // ThreadContext injected user return true; } - + if (!isInitialized()) { log.error("Not yet initialized (you may need to run securityadmin)"); channel.sendResponse(new BytesRestResponse(RestStatus.SERVICE_UNAVAILABLE, "OpenSearch Security not initialized.")); return false; } - + final TransportAddress remoteAddress = xffResolver.resolve(request); final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { @@ -291,7 +291,7 @@ public boolean authenticate(final RestRequest request, final RestChannel channel } } - //http completed + //http completed authenticatedUser = authcz(userCache, restRoleCache, ac, authDomain.getBackend(), restAuthorizers); if(authenticatedUser == null) { @@ -479,7 +479,7 @@ private User authcz(final Cache cache, Cache * A HTTP authenticator extracts {@link AuthCredentials} from a {@link RestRequest} *

- * + * * Implementation classes must provide a public constructor *

* {@code public MyHTTPAuthenticator(org.opensearch.common.settings.Settings settings, java.nio.file.Path configPath)} @@ -51,14 +51,14 @@ public interface HTTPAuthenticator { /** - * The type (name) of the authenticator. Only for logging. + * The type (name) of the authenticator. Only for logging. * @return the type */ String getType(); - + /** * Extract {@link AuthCredentials} from {@link RestRequest} - * + * * @param request The rest request * @param context The current thread context * @return The authentication credentials (complete or incomplete) or null when no credentials are found in the request @@ -68,17 +68,17 @@ public interface HTTPAuthenticator { * @throws OpenSearchSecurityException */ AuthCredentials extractCredentials(RestRequest request, ThreadContext context) throws OpenSearchSecurityException; - + /** * If the {@code extractCredentials()} call was not successful or the authentication flow needs another roundtrip this method * will be called. If the custom HTTP authenticator does not support this method is a no-op and false should be returned. - * + * * If the custom HTTP authenticator does support re-request authentication or supports authentication flows with multiple roundtrips * then the response should be sent (through the channel) and true must be returned. - * + * * @param channel The rest channel to sent back the response via {@code channel.sendResponse()} * @param credentials The credentials from the prior authentication attempt - * @return false if re-request is not supported/necessary, true otherwise. + * @return false if re-request is not supported/necessary, true otherwise. * If true is returned {@code channel.sendResponse()} must be called so that the request completes. */ boolean reRequestAuthentication(final RestChannel channel, AuthCredentials credentials); diff --git a/src/main/java/org/opensearch/security/auth/blocking/ClientBlockRegistry.java b/src/main/java/org/opensearch/security/auth/blocking/ClientBlockRegistry.java index a5eba40353..e74c3ad70a 100644 --- a/src/main/java/org/opensearch/security/auth/blocking/ClientBlockRegistry.java +++ b/src/main/java/org/opensearch/security/auth/blocking/ClientBlockRegistry.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.auth.blocking; diff --git a/src/main/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistry.java b/src/main/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistry.java index 363645cd90..450dda54db 100644 --- a/src/main/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistry.java +++ b/src/main/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistry.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.auth.blocking; diff --git a/src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java b/src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java index 3f603437a0..a4d596b61d 100644 --- a/src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java +++ b/src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.auth.limiting; diff --git a/src/main/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiter.java b/src/main/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiter.java index 42f1d1f165..35a6571f8f 100644 --- a/src/main/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiter.java +++ b/src/main/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiter.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.auth.limiting; diff --git a/src/main/java/org/opensearch/security/auth/limiting/UserNameBasedRateLimiter.java b/src/main/java/org/opensearch/security/auth/limiting/UserNameBasedRateLimiter.java index 7fe5ac05d6..3fd0c12246 100644 --- a/src/main/java/org/opensearch/security/auth/limiting/UserNameBasedRateLimiter.java +++ b/src/main/java/org/opensearch/security/auth/limiting/UserNameBasedRateLimiter.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.auth.limiting; diff --git a/src/main/java/org/opensearch/security/configuration/AdminDNs.java b/src/main/java/org/opensearch/security/configuration/AdminDNs.java index ced262d543..72a4485e9f 100644 --- a/src/main/java/org/opensearch/security/configuration/AdminDNs.java +++ b/src/main/java/org/opensearch/security/configuration/AdminDNs.java @@ -62,7 +62,7 @@ public AdminDNs(final Settings settings) { this.injectAdminUserEnabled = settings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_ADMIN_USER_ENABLED, false); final List adminDnsA = settings.getAsList(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, Collections.emptyList()); - + for (String dn:adminDnsA) { try { log.debug("{} is registered as an admin dn", dn); @@ -73,13 +73,13 @@ public AdminDNs(final Settings settings) { if (log.isDebugEnabled()) { log.debug("Admin DN not an LDAP name, but admin user injection enabled. Will add {} to admin usernames", dn); } - adminUsernames.add(dn); + adminUsernames.add(dn); } else { - log.error("Unable to parse admin dn {}",dn, e); + log.error("Unable to parse admin dn {}",dn, e); } } } - + log.debug("Loaded {} admin DN's {}",adminDn.size(), adminDn); final Settings impersonationDns = settings.getByPrefix(ConfigConstants.SECURITY_AUTHCZ_IMPERSONATION_DN+"."); @@ -95,7 +95,7 @@ public AdminDNs(final Settings settings) { ); log.debug("Loaded {} impersonation DN's {}", allowedDnsImpersonations.size(), allowedDnsImpersonations); - + final Settings impersonationUsersRest = settings.getByPrefix(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+"."); allowedRestImpersonations = impersonationUsersRest.keySet().stream() @@ -103,9 +103,9 @@ public AdminDNs(final Settings settings) { ImmutableMap.toImmutableMap( Function.identity(), user -> WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+"."+user)) - ) - ); - + ) + ); + log.debug("Loaded {} impersonation users for REST {}",allowedRestImpersonations.size(), allowedRestImpersonations); } @@ -129,11 +129,11 @@ public boolean isAdmin(User user) { } return false; } - + public boolean isAdminDN(String dn) { - + if(dn == null) return false; - + try { return isAdminDN(new LdapName(dn)); } catch (InvalidNameException e) { @@ -143,16 +143,16 @@ public boolean isAdminDN(String dn) { private boolean isAdminDN(LdapName dn) { if(dn == null) return false; - + boolean isAdmin = adminDn.contains(dn); - + if (log.isTraceEnabled()) { log.trace("Is principal {} an admin cert? {}", dn.toString(), isAdmin); } - + return isAdmin; } - + public boolean isRestImpersonationAllowed(final String originalUser, final String impersonated) { return (originalUser != null) ? allowedRestImpersonations.getOrDefault(originalUser, WildcardMatcher.NONE).test(impersonated) : false; } diff --git a/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java b/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java index 61877f2bf2..1c42321986 100644 --- a/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java +++ b/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java @@ -40,7 +40,7 @@ public class ClusterInfoHolder implements ClusterStateListener { private volatile DiscoveryNodes nodes = null; private volatile Boolean isLocalNodeElectedClusterManager = null; private volatile boolean initialized; - + @Override public void clusterChanged(ClusterChangedEvent event) { if(nodes == null || event.nodesChanged()) { @@ -50,7 +50,7 @@ public void clusterChanged(ClusterChangedEvent event) { } initialized = true; } - + isLocalNodeElectedClusterManager = event.localNodeClusterManager()?Boolean.TRUE:Boolean.FALSE; } @@ -69,7 +69,7 @@ public Boolean hasNode(DiscoveryNode node) { } return null; } - + return nodes.nodeExists(node)?Boolean.TRUE:Boolean.FALSE; } } diff --git a/src/main/java/org/opensearch/security/configuration/CompatConfig.java b/src/main/java/org/opensearch/security/configuration/CompatConfig.java index 6912cb4fbb..48f91b10be 100644 --- a/src/main/java/org/opensearch/security/configuration/CompatConfig.java +++ b/src/main/java/org/opensearch/security/configuration/CompatConfig.java @@ -50,13 +50,13 @@ public CompatConfig(final Environment environment, final OpensearchDynamicSettin this.staticSettings = environment.settings(); this.transportPassiveAuthSetting = transportPassiveAuthSetting; } - + @Subscribe public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { this.dcm = dcm; log.debug("dynamicSecurityConfig updated?: {}", (dcm != null)); } - + //true is default public boolean restAuthEnabled() { final boolean restInitiallyDisabled = staticSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY, false); @@ -79,7 +79,7 @@ public boolean restAuthEnabled() { } } - + //true is default public boolean transportInterClusterAuthEnabled() { final boolean interClusterAuthInitiallyDisabled = staticSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY, false); diff --git a/src/main/java/org/opensearch/security/configuration/ConfigCallback.java b/src/main/java/org/opensearch/security/configuration/ConfigCallback.java index d7d1ed0cee..cb8fc1eedf 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigCallback.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigCallback.java @@ -30,7 +30,7 @@ import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; public interface ConfigCallback { - + void success(SecurityDynamicConfiguration dConf); void noData(String id); void singleFailure(Failure failure); diff --git a/src/main/java/org/opensearch/security/configuration/ConfigUpdateAlreadyInProgressException.java b/src/main/java/org/opensearch/security/configuration/ConfigUpdateAlreadyInProgressException.java index ea7799e014..c628a3156e 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigUpdateAlreadyInProgressException.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigUpdateAlreadyInProgressException.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.configuration; diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java b/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java index 4aa9fadcae..f5751efcae 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java @@ -36,7 +36,7 @@ import org.opensearch.threadpool.ThreadPool; public interface DlsFlsRequestValve { - + boolean invoke(String action, ActionRequest request, ActionListener listener, EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, Resolved resolved); void handleSearchContext(SearchContext context, ThreadPool threadPool, NamedXContentRegistry namedXContentRegistry); @@ -61,5 +61,5 @@ public void onQueryPhase(QuerySearchResult queryResult) { } } - + } diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java index 532f820210..947557d342 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java @@ -266,7 +266,7 @@ public boolean invoke(String action, ActionRequest request, final ActionListener RestStatus.FORBIDDEN)); return false; } - + if (evaluatedDlsFlsConfig.hasDls()) { if (request instanceof SearchRequest) { @@ -317,7 +317,7 @@ public void handleSearchContext(SearchContext context, ThreadPool threadPool, Na assert context.parsedQuery() != null; final Set unparsedDlsQueries = queries.get(dlsEval); - + if (unparsedDlsQueries != null && !unparsedDlsQueries.isEmpty()) { BooleanQuery.Builder queryBuilder = dlsQueryParser.parse(unparsedDlsQueries, context.getQueryShardContext(), (q) -> new ConstantScoreQuery(q)); @@ -390,7 +390,7 @@ private void setDlsHeaders(EvaluatedDlsFlsConfig dlsFls, ActionRequest request) } else { if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER) != null) { Object deserializedDlsQueries = Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER)); - if (!dlsQueries.equals(deserializedDlsQueries)) { + if (!dlsQueries.equals(deserializedDlsQueries)) { throw new OpenSearchSecurityException(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER + " does not match (SG 900D)"); } } else { diff --git a/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java b/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java index fd3b3aee98..a5f07541c8 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java +++ b/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java @@ -99,7 +99,7 @@ public BooleanQuery.Builder parse(Set unparsedDlsQueries, QueryShardCont return dlsQueryBuilder; } - + private static void handleNested(final QueryShardContext queryShardContext, final BooleanQuery.Builder dlsQueryBuilder, final Query parentQuery) { final BitSetProducer parentDocumentsFilter = queryShardContext.bitsetFilter(NON_NESTED_QUERY); @@ -131,7 +131,7 @@ boolean containsTermLookupQuery(Set unparsedQueries) { if (log.isDebugEnabled()) { log.debug("containsTermLookupQuery() returns true due to " + query + "\nqueries: " + unparsedQueries); } - + return true; } } @@ -139,7 +139,7 @@ boolean containsTermLookupQuery(Set unparsedQueries) { if (log.isDebugEnabled()) { log.debug("containsTermLookupQuery() returns false\nqueries: " + unparsedQueries); } - + return false; } @@ -156,5 +156,5 @@ boolean containsTermLookupQuery(String query) { } } - + } diff --git a/src/main/java/org/opensearch/security/configuration/EmptyFilterLeafReader.java b/src/main/java/org/opensearch/security/configuration/EmptyFilterLeafReader.java index 4b603fa804..79069ef53e 100644 --- a/src/main/java/org/opensearch/security/configuration/EmptyFilterLeafReader.java +++ b/src/main/java/org/opensearch/security/configuration/EmptyFilterLeafReader.java @@ -102,7 +102,7 @@ public EmptyDirectoryReader(final DirectoryReader in) throws IOException { protected DirectoryReader doWrapDirectoryReader(final DirectoryReader in) throws IOException { return new EmptyDirectoryReader(in); } - + @Override public CacheHelper getReaderCacheHelper() { return in.getReaderCacheHelper(); diff --git a/src/main/java/org/opensearch/security/configuration/InvalidConfigException.java b/src/main/java/org/opensearch/security/configuration/InvalidConfigException.java index 5e96af6449..ba6a29b08a 100644 --- a/src/main/java/org/opensearch/security/configuration/InvalidConfigException.java +++ b/src/main/java/org/opensearch/security/configuration/InvalidConfigException.java @@ -29,7 +29,7 @@ public class InvalidConfigException extends Exception { /** - * + * */ private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/opensearch/security/configuration/StaticResourceException.java b/src/main/java/org/opensearch/security/configuration/StaticResourceException.java index b4d787aa0f..8574a170bb 100644 --- a/src/main/java/org/opensearch/security/configuration/StaticResourceException.java +++ b/src/main/java/org/opensearch/security/configuration/StaticResourceException.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.configuration; diff --git a/src/main/java/org/opensearch/security/filter/SecurityFilter.java b/src/main/java/org/opensearch/security/filter/SecurityFilter.java index 7bdd5946d5..4dd629c010 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityFilter.java @@ -192,7 +192,7 @@ private void ap if (user != null) { org.apache.logging.log4j.ThreadContext.put("user", user.getName()); } - + if (isActionTraceEnabled()) { String count = ""; @@ -232,12 +232,12 @@ private void ap chain.proceed(task, action, request, listener); return; } - - + + if(immutableIndicesMatcher != WildcardMatcher.NONE) { - + boolean isImmutable = false; - + if(request instanceof BulkShardRequest) { for(BulkItemRequest bsr: ((BulkShardRequest) request).items()) { isImmutable = checkImmutableIndices(bsr.request(), listener); @@ -248,7 +248,7 @@ private void ap } else { isImmutable = checkImmutableIndices(request, listener); } - + if(isImmutable) { return; } @@ -301,7 +301,7 @@ private void ap } final PrivilegesEvaluatorResponse pres = eval.evaluate(user, action, request, task, injectedRoles); - + if (log.isDebugEnabled()) { log.debug(pres.toString()); } @@ -384,7 +384,7 @@ private static boolean isUserAdmin(User user, final AdminDNs adminDns) { } private void attachSourceFieldContext(ActionRequest request) { - + if(request instanceof SearchRequest && SourceFieldsContext.isNeeded((SearchRequest) request)) { if(threadContext.getHeader("_opendistro_security_source_field_context") == null) { final String serializedSourceFieldContext = Base64Helper.serializeObject(new SourceFieldsContext((SearchRequest) request)); @@ -397,7 +397,7 @@ private void attachSourceFieldContext(ActionRequest request) { } } } - + @SuppressWarnings("rawtypes") private boolean checkImmutableIndices(Object request, ActionListener listener) { final boolean isModifyIndexRequest = request instanceof DeleteRequest @@ -413,11 +413,11 @@ private boolean checkImmutableIndices(Object request, ActionListener listener) { listener.onFailure(new OpenSearchSecurityException("Index is immutable", RestStatus.FORBIDDEN)); return true; } - + if ((request instanceof IndexRequest) && isRequestIndexImmutable(request)) { ((IndexRequest) request).opType(OpType.CREATE); } - + return false; } diff --git a/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java b/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java index 2fec235f3e..a5a23957ed 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java @@ -117,7 +117,7 @@ public SecurityRestFilter(final BackendRegistry registry, final AuditLog auditLo */ public RestHandler wrap(RestHandler original, AdminDNs adminDNs) { return new RestHandler() { - + @Override public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception { org.apache.logging.log4j.ThreadContext.clearAll(); @@ -142,7 +142,7 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha NodeClient client) throws Exception { threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, Origin.REST.toString()); - + if(HTTPHelper.containsBadHeader(request)) { final OpenSearchException exception = ExceptionUtils.createBadHeaderException(); log.error(exception.toString()); @@ -150,7 +150,7 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha channel.sendResponse(new BytesRestResponse(channel, RestStatus.FORBIDDEN, exception)); return true; } - + if(SSLRequestHelper.containsBadHeader(threadContext, ConfigConstants.OPENDISTRO_SECURITY_CONFIG_PREFIX)) { final OpenSearchException exception = ExceptionUtils.createBadHeaderException(); log.error(exception.toString()); @@ -165,7 +165,7 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha if(sslInfo.getPrincipal() != null) { threadContext.putTransient("_opendistro_security_ssl_principal", sslInfo.getPrincipal()); } - + if(sslInfo.getX509Certs() != null) { threadContext.putTransient("_opendistro_security_ssl_peer_certificates", sslInfo.getX509Certs()); } @@ -178,7 +178,7 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha channel.sendResponse(new BytesRestResponse(channel, RestStatus.FORBIDDEN, e)); return true; } - + if(!compatConfig.restAuthEnabled()) { return false; } @@ -197,7 +197,7 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha org.apache.logging.log4j.ThreadContext.put("user", ((User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER)).getName()); } } - + return false; } diff --git a/src/main/java/org/opensearch/security/http/HTTPBasicAuthenticator.java b/src/main/java/org/opensearch/security/http/HTTPBasicAuthenticator.java index 35278d261f..30e6134381 100644 --- a/src/main/java/org/opensearch/security/http/HTTPBasicAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/HTTPBasicAuthenticator.java @@ -47,20 +47,20 @@ public class HTTPBasicAuthenticator implements HTTPAuthenticator { protected final Logger log = LogManager.getLogger(this.getClass()); public HTTPBasicAuthenticator(final Settings settings, final Path configPath) { - + } @Override public AuthCredentials extractCredentials(final RestRequest request, ThreadContext threadContext) { final boolean forceLogin = request.paramAsBoolean("force_login", false); - + if(forceLogin) { return null; } - + final String authorizationHeader = request.header("Authorization"); - + return HTTPHelper.extractCredentials(authorizationHeader, log); } diff --git a/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java b/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java index 51ff6304b1..373919669d 100644 --- a/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java @@ -48,7 +48,7 @@ import org.opensearch.security.user.AuthCredentials; public class HTTPClientCertAuthenticator implements HTTPAuthenticator { - + protected final Logger log = LogManager.getLogger(this.getClass()); protected final Settings settings; @@ -62,29 +62,29 @@ public AuthCredentials extractCredentials(final RestRequest request, final Threa final String principal = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PRINCIPAL); if (!Strings.isNullOrEmpty(principal)) { - + final String usernameAttribute = settings.get("username_attribute"); final String rolesAttribute = settings.get("roles_attribute"); - + try { final LdapName rfc2253dn = new LdapName(principal); String username = principal.trim(); String[] backendRoles = null; - + if(usernameAttribute != null && usernameAttribute.length() > 0) { final List usernames = getDnAttribute(rfc2253dn, usernameAttribute); if(usernames.isEmpty() == false) { username = usernames.get(0); } } - + if(rolesAttribute != null && rolesAttribute.length() > 0) { final List roles = getDnAttribute(rfc2253dn, rolesAttribute); if(roles.isEmpty() == false) { backendRoles = roles.toArray(new String[0]); } } - + return new AuthCredentials(username, backendRoles).markComplete(); } catch (InvalidNameException e) { log.error("Client cert had no properly formed DN (was: {})", principal); @@ -106,8 +106,8 @@ public boolean reRequestAuthentication(final RestChannel channel, AuthCredential public String getType() { return "clientcert"; } - - private List getDnAttribute(LdapName rfc2253dn, String attribute) { + + private List getDnAttribute(LdapName rfc2253dn, String attribute) { final List attrValues = new ArrayList<>(rfc2253dn.size()); final List reverseRdn = new ArrayList<>(rfc2253dn.getRdns()); Collections.reverse(reverseRdn); @@ -117,7 +117,7 @@ private List getDnAttribute(LdapName rfc2253dn, String attribute) { attrValues.add(rdn.getValue().toString()); } } - + return Collections.unmodifiableList(attrValues); } } diff --git a/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java b/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java index 28fb80e0db..348811b694 100644 --- a/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java @@ -57,14 +57,14 @@ public HTTPProxyAuthenticator(Settings settings, final Path configPath) { @Override public AuthCredentials extractCredentials(final RestRequest request, ThreadContext context) { - + if(context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE) != Boolean.TRUE) { throw new OpenSearchSecurityException("xff not done"); } - + final String userHeader = settings.get("user_header"); final String rolesHeader = settings.get("roles_header"); - + if (log.isDebugEnabled()) { log.debug("Headers {}", request.getHeaders()); log.debug("UserHeader {}, value {}", userHeader, userHeader == null ? null : request.header(userHeader)); diff --git a/src/main/java/org/opensearch/security/http/RemoteIpDetector.java b/src/main/java/org/opensearch/security/http/RemoteIpDetector.java index 404fd2dcc2..5d9e933c8f 100644 --- a/src/main/java/org/opensearch/security/http/RemoteIpDetector.java +++ b/src/main/java/org/opensearch/security/http/RemoteIpDetector.java @@ -120,23 +120,23 @@ String detect(RestRequest request, ThreadContext threadContext){ if (isTraceEnabled) { log.trace("originalRemoteAddr {}", originalRemoteAddr); } - + //X-Forwarded-For: client1, proxy1, proxy2 // ^^^^^^ originalRemoteAddr - + //originalRemoteAddr need to be in the list of internalProxies if (internalProxies !=null && internalProxies.matcher(originalRemoteAddr).matches()) { String remoteIp = null; final StringBuilder concatRemoteIpHeaderValue = new StringBuilder(); - + //client1, proxy1, proxy2 final List remoteIpHeaders = request.getHeaders().get(remoteIpHeader); //X-Forwarded-For if(remoteIpHeaders == null || remoteIpHeaders.isEmpty()) { return originalRemoteAddr; } - + for (String rh:remoteIpHeaders) { if (concatRemoteIpHeaderValue.length() > 0) { concatRemoteIpHeaderValue.append(", "); @@ -144,7 +144,7 @@ String detect(RestRequest request, ThreadContext threadContext){ concatRemoteIpHeaderValue.append(rh); } - + if (isTraceEnabled) { log.trace("concatRemoteIpHeaderValue {}", concatRemoteIpHeaderValue.toString()); } @@ -162,14 +162,14 @@ String detect(RestRequest request, ThreadContext threadContext){ break; } } - + // continue to loop on remoteIpHeaderValue to build the new value of the remoteIpHeader final LinkedList newRemoteIpHeaderValue = new LinkedList<>(); for (; idx >= 0; idx--) { String currentRemoteIp = remoteIpHeaderValue[idx]; newRemoteIpHeaderValue.addFirst(currentRemoteIp); } - + if (remoteIp != null) { if (isTraceEnabled) { final String originalRemoteHost = ((InetSocketAddress)request.getHttpChannel().getRemoteAddress()).getAddress().getHostName(); @@ -178,17 +178,17 @@ String detect(RestRequest request, ThreadContext threadContext){ threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE, Boolean.TRUE); return remoteIp; - + } else { log.warn("Remote ip could not be detected, this should normally not happen"); } - + } else { if (isTraceEnabled) { log.trace("Skip RemoteIpDetector for request {} with originalRemoteAddr '{}' cause no internal proxy matches", request.uri(), request.getHttpChannel().getRemoteAddress()); } } - + return originalRemoteAddr; } diff --git a/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java b/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java index 3d977dcc7e..6f2f57053f 100644 --- a/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java +++ b/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java @@ -39,7 +39,7 @@ import org.opensearch.transport.SharedGroupFactory; public class SecurityHttpServerTransport extends SecuritySSLNettyHttpServerTransport { - + public SecurityHttpServerTransport(final Settings settings, final NetworkService networkService, final BigArrays bigArrays, final ThreadPool threadPool, final SecurityKeyStore odsks, final SslExceptionHandler sslExceptionHandler, final NamedXContentRegistry namedXContentRegistry, final ValidatingDispatcher dispatcher, final ClusterSettings clusterSettings, SharedGroupFactory sharedGroupFactory) { diff --git a/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java b/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java index b05153db4c..3c1dedc55e 100644 --- a/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java +++ b/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java @@ -54,7 +54,7 @@ public ChannelHandler configureServerChannelHandler() { } protected class NonSslHttpChannelHandler extends Netty4HttpServerTransport.HttpChannelHandler { - + protected NonSslHttpChannelHandler(Netty4HttpServerTransport transport, final HttpHandlingSettings handlingSettings) { super(transport, handlingSettings); } diff --git a/src/main/java/org/opensearch/security/http/XFFResolver.java b/src/main/java/org/opensearch/security/http/XFFResolver.java index 23de8e3676..c44e98537d 100644 --- a/src/main/java/org/opensearch/security/http/XFFResolver.java +++ b/src/main/java/org/opensearch/security/http/XFFResolver.java @@ -47,7 +47,7 @@ public class XFFResolver { private volatile boolean enabled; private volatile RemoteIpDetector detector; private final ThreadContext threadContext; - + public XFFResolver(final ThreadPool threadPool) { super(); this.threadContext = threadPool.getThreadContext(); @@ -58,16 +58,16 @@ public TransportAddress resolve(final RestRequest request) throws OpenSearchSecu if (isTraceEnabled) { log.trace("resolve {}", request.getHttpChannel().getRemoteAddress()); } - + if(enabled && request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress && request.getHttpChannel() instanceof Netty4HttpChannel) { final InetSocketAddress isa = new InetSocketAddress(detector.detect(request, threadContext), ((InetSocketAddress)request.getHttpChannel().getRemoteAddress()).getPort()); - - if(isa.isUnresolved()) { + + if(isa.isUnresolved()) { throw new OpenSearchSecurityException("Cannot resolve address "+isa.getHostString()); } - - + + if (isTraceEnabled) { if(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE) == Boolean.TRUE) { log.trace("xff resolved {} to {}", request.getHttpChannel().getRemoteAddress(), isa); @@ -77,7 +77,7 @@ public TransportAddress resolve(final RestRequest request) throws OpenSearchSecu } return new TransportAddress(isa); } else if(request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress){ - + if (isTraceEnabled) { log.trace("no xff done (enabled or no netty request) {},{},{},{}",enabled, request.getClass()); diff --git a/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java b/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java index d792158fea..e98f26d85a 100644 --- a/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java @@ -60,7 +60,7 @@ public AuthCredentials extractCredentials(final RestRequest request, ThreadConte if(credentials == null) { return null; } - + String attrHeaderPrefix = settings.get("attr_header_prefix"); if(Strings.isNullOrEmpty(attrHeaderPrefix)) { log.debug("attr_header_prefix is null. Skipping additional attribute extraction"); @@ -68,7 +68,7 @@ public AuthCredentials extractCredentials(final RestRequest request, ThreadConte } else if(log.isDebugEnabled()) { log.debug("attrHeaderPrefix {}", attrHeaderPrefix); } - + credentials.addAttribute(ATTR_PROXY_USERNAME, credentials.getUsername()); attrHeaderPrefix = attrHeaderPrefix.toLowerCase(); for (Entry> entry : request.getHeaders().entrySet()) { diff --git a/src/main/java/org/opensearch/security/httpclient/HttpClient.java b/src/main/java/org/opensearch/security/httpclient/HttpClient.java index d032ca3544..ad507ea47c 100644 --- a/src/main/java/org/opensearch/security/httpclient/HttpClient.java +++ b/src/main/java/org/opensearch/security/httpclient/HttpClient.java @@ -195,7 +195,7 @@ public boolean index(final String content, final String index, final String type try { final IndexRequest ir = new IndexRequest(index); - + final IndexResponse response = rclient.index(ir .setRefreshPolicy(refresh?RefreshPolicy.IMMEDIATE:RefreshPolicy.NONE) .source(content, XContentType.JSON), RequestOptions.DEFAULT); diff --git a/src/main/java/org/opensearch/security/privileges/DocumentAllowList.java b/src/main/java/org/opensearch/security/privileges/DocumentAllowList.java index 8bfbb7c0db..129233a007 100644 --- a/src/main/java/org/opensearch/security/privileges/DocumentAllowList.java +++ b/src/main/java/org/opensearch/security/privileges/DocumentAllowList.java @@ -193,7 +193,7 @@ public static class Entry { if (index.indexOf('/') != -1 || index.indexOf('|') != -1) { throw new IllegalArgumentException("Invalid index name: " + index); } - + this.index = index; this.id = id; } diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 36d53b2a9e..278dc86b7c 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -134,7 +134,7 @@ public class PrivilegesEvaluator { private final boolean dfmEmptyOverwritesAll; private DynamicConfigModel dcm; private final NamedXContentRegistry namedXContentRegistry; - + public PrivilegesEvaluator(final ClusterService clusterService, final ThreadPool threadPool, final ConfigurationRepository configurationRepository, final IndexNameExpressionResolver resolver, AuditLog auditLog, final Settings settings, final PrivilegesInterceptor privilegesInterceptor, final ClusterInfoHolder clusterInfoHolder, @@ -309,7 +309,7 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin } presponse.evaluatedDlsFlsConfig = getSecurityRoles(mappedRoles).getDlsFls(user, dfmEmptyOverwritesAll, resolver, clusterService, namedXContentRegistry); - + if (isClusterPerm(action0)) { if(!securityRoles.impliesClusterPermissionPermission(action0)) { @@ -384,7 +384,7 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin presponse.allowed = true; return presponse; } - + // term aggregations if (termsAggregationEvaluator.evaluate(requestedResolved, request, clusterService, user, securityRoles, resolver, presponse) .isComplete()) { return presponse; @@ -728,7 +728,7 @@ private boolean checkDocAllowListHeader(User user, String action, ActionRequest if (log.isDebugEnabled()) { log.debug("Request " + request + " is allowed by " + documentAllowList); } - + return true; } else { return false; @@ -739,7 +739,7 @@ private boolean checkDocAllowListHeader(User user, String action, ActionRequest return false; } } - + private List toString(List aliases) { if(aliases == null || aliases.size() == 0) { return Collections.emptyList(); diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java index 8b3e51f045..31ce7095d2 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java @@ -42,11 +42,11 @@ public class PrivilegesEvaluatorResponse { PrivilegesEvaluatorResponseState state = PrivilegesEvaluatorResponseState.PENDING; Resolved resolved; CreateIndexRequestBuilder createIndexRequestBuilder; - + public Resolved getResolved() { return resolved; } - + public boolean isAllowed() { return allowed; } @@ -61,11 +61,11 @@ public Set getMissingPrivileges() { public EvaluatedDlsFlsConfig getEvaluatedDlsFlsConfig() { return evaluatedDlsFlsConfig; } - + public CreateIndexRequestBuilder getCreateIndexRequestBuilder() { return createIndexRequestBuilder; } - + public PrivilegesEvaluatorResponse markComplete() { this.state = PrivilegesEvaluatorResponseState.COMPLETE; return this; @@ -89,10 +89,10 @@ public String toString() { return "PrivEvalResponse [allowed=" + allowed + ", missingPrivileges=" + missingPrivileges + ", evaluatedDlsFlsConfig=" + evaluatedDlsFlsConfig + "]"; } - + public static enum PrivilegesEvaluatorResponseState { PENDING, COMPLETE; } - + } diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesInterceptor.java b/src/main/java/org/opensearch/security/privileges/PrivilegesInterceptor.java index c76910474f..dd569b05fb 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesInterceptor.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesInterceptor.java @@ -65,7 +65,7 @@ protected static ReplaceResult newAccessGrantedReplaceResult(CreateIndexRequestB protected final Client client; protected final ThreadPool threadPool; - public PrivilegesInterceptor(final IndexNameExpressionResolver resolver, final ClusterService clusterService, + public PrivilegesInterceptor(final IndexNameExpressionResolver resolver, final ClusterService clusterService, final Client client, ThreadPool threadPool) { this.resolver = resolver; this.clusterService = clusterService; @@ -77,7 +77,7 @@ public ReplaceResult replaceDashboardsIndex(final ActionRequest request, final S final Resolved requestedResolved, final Map tenants) { throw new RuntimeException("not implemented"); } - + protected final ThreadContext getThreadContext() { return threadPool.getThreadContext(); } diff --git a/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java index 60456a4eb3..a74ea17ccd 100644 --- a/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java @@ -47,9 +47,9 @@ import org.opensearch.tasks.Task; public class SecurityIndexAccessEvaluator { - + Logger log = LogManager.getLogger(this.getClass()); - + private final String securityIndex; private final AuditLog auditLog; private final WildcardMatcher securityDeniedActionMatcher; @@ -86,7 +86,7 @@ public SecurityIndexAccessEvaluator(final Settings settings, AuditLog auditLog, securityDeniedActionMatcher = WildcardMatcher.from(restoreSecurityIndexEnabled ? securityIndexDeniedActionPatternsList : securityIndexDeniedActionPatternsListNoSnapshot); } - + public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final Task task, final String action, final Resolved requestedResolved, final PrivilegesEvaluatorResponse presponse) { final boolean isDebugEnabled = log.isDebugEnabled(); diff --git a/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java b/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java index c457b42624..c536ae2d2e 100644 --- a/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java @@ -47,7 +47,7 @@ public class SnapshotRestoreEvaluator { private final String securityIndex; private final AuditLog auditLog; private final boolean restoreSecurityIndexEnabled; - + public SnapshotRestoreEvaluator(final Settings settings, AuditLog auditLog) { this.enableSnapshotRestorePrivilege = settings.getAsBoolean(ConfigConstants.SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, ConfigConstants.SECURITY_DEFAULT_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE); @@ -63,27 +63,27 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T if (!(request instanceof RestoreSnapshotRequest)) { return presponse; } - + // snapshot restore for regular users not enabled if (!enableSnapshotRestorePrivilege) { log.warn("{} is not allowed for a regular user", action); presponse.allowed = false; - return presponse.markComplete(); + return presponse.markComplete(); } // if this feature is enabled, users can also snapshot and restore // the Security index and the global state if (restoreSecurityIndexEnabled) { presponse.allowed = true; - return presponse; + return presponse; } - + if (clusterInfoHolder.isLocalNodeElectedClusterManager() == Boolean.FALSE) { presponse.allowed = true; - return presponse.markComplete(); + return presponse.markComplete(); } - + final RestoreSnapshotRequest restoreRequest = (RestoreSnapshotRequest) request; // Do not allow restore of global state @@ -91,7 +91,7 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T auditLog.logSecurityIndexAttempt(request, action, task); log.warn("{} with 'include_global_state' enabled is not allowed", action); presponse.allowed = false; - return presponse.markComplete(); + return presponse.markComplete(); } final List rs = SnapshotRestoreHelper.resolveOriginalIndices(restoreRequest); @@ -100,7 +100,7 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T auditLog.logSecurityIndexAttempt(request, action, task); log.warn("{} for '{}' as source index is not allowed", action, securityIndex); presponse.allowed = false; - return presponse.markComplete(); + return presponse.markComplete(); } return presponse; } diff --git a/src/main/java/org/opensearch/security/privileges/TermsAggregationEvaluator.java b/src/main/java/org/opensearch/security/privileges/TermsAggregationEvaluator.java index 1d1d048350..53709458fd 100644 --- a/src/main/java/org/opensearch/security/privileges/TermsAggregationEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/TermsAggregationEvaluator.java @@ -56,12 +56,12 @@ public class TermsAggregationEvaluator { "indices:data/read/field_caps*" //"indices:admin/mappings/fields/get*" }; - + private static final QueryBuilder NONE_QUERY = new MatchNoneQueryBuilder(); - + public TermsAggregationEvaluator() { } - + public PrivilegesEvaluatorResponse evaluate(final Resolved resolved, final ActionRequest request, ClusterService clusterService, User user, SecurityRoles securityRoles, IndexNameExpressionResolver resolver, PrivilegesEvaluatorResponse presponse) { try { if(request instanceof SearchRequest) { @@ -81,14 +81,14 @@ public PrivilegesEvaluatorResponse evaluate(final Resolved resolved, final Actio && ab.getPipelineAggregations().isEmpty() && ab.getSubAggregations().isEmpty()) { - + final Set allPermittedIndices = securityRoles.getAllPermittedIndicesForDashboards(resolved, user, READ_ACTIONS, resolver, clusterService); if(allPermittedIndices == null || allPermittedIndices.isEmpty()) { sr.source().query(NONE_QUERY); } else { sr.source().query(new TermsQueryBuilder("_index", allPermittedIndices)); - } - + } + presponse.allowed = true; return presponse.markComplete(); } @@ -99,7 +99,7 @@ public PrivilegesEvaluatorResponse evaluate(final Resolved resolved, final Actio log.warn("Unable to evaluate terms aggregation",e); return presponse; } - + return presponse; } } diff --git a/src/main/java/org/opensearch/security/resolver/IndexResolverReplacer.java b/src/main/java/org/opensearch/security/resolver/IndexResolverReplacer.java index d2d0685860..5892a91a30 100644 --- a/src/main/java/org/opensearch/security/resolver/IndexResolverReplacer.java +++ b/src/main/java/org/opensearch/security/resolver/IndexResolverReplacer.java @@ -369,7 +369,7 @@ public final static class Resolved { private final Set remoteIndices; private final boolean isLocalAll; private final IndicesOptions indicesOptions; - + public Resolved(final ImmutableSet aliases, final ImmutableSet allIndices, final ImmutableSet originalRequested, @@ -394,15 +394,15 @@ public Set getAliases() { public Set getAllIndices() { return allIndices; } - + public Set getAllIndicesResolved(ClusterService clusterService, IndexNameExpressionResolver resolver) { - if (isLocalAll) { + if (isLocalAll) { return new HashSet<>(Arrays.asList(resolver.concreteIndexNames(clusterService.state(), indicesOptions, "*"))); - } else { + } else { return allIndices; } } - + public boolean isAllIndicesEmpty() { return allIndices.isEmpty(); } @@ -711,7 +711,7 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid } private IndicesOptions indicesOptionsFrom(Object localRequest) { - + if(!respectRequestIndicesOptions) { return IndicesOptions.fromOptions(false, true, true, false, true); } diff --git a/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java b/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java index 0fd88e7565..a7620f6bdc 100644 --- a/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java @@ -92,9 +92,9 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli public void accept(RestChannel channel) throws Exception { XContentBuilder builder = channel.newBuilder(); //NOSONAR BytesRestResponse response = null; - + try { - + final User user = (User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); builder.startObject(); @@ -131,6 +131,6 @@ public void accept(RestChannel channel) throws Exception { public String getName() { return "Kibana Info Action"; } - - + + } diff --git a/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java b/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java index b88d2700c9..17d5ee122f 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java @@ -67,7 +67,7 @@ public List routes() { @Override protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { return new RestChannelConsumer() { - + final String mode = request.param("mode","strict"); @Override @@ -76,8 +76,8 @@ public void accept(RestChannel channel) throws Exception { RestStatus restStatus = RestStatus.OK; BytesRestResponse response = null; try { - - + + String status = "UP"; String message = null; @@ -98,12 +98,12 @@ public void accept(RestChannel channel) throws Exception { } finally { builder.close(); } - - + + channel.sendResponse(response); } - - + + }; } diff --git a/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java b/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java index f8e03da5d2..7867e8790d 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java @@ -88,12 +88,12 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli public void accept(RestChannel channel) throws Exception { XContentBuilder builder = channel.newBuilder(); //NOSONAR BytesRestResponse response = null; - + try { - + final boolean verbose = request.paramAsBoolean("verbose", false); - + final X509Certificate[] certs = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PEER_CERTIFICATES); final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); final TransportAddress remoteAddress = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); @@ -112,7 +112,7 @@ public void accept(RestChannel channel) throws Exception { builder.field("principal", (String)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PRINCIPAL)); builder.field("peer_certificates", certs != null && certs.length > 0 ? certs.length + "" : "0"); builder.field("sso_logout_url", (String)threadContext.getTransient(ConfigConstants.SSO_LOGOUT_URL)); - + if(user != null && verbose) { try { builder.field("size_of_user", RamUsageEstimator.humanReadableUnits(Base64Helper.serializeObject(user).length())); @@ -122,8 +122,8 @@ public void accept(RestChannel channel) throws Exception { //ignore } } - - + + builder.endObject(); response = new BytesRestResponse(RestStatus.OK, builder); @@ -144,7 +144,7 @@ public void accept(RestChannel channel) throws Exception { } }; } - + @Override public String getName() { return "OpenSearch Security Info Action"; diff --git a/src/main/java/org/opensearch/security/rest/TenantInfoAction.java b/src/main/java/org/opensearch/security/rest/TenantInfoAction.java index 266d2edf49..f7b2a606c6 100644 --- a/src/main/java/org/opensearch/security/rest/TenantInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/TenantInfoAction.java @@ -78,7 +78,7 @@ public class TenantInfoAction extends BaseRestHandler { private final AdminDNs adminDns; private final ConfigurationRepository configurationRepository; - public TenantInfoAction(final Settings settings, final RestController controller, + public TenantInfoAction(final Settings settings, final RestController controller, final PrivilegesEvaluator evaluator, final ThreadPool threadPool, final ClusterService clusterService, final AdminDNs adminDns, final ConfigurationRepository configurationRepository) { super(); @@ -102,18 +102,18 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli public void accept(RestChannel channel) throws Exception { XContentBuilder builder = channel.newBuilder(); //NOSONAR BytesRestResponse response = null; - + try { final User user = (User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - + //only allowed for admins or the kibanaserveruser if(!isAuthorized()) { response = new BytesRestResponse(RestStatus.FORBIDDEN,""); } else { builder.startObject(); - + final SortedMap lookup = clusterService.state().metadata().getIndicesLookup(); for(final String indexOrAlias: lookup.keySet()) { final String tenant = tenantNameForIndex(indexOrAlias); @@ -123,7 +123,7 @@ public void accept(RestChannel channel) throws Exception { } builder.endObject(); - + response = new BytesRestResponse(RestStatus.OK, builder); } } catch (final Exception e1) { @@ -179,21 +179,21 @@ private final SecurityDynamicConfiguration load(final CType config, boolean l private String tenantNameForIndex(String index) { String[] indexParts; - if(index == null + if(index == null || (indexParts = index.split("_")).length != 3 ) { return null; } - - + + if(!indexParts[0].equals(evaluator.dashboardsIndex())) { return null; } - + try { final int expectedHash = Integer.parseInt(indexParts[1]); final String sanitizedName = indexParts[2]; - + for(String tenant: evaluator.getAllConfiguredTenantNames()) { if(tenant.hashCode() == expectedHash && sanitizedName.equals(tenant.toLowerCase().replaceAll("[^a-z0-9]+",""))) { return tenant; @@ -211,6 +211,6 @@ private String tenantNameForIndex(String index) { public String getName() { return "Tenant Info Action"; } - - + + } diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java index 987b8fac64..7a978034f1 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java @@ -83,9 +83,9 @@ public ConfigModelV6( SecurityDynamicConfiguration rolesmapping, DynamicConfigModel dcm, Settings opensearchSettings) { - + this.roles = roles; - + try { rolesMappingResolution = ConfigConstants.RolesMappingResolution.valueOf( opensearchSettings.get(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, ConfigConstants.RolesMappingResolution.MAPPING_ONLY.toString()) @@ -94,13 +94,13 @@ public ConfigModelV6( log.error("Cannot apply roles mapping resolution", e); rolesMappingResolution = ConfigConstants.RolesMappingResolution.MAPPING_ONLY; } - + agr = reloadActionGroups(actiongroups); securityRoles = reload(roles); tenantHolder = new TenantHolder(roles); roleMappingHolder = new RoleMappingHolder(rolesmapping, dcm.getHostsResolverMode()); } - + public Set getAllConfiguredTenantNames() { final Set configuredTenants = new HashSet<>(); for (Entry securityRole : roles.getCEntries().entrySet()) { @@ -114,18 +114,18 @@ public Set getAllConfiguredTenantNames() { return Collections.unmodifiableSet(configuredTenants); } - + public SecurityRoles getSecurityRoles() { return securityRoles; } - + private static interface ActionGroupResolver { Set resolvedActions(final List actions); } - + private ActionGroupResolver reloadActionGroups(SecurityDynamicConfiguration actionGroups) { return new ActionGroupResolver() { - + private Set getGroupMembers(final String groupname) { if (actionGroups == null) { @@ -134,27 +134,27 @@ private Set getGroupMembers(final String groupname) { return Collections.unmodifiableSet(resolve(actionGroups, groupname)); } - + private Set resolve(final SecurityDynamicConfiguration actionGroups, final String entry) { - + // SG5 format, plain array //List en = actionGroups.getAsList(DotPath.of(entry)); //if (en.isEmpty()) { // try SG6 format including readonly and permissions key // en = actionGroups.getAsList(DotPath.of(entry + "." + ConfigConstants.CONFIGKEY_ACTION_GROUPS_PERMISSIONS)); //} - + if(!actionGroups.getCEntries().containsKey(entry)) { return Collections.emptySet(); } - + final Set ret = new HashSet(); - + final Object actionGroupAsObject = actionGroups.getCEntries().get(entry); - + if(actionGroupAsObject != null && actionGroupAsObject instanceof List) { - + for (final String perm: ((List) actionGroupAsObject)) { if (actionGroups.getCEntries().keySet().contains(perm)) { ret.addAll(resolve(actionGroups,perm)); @@ -162,8 +162,8 @@ private Set resolve(final SecurityDynamicConfiguration actionGroups, ret.add(perm); } } - - + + } else if(actionGroupAsObject != null && actionGroupAsObject instanceof ActionGroupsV6) { for (final String perm: ((ActionGroupsV6) actionGroupAsObject).getPermissions()) { if (actionGroups.getCEntries().keySet().contains(perm)) { @@ -175,10 +175,10 @@ private Set resolve(final SecurityDynamicConfiguration actionGroups, } else { throw new RuntimeException("Unable to handle "+actionGroupAsObject); } - + return Collections.unmodifiableSet(ret); } - + @Override public Set resolvedActions(final List actions) { final Set resolvedActions = new HashSet(); @@ -208,7 +208,7 @@ private SecurityRoles reload(SecurityDynamicConfiguration settings) { @Override public SecurityRole call() throws Exception { SecurityRole _securityRole = new SecurityRole(securityRole.getKey()); - + if(securityRole.getValue() == null) { return null; } @@ -261,8 +261,8 @@ public SecurityRole call() throws Exception { _securityRole.addIndexPattern(_indexPattern); } - - + + return _securityRole; } }); @@ -352,7 +352,7 @@ public Set getRoles() { public Set getRoleNames() { return getRoles().stream().map(r -> r.getName()).collect(Collectors.toSet()); } - + public SecurityRoles filter(Set keep) { final SecurityRoles retVal = new SecurityRoles(roles.size()); for (SecurityRole sr : roles) { @@ -371,7 +371,7 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, final Map> dlsQueries = new HashMap>(); final Map> flsFields = new HashMap>(); final Map> maskedFieldsMap = new HashMap>(); - + for (SecurityRole sr : roles) { for (IndexPattern ip : sr.getIpatterns()) { final Set fls = ip.getFls(); @@ -423,7 +423,7 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, } } } - + if (maskedFields != null && maskedFields.size() > 0) { if (maskedFieldsMap.containsKey(indexPattern)) { @@ -444,7 +444,7 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, } } } - + return new EvaluatedDlsFlsConfig(dlsQueries, flsFields, maskedFieldsMap); } @@ -1010,10 +1010,10 @@ private static boolean impliesTypePerm(Set ipatterns, Resolved res ); } - - + + //####### - + private class TenantHolder { private SetMultimap> tenantsMM = null; @@ -1024,7 +1024,7 @@ public TenantHolder(SecurityDynamicConfiguration roles) { final ExecutorService execs = Executors.newFixedThreadPool(10); for(Entry securityRole: roles.getCEntries().entrySet()) { - + if(securityRole.getValue() == null) { continue; } @@ -1036,7 +1036,7 @@ public Tuple>> call() throws Exception { final Map tenants = securityRole.getValue().getTenants(); if (tenants != null) { - + for (String tenant : tenants.keySet()) { if ("RW".equalsIgnoreCase(tenants.get(tenant))) { @@ -1125,7 +1125,7 @@ private class RoleMappingHolder { private RoleMappingHolder(final SecurityDynamicConfiguration rolesMapping, final String hostResolverMode) { this.hostResolverMode = hostResolverMode; - + if (rolesMapping != null) { users = ArrayListMultimap.create(); @@ -1228,10 +1228,10 @@ private Set map(final User user, final TransportAddress caller) { } } - - - - + + + + public Map mapTenants(User user, Set roles) { return tenantHolder.mapTenants(user, roles); diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index 1e2adee1db..560cfb8a6d 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -92,7 +92,7 @@ public ConfigModelV7( this.roles = roles; this.tenants = tenants; - + try { rolesMappingResolution = ConfigConstants.RolesMappingResolution.valueOf( opensearchSettings.get(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, ConfigConstants.RolesMappingResolution.MAPPING_ONLY.toString()) @@ -111,18 +111,18 @@ public ConfigModelV7( public Set getAllConfiguredTenantNames() { return Collections.unmodifiableSet(tenants.getCEntries().keySet()); } - + public SecurityRoles getSecurityRoles() { return securityRoles; } - + private static interface ActionGroupResolver { Set resolvedActions(final List actions); } private ActionGroupResolver reloadActionGroups(SecurityDynamicConfiguration actionGroups) { return new ActionGroupResolver() { - + private Set getGroupMembers(final String groupname) { if (actionGroups == null) { @@ -131,27 +131,27 @@ private Set getGroupMembers(final String groupname) { return Collections.unmodifiableSet(resolve(actionGroups, groupname)); } - + private Set resolve(final SecurityDynamicConfiguration actionGroups, final String entry) { - + // SG5 format, plain array //List en = actionGroups.getAsList(DotPath.of(entry)); //if (en.isEmpty()) { // try SG6 format including readonly and permissions key // en = actionGroups.getAsList(DotPath.of(entry + "." + ConfigConstants.CONFIGKEY_ACTION_GROUPS_PERMISSIONS)); //} - + if(!actionGroups.getCEntries().containsKey(entry)) { return Collections.emptySet(); } - + final Set ret = new HashSet(); - + final Object actionGroupAsObject = actionGroups.getCEntries().get(entry); - + if(actionGroupAsObject != null && actionGroupAsObject instanceof List) { - + for (final String perm: ((List) actionGroupAsObject)) { if (actionGroups.getCEntries().keySet().contains(perm)) { ret.addAll(resolve(actionGroups,perm)); @@ -159,8 +159,8 @@ private Set resolve(final SecurityDynamicConfiguration actionGroups, ret.add(perm); } } - - + + } else if(actionGroupAsObject != null && actionGroupAsObject instanceof ActionGroupsV7) { for (final String perm: ((ActionGroupsV7) actionGroupAsObject).getAllowed_actions()) { if (actionGroups.getCEntries().keySet().contains(perm)) { @@ -172,10 +172,10 @@ private Set resolve(final SecurityDynamicConfiguration actionGroups, } else { throw new RuntimeException("Unable to handle "+actionGroupAsObject); } - + return Collections.unmodifiableSet(ret); } - + @Override public Set resolvedActions(final List actions) { final Set resolvedActions = new HashSet(); @@ -205,7 +205,7 @@ private SecurityRoles reload(SecurityDynamicConfiguration settings) { @Override public SecurityRole call() throws Exception { SecurityRole.Builder _securityRole = new SecurityRole.Builder(securityRole.getKey()); - + if(securityRole.getValue() == null) { return null; } @@ -238,21 +238,21 @@ public SecurityRole call() throws Exception { _indexPattern.addFlsFields(fls); _indexPattern.addMaskedFields(maskedFields); _indexPattern.addPerm(agr.resolvedActions(permittedAliasesIndex.getAllowed_actions())); - + /*for(Entry> type: permittedAliasesIndex.getValue().getTypes(-).entrySet()) { TypePerm typePerm = new TypePerm(type.getKey()); final List perms = type.getValue(); typePerm.addPerms(agr.resolvedActions(perms)); _indexPattern.addTypePerms(typePerm); }*/ - + _securityRole.addIndexPattern(_indexPattern); - + } } - - + + return _securityRole.build(); } }); @@ -339,7 +339,7 @@ public String toString() { public Set getRoles() { return Collections.unmodifiableSet(roles); } - + public Set getRoleNames() { return getRoles().stream().map(r -> r.getName()).collect(Collectors.toSet()); } @@ -367,8 +367,8 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, return EvaluatedDlsFlsConfig.EMPTY; } - - Map> dlsQueriesByIndex = new HashMap>(); + + Map> dlsQueriesByIndex = new HashMap>(); Map> flsFields = new HashMap>(); Map> maskedFieldsMap = new HashMap>(); @@ -379,7 +379,7 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, Set noDlsConcreteIndices = new HashSet<>(); Set noFlsConcreteIndices = new HashSet<>(); Set noMaskedFieldConcreteIndices = new HashSet<>(); - + for (SecurityRole role : roles) { for (IndexPattern ip : role.getIpatterns()) { final Set concreteIndices = ip.concreteIndexNames(user, resolver, cs); @@ -409,12 +409,12 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, } else if (dfmEmptyOverwritesAll) { noFlsConcreteIndices.addAll(concreteIndices); } - + Set maskedFields = ip.getMaskedFields(); if (maskedFields != null && maskedFields.size() > 0) { - for (String concreteIndex : concreteIndices) { + for (String concreteIndex : concreteIndices) { if (maskedFieldsMap.containsKey(concreteIndex)) { maskedFieldsMap.get(concreteIndex).addAll(Sets.newHashSet(maskedFields)); } else { @@ -498,7 +498,7 @@ public boolean impliesTypePermGlobal(Resolved resolved, User user, String[] acti roles.stream().forEach(p -> ipatterns.addAll(p.getIpatterns())); return ConfigModelV7.impliesTypePerm(ipatterns, resolved, user, actions, resolver, cs); } - + private boolean containsDlsFlsConfig() { for (SecurityRole role : roles) { for (IndexPattern ip : role.getIpatterns()) { @@ -562,7 +562,7 @@ private Set getAllResolvedPermittedIndices(Resolved resolved, User user, for (IndexPattern p : ipatterns) { //what if we cannot resolve one (for create purposes) final boolean patternMatch = p.getPerms().matchAll(actions); - + // final Set tperms = p.getTypePerms(); // for (TypePerm tp : tperms) { // if (WildcardMatcher.matchAny(tp.typePattern, resolved.getTypes(-).toArray(new String[0]))) { @@ -810,7 +810,7 @@ public String getDlsQuery(User user) { public boolean hasDlsQuery() { return dlsQuery != null && !dlsQuery.isEmpty(); } - + public Set getFls() { return Collections.unmodifiableSet(fls); } @@ -818,7 +818,7 @@ public Set getFls() { public boolean hasFlsFields() { return fls != null && !fls.isEmpty(); } - + public Set getMaskedFields() { return Collections.unmodifiableSet(maskedFields); } @@ -826,12 +826,12 @@ public Set getMaskedFields() { public boolean hasMaskedFields() { return maskedFields != null && !maskedFields.isEmpty(); } - + public WildcardMatcher getPerms() { return WildcardMatcher.from(perms); } - + } /*public static class TypePerm { @@ -1033,7 +1033,7 @@ private static boolean impliesTypePerm(Set ipatterns, Resolved res ) ); } - + private class TenantHolder { private SetMultimap> tenantsMM = null; @@ -1055,7 +1055,7 @@ public Tuple>> call() throws Exception { final Set> tuples = new HashSet<>(); final List tenants = securityRole.getValue().getTenant_permissions(); if (tenants != null) { - + for (RoleV7.Tenant tenant : tenants) { // find Wildcarded tenant patterns @@ -1166,7 +1166,7 @@ private class RoleMappingHolder { private RoleMappingHolder(final SecurityDynamicConfiguration rolemappings, final String hostResolverMode) { this.hostResolverMode = hostResolverMode; - + if (roles != null) { users = ArrayListMultimap.create(); @@ -1267,10 +1267,10 @@ private Set map(final User user, final TransportAddress caller) { } } - - - - + + + + public Map mapTenants(User user, Set roles) { return tenantHolder.mapTenants(user, roles); diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java index 262eb37cf8..9d8c36576c 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java @@ -117,7 +117,7 @@ public final static SecurityDynamicConfiguration addStatics(SecurityDynamicCo return original; } - + protected final Logger log = LogManager.getLogger(this.getClass()); private final ConfigurationRepository cr; private final AtomicBoolean initialized = new AtomicBoolean(); @@ -127,7 +127,7 @@ public final static SecurityDynamicConfiguration addStatics(SecurityDynamicCo private final InternalAuthenticationBackend iab = new InternalAuthenticationBackend(); SecurityDynamicConfiguration config; - + public DynamicConfigFactory(ConfigurationRepository cr, final Settings opensearchSettings, final Path configPath, Client client, ThreadPool threadPool, ClusterInfoHolder cih) { super(); @@ -144,11 +144,11 @@ public DynamicConfigFactory(ConfigurationRepository cr, final Settings opensearc } else { log.info("Static resources will not be loaded."); } - + registerDCFListener(this.iab); this.cr.subscribeOnChange(this); } - + @Override public void onChange(Map> typeToConfig) { @@ -187,7 +187,7 @@ public void onChange(Map> typeToConfig) { if(config.getImplementingClass() == ConfigV7.class) { //statics - + if(roles.containsAny(staticRoles)) { throw new StaticResourceException("Cannot override static roles"); } @@ -205,24 +205,24 @@ public void onChange(Map> typeToConfig) { if(!actionGroups.add(staticActionGroups) && !staticActionGroups.getCEntries().isEmpty()) { throw new StaticResourceException("Unable to load static action groups"); } - + log.debug("Static action groups loaded ({})", staticActionGroups.getCEntries().size()); - + if(tenants.containsAny(staticTenants)) { throw new StaticResourceException("Cannot override static tenants"); } if(!tenants.add(staticTenants) && !staticTenants.getCEntries().isEmpty()) { throw new StaticResourceException("Unable to load static tenants"); } - + log.debug("Static tenants loaded ({})", staticTenants.getCEntries().size()); log.debug("Static configuration loaded (total roles: {}/total action groups: {}/total tenants: {})", roles.getCEntries().size(), actionGroups.getCEntries().size(), tenants.getCEntries().size()); - + //rebuild v7 Models dcm = new DynamicConfigModelV7(getConfigV7(config), opensearchSettings, configPath, iab); @@ -252,26 +252,26 @@ public void onChange(Map> typeToConfig) { } initialized.set(true); - + } - + private static ConfigV6 getConfigV6(SecurityDynamicConfiguration sdc) { @SuppressWarnings("unchecked") SecurityDynamicConfiguration c = (SecurityDynamicConfiguration) sdc; return c.getCEntry("opendistro_security"); } - + private static ConfigV7 getConfigV7(SecurityDynamicConfiguration sdc) { @SuppressWarnings("unchecked") SecurityDynamicConfiguration c = (SecurityDynamicConfiguration) sdc; return c.getCEntry("config"); } - + @Override public final boolean isInitialized() { return initialized.get(); } - + public void registerDCFListener(Object listener) { eventBus.register(listener); } @@ -279,15 +279,15 @@ public void registerDCFListener(Object listener) { public void unregisterDCFListener(Object listener) { eventBus.unregister(listener); } - + private static class InternalUsersModelV7 extends InternalUsersModel { - + private final SecurityDynamicConfiguration internalUserV7SecurityDynamicConfiguration; private final SecurityDynamicConfiguration rolesV7SecurityDynamicConfiguration; private final SecurityDynamicConfiguration rolesMappingsV7SecurityDynamicConfiguration; - + public InternalUsersModelV7(SecurityDynamicConfiguration internalUserV7SecurityDynamicConfiguration, SecurityDynamicConfiguration rolesV7SecurityDynamicConfiguration, SecurityDynamicConfiguration rolesMappingsV7SecurityDynamicConfiguration) { @@ -325,7 +325,7 @@ public String getHash(String user) { InternalUserV7 tmp = internalUserV7SecurityDynamicConfiguration.getCEntry(user); return tmp==null?null:tmp.getHash(); } - + public List getSecurityRoles(String user) { InternalUserV7 tmp = internalUserV7SecurityDynamicConfiguration.getCEntry(user); @@ -341,11 +341,11 @@ private boolean isRolesMappingHidden(String rolename) { return roleMapping!=null && roleMapping.isHidden(); } } - + private static class InternalUsersModelV6 extends InternalUsersModel { - + SecurityDynamicConfiguration configuration; - + public InternalUsersModelV6(SecurityDynamicConfiguration configuration) { super(); @@ -379,7 +379,7 @@ public String getHash(String user) { InternalUserV6 tmp = configuration.getCEntry(user); return tmp==null?null:tmp.getHash(); } - + public List getSecurityRoles(String user) { return Collections.emptyList(); } diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java index f91e768283..22121bca7f 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java @@ -53,7 +53,7 @@ import org.opensearch.security.http.proxy.HTTPExtendedProxyAuthenticator; public abstract class DynamicConfigModel { - + protected final Logger log = LogManager.getLogger(this.getClass()); public abstract SortedSet getRestAuthDomains(); public abstract Set getRestAuthorizers(); @@ -75,17 +75,17 @@ public abstract class DynamicConfigModel { public abstract String getFilteredAliasMode(); public abstract String getHostsResolverMode(); public abstract boolean isDnfofForEmptyResultsEnabled(); - + public abstract List getIpAuthFailureListeners(); public abstract Multimap getAuthBackendFailureListeners(); public abstract List> getIpClientBlockRegistries(); public abstract Multimap> getAuthBackendClientBlockRegistries(); - + protected final Map authImplMap = new HashMap<>(); public DynamicConfigModel() { super(); - + authImplMap.put("intern_c", InternalAuthenticationBackend.class.getName()); authImplMap.put("intern_z", NoOpAuthorizationBackend.class.getName()); @@ -97,7 +97,7 @@ public DynamicConfigModel() { authImplMap.put("ldap_c", "com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); authImplMap.put("ldap_z", "com.amazon.dlic.auth.ldap.backend.LDAPAuthorizationBackend"); - + authImplMap.put("ldap2_c", "com.amazon.dlic.auth.ldap2.LDAPAuthenticationBackend2"); authImplMap.put("ldap2_z", "com.amazon.dlic.auth.ldap2.LDAPAuthorizationBackend2"); @@ -109,11 +109,11 @@ public DynamicConfigModel() { authImplMap.put("jwt_h", "com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator"); authImplMap.put("openid_h", "com.amazon.dlic.auth.http.jwt.keybyoidc.HTTPJwtKeyByOpenIdConnectAuthenticator"); authImplMap.put("saml_h", "com.amazon.dlic.auth.http.saml.HTTPSamlAuthenticator"); - + authImplMap.put("ip_authFailureListener", AddressBasedRateLimiter.class.getName()); authImplMap.put("username_authFailureListener", UserNameBasedRateLimiter.class.getName()); } - - - + + + } diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java index 40b3e3319a..2dce89ba7c 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java @@ -62,7 +62,7 @@ import org.opensearch.security.support.ReflectionHelper; public class DynamicConfigModelV6 extends DynamicConfigModel { - + private final ConfigV6 config; private final Settings opensearchSettings; private final Path configPath; @@ -72,12 +72,12 @@ public class DynamicConfigModelV6 extends DynamicConfigModel { private Set transportAuthorizers; private List destroyableComponents; private final InternalAuthenticationBackend iab; - + private List ipAuthFailureListeners; private Multimap authBackendFailureListeners; private List> ipClientBlockRegistries; private Multimap> authBackendClientBlockRegistries; - + public DynamicConfigModelV6(ConfigV6 config, Settings opensearchSettings, Path configPath, InternalAuthenticationBackend iab) { super(); this.config = config; @@ -158,7 +158,7 @@ public boolean isMultiRolespanEnabled() { public String getFilteredAliasMode() { return config.dynamic.filtered_alias_mode; } - + @Override public boolean isDnfofForEmptyResultsEnabled() { return config.dynamic.do_not_fail_on_forbidden_empty; @@ -168,29 +168,29 @@ public boolean isDnfofForEmptyResultsEnabled() { public String getHostsResolverMode() { return config.dynamic.hosts_resolver_mode; } - + @Override public List getIpAuthFailureListeners() { return Collections.unmodifiableList(ipAuthFailureListeners); } - + @Override public Multimap getAuthBackendFailureListeners() { return Multimaps.unmodifiableMultimap(authBackendFailureListeners); } - + @Override public List> getIpClientBlockRegistries() { return Collections.unmodifiableList(ipClientBlockRegistries); } - + @Override public Multimap> getAuthBackendClientBlockRegistries() { return Multimaps.unmodifiableMultimap(authBackendClientBlockRegistries); } - + private void buildAAA() { - + final SortedSet restAuthDomains0 = new TreeSet<>(); final Set restAuthorizers0 = new HashSet<>(); final SortedSet transportAuthDomains0 = new TreeSet<>(); @@ -214,7 +214,7 @@ private void buildAAA() { final String authzBackendClazz = ad.getValue().authorization_backend.type; final AuthorizationBackend authorizationBackend; - + if(authzBackendClazz.equals(InternalAuthenticationBackend.class.getName()) //NOSONAR || authzBackendClazz.equals("internal") || authzBackendClazz.equals("intern")) { @@ -229,7 +229,7 @@ private void buildAAA() { .put(Settings.builder().loadFromSource(ad.getValue().authorization_backend.configAsJson(), XContentType.JSON).build()).build() , configPath); } - + if (httpEnabled) { restAuthorizers0.add(authorizationBackend); } @@ -237,7 +237,7 @@ private void buildAAA() { if (transportEnabled) { transportAuthorizers0.add(authorizationBackend); } - + if (authorizationBackend instanceof Destroyable) { destroyableComponents0.add((Destroyable) authorizationBackend); } @@ -276,7 +276,7 @@ private void buildAAA() { String httpAuthenticatorType = ad.getValue().http_authenticator.type; //no default HTTPAuthenticator httpAuthenticator = httpAuthenticatorType==null?null: (HTTPAuthenticator) newInstance(httpAuthenticatorType,"h", Settings.builder().put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("http_authenticator.config")), DynamicConfiguration.checkKeyFunction()).build(), + //.putProperties(ads.getAsStringMap(DotPath.of("http_authenticator.config")), DynamicConfiguration.checkKeyFunction()).build(), .put(Settings.builder().loadFromSource(ad.getValue().http_authenticator.configAsJson(), XContentType.JSON).build()).build() , configPath); @@ -291,15 +291,15 @@ private void buildAAA() { if (transportEnabled) { transportAuthDomains0.add(_ad); } - + if (httpAuthenticator instanceof Destroyable) { destroyableComponents0.add((Destroyable) httpAuthenticator); } - + if (authenticationBackend instanceof Destroyable) { destroyableComponents0.add((Destroyable) authenticationBackend); } - + } catch (final Exception e) { log.error("Unable to initialize auth domain {} due to {}", ad, e.toString(), e); } @@ -308,30 +308,30 @@ private void buildAAA() { } List originalDestroyableComponents = destroyableComponents; - + restAuthDomains = Collections.unmodifiableSortedSet(restAuthDomains0); transportAuthDomains = Collections.unmodifiableSortedSet(transportAuthDomains0); restAuthorizers = Collections.unmodifiableSet(restAuthorizers0); transportAuthorizers = Collections.unmodifiableSet(transportAuthorizers0); - + destroyableComponents = Collections.unmodifiableList(destroyableComponents0); - + if(originalDestroyableComponents != null) { destroyDestroyables(originalDestroyableComponents); } - + originalDestroyableComponents = null; - + createAuthFailureListeners(ipAuthFailureListeners0, authBackendFailureListeners0, ipClientBlockRegistries0, authBackendClientBlockRegistries0, destroyableComponents0); - + ipAuthFailureListeners = Collections.unmodifiableList(ipAuthFailureListeners0); ipClientBlockRegistries = Collections.unmodifiableList(ipClientBlockRegistries0); authBackendClientBlockRegistries = Multimaps.unmodifiableMultimap(authBackendClientBlockRegistries0); authBackendFailureListeners = Multimaps.unmodifiableMultimap(authBackendFailureListeners0); } - + private void destroyDestroyables(List destroyableComponents) { for (Destroyable destroyable : destroyableComponents) { try { @@ -341,7 +341,7 @@ private void destroyDestroyables(List destroyableComponents) { } } } - + private T newInstance(final String clazzOrShortcut, String type, final Settings settings, final Path configPath) { String clazz = clazzOrShortcut; @@ -352,7 +352,7 @@ private T newInstance(final String clazzOrShortcut, String type, final Setti return ReflectionHelper.instantiateAAA(clazz, settings, configPath); } - + private String translateShortcutToClassName(final String clazzOrShortcut, final String type) { if (authImplMap.containsKey(clazzOrShortcut + "_" + type)) { @@ -361,17 +361,17 @@ private String translateShortcutToClassName(final String clazzOrShortcut, final return clazzOrShortcut; } } - + private void createAuthFailureListeners(List ipAuthFailureListeners, Multimap authBackendFailureListeners, List> ipClientBlockRegistries, Multimap> authBackendUserClientBlockRegistries, List destroyableComponents0) { for (Entry entry : config.dynamic.auth_failure_listeners.getListeners().entrySet()) { - + Settings entrySettings = Settings.builder() .put(opensearchSettings) .put(Settings.builder().loadFromSource(entry.getValue().asJson(), XContentType.JSON).build()).build(); - + String type = entry.getValue().type; String authenticationBackend = entry.getValue().authentication_backend; diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java index 6db5fba0a7..8e92675dcc 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java @@ -62,7 +62,7 @@ import org.opensearch.security.support.ReflectionHelper; public class DynamicConfigModelV7 extends DynamicConfigModel { - + private final ConfigV7 config; private final Settings opensearchSettings; private final Path configPath; @@ -77,7 +77,7 @@ public class DynamicConfigModelV7 extends DynamicConfigModel { private Multimap authBackendFailureListeners; private List> ipClientBlockRegistries; private Multimap> authBackendClientBlockRegistries; - + public DynamicConfigModelV7(ConfigV7 config, Settings opensearchSettings, Path configPath, InternalAuthenticationBackend iab) { super(); this.config = config; @@ -163,35 +163,35 @@ public String getFilteredAliasMode() { public String getHostsResolverMode() { return config.dynamic.hosts_resolver_mode; } - + @Override public boolean isDnfofForEmptyResultsEnabled() { return config.dynamic.do_not_fail_on_forbidden_empty; } - + @Override public List getIpAuthFailureListeners() { return Collections.unmodifiableList(ipAuthFailureListeners); } - + @Override public Multimap getAuthBackendFailureListeners() { return Multimaps.unmodifiableMultimap(authBackendFailureListeners); } - + @Override public List> getIpClientBlockRegistries() { return Collections.unmodifiableList(ipClientBlockRegistries); } - + @Override public Multimap> getAuthBackendClientBlockRegistries() { return Multimaps.unmodifiableMultimap(authBackendClientBlockRegistries); } - - + + private void buildAAA() { - + final SortedSet restAuthDomains0 = new TreeSet<>(); final Set restAuthorizers0 = new HashSet<>(); final SortedSet transportAuthDomains0 = new TreeSet<>(); @@ -214,7 +214,7 @@ private void buildAAA() { final String authzBackendClazz = ad.getValue().authorization_backend.type; final AuthorizationBackend authorizationBackend; - + if(authzBackendClazz.equals(InternalAuthenticationBackend.class.getName()) //NOSONAR || authzBackendClazz.equals("internal") || authzBackendClazz.equals("intern")) { @@ -229,7 +229,7 @@ private void buildAAA() { .put(Settings.builder().loadFromSource(ad.getValue().authorization_backend.configAsJson(), XContentType.JSON).build()).build() , configPath); } - + if (httpEnabled) { restAuthorizers0.add(authorizationBackend); } @@ -237,7 +237,7 @@ private void buildAAA() { if (transportEnabled) { transportAuthorizers0.add(authorizationBackend); } - + if (authorizationBackend instanceof Destroyable) { destroyableComponents0.add((Destroyable) authorizationBackend); } @@ -275,7 +275,7 @@ private void buildAAA() { String httpAuthenticatorType = ad.getValue().http_authenticator.type; //no default HTTPAuthenticator httpAuthenticator = httpAuthenticatorType==null?null: (HTTPAuthenticator) newInstance(httpAuthenticatorType,"h", Settings.builder().put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("http_authenticator.config")), DynamicConfiguration.checkKeyFunction()).build(), + //.putProperties(ads.getAsStringMap(DotPath.of("http_authenticator.config")), DynamicConfiguration.checkKeyFunction()).build(), .put(Settings.builder().loadFromSource(ad.getValue().http_authenticator.configAsJson(), XContentType.JSON).build()).build() , configPath); @@ -290,15 +290,15 @@ private void buildAAA() { if (transportEnabled) { transportAuthDomains0.add(_ad); } - + if (httpAuthenticator instanceof Destroyable) { destroyableComponents0.add((Destroyable) httpAuthenticator); } - + if (authenticationBackend instanceof Destroyable) { destroyableComponents0.add((Destroyable) authenticationBackend); } - + } catch (final Exception e) { log.error("Unable to initialize auth domain {} due to {}", ad, e.toString(), e); } @@ -307,23 +307,23 @@ private void buildAAA() { } List originalDestroyableComponents = destroyableComponents; - + restAuthDomains = Collections.unmodifiableSortedSet(restAuthDomains0); transportAuthDomains = Collections.unmodifiableSortedSet(transportAuthDomains0); restAuthorizers = Collections.unmodifiableSet(restAuthorizers0); transportAuthorizers = Collections.unmodifiableSet(transportAuthorizers0); - + destroyableComponents = Collections.unmodifiableList(destroyableComponents0); - + if(originalDestroyableComponents != null) { destroyDestroyables(originalDestroyableComponents); } - + originalDestroyableComponents = null; createAuthFailureListeners(ipAuthFailureListeners0, authBackendFailureListeners0, ipClientBlockRegistries0, authBackendClientBlockRegistries0, destroyableComponents0); - + ipAuthFailureListeners = Collections.unmodifiableList(ipAuthFailureListeners0); ipClientBlockRegistries = Collections.unmodifiableList(ipClientBlockRegistries0); authBackendClientBlockRegistries = Multimaps.unmodifiableMultimap(authBackendClientBlockRegistries0); @@ -340,7 +340,7 @@ private void destroyDestroyables(List destroyableComponents) { } } } - + private T newInstance(final String clazzOrShortcut, String type, final Settings settings, final Path configPath) { String clazz = clazzOrShortcut; @@ -351,7 +351,7 @@ private T newInstance(final String clazzOrShortcut, String type, final Setti return ReflectionHelper.instantiateAAA(clazz, settings, configPath); } - + private String translateShortcutToClassName(final String clazzOrShortcut, final String type) { if (authImplMap.containsKey(clazzOrShortcut + "_" + type)) { @@ -360,17 +360,17 @@ private String translateShortcutToClassName(final String clazzOrShortcut, final return clazzOrShortcut; } } - + private void createAuthFailureListeners(List ipAuthFailureListeners, Multimap authBackendFailureListeners, List> ipClientBlockRegistries, Multimap> authBackendUserClientBlockRegistries, List destroyableComponents0) { for (Entry entry : config.dynamic.auth_failure_listeners.getListeners().entrySet()) { - + Settings entrySettings = Settings.builder() .put(opensearchSettings) .put(Settings.builder().loadFromSource(entry.getValue().asJson(), XContentType.JSON).build()).build(); - + String type = entry.getValue().type; String authenticationBackend = entry.getValue().authentication_backend; diff --git a/src/main/java/org/opensearch/security/securityconf/EvaluatedDlsFlsConfig.java b/src/main/java/org/opensearch/security/securityconf/EvaluatedDlsFlsConfig.java index 9100e7dd02..8870cb3aad 100644 --- a/src/main/java/org/opensearch/security/securityconf/EvaluatedDlsFlsConfig.java +++ b/src/main/java/org/opensearch/security/securityconf/EvaluatedDlsFlsConfig.java @@ -87,7 +87,7 @@ public EvaluatedDlsFlsConfig filter(Resolved indices) { return this; } else { Set allIndices = indices.getAllIndices(); - + return new EvaluatedDlsFlsConfig(filter(dlsQueriesByIndex, allIndices), filter(flsByIndex, allIndices), filter(fieldMaskingByIndex, allIndices)); } @@ -108,7 +108,7 @@ private Map> filter(Map> map, Set> result = new HashMap<>(map.size()); - for (Map.Entry> entry : map.entrySet()) { + for (Map.Entry> entry : map.entrySet()) { if (WildcardMatcher.from(entry.getKey(), false).matchAny(allIndices)) { result.put(entry.getKey(), entry.getValue()); } diff --git a/src/main/java/org/opensearch/security/securityconf/Hideable.java b/src/main/java/org/opensearch/security/securityconf/Hideable.java index 9b1df8f157..8744575d64 100644 --- a/src/main/java/org/opensearch/security/securityconf/Hideable.java +++ b/src/main/java/org/opensearch/security/securityconf/Hideable.java @@ -28,7 +28,7 @@ package org.opensearch.security.securityconf; public interface Hideable { - + boolean isHidden(); boolean isReserved(); diff --git a/src/main/java/org/opensearch/security/securityconf/Initializable.java b/src/main/java/org/opensearch/security/securityconf/Initializable.java index fafc717866..ab1a1ebd4a 100644 --- a/src/main/java/org/opensearch/security/securityconf/Initializable.java +++ b/src/main/java/org/opensearch/security/securityconf/Initializable.java @@ -28,7 +28,7 @@ package org.opensearch.security.securityconf; public interface Initializable { - + boolean isInitialized(); } diff --git a/src/main/java/org/opensearch/security/securityconf/InternalUsersModel.java b/src/main/java/org/opensearch/security/securityconf/InternalUsersModel.java index 3ff1554a94..41c3116874 100644 --- a/src/main/java/org/opensearch/security/securityconf/InternalUsersModel.java +++ b/src/main/java/org/opensearch/security/securityconf/InternalUsersModel.java @@ -31,7 +31,7 @@ import java.util.Map; public abstract class InternalUsersModel { - + public abstract boolean exists(String user); public abstract List getBackenRoles(String user); public abstract Map getAttributes(String user); diff --git a/src/main/java/org/opensearch/security/securityconf/Migration.java b/src/main/java/org/opensearch/security/securityconf/Migration.java index 3cb111f11c..ec6a5525b9 100644 --- a/src/main/java/org/opensearch/security/securityconf/Migration.java +++ b/src/main/java/org/opensearch/security/securityconf/Migration.java @@ -55,15 +55,15 @@ public class Migration { - + public static Tuple,SecurityDynamicConfiguration> migrateRoles(SecurityDynamicConfiguration r6cs, SecurityDynamicConfiguration rms6) throws MigrationException { - + final SecurityDynamicConfiguration r7 = SecurityDynamicConfiguration.empty(); r7.setCType(r6cs.getCType()); r7.set_meta(new Meta()); r7.get_meta().setConfig_version(2); r7.get_meta().setType("roles"); - + final SecurityDynamicConfiguration t7 = SecurityDynamicConfiguration.empty(); t7.setCType(CType.TENANTS); t7.set_meta(new Meta()); @@ -71,11 +71,11 @@ public static Tuple,SecurityDynamicConfigur t7.get_meta().setType("tenants"); Set dedupTenants = new HashSet<>(); - + for(final Entry r6e: r6cs.getCEntries().entrySet()) { final String roleName = r6e.getKey(); final RoleV6 r6 = r6e.getValue(); - + if(r6 == null) { RoleV7 noPermRole = new RoleV7(); noPermRole.setDescription("Migrated from v6, was empty"); @@ -84,52 +84,52 @@ public static Tuple,SecurityDynamicConfigur } r7.putCEntry(roleName, new RoleV7(r6)); - + for(Entry tenant: r6.getTenants().entrySet()) { dedupTenants.add(tenant.getKey()); } } - + if(rms6 != null) { for(final Entry r6m: rms6.getCEntries().entrySet()) { final String roleName = r6m.getKey(); //final RoleMappingsV6 r6 = r6m.getValue(); - + if(!r7.exists(roleName)) { //rolemapping but role does not exists RoleV7 noPermRole = new RoleV7(); noPermRole.setDescription("Migrated from v6, was in rolemappings but no role existed"); r7.putCEntry(roleName, noPermRole); } - + } } - + for(String tenantName: dedupTenants) { TenantV7 entry = new TenantV7(); entry.setDescription("Migrated from v6"); t7.putCEntry(tenantName, entry); } - + return new Tuple, SecurityDynamicConfiguration>(r7, t7); - + } - + public static SecurityDynamicConfiguration migrateConfig(SecurityDynamicConfiguration r6cs) throws MigrationException { final SecurityDynamicConfiguration c7 = SecurityDynamicConfiguration.empty(); c7.setCType(r6cs.getCType()); c7.set_meta(new Meta()); c7.get_meta().setConfig_version(2); c7.get_meta().setType("config"); - + if(r6cs.getCEntries().size() != 1) { throw new MigrationException("Unable to migrate config because expected size was 1 but actual size is "+r6cs.getCEntries().size()); } - + if(r6cs.getCEntries().get("opendistro_security") == null) { throw new MigrationException("Unable to migrate config because 'opendistro_security' key not found"); } - + for(final Entry r6c: r6cs.getCEntries().entrySet()) { c7.putCEntry("config", new ConfigV7(r6c.getValue())); } @@ -181,23 +181,23 @@ public static SecurityDynamicConfiguration migrateInternalUsers i7.set_meta(new Meta()); i7.get_meta().setConfig_version(2); i7.get_meta().setType("internalusers"); - + for(final Entry r6i: r6is.getCEntries().entrySet()) { final String username = !Strings.isNullOrEmpty(r6i.getValue().getUsername())?r6i.getValue().getUsername():r6i.getKey(); i7.putCEntry(username, new InternalUserV7(r6i.getValue())); } - + return i7; } - + public static SecurityDynamicConfiguration migrateActionGroups(SecurityDynamicConfiguration r6as) throws MigrationException { - + final SecurityDynamicConfiguration a7 = SecurityDynamicConfiguration.empty(); a7.setCType(r6as.getCType()); a7.set_meta(new Meta()); a7.get_meta().setConfig_version(2); a7.get_meta().setType("actiongroups"); - + if(r6as.getImplementingClass().isAssignableFrom(List.class)) { for(final Entry r6a: r6as.getCEntries().entrySet()) { a7.putCEntry(r6a.getKey(), new ActionGroupsV7(r6a.getKey(), (List) r6a.getValue())); @@ -210,18 +210,18 @@ public static SecurityDynamicConfiguration migrateActionGroups( return a7; } - + public static SecurityDynamicConfiguration migrateRoleMappings(SecurityDynamicConfiguration r6rms) throws MigrationException { final SecurityDynamicConfiguration rms7 = SecurityDynamicConfiguration.empty(); rms7.setCType(r6rms.getCType()); rms7.set_meta(new Meta()); rms7.get_meta().setConfig_version(2); rms7.get_meta().setType("rolesmapping"); - + for(final Entry r6m: r6rms.getCEntries().entrySet()) { rms7.putCEntry(r6m.getKey(), new RoleMappingsV7(r6m.getValue())); } - + return rms7; } diff --git a/src/main/java/org/opensearch/security/securityconf/StaticDefinable.java b/src/main/java/org/opensearch/security/securityconf/StaticDefinable.java index d6ffb106cf..06b92100fa 100644 --- a/src/main/java/org/opensearch/security/securityconf/StaticDefinable.java +++ b/src/main/java/org/opensearch/security/securityconf/StaticDefinable.java @@ -28,7 +28,7 @@ package org.opensearch.security.securityconf; public interface StaticDefinable { - + boolean isStatic(); } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/Meta.java b/src/main/java/org/opensearch/security/securityconf/impl/Meta.java index 42912c1dda..1e9060efa1 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/Meta.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/Meta.java @@ -30,17 +30,17 @@ import com.fasterxml.jackson.annotation.JsonIgnore; public class Meta { - - + + private String type; private int config_version; - + private CType cType; - + public String getType() { return type; } - + public void setType(String type) { this.type = type; cType = CType.fromString(type); @@ -51,7 +51,7 @@ public int getConfig_version() { public void setConfig_version(int config_version) { this.config_version = config_version; } - + @JsonIgnore public CType getCType() { return cType; @@ -61,6 +61,6 @@ public CType getCType() { public String toString() { return "Meta [type=" + type + ", config_version=" + config_version + ", cType=" + cType + "]"; } - - + + } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java b/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java index 09eeee41e3..c282f439e8 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java @@ -52,7 +52,7 @@ import org.opensearch.security.securityconf.StaticDefinable; public class SecurityDynamicConfiguration implements ToXContent { - + private static final TypeReference> typeRefMSO = new TypeReference>() {}; @JsonIgnore @@ -61,7 +61,7 @@ public class SecurityDynamicConfiguration implements ToXContent { private long primaryTerm= -1; private CType ctype; private int version = -1; - + public static SecurityDynamicConfiguration empty() { return new SecurityDynamicConfiguration(); } @@ -83,11 +83,11 @@ public static SecurityDynamicConfiguration fromJson(String json, CType ct sdc = DefaultObjectMapper.readValue(json, DefaultObjectMapper.getTypeFactory().constructParametricType(SecurityDynamicConfiguration.class, implementationClass)); } validate(sdc, version, ctype); - + } else { sdc = new SecurityDynamicConfiguration(); } - + sdc.ctype = ctype; sdc.seqNo = seqNo; sdc.primaryTerm = primaryTerm; @@ -95,35 +95,35 @@ public static SecurityDynamicConfiguration fromJson(String json, CType ct return sdc; } - + public static void validate(SecurityDynamicConfiguration sdc, int version, CType ctype) throws IOException { if(version < 2 && sdc.get_meta() != null) { throw new IOException("A version of "+version+" can not have a _meta key for "+ctype); } - + if(version >= 2 && sdc.get_meta() == null) { throw new IOException("A version of "+version+" must have a _meta key for "+ctype); } - + if(version < 2 && ctype == CType.CONFIG && (sdc.getCEntries().size() != 1 || !sdc.getCEntries().keySet().contains("opendistro_security"))) { throw new IOException("A version of "+version+" must have a single toplevel key named 'opendistro_security' for "+ctype); } - + if(version >= 2 && ctype == CType.CONFIG && (sdc.getCEntries().size() != 1 || !sdc.getCEntries().keySet().contains("config"))) { throw new IOException("A version of "+version+" must have a single toplevel key named 'config' for "+ctype); } - + } public static SecurityDynamicConfiguration fromNode(JsonNode json, CType ctype, int version, long seqNo, long primaryTerm) throws IOException { return fromJson(DefaultObjectMapper.writeValueAsString(json, false), ctype, version, seqNo, primaryTerm); } - + //for Jackson private SecurityDynamicConfiguration() { super(); } - + private Meta _meta; public Meta get_meta() { @@ -134,17 +134,17 @@ public void set_meta(Meta _meta) { this._meta = _meta; } - + @JsonAnySetter void setCEntries(String key, T value) { putCEntry(key, value); } - + @JsonAnyGetter public Map getCEntries() { return centries; } - + @JsonIgnore public void removeHidden() { for(Entry entry: new HashMap(centries).entrySet()) { @@ -153,7 +153,7 @@ public void removeHidden() { } } } - + @JsonIgnore public void removeStatic() { for(Entry entry: new HashMap(centries).entrySet()) { @@ -162,38 +162,38 @@ public void removeStatic() { } } } - + @JsonIgnore public void clearHashes() { for(Entry entry: centries.entrySet()) { if(entry.getValue() instanceof Hashed) { - ((Hashed) entry.getValue()).clearHash(); + ((Hashed) entry.getValue()).clearHash(); } } } - + public void removeOthers(String key) { T tmp = this.centries.get(key); this.centries.clear(); this.centries.put(key, tmp); } - + @JsonIgnore public T putCEntry(String key, T value) { return centries.put(key, value); } - + @JsonIgnore public void putCObject(String key, Object value) { centries.put(key, (T) value); } - + @JsonIgnore public T getCEntry(String key) { return centries.get(key); } - + @JsonIgnore public boolean exists(String key) { return centries.containsKey(key); @@ -216,7 +216,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws final boolean omitDefaults = params != null && params.paramAsBoolean("omit_defaults", false); return builder.map(DefaultObjectMapper.readValue(DefaultObjectMapper.writeValueAsString(this, omitDefaults), typeRefMSO)); } - + @Override @JsonIgnore public boolean isFragment() { @@ -237,7 +237,7 @@ public long getPrimaryTerm() { public CType getCType() { return ctype; } - + @JsonIgnore public void setCType(CType ctype) { this.ctype = ctype; @@ -247,7 +247,7 @@ public void setCType(CType ctype) { public int getVersion() { return version; } - + @JsonIgnore public Class getImplementingClass() { return ctype==null?null:ctype.getImplementationClass().get(getVersion()); @@ -265,7 +265,7 @@ public SecurityDynamicConfiguration deepClone() { @JsonIgnore public void remove(String key) { centries.remove(key); - + } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -273,19 +273,19 @@ public boolean add(SecurityDynamicConfiguration other) { if(other.ctype == null || !other.ctype.equals(this.ctype)) { return false; } - + if(other.getImplementingClass() == null || !other.getImplementingClass().equals(this.getImplementingClass())) { return false; } - + if(other.version != this.version) { return false; } - + this.centries.putAll(other.centries); return true; } - + @JsonIgnore @SuppressWarnings({ "rawtypes" }) public boolean containsAny(SecurityDynamicConfiguration other) { diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/ActionGroupsV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/ActionGroupsV6.java index b64becf0a4..99bef31505 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/ActionGroupsV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/ActionGroupsV6.java @@ -35,7 +35,7 @@ public class ActionGroupsV6 implements Hideable { - + private boolean readonly; private boolean hidden; private List permissions = Collections.emptyList(); @@ -43,7 +43,7 @@ public class ActionGroupsV6 implements Hideable { public ActionGroupsV6() { super(); } - + @JsonIgnore public boolean isReserved() { return readonly; @@ -70,6 +70,6 @@ public void setPermissions(List permissions) { public String toString() { return "ActionGroups [readonly=" + readonly + ", hidden=" + hidden + ", permissions=" + permissions + "]"; } - - + + } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java index 6599942d34..7f90e386a8 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java @@ -46,8 +46,8 @@ public class ConfigV6 { public Dynamic dynamic; - - + + @Override public String toString() { return "Config [dynamic=" + dynamic + "]"; @@ -71,7 +71,7 @@ public static class Dynamic { public String hosts_resolver_mode = "ip-only"; public String transport_userrname_attribute; public boolean do_not_fail_on_forbidden_empty; - + @Override public String toString() { return "Dynamic [filtered_alias_mode=" + filtered_alias_mode + ", kibana=" + kibana + ", http=" + http + ", authc=" + authc + ", authz=" @@ -96,9 +96,9 @@ public String toString() { return "Kibana [multitenancy_enabled=" + multitenancy_enabled + ", server_username=" + server_username + ", opendistro_role=" + opendistro_role + ", index=" + index + ", do_not_fail_on_forbidden=" + do_not_fail_on_forbidden + "]"; } - - - + + + } public static class Http { @@ -108,8 +108,8 @@ public static class Http { public String toString() { return "Http [anonymous_auth_enabled=" + anonymous_auth_enabled + ", xff=" + xff + "]"; } - - + + } public static class AuthFailureListeners { @@ -126,7 +126,7 @@ public Map getListeners() { return listeners; } - + } public static class AuthFailureListener { @@ -137,11 +137,11 @@ public static class AuthFailureListener { public int block_expiry_seconds = 60 * 10; public int max_blocked_clients = 100_000; public int max_tracked_clients = 100_000; - + public AuthFailureListener() { super(); } - + @JsonIgnore public String asJson() { try { @@ -171,12 +171,12 @@ public String toString() { return "Xff [enabled=" + enabled + ", internalProxies=" + internalProxies + ", remoteIpHeader=" + remoteIpHeader + ", proxiesHeader=" + proxiesHeader + ", trustedProxies=" + trustedProxies + "]"; } - - + + } public static class Authc { - + @JsonIgnore private final Map domains = new HashMap<>(); @@ -194,8 +194,8 @@ public Map getDomains() { public String toString() { return "Authc [domains=" + domains + "]"; } - - + + } public static class AuthcDomain { @@ -213,8 +213,8 @@ public String toString() { return "AuthcDomain [http_enabled=" + http_enabled + ", transport_enabled=" + transport_enabled + ", enabled=" + enabled + ", order=" + order + ", http_authenticator=" + http_authenticator + ", authentication_backend=" + authentication_backend + "]"; } - - + + } public static class HttpAuthenticator { @@ -222,7 +222,7 @@ public static class HttpAuthenticator { public boolean challenge = true; public String type; public Map config = Collections.emptyMap(); - + @JsonIgnore public String configAsJson() { try { @@ -236,14 +236,14 @@ public String configAsJson() { public String toString() { return "HttpAuthenticator [challenge=" + challenge + ", type=" + type + ", config=" + config + "]"; } - - + + } public static class AuthzBackend { public String type = "noop"; public Map config = Collections.emptyMap(); - + @JsonIgnore public String configAsJson() { try { @@ -257,14 +257,14 @@ public String configAsJson() { public String toString() { return "AuthzBackend [type=" + type + ", config=" + config + "]"; } - - + + } public static class AuthcBackend { public String type = InternalAuthenticationBackend.class.getName(); public Map config = Collections.emptyMap(); - + @JsonIgnore public String configAsJson() { try { @@ -278,8 +278,8 @@ public String configAsJson() { public String toString() { return "AuthcBackend [type=" + type + ", config=" + config + "]"; } - - + + } public static class Authz { @@ -300,8 +300,8 @@ public Map getDomains() { public String toString() { return "Authz [domains=" + domains + "]"; } - - + + } public static class AuthzDomain { @@ -316,8 +316,8 @@ public static class AuthzDomain { public String toString() { return "AuthzDomain [http_enabled=" + http_enabled + ", transport_enabled=" + transport_enabled + ", enabled=" + enabled + ", authorization_backend=" + authorization_backend + "]"; } - - + + } - + } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/InternalUserV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/InternalUserV6.java index 827e573a3a..f650101b64 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/InternalUserV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/InternalUserV6.java @@ -37,7 +37,7 @@ import org.opensearch.security.securityconf.Hideable; public class InternalUserV6 implements Hideable, Hashed { - + private String hash; private boolean readonly; private boolean hidden; @@ -45,7 +45,7 @@ public class InternalUserV6 implements Hideable, Hashed { private Map attributes = Collections.emptyMap(); private String username; - + public InternalUserV6(String hash, boolean readonly, boolean hidden, List roles, Map attributes, String username) { super(); @@ -69,7 +69,7 @@ public InternalUserV6() { super(); //default constructor } - + public String getHash() { return hash; } @@ -80,7 +80,7 @@ public void setHash(String hash) { public void setPassword(String password){ // no-op setter. Due to a bug in 6.x, empty "password" may be saved to the internalusers doc. Ignore it. } - + public boolean isReadonly() { return readonly; } @@ -111,7 +111,7 @@ public String toString() { return "SgInternalUser [hash=" + hash + ", readonly=" + readonly + ", hidden=" + hidden + ", roles=" + roles + ", attributes=" + attributes + "]"; } - + @JsonIgnore public boolean isReserved() { return readonly; @@ -122,7 +122,7 @@ public boolean isReserved() { public void clearHash() { hash = ""; } - - + + } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleMappingsV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleMappingsV6.java index 1c28f99113..bb10fd8812 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleMappingsV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleMappingsV6.java @@ -82,7 +82,7 @@ public String toString() { return "RoleMappings [readonly=" + readonly + ", hidden=" + hidden + ", backendroles=" + backendroles + ", hosts=" + getHosts() + ", users=" + getUsers() + ", andBackendroles=" + andBackendroles + "]"; } - + @JsonIgnore public boolean isReserved() { return readonly; diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleV6.java index f0377f797b..e8254d4530 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleV6.java @@ -60,13 +60,13 @@ void setTypes0(String key, List value) { public Map> getTypes() { return types; } - + private String _dls_; private List _fls_; private List _masked_fields_; - - + + public String get_dls_() { return _dls_; } @@ -84,7 +84,7 @@ public String toString() { return "Index [types=" + types + ", _dls_=" + _dls_ + ", _fls_=" + _fls_ + ", _masked_fields_=" + _masked_fields_ + "]"; } - + } @@ -132,7 +132,7 @@ public void setIndices(Map indices) { public String toString() { return "Role [readonly=" + readonly + ", hidden=" + hidden + ", cluster=" + cluster + ", tenants=" + tenants + ", indices=" + indices + "]"; } - + @JsonIgnore public boolean isReserved() { return readonly; diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/ActionGroupsV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/ActionGroupsV7.java index d728dfb8cf..f38978eec2 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/ActionGroupsV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/ActionGroupsV7.java @@ -38,8 +38,8 @@ public class ActionGroupsV7 implements Hideable, StaticDefinable { - - + + private boolean reserved; private boolean hidden; @JsonProperty(value = "static") @@ -47,7 +47,7 @@ public class ActionGroupsV7 implements Hideable, StaticDefinable { private List allowed_actions = Collections.emptyList(); private String type; private String description; - + public ActionGroupsV7() { super(); } @@ -76,8 +76,8 @@ public String getDescription() { public void setDescription(String description) { this.description = description; } - - + + public boolean isReserved() { return reserved; } @@ -109,8 +109,8 @@ public String toString() { return "ActionGroupsV7 [reserved=" + reserved + ", hidden=" + hidden + ", _static=" + _static + ", allowed_actions=" + allowed_actions + ", type=" + type + ", description=" + description + "]"; } - - - - + + + + } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java index 3029b42abf..337dec8e31 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java @@ -53,7 +53,7 @@ public ConfigV7() { public ConfigV7(ConfigV6 c6) { dynamic = new Dynamic(); - + dynamic.filtered_alias_mode = c6.dynamic.filtered_alias_mode; dynamic.disable_rest_auth = c6.dynamic.disable_rest_auth; dynamic.disable_intertransport_auth = c6.dynamic.disable_intertransport_auth; @@ -64,40 +64,40 @@ public ConfigV7(ConfigV6 c6) { dynamic.multi_rolespan_enabled = c6.dynamic.multi_rolespan_enabled; dynamic.hosts_resolver_mode = c6.dynamic.hosts_resolver_mode; dynamic.transport_userrname_attribute = c6.dynamic.transport_userrname_attribute; - + dynamic.kibana = new Kibana(); - + dynamic.kibana.index = c6.dynamic.kibana.index; dynamic.kibana.multitenancy_enabled = c6.dynamic.kibana.multitenancy_enabled; dynamic.kibana.private_tenant_enabled = true; dynamic.kibana.default_tenant = ""; dynamic.kibana.server_username = c6.dynamic.kibana.server_username; - + dynamic.http = new Http(); - + dynamic.http.anonymous_auth_enabled = c6.dynamic.http.anonymous_auth_enabled; - + dynamic.http.xff = new Xff(); - + dynamic.http.xff.enabled = c6.dynamic.http.xff.enabled; dynamic.http.xff.internalProxies = c6.dynamic.http.xff.internalProxies; dynamic.http.xff.remoteIpHeader = c6.dynamic.http.xff.remoteIpHeader; - + dynamic.authc = new Authc(); - + dynamic.authc.domains.putAll(c6.dynamic.authc.getDomains().entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey(), + entry -> entry.getKey(), entry -> new AuthcDomain(entry.getValue())))); - + dynamic.authz = new Authz(); - + dynamic.authz.domains.putAll(c6.dynamic.authz.getDomains().entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey(), + entry -> entry.getKey(), entry -> new AuthzDomain(entry.getValue())))); - + dynamic.auth_failure_listeners = new AuthFailureListeners(); dynamic.auth_failure_listeners.listeners.putAll(c6.dynamic.auth_failure_listeners.getListeners().entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey(), + entry -> entry.getKey(), entry -> new AuthFailureListener(entry.getValue())))); } @@ -125,7 +125,7 @@ public static class Dynamic { public String hosts_resolver_mode = "ip-only"; public String transport_userrname_attribute; public boolean do_not_fail_on_forbidden_empty; - + @Override public String toString() { return "Dynamic [filtered_alias_mode=" + filtered_alias_mode + ", kibana=" + kibana + ", http=" + http + ", authc=" + authc + ", authz=" @@ -151,9 +151,9 @@ public String toString() { server_username + ", opendistro_role=" + opendistro_role + ", index=" + index + "]"; } - - - + + + } public static class Http { @@ -163,8 +163,8 @@ public static class Http { public String toString() { return "Http [anonymous_auth_enabled=" + anonymous_auth_enabled + ", xff=" + xff + "]"; } - - + + } public static class AuthFailureListeners { @@ -181,7 +181,7 @@ public Map getListeners() { return listeners; } - + } public static class AuthFailureListener { @@ -192,9 +192,9 @@ public static class AuthFailureListener { public int block_expiry_seconds = 60 * 10; public int max_blocked_clients = 100_000; public int max_tracked_clients = 100_000; - - - + + + public AuthFailureListener() { super(); } @@ -209,7 +209,7 @@ public AuthFailureListener(ConfigV6.AuthFailureListener v6) { this.max_blocked_clients = v6.max_blocked_clients; this.max_tracked_clients = v6.max_tracked_clients; } - + @JsonIgnore public String asJson() { try { @@ -235,12 +235,12 @@ public static class Xff { public String toString() { return "Xff [enabled=" + enabled + ", internalProxies=" + internalProxies + ", remoteIpHeader=" + remoteIpHeader+"]"; } - - + + } public static class Authc { - + @JsonIgnore private final Map domains = new HashMap<>(); @@ -258,9 +258,9 @@ public Map getDomains() { public String toString() { return "Authc [domains=" + domains + "]"; } - - - + + + } public static class AuthcDomain { @@ -274,11 +274,11 @@ public static class AuthcDomain { public HttpAuthenticator http_authenticator = new HttpAuthenticator(); public AuthcBackend authentication_backend = new AuthcBackend(); public String description; - + public AuthcDomain() { super(); } - + public AuthcDomain(ConfigV6.AuthcDomain v6) { super(); http_enabled = v6.http_enabled && v6.enabled; @@ -299,8 +299,8 @@ public String toString() { + ", http_authenticator=" + http_authenticator + ", authentication_backend=" + authentication_backend + ", description=" + description + "]"; } - - + + } public static class HttpAuthenticator { @@ -308,7 +308,7 @@ public static class HttpAuthenticator { public boolean challenge = true; public String type; public Map config = Collections.emptyMap(); - + public HttpAuthenticator() { super(); } @@ -335,16 +335,16 @@ public String configAsJson() { public String toString() { return "HttpAuthenticator [challenge=" + challenge + ", type=" + type + ", config=" + config + "]"; } - - + + } public static class AuthzBackend { public String type = "noop"; public Map config = Collections.emptyMap(); - - - + + + public AuthzBackend() { super(); } @@ -373,16 +373,16 @@ public String configAsJson() { public String toString() { return "AuthzBackend [type=" + type + ", config=" + config + "]"; } - - + + } public static class AuthcBackend { public String type = InternalAuthenticationBackend.class.getName(); public Map config = Collections.emptyMap(); - - - + + + public AuthcBackend() { super(); } @@ -411,8 +411,8 @@ public String configAsJson() { public String toString() { return "AuthcBackend [type=" + type + ", config=" + config + "]"; } - - + + } public static class Authz { @@ -433,8 +433,8 @@ public Map getDomains() { public String toString() { return "Authz [domains=" + domains + "]"; } - - + + } public static class AuthzDomain { @@ -444,11 +444,11 @@ public static class AuthzDomain { public boolean transport_enabled = true; public AuthzBackend authorization_backend = new AuthzBackend(); public String description; - + public AuthzDomain() { super(); } - + public AuthzDomain(ConfigV6.AuthzDomain v6) { http_enabled = v6.http_enabled && v6.enabled; transport_enabled = v6.transport_enabled && v6.enabled; @@ -461,8 +461,8 @@ public String toString() { return "AuthzDomain [http_enabled=" + http_enabled + ", transport_enabled=" + transport_enabled + ", authorization_backend=" + authorization_backend + ", description=" + description + "]"; } - - + + } - + } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java index b697a9485b..a7aed05cc1 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java @@ -40,7 +40,7 @@ import org.opensearch.security.securityconf.impl.v6.InternalUserV6; public class InternalUserV7 implements Hideable, Hashed, StaticDefinable { - + private String hash; private boolean reserved; private boolean hidden; @@ -79,7 +79,7 @@ public InternalUserV7() { super(); //default constructor } - + public InternalUserV7(InternalUserV6 u6) { hash = u6.getHash(); reserved = u6.isReserved(); @@ -95,15 +95,15 @@ public String getHash() { public void setHash(String hash) { this.hash = hash; } - - + + public boolean isHidden() { return hidden; } public void setHidden(boolean hidden) { this.hidden = hidden; } - + public List getBackend_roles() { return backend_roles; @@ -171,7 +171,7 @@ public boolean isReserved() { public void setReserved(boolean reserved) { this.reserved = reserved; } - + @JsonProperty(value = "static") public boolean isStatic() { return _static; @@ -180,6 +180,6 @@ public boolean isStatic() { public void setStatic(boolean _static) { this._static = _static; } - - + + } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleMappingsV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleMappingsV7.java index c97ad2f92f..364d524497 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleMappingsV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleMappingsV7.java @@ -124,6 +124,6 @@ public String toString() { } - + } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleV7.java index 262f8ed21d..dce4652d63 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleV7.java @@ -51,11 +51,11 @@ public class RoleV7 implements Hideable, StaticDefinable { private List cluster_permissions = Collections.emptyList(); private List index_permissions = Collections.emptyList(); private List tenant_permissions = Collections.emptyList(); - + public RoleV7() { - + } - + public RoleV7(RoleV6 roleV6) { this.reserved = roleV6.isReserved(); this.hidden = roleV6.isHidden(); @@ -63,24 +63,24 @@ public RoleV7(RoleV6 roleV6) { this.cluster_permissions = roleV6.getCluster(); index_permissions = new ArrayList<>(); tenant_permissions = new ArrayList<>(); - + for(Entry v6i: roleV6.getIndices().entrySet()) { index_permissions.add(new Index(v6i.getKey(), v6i.getValue())); } - + //rw tenants List rwTenants = roleV6.getTenants().entrySet().stream().filter(e-> "rw".equalsIgnoreCase(e.getValue())).map(e->e.getKey()).collect(Collectors.toList()); - + if(rwTenants != null && !rwTenants.isEmpty()) { Tenant t = new Tenant(); t.setAllowed_actions(Collections.singletonList("kibana_all_write")); t.setTenant_patterns(rwTenants); tenant_permissions.add(t); } - - + + List roTenants = roleV6.getTenants().entrySet().stream().filter(e-> "ro".equalsIgnoreCase(e.getValue())).map(e->e.getKey()).collect(Collectors.toList()); - + if(roTenants != null && !roTenants.isEmpty()) { Tenant t = new Tenant(); t.setAllowed_actions(Collections.singletonList("kibana_all_read")); @@ -97,25 +97,25 @@ public static class Index { private List fls = Collections.emptyList(); private List masked_fields = Collections.emptyList(); private List allowed_actions = Collections.emptyList(); - + public Index(String pattern, RoleV6.Index v6Index) { super(); index_patterns = Collections.singletonList(pattern); dls = v6Index.get_dls_(); fls = v6Index.get_fls_(); masked_fields = v6Index.get_masked_fields_(); - Set tmpActions = new HashSet<>(); + Set tmpActions = new HashSet<>(); for(Entry> type: v6Index.getTypes().entrySet()) { tmpActions.addAll(type.getValue()); } allowed_actions = new ArrayList<>(tmpActions); } - - + + public Index() { super(); } - + public List getIndex_patterns() { return index_patterns; } @@ -152,27 +152,27 @@ public String toString() { + ", allowed_actions=" + allowed_actions + "]"; } } - - + + public static class Tenant { private List tenant_patterns = Collections.emptyList(); private List allowed_actions = Collections.emptyList(); - + /*public Index(String pattern, RoleV6.Index v6Index) { super(); index_patterns = Collections.singletonList(pattern); dls = v6Index.get_dls_(); fls = v6Index.get_fls_(); masked_fields = v6Index.get_masked_fields_(); - Set tmpActions = new HashSet<>(); + Set tmpActions = new HashSet<>(); for(Entry> type: v6Index.getTypes().entrySet()) { tmpActions.addAll(type.getValue()); } allowed_actions = new ArrayList<>(tmpActions); }*/ - - + + public Tenant() { super(); } @@ -197,10 +197,10 @@ public void setAllowed_actions(List allowed_actions) { public String toString() { return "Tenant [tenant_patterns=" + tenant_patterns + ", allowed_actions=" + allowed_actions + "]"; } - - + + } - + public boolean isHidden() { return hidden; @@ -226,7 +226,7 @@ public void setCluster_permissions(List cluster_permissions) { this.cluster_permissions = cluster_permissions; } - + public List getIndex_permissions() { return index_permissions; @@ -267,9 +267,9 @@ public String toString() { + ", cluster_permissions=" + cluster_permissions + ", index_permissions=" + index_permissions + ", tenant_permissions=" + tenant_permissions + "]"; } - - - + + + } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/TenantV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/TenantV7.java index 210fada185..1c55d0cd95 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/TenantV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/TenantV7.java @@ -39,7 +39,7 @@ public class TenantV7 implements Hideable, StaticDefinable { @JsonProperty(value = "static") private boolean _static; private String description; - + public boolean isHidden() { return hidden; } @@ -58,8 +58,8 @@ public boolean isReserved() { public void setReserved(boolean reserved) { this.reserved = reserved; } - - + + @JsonProperty(value = "static") public boolean isStatic() { return _static; @@ -72,6 +72,6 @@ public void setStatic(boolean _static) { public String toString() { return "TenantV7 [reserved=" + reserved + ", hidden=" + hidden + ", _static=" + _static + ", description=" + description + "]"; } - - + + } diff --git a/src/main/java/org/opensearch/security/ssl/ExternalSecurityKeyStore.java b/src/main/java/org/opensearch/security/ssl/ExternalSecurityKeyStore.java index 3fbbab69d6..87713a1d3a 100644 --- a/src/main/java/org/opensearch/security/ssl/ExternalSecurityKeyStore.java +++ b/src/main/java/org/opensearch/security/ssl/ExternalSecurityKeyStore.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl; @@ -45,13 +45,13 @@ public ExternalSecurityKeyStore(final Settings settings) { this.settings = Objects.requireNonNull(settings); final String externalContextId = settings .get(SSLConfigConstants.SECURITY_SSL_CLIENT_EXTERNAL_CONTEXT_ID, null); - + if(externalContextId == null || externalContextId.length() == 0) { throw new OpenSearchException("no external ssl context id was set"); } - + externalSslContext = contextMap.get(externalContextId); - + if(externalSslContext == null) { throw new OpenSearchException("no external ssl context for id "+externalContextId); } @@ -70,7 +70,7 @@ public SSLEngine createServerTransportSSLEngine() throws SSLException { @Override public SSLEngine createClientTransportSSLEngine(final String peerHost, final int peerPort) throws SSLException { if (peerHost != null) { - final SSLEngine engine = externalSslContext.createSSLEngine(peerHost, peerPort); + final SSLEngine engine = externalSslContext.createSSLEngine(peerHost, peerPort); final SSLParameters sslParams = new SSLParameters(); sslParams.setEndpointIdentificationAlgorithm("HTTPS"); engine.setSSLParameters(sslParams); @@ -135,31 +135,31 @@ public String getSubjectAlternativeNames(X509Certificate cert) { public static void registerExternalSslContext(String id, SSLContext externalSsslContext) { contextMap.put(Objects.requireNonNull(id), Objects.requireNonNull(externalSsslContext)); } - + public static boolean hasExternalSslContext(Settings settings) { - + final String externalContextId = settings .get(SSLConfigConstants.SECURITY_SSL_CLIENT_EXTERNAL_CONTEXT_ID, null); - + if(externalContextId == null || externalContextId.length() == 0) { return false; } - + return contextMap.containsKey(externalContextId); } - + public static boolean hasExternalSslContext(String id) { return contextMap.containsKey(id); } - + public static void removeExternalSslContext(String id) { contextMap.remove(id); } - + public static void removeAllExternalSslContexts() { contextMap.clear(); } - + private String[] evalSecure(String[] engineEnabled, String[] secure) { List tmp = new ArrayList<>(Arrays.asList(engineEnabled)); tmp.retainAll(Arrays.asList(secure)); diff --git a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java index 755a2b188f..ab96c7bb3e 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl; @@ -122,7 +122,7 @@ protected OpenSearchSecuritySSLPlugin(final Settings settings, final Path config this.sks = null; this.configPath = null; SSLConfig = new SSLConfig(false, false); - + AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { @@ -130,36 +130,36 @@ public Object run() { return null; } }); - - + + return; } SSLConfig = new SSLConfig(settings); this.configPath = configPath; - + if(this.configPath != null) { log.info("OpenSearch Config path is {}", this.configPath.toAbsolutePath()); } else { log.info("OpenSearch Config path is not set"); } - + final boolean allowClientInitiatedRenegotiation = settings.getAsBoolean(SSLConfigConstants.SECURITY_SSL_ALLOW_CLIENT_INITIATED_RENEGOTIATION, false); final boolean rejectClientInitiatedRenegotiation = Boolean.parseBoolean(System.getProperty(SSLConfigConstants.JDK_TLS_REJECT_CLIENT_INITIATED_RENEGOTIATION)); - + if(allowClientInitiatedRenegotiation && !rejectClientInitiatedRenegotiation) { final String renegoMsg = "Client side initiated TLS renegotiation enabled. This can open a vulnerablity for DoS attacks through client side initiated TLS renegotiation."; log.warn(renegoMsg); System.out.println(renegoMsg); System.err.println(renegoMsg); - } else { + } else { if(!rejectClientInitiatedRenegotiation) { - + final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new SpecialPermission()); } - + AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { @@ -198,7 +198,7 @@ public Object run() { NonValidatingObjectMapper.inject(injectableValues); client = !"node".equals(this.settings.get(OpenSearchSecuritySSLPlugin.CLIENT_TYPE)); - + httpSSLEnabled = settings.getAsBoolean(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_DEFAULT); transportSSLEnabled = settings.getAsBoolean(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, @@ -211,7 +211,7 @@ public Object run() { System.out.println("SSL not activated for http and/or transport."); System.err.println("SSL not activated for http and/or transport."); } - + if(ExternalSecurityKeyStore.hasExternalSslContext(settings)) { this.sks = new ExternalSecurityKeyStore(settings); } else { @@ -223,9 +223,9 @@ public Object run() { public Map> getHttpTransports(Settings settings, ThreadPool threadPool, BigArrays bigArrays, PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, NetworkService networkService, Dispatcher dispatcher, ClusterSettings clusterSettings) { - + if (!client && httpSSLEnabled) { - + final ValidatingDispatcher validatingDispatcher = new ValidatingDispatcher(threadPool.getThreadContext(), dispatcher, settings, configPath, NOOP_SSL_EXCEPTION_HANDLER); final SecuritySSLNettyHttpServerTransport sgsnht = new SecuritySSLNettyHttpServerTransport(settings, networkService, bigArrays, threadPool, @@ -233,7 +233,7 @@ public Map> getHttpTransports(Settings set sharedGroupFactory); return Collections.singletonMap("org.opensearch.security.ssl.http.netty.SecuritySSLNettyHttpServerTransport", () -> sgsnht); - + } return Collections.emptyMap(); @@ -243,35 +243,35 @@ public Map> getHttpTransports(Settings set public List getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings, IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter, IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster) { - + final List handlers = new ArrayList(1); - + if (!client) { handlers.add(new SecuritySSLInfoAction(settings, configPath, restController, sks, Objects.requireNonNull(principalExtractor))); } - + return handlers; } - - - + + + @Override public List getTransportInterceptors(NamedWriteableRegistry namedWriteableRegistry, ThreadContext threadContext) { List interceptors = new ArrayList(1); - + if(transportSSLEnabled && !client) { interceptors.add(new SecuritySSLTransportInterceptor(settings, null, null, SSLConfig, NOOP_SSL_EXCEPTION_HANDLER)); } - + return interceptors; } - - + + @Override public Map> getTransports(Settings settings, ThreadPool threadPool, PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedWriteableRegistry namedWriteableRegistry, NetworkService networkService) { - + Map> transports = new HashMap>(); if (transportSSLEnabled) { transports.put("org.opensearch.security.ssl.http.netty.SecuritySSLNettyTransport", @@ -290,11 +290,11 @@ public Collection createComponents(Client localClient, ClusterService cl IndexNameExpressionResolver indexNameExpressionResolver, Supplier repositoriesServiceSupplier) { final List components = new ArrayList<>(1); - + if(client) { return components; } - + final String principalExtractorClass = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PRINCIPAL_EXTRACTOR_CLASS, null); if(principalExtractorClass == null) { @@ -309,9 +309,9 @@ public Collection createComponents(Client localClient, ClusterService cl throw new OpenSearchException(e); } } - + components.add(principalExtractor); - + return components; } @@ -389,24 +389,24 @@ public List> getSettings() { @Override public Settings additionalSettings() { final Settings.Builder builder = Settings.builder(); - + if(!client && httpSSLEnabled) { - + if(settings.get("http.compression") == null) { builder.put("http.compression", false); log.info("Disabled https compression by default to mitigate BREACH attacks. You can enable it by setting 'http.compression: true' in opensearch.yml"); } - + builder.put(NetworkModule.HTTP_TYPE_KEY, "org.opensearch.security.ssl.http.netty.SecuritySSLNettyHttpServerTransport"); } - + if (transportSSLEnabled) { builder.put(NetworkModule.TRANSPORT_TYPE_KEY, "org.opensearch.security.ssl.http.netty.SecuritySSLNettyTransport"); } - + return builder.build(); } - + @Override public List getSettingsFilter() { List settingsFilter = new ArrayList<>(); diff --git a/src/main/java/org/opensearch/security/ssl/SecurityKeyStore.java b/src/main/java/org/opensearch/security/ssl/SecurityKeyStore.java index a14e8e7df1..107c48f0f5 100644 --- a/src/main/java/org/opensearch/security/ssl/SecurityKeyStore.java +++ b/src/main/java/org/opensearch/security/ssl/SecurityKeyStore.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl; @@ -33,7 +33,7 @@ public interface SecurityKeyStore { public String getHTTPProviderName(); public String getTransportServerProviderName(); public String getTransportClientProviderName(); - public String getSubjectAlternativeNames(X509Certificate cert); + public String getSubjectAlternativeNames(X509Certificate cert); public void initHttpSSLConfig(); public void initTransportSSLConfig(); diff --git a/src/main/java/org/opensearch/security/ssl/SslExceptionHandler.java b/src/main/java/org/opensearch/security/ssl/SslExceptionHandler.java index 531711dc54..427debfdba 100644 --- a/src/main/java/org/opensearch/security/ssl/SslExceptionHandler.java +++ b/src/main/java/org/opensearch/security/ssl/SslExceptionHandler.java @@ -1,10 +1,10 @@ /* * Copyright 2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl; @@ -22,15 +22,15 @@ import org.opensearch.transport.TransportRequest; public interface SslExceptionHandler { - + default void logError(Throwable t, RestRequest request, int type) { //no-op } - + default void logError(Throwable t, boolean isRest) { //no-op } - + default void logError(Throwable t, final TransportRequest request, String action, Task task, int type) { //no-op } diff --git a/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java b/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java index 7c53b6268a..b6012f036e 100644 --- a/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java +++ b/src/main/java/org/opensearch/security/ssl/http/netty/SecuritySSLNettyHttpServerTransport.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl.http.netty; @@ -45,7 +45,7 @@ public class SecuritySSLNettyHttpServerTransport extends Netty4HttpServerTranspo private static final Logger logger = LogManager.getLogger(SecuritySSLNettyHttpServerTransport.class); private final SecurityKeyStore sks; private final SslExceptionHandler errorHandler; - + public SecuritySSLNettyHttpServerTransport(final Settings settings, final NetworkService networkService, final BigArrays bigArrays, final ThreadPool threadPool, final SecurityKeyStore sks, final NamedXContentRegistry namedXContentRegistry, final ValidatingDispatcher dispatcher, final SslExceptionHandler errorHandler, ClusterSettings clusterSettings, SharedGroupFactory sharedGroupFactory) { @@ -94,7 +94,7 @@ protected void configurePipeline(ChannelHandlerContext ctx, String protocol) thr throw new IllegalStateException("Unknown application protocol: " + protocol); } } - + @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); @@ -119,7 +119,7 @@ protected void initChannel(Channel ch) throws Exception { final SslHandler sslHandler = new SslHandler(SecuritySSLNettyHttpServerTransport.this.sks.createHTTPSSLEngine()); ch.pipeline().addFirst("ssl_http", sslHandler); } - + @Override protected void configurePipeline(Channel ch) { ch.pipeline().addLast(new Http2OrHttpHandler()); diff --git a/src/main/java/org/opensearch/security/ssl/http/netty/ValidatingDispatcher.java b/src/main/java/org/opensearch/security/ssl/http/netty/ValidatingDispatcher.java index 66976a930b..32a1f73568 100644 --- a/src/main/java/org/opensearch/security/ssl/http/netty/ValidatingDispatcher.java +++ b/src/main/java/org/opensearch/security/ssl/http/netty/ValidatingDispatcher.java @@ -1,10 +1,10 @@ /* * Copyright 2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl.http.netty; @@ -47,7 +47,7 @@ public class ValidatingDispatcher implements Dispatcher { private final Settings settings; private final Path configPath; - public ValidatingDispatcher(final ThreadContext threadContext, final Dispatcher originalDispatcher, + public ValidatingDispatcher(final ThreadContext threadContext, final Dispatcher originalDispatcher, final Settings settings, final Path configPath, final SslExceptionHandler errorHandler) { super(); this.threadContext = threadContext; @@ -68,15 +68,15 @@ public void dispatchBadRequest(RestChannel channel, ThreadContext threadContext, checkRequest(channel.request(), channel); originalDispatcher.dispatchBadRequest(channel, threadContext, cause); } - + protected void checkRequest(final RestRequest request, final RestChannel channel) { - + if(SSLRequestHelper.containsBadHeader(threadContext, "_opendistro_security_ssl_")) { final OpenSearchException exception = ExceptionUtils.createBadHeaderException(); errorHandler.logError(exception, request, 1); throw exception; } - + try { if(SSLRequestHelper.getSSLInfo(settings, configPath, request, null) == null) { logger.error("Not an SSL request"); diff --git a/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLInfoAction.java b/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLInfoAction.java index f5050d3242..5b1fae01e5 100644 --- a/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLInfoAction.java +++ b/src/main/java/org/opensearch/security/ssl/rest/SecuritySSLInfoAction.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl.rest; @@ -68,11 +68,11 @@ public SecuritySSLInfoAction(final Settings settings, final Path configPath, fin public List routes() { return routes; } - + @Override protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { return new RestChannelConsumer() { - + final Boolean showDn = request.paramAsBoolean("show_dn", Boolean.FALSE); @Override @@ -81,7 +81,7 @@ public void accept(RestChannel channel) throws Exception { BytesRestResponse response = null; try { - + SSLInfo sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor); X509Certificate[] certs = sslInfo == null?null:sslInfo.getX509Certs(); X509Certificate[] localCerts = sslInfo == null?null:sslInfo.getLocalCertificates(); @@ -123,7 +123,7 @@ public void accept(RestChannel channel) throws Exception { builder.close(); } } - + channel.sendResponse(response); } }; diff --git a/src/main/java/org/opensearch/security/ssl/transport/DefaultPrincipalExtractor.java b/src/main/java/org/opensearch/security/ssl/transport/DefaultPrincipalExtractor.java index d717df2da6..001059eefa 100644 --- a/src/main/java/org/opensearch/security/ssl/transport/DefaultPrincipalExtractor.java +++ b/src/main/java/org/opensearch/security/ssl/transport/DefaultPrincipalExtractor.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl.transport; @@ -38,7 +38,7 @@ public class DefaultPrincipalExtractor implements PrincipalExtractor { protected final Logger log = LogManager.getLogger(this.getClass()); - + @Override @SuppressWarnings("removal") public String extractPrincipal(final X509Certificate x509Certificate, final Type type) { @@ -54,7 +54,7 @@ public String extractPrincipal(final X509Certificate x509Certificate, final Type String dnString = AccessController.doPrivileged(new PrivilegedAction() { @Override - public String run() { + public String run() { final X500Principal principal = x509Certificate.getSubjectX500Principal(); return principal.toString(); } @@ -69,12 +69,12 @@ public String run() { } catch (InvalidNameException e) { log.error("Unable to parse: {}",dnString, e); } - - + + if(log.isTraceEnabled()) { log.trace("principal: {}", dnString); } - + return dnString; } diff --git a/src/main/java/org/opensearch/security/ssl/transport/PrincipalExtractor.java b/src/main/java/org/opensearch/security/ssl/transport/PrincipalExtractor.java index 06213af642..cccca06e0c 100644 --- a/src/main/java/org/opensearch/security/ssl/transport/PrincipalExtractor.java +++ b/src/main/java/org/opensearch/security/ssl/transport/PrincipalExtractor.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl.transport; @@ -20,7 +20,7 @@ import java.security.cert.X509Certificate; public interface PrincipalExtractor { - + public enum Type { HTTP, TRANSPORT @@ -28,13 +28,13 @@ public enum Type { /** * Extract the principal name - * + * * Please note that this method gets called for principal extraction of other nodes * as well as transport clients. It's up to the implementer to distinguish between them * and handle them appropriately. - * + * * Implementations must be public classes with a default public default constructor. - * + * * @param x509Certificate The first X509 certificate in the peer certificate chain * This can be null, in this case the method must also return null. * @return The principal as string. This may be null in case where x509Certificate is null diff --git a/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLNettyTransport.java b/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLNettyTransport.java index 58da4cb55e..5a38909f81 100644 --- a/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLNettyTransport.java +++ b/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLNettyTransport.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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. - * + * */ /* @@ -101,7 +101,7 @@ public void onException(TcpChannel channel, Exception e) { protected ChannelHandler getServerChannelInitializer(String name) { return new SSLServerChannelInitializer(name); } - + @Override protected ChannelHandler getClientChannelInitializer(DiscoveryNode node) { return new SSLClientChannelInitializer(node); @@ -127,7 +127,7 @@ protected void initChannel(Channel ch) throws Exception { ch.pipeline().addFirst("ssl_server", sslHandler); } } - + @Override public final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause instanceof DecoderException && cause != null) { @@ -147,7 +147,7 @@ protected static class ClientSSLHandler extends ChannelOutboundHandlerAdapter { private final boolean hostnameVerificationEnabled; private final boolean hostnameVerificationResovleHostName; private final SslExceptionHandler errorHandler; - + private ClientSSLHandler(final SecurityKeyStore sks, final boolean hostnameVerificationEnabled, final boolean hostnameVerificationResovleHostName, final SslExceptionHandler errorHandler) { @@ -156,14 +156,14 @@ private ClientSSLHandler(final SecurityKeyStore sks, final boolean hostnameVerif this.hostnameVerificationResovleHostName = hostnameVerificationResovleHostName; this.errorHandler = errorHandler; } - + @Override public final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause instanceof DecoderException && cause != null) { cause = cause.getCause(); } - + errorHandler.logError(cause, false); logger.error("Exception during establishing a SSL connection: " + cause, cause); @@ -186,7 +186,7 @@ public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, Sock if(log.isDebugEnabled()) { log.debug("Hostname of peer is {} ({}/{}) with hostnameVerificationResovleHostName: {}", hostname, inetSocketAddress.getHostName(), inetSocketAddress.getHostString(), hostnameVerificationResovleHostName); } - + engine = sks.createClientTransportSSLEngine(hostname, inetSocketAddress.getPort()); } else { engine = sks.createClientTransportSSLEngine(null, -1); @@ -239,7 +239,7 @@ protected void initChannel(Channel ch) throws Exception { logger.debug("Connection to {} needs to be non ssl", node.getHostName()); } } - + @Override public final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause instanceof DecoderException && cause != null) { @@ -249,7 +249,7 @@ public final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) th errorHandler.logError(cause, false); logger.error("Exception during establishing a SSL connection: " + cause, cause); - + super.exceptionCaught(ctx, cause); } } diff --git a/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLRequestHandler.java b/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLRequestHandler.java index 925f5cf0e9..53877df0f0 100644 --- a/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLRequestHandler.java +++ b/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLRequestHandler.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl.transport; @@ -47,7 +47,7 @@ public class SecuritySSLRequestHandler implements TransportRequestHandler { - + private final String action; private final TransportRequestHandler actualHandler; private final ThreadPool threadPool; @@ -68,7 +68,7 @@ public SecuritySSLRequestHandler(String action, TransportRequestHandler actua this.SSLConfig = SSLConfig; this.errorHandler = errorHandler; } - + protected ThreadContext getThreadContext() { if(threadPool == null) { return null; @@ -80,7 +80,7 @@ protected ThreadContext getThreadContext() { @Override public final void messageReceived(T request, TransportChannel channel, Task task) throws Exception { ThreadContext threadContext = getThreadContext() ; - + if(SSLRequestHelper.containsBadHeader(threadContext, "_opendistro_security_ssl_")) { final Exception exception = ExceptionUtils.createBadHeaderException(); channel.sendResponse(exception); @@ -91,12 +91,12 @@ public final void messageReceived(T request, TransportChannel channel, Task task if (!channelType.equals("direct") && !channelType.equals("transport")) { channel = getInnerChannel(channel); } - + if (!"transport".equals(channel.getChannelType())) { //netty4 messageReceivedDecorate(request, actualHandler, channel, task); return; } - + try { Netty4TcpChannel nettyChannel = null; @@ -111,7 +111,7 @@ public final void messageReceived(T request, TransportChannel channel, Task task } else { throw new Exception("Invalid channel of type "+channel.getClass()+ " ("+channel.getChannelType()+")"); } - + final SslHandler sslhandler = (SslHandler) nettyChannel.getNettyChannel().pipeline().get("ssl_server"); if (sslhandler == null) { @@ -131,11 +131,11 @@ public final void messageReceived(T request, TransportChannel channel, Task task final Certificate[] peerCerts = sslhandler.engine().getSession().getPeerCertificates(); final Certificate[] localCerts = sslhandler.engine().getSession().getLocalCertificates(); - - if (peerCerts != null - && peerCerts.length > 0 - && peerCerts[0] instanceof X509Certificate - && localCerts != null && localCerts.length > 0 + + if (peerCerts != null + && peerCerts.length > 0 + && peerCerts[0] instanceof X509Certificate + && localCerts != null && localCerts.length > 0 && localCerts[0] instanceof X509Certificate) { final X509Certificate[] x509PeerCerts = Arrays.copyOf(peerCerts, peerCerts.length, X509Certificate[].class); final X509Certificate[] x509LocalCerts = Arrays.copyOf(localCerts, localCerts.length, X509Certificate[].class); @@ -168,7 +168,7 @@ public final void messageReceived(T request, TransportChannel channel, Task task errorHandler.logError(e, request, action, task, 0); throw e; } - + } protected TransportChannel getInnerChannel(TransportChannel transportChannel) throws Exception { @@ -182,12 +182,12 @@ protected TransportChannel getInnerChannel(TransportChannel transportChannel) th throw new RuntimeException("Unknown channel type " + transportChannel.getChannelType() + " does not implement getInnerChannel method."); } } - + protected void addAdditionalContextValues(final String action, final TransportRequest request, final X509Certificate[] localCerts, final X509Certificate[] peerCerts, final String principal) throws Exception { // no-op } - + protected void messageReceivedDecorate(final T request, final TransportRequestHandler actualHandler, final TransportChannel transportChannel, Task task) throws Exception { actualHandler.messageReceived(request, transportChannel, task); } diff --git a/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLTransportInterceptor.java b/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLTransportInterceptor.java index 165662d429..aa83265125 100644 --- a/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLTransportInterceptor.java +++ b/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLTransportInterceptor.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl.transport; @@ -28,7 +28,7 @@ import org.opensearch.transport.TransportRequestHandler; public final class SecuritySSLTransportInterceptor implements TransportInterceptor { - + protected final Logger log = LogManager.getLogger(this.getClass()); protected final ThreadPool threadPool; protected final PrincipalExtractor principalExtractor; @@ -49,6 +49,6 @@ public TransportRequestHandler interceptHandler( TransportRequestHandler actualHandler) { return new SecuritySSLRequestHandler(action, actualHandler, threadPool, principalExtractor, SSLConfig, errorHandler); } - - + + } diff --git a/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java b/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java index 0a12ffc2b5..b0a975b618 100644 --- a/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java +++ b/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java @@ -59,14 +59,14 @@ * Allows specifying Certificate Revocation List (CRL), as well as enabling * CRL Distribution Points Protocol (CRLDP) certificate extension support, * and also enabling On-Line Certificate Status Protocol (OCSP) support. - * + * * IMPORTANT: at least one of the above mechanisms *MUST* be configured and * operational, otherwise certificate validation *WILL FAIL* unconditionally. */ // CS-ENFORCE-SINGLE public class CertificateValidator { - + boolean isPreferCrl() { return preferCrl; } @@ -95,16 +95,16 @@ void setCheckOnlyEndEntities(boolean checkOnlyEndEntities) { private boolean _enableOCSP = false; /** Location of OCSP Responder */ private String _ocspResponderURL; - + private boolean preferCrl = false; private boolean checkOnlyEndEntities = true; private Date date = null; //current date - + /** - * creates an instance of the certificate validator + * creates an instance of the certificate validator * - * @param trustStore the truststore to use - * @param crls the Certificate Revocation List to use + * @param trustStore the truststore to use + * @param crls the Certificate Revocation List to use */ public CertificateValidator(KeyStore trustStore, Collection crls) { @@ -112,18 +112,18 @@ public CertificateValidator(KeyStore trustStore, Collection crls) { throw new InvalidParameterException("TrustStore must be specified for CertificateValidator."); } - + _trustStore = trustStore; _crls = crls; } - + public CertificateValidator(X509Certificate[] trustedCert, Collection crls) { if (trustedCert == null || trustedCert.length == 0) { throw new InvalidParameterException("trustedCert must be specified for CertificateValidator."); } - + _trustedCert = trustedCert; _crls = crls; } @@ -137,7 +137,7 @@ public void validate(Certificate[] certChain) throws CertificateException { if (item == null) continue; - + if (!(item instanceof X509Certificate)) { throw new IllegalStateException("Invalid certificate type in chain"); @@ -149,34 +149,34 @@ public void validate(Certificate[] certChain) throws CertificateException if (certList.isEmpty()) { throw new IllegalStateException("Invalid certificate chain"); - + } - + X509CertSelector certSelect = new X509CertSelector(); certSelect.setCertificate(certList.get(0)); - + CertPathBuilder certPathBuilder = CertPathBuilder.getInstance("PKIX"); PKIXRevocationChecker revocationChecker = (PKIXRevocationChecker) certPathBuilder.getRevocationChecker(); Set opts = new HashSet<>(); - + if(preferCrl) { opts.add(PKIXRevocationChecker.Option.PREFER_CRLS); } - + //opts.add(PKIXRevocationChecker.Option.SOFT_FAIL); - + //opts.add(PKIXRevocationChecker.Option.NO_FALLBACK); - + if(checkOnlyEndEntities) { opts.add(PKIXRevocationChecker.Option.ONLY_END_ENTITY); } - + revocationChecker.setOptions(opts); // Configure certification path builder parameters PKIXBuilderParameters pbParams = null; - + if(_trustStore != null) { pbParams = new PKIXBuilderParameters(_trustStore, certSelect); } else { @@ -189,25 +189,25 @@ public void validate(Certificate[] certChain) throws CertificateException pbParams = new PKIXBuilderParameters(trustAnchors, certSelect); } - + pbParams.addCertPathChecker(revocationChecker); - + pbParams.setDate(date); - + pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList))); - + // Set maximum certification path length pbParams.setMaxPathLength(_maxCertPathLength); - + // Enable revocation checking pbParams.setRevocationEnabled(true); - + // Set static Certificate Revocation List if (_crls != null && !_crls.isEmpty()) { pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(_crls))); } - + // Enable On-Line Certificate Status Protocol (OCSP) support if (_enableOCSP) { @@ -218,10 +218,10 @@ public void validate(Certificate[] certChain) throws CertificateException { System.setProperty("com.sun.security.enableCRLDP","true"); } - + // Build certification path - CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams); - + CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams); + // Validate certification path CertPathValidator.getInstance("PKIX").validate(buildResult.getCertPath(),pbParams); } @@ -255,9 +255,9 @@ public void setMaxCertPathLength(int maxCertPathLength) { _maxCertPathLength = maxCertPathLength; } - + /* ------------------------------------------------------------ */ - /** + /** * @return true if CRL Distribution Points support is enabled */ public boolean isEnableCRLDP() @@ -275,7 +275,7 @@ public void setEnableCRLDP(boolean enableCRLDP) } /* ------------------------------------------------------------ */ - /** + /** * @return true if On-Line Certificate Status Protocol support is enabled */ public boolean isEnableOCSP() @@ -293,7 +293,7 @@ public void setEnableOCSP(boolean enableOCSP) } /* ------------------------------------------------------------ */ - /** + /** * @return Location of the OCSP Responder */ public String getOcspResponderURL() diff --git a/src/main/java/org/opensearch/security/ssl/util/ExceptionUtils.java b/src/main/java/org/opensearch/security/ssl/util/ExceptionUtils.java index f06c4d9808..e2af22844f 100644 --- a/src/main/java/org/opensearch/security/ssl/util/ExceptionUtils.java +++ b/src/main/java/org/opensearch/security/ssl/util/ExceptionUtils.java @@ -1,10 +1,10 @@ /* * Copyright 2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl.util; @@ -20,30 +20,30 @@ import org.opensearch.OpenSearchException; public class ExceptionUtils { - + public static Throwable getRootCause(final Throwable e) { - + if(e == null) { return null; } - + final Throwable cause = e.getCause(); if(cause == null) { return e; } return getRootCause(cause); } - + public static Throwable findMsg(final Throwable e, String msg) { - + if(e == null) { return null; } - + if(e.getMessage() != null && e.getMessage().contains(msg)) { return e; } - + final Throwable cause = e.getCause(); if(cause == null) { return null; diff --git a/src/main/java/org/opensearch/security/ssl/util/SSLCertificateHelper.java b/src/main/java/org/opensearch/security/ssl/util/SSLCertificateHelper.java index ff36cc40a8..42b44a3817 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLCertificateHelper.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLCertificateHelper.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl.util; @@ -40,20 +40,20 @@ public class SSLCertificateHelper { private static final Logger log = LogManager.getLogger(SSLCertificateHelper.class); private static boolean stripRootFromChain = true; //TODO check - + public static X509Certificate[] exportRootCertificates(final KeyStore ks, final String alias) throws KeyStoreException { logKeyStore(ks); - + final List trustedCerts = new ArrayList(); - + if (Strings.isNullOrEmpty(alias)) { - + if(log.isDebugEnabled()) { log.debug("No alias given, will trust all of the certificates in the store"); } - + final List aliases = toList(ks.aliases()); - + for (final String _alias : aliases) { if (ks.isCertificateEntry(_alias)) { @@ -79,12 +79,12 @@ public static X509Certificate[] exportRootCertificates(final KeyStore ks, final } return trustedCerts.toArray(new X509Certificate[0]); - } - + } + public static X509Certificate[] exportServerCertChain(final KeyStore ks, String alias) throws KeyStoreException { logKeyStore(ks); final List aliases = toList(ks.aliases()); - + if (Strings.isNullOrEmpty(alias)) { if(aliases.isEmpty()) { log.error("Keystore does not contain any aliases"); @@ -92,7 +92,7 @@ public static X509Certificate[] exportServerCertChain(final KeyStore ks, String alias = aliases.get(0); log.info("No alias given, use the first one: {}", alias); } - } + } final Certificate[] certs = ks.getCertificateChain(alias); if (certs != null && certs.length > 0) { @@ -103,7 +103,7 @@ public static X509Certificate[] exportServerCertChain(final KeyStore ks, String if (lastCertificate.getBasicConstraints() > -1 && lastCertificate.getSubjectX500Principal().equals(lastCertificate.getIssuerX500Principal())) { log.warn("Certificate chain for alias {} contains a root certificate", alias); - + if(stripRootFromChain ) { x509Certs = Arrays.copyOf(certs, certs.length-1, X509Certificate[].class); } @@ -143,7 +143,7 @@ public static PrivateKey exportDecryptedKey(final KeyStore ks, final String alia return null; } - + private static void logKeyStore(final KeyStore ks) { try { final List aliases = toList(ks.aliases()); @@ -175,14 +175,14 @@ private static void logKeyStore(final KeyStore ks) { log.error("Error logging keystore due to "+e, e); } } - + private static List toList(final Enumeration enumeration) { final List aliases = new ArrayList<>(); while (enumeration.hasMoreElements()) { aliases.add(enumeration.nextElement()); } - + return Collections.unmodifiableList(aliases); } } diff --git a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java index 65eaec238a..63b2ae5502 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl.util; @@ -88,15 +88,15 @@ public final class SSLConfigConstants { public static final String SECURITY_SSL_ALLOW_CLIENT_INITIATED_RENEGOTIATION = "plugins.security.ssl.allow_client_initiated_renegotiation"; public static final String DEFAULT_STORE_PASSWORD = "changeit"; //#16 - + public static final String JDK_TLS_REJECT_CLIENT_INITIATED_RENEGOTIATION = "jdk.tls.rejectClientInitiatedRenegotiation"; - + private static final String[] _SECURE_SSL_PROTOCOLS = {"TLSv1.3", "TLSv1.2", "TLSv1.1"}; - + public static final String[] getSecureSSLProtocols(Settings settings, boolean http) { List configuredProtocols = null; - + if(settings != null) { if(http) { configuredProtocols = settings.getAsList(SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, Collections.emptyList()); @@ -104,26 +104,26 @@ public static final String[] getSecureSSLProtocols(Settings settings, boolean ht configuredProtocols = settings.getAsList(SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, Collections.emptyList()); } } - + if(configuredProtocols != null && configuredProtocols.size() > 0) { return configuredProtocols.toArray(new String[0]); } - + return _SECURE_SSL_PROTOCOLS.clone(); } - + // @formatter:off - private static final String[] _SECURE_SSL_CIPHERS = + private static final String[] _SECURE_SSL_CIPHERS = { //TLS__WITH_ - + //Example (including unsafe ones) //Protocol: TLS, SSL //Key Exchange RSA, Diffie-Hellman, ECDH, SRP, PSK //Authentication RSA, DSA, ECDSA //Bulk Ciphers RC4, 3DES, AES //Message Authentication HMAC-SHA256, HMAC-SHA1, HMAC-MD5 - + //thats what chrome 48 supports (https://cc.dcsec.uni-hannover.de/) //(c0,2b)ECDHE-ECDSA-AES128-GCM-SHA256128 BitKey exchange: ECDH, encryption: AES, MAC: SHA256. @@ -141,7 +141,7 @@ public static final String[] getSecureSSLProtocols(Settings settings, boolean ht //(00,35)RSA-AES256-SHA256 BitKey exchange: RSA, encryption: AES, MAC: SHA1. //(00,2f)RSA-AES128-SHA128 BitKey exchange: RSA, encryption: AES, MAC: SHA1. //(00,0a)RSA-3DES-EDE-SHA168 BitKey exchange: RSA, encryption: 3DES, MAC: SHA1. - + //thats what firefox 42 supports (https://cc.dcsec.uni-hannover.de/) //(c0,2b) ECDHE-ECDSA-AES128-GCM-SHA256 //(c0,2f) ECDHE-RSA-AES128-GCM-SHA256 @@ -176,18 +176,18 @@ public static final String[] getSecureSSLProtocols(Settings settings, boolean ht "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - + //TLS 1.3 "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256", //Open SSL >= 1.1.1 and Java >= 12 - + //TLS 1.2 CHACHA20 POLY1305 supported by Java >= 12 and //OpenSSL >= 1.1.0 "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - + //IBM "SSL_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "SSL_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", @@ -211,14 +211,14 @@ public static final String[] getSecureSSLProtocols(Settings settings, boolean ht "SSL_DHE_RSA_WITH_AES_256_CBC_SHA256", "SSL_DHE_DSS_WITH_AES_256_CBC_SHA", "SSL_DHE_RSA_WITH_AES_256_CBC_SHA" - + //some others //"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", //"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", - //"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - //"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + //"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + //"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", //"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", - //"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + //"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", //"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", //"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", //"TLS_RSA_WITH_AES_128_CBC_SHA256", @@ -227,11 +227,11 @@ public static final String[] getSecureSSLProtocols(Settings settings, boolean ht //"TLS_RSA_WITH_AES_256_CBC_SHA", }; // @formatter:on - + public static final List getSecureSSLCiphers(Settings settings, boolean http) { - + List configuredCiphers = null; - + if(settings != null) { if(http) { configuredCiphers = settings.getAsList(SECURITY_SSL_HTTP_ENABLED_CIPHERS, Collections.emptyList()); @@ -239,14 +239,14 @@ public static final List getSecureSSLCiphers(Settings settings, boolean configuredCiphers = settings.getAsList(SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, Collections.emptyList()); } } - + if(configuredCiphers != null && configuredCiphers.size() > 0) { return configuredCiphers; } return Collections.unmodifiableList(Arrays.asList(_SECURE_SSL_CIPHERS)); } - + private SSLConfigConstants() { } diff --git a/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java b/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java index 87452f8a9c..42aad19a09 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLRequestHelper.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl.util; @@ -55,7 +55,7 @@ public class SSLRequestHelper { private static final Logger log = LogManager.getLogger(SSLRequestHelper.class); - + public static class SSLInfo { private final X509Certificate[] x509Certs; private final X509Certificate[] localCertificates; @@ -79,7 +79,7 @@ public SSLInfo(final X509Certificate[] x509Certs, final String principal, final public X509Certificate[] getX509Certs() { return x509Certs == null ? null : x509Certs.clone(); } - + public X509Certificate[] getLocalCertificates() { return localCertificates == null ? null : localCertificates.clone(); } @@ -106,7 +106,7 @@ public String toString() { @SuppressWarnings("removal") public static SSLInfo getSSLInfo(final Settings settings, final Path configPath, final RestRequest request, PrincipalExtractor principalExtractor) throws SSLPeerUnverifiedException { - + if(request == null || request.getHttpChannel() == null || !(request.getHttpChannel() instanceof Netty4HttpChannel)) { return null; } @@ -138,7 +138,7 @@ public static SSLInfo getSSLInfo(final Settings settings, final Path configPath, if (certs != null && certs.length > 0 && certs[0] instanceof X509Certificate) { x509Certs = Arrays.copyOf(certs, certs.length, X509Certificate[].class); final X509Certificate[] x509CertsF = x509Certs; - + final SecurityManager sm = System.getSecurityManager(); if (sm != null) { @@ -147,7 +147,7 @@ public static SSLInfo getSSLInfo(final Settings settings, final Path configPath, validationFailure = AccessController.doPrivileged(new PrivilegedAction() { @Override - public Boolean run() { + public Boolean run() { return !validate(x509CertsF, settings, configPath); } }); @@ -171,7 +171,7 @@ public Boolean run() { Certificate[] localCerts = session.getLocalCertificates(); return new SSLInfo(x509Certs, principal, protocol, cipher, localCerts==null?null:Arrays.copyOf(localCerts, localCerts.length, X509Certificate[].class)); } - + public static boolean containsBadHeader(final ThreadContext context, String prefix) { if (context != null) { for (final Entry header : context.getHeaders().entrySet()) { @@ -180,27 +180,27 @@ public static boolean containsBadHeader(final ThreadContext context, String pref } } } - + return false; } - + private static boolean validate(X509Certificate[] x509Certs, final Settings settings, final Path configPath) { - + final boolean validateCrl = settings.getAsBoolean(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATE, false); final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { log.trace("validateCrl: {}", validateCrl); } - + if(!validateCrl) { return true; } - + final Environment env = new Environment(settings, configPath); - + try { - + Collection crls = null; final String crlFile = settings.get(SSLConfigConstants.SSECURITY_SSL_HTTP_CRL_FILE); @@ -209,7 +209,7 @@ private static boolean validate(X509Certificate[] x509Certs, final Settings sett try(FileInputStream crlin = new FileInputStream(crl)) { crls = CertificateFactory.getInstance("X.509").generateCRLs(crlin); } - + if (isTraceEnabled) { log.trace("crls from file: {}", crls.size()); } @@ -218,15 +218,15 @@ private static boolean validate(X509Certificate[] x509Certs, final Settings sett log.trace("no crl file configured"); } } - + final String truststore = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH); CertificateValidator validator = null; - + if(truststore != null) { final String truststoreType = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_TYPE, "JKS"); final String truststorePassword = SECURITY_SSL_HTTP_TRUSTSTORE_PASSWORD.getSetting(settings); //final String truststoreAlias = settings.get(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_ALIAS, null); - + final KeyStore ts = KeyStore.getInstance(truststoreType); try(FileInputStream fin = new FileInputStream(new File(env.configDir().resolve(truststore).toAbsolutePath().toString()))) { ts.load(fin, (truststorePassword == null || truststorePassword.length() == 0) ?null:truststorePassword.toCharArray()); @@ -237,9 +237,9 @@ private static boolean validate(X509Certificate[] x509Certs, final Settings sett try(FileInputStream trin = new FileInputStream(trustedCas)) { Collection cert = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); validator = new CertificateValidator(cert.toArray(new X509Certificate[0]), crls); - } + } } - + validator.setEnableCRLDP(!settings.getAsBoolean(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_DISABLE_CRLDP, false)); validator.setEnableOCSP(!settings.getAsBoolean(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_DISABLE_OCSP, false)); validator.setCheckOnlyEndEntities(settings.getAsBoolean(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_CHECK_ONLY_END_ENTITIES, true)); @@ -250,13 +250,13 @@ private static boolean validate(X509Certificate[] x509Certs, final Settings sett } validator.setDate(dateTimestamp==null?null:new Date(dateTimestamp.longValue())); validator.validate(x509Certs); - + return true; - + } catch (Exception e) { log.warn("Unable to validate CRL: ", ExceptionUtils.getRootCause(e)); } - + return false; } } diff --git a/src/main/java/org/opensearch/security/ssl/util/Utils.java b/src/main/java/org/opensearch/security/ssl/util/Utils.java index 2035c4fb1e..2a6fcd2550 100644 --- a/src/main/java/org/opensearch/security/ssl/util/Utils.java +++ b/src/main/java/org/opensearch/security/ssl/util/Utils.java @@ -1,10 +1,10 @@ /* * Copyright 2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl.util; @@ -23,18 +23,18 @@ public static T coalesce(T first, T... more) { if (first != null) { return first; } - + if(more == null || more.length == 0) { return null; } - + for (int i = 0; i < more.length; i++) { T t = more[i]; if(t != null) { return t; } } - + return null; } diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index 83281c9d0b..f5c64bccd3 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -49,7 +49,7 @@ public class ConfigConstants { public static final String OPENDISTRO_SECURITY_ORIGIN_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX+"origin_header"; public static final String OPENDISTRO_SECURITY_DLS_QUERY_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX+"dls_query"; - + public static final String OPENDISTRO_SECURITY_DLS_FILTER_LEVEL_QUERY_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX+"dls_filter_level_query"; public static final String OPENDISTRO_SECURITY_DLS_FILTER_LEVEL_QUERY_TRANSIENT = OPENDISTRO_SECURITY_CONFIG_PREFIX+"dls_filter_level_query_t"; @@ -57,7 +57,7 @@ public class ConfigConstants { public static final String OPENDISTRO_SECURITY_DLS_MODE_TRANSIENT = OPENDISTRO_SECURITY_CONFIG_PREFIX+"dls_mode_t"; public static final String OPENDISTRO_SECURITY_FLS_FIELDS_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX+"fls_fields"; - + public static final String OPENDISTRO_SECURITY_MASKED_FIELD_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX+"masked_fields"; @@ -65,7 +65,7 @@ public class ConfigConstants { public static final String OPENDISTRO_SECURITY_DOC_ALLOWLIST_TRANSIENT = OPENDISTRO_SECURITY_CONFIG_PREFIX+"doc_allowlist_t"; public static final String OPENDISTRO_SECURITY_FILTER_LEVEL_DLS_DONE = OPENDISTRO_SECURITY_CONFIG_PREFIX+"filter_level_dls_done"; - + public static final String OPENDISTRO_SECURITY_DLS_QUERY_CCS = OPENDISTRO_SECURITY_CONFIG_PREFIX+"dls_query_ccs"; public static final String OPENDISTRO_SECURITY_FLS_FIELDS_CCS = OPENDISTRO_SECURITY_CONFIG_PREFIX+"fls_fields_ccs"; @@ -76,7 +76,7 @@ public class ConfigConstants { public static final String OPENDISTRO_SECURITY_REMOTE_ADDRESS = OPENDISTRO_SECURITY_CONFIG_PREFIX+"remote_address"; public static final String OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX+"remote_address_header"; - + public static final String OPENDISTRO_SECURITY_INITIAL_ACTION_CLASS_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX+"initial_action_class_header"; /** @@ -118,7 +118,7 @@ public class ConfigConstants { public static final String SSO_LOGOUT_URL = OPENDISTRO_SECURITY_CONFIG_PREFIX+"sso_logout_url"; - + public static final String OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX = ".opendistro_security"; public static final String SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE = "plugins.security.enable_snapshot_restore_privilege"; @@ -141,7 +141,7 @@ public class ConfigConstants { public static final String SECURITY_CONFIG_INDEX_NAME = "plugins.security.config_index_name"; public static final String SECURITY_AUTHCZ_IMPERSONATION_DN = "plugins.security.authcz.impersonation_dn"; public static final String SECURITY_AUTHCZ_REST_IMPERSONATION_USERS="plugins.security.authcz.rest_impersonation_user"; - + public static final String SECURITY_AUDIT_TYPE_DEFAULT = "plugins.security.audit.type"; public static final String SECURITY_AUDIT_CONFIG_DEFAULT = "plugins.security.audit.config"; public static final String SECURITY_AUDIT_CONFIG_ROUTES = "plugins.security.audit.routes"; @@ -162,14 +162,14 @@ public class ConfigConstants { public static final boolean OPENDISTRO_SECURITY_AUDIT_SSL_VERIFY_HOSTNAMES_DEFAULT = true; public static final boolean OPENDISTRO_SECURITY_AUDIT_SSL_ENABLE_SSL_CLIENT_AUTH_DEFAULT = false; public static final String OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS = "opendistro_security.audit.exclude_sensitive_headers"; - + public static final String SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX = "plugins.security.audit.config."; // Internal / External OpenSearch public static final String SECURITY_AUDIT_OPENSEARCH_INDEX = "index"; public static final String SECURITY_AUDIT_OPENSEARCH_TYPE = "type"; - + // External OpenSearch public static final String SECURITY_AUDIT_EXTERNAL_OPENSEARCH_HTTP_ENDPOINTS = "http_endpoints"; public static final String SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME = "username"; @@ -188,22 +188,22 @@ public class ConfigConstants { public static final String SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_CIPHERS = "enabled_ssl_ciphers"; public static final String SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_PROTOCOLS = "enabled_ssl_protocols"; - // Webhooks + // Webhooks public static final String SECURITY_AUDIT_WEBHOOK_URL = "webhook.url"; public static final String SECURITY_AUDIT_WEBHOOK_FORMAT = "webhook.format"; public static final String SECURITY_AUDIT_WEBHOOK_SSL_VERIFY = "webhook.ssl.verify"; public static final String SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH = "webhook.ssl.pemtrustedcas_filepath"; public static final String SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT = "webhook.ssl.pemtrustedcas_content"; - + // Log4j public static final String SECURITY_AUDIT_LOG4J_LOGGER_NAME = "log4j.logger_name"; public static final String SECURITY_AUDIT_LOG4J_LEVEL = "log4j.level"; - + //retry public static final String SECURITY_AUDIT_RETRY_COUNT = "plugins.security.audit.config.retry_count"; public static final String SECURITY_AUDIT_RETRY_DELAY_MS = "plugins.security.audit.config.retry_delay_ms"; - + public static final String SECURITY_KERBEROS_KRB5_FILEPATH = "plugins.security.kerberos.krb5_filepath"; public static final String SECURITY_KERBEROS_ACCEPTOR_KEYTAB_FILEPATH = "plugins.security.kerberos.acceptor_keytab_filepath"; public static final String SECURITY_KERBEROS_ACCEPTOR_PRINCIPAL = "plugins.security.kerberos.acceptor_principal"; diff --git a/src/main/java/org/opensearch/security/support/HTTPHelper.java b/src/main/java/org/opensearch/security/support/HTTPHelper.java index e7554f120d..5e9fc78f8c 100644 --- a/src/main/java/org/opensearch/security/support/HTTPHelper.java +++ b/src/main/java/org/opensearch/security/support/HTTPHelper.java @@ -55,7 +55,7 @@ public static AuthCredentials extractCredentials(String authorizationHeader, Log // username:pass:word //blank password // username: - + final int firstColonIndex = decodedBasicHeader.indexOf(':'); String username = null; @@ -63,7 +63,7 @@ public static AuthCredentials extractCredentials(String authorizationHeader, Log if (firstColonIndex > 0) { username = decodedBasicHeader.substring(0, firstColonIndex); - + if(decodedBasicHeader.length() - 1 != firstColonIndex) { password = decodedBasicHeader.substring(firstColonIndex + 1); } else { @@ -83,20 +83,20 @@ public static AuthCredentials extractCredentials(String authorizationHeader, Log return null; } } - + public static boolean containsBadHeader(final RestRequest request) { - + final Map> headers; - + if (request != null && ( headers = request.getHeaders()) != null) { for (final String key: headers.keySet()) { - if ( key != null + if ( key != null && key.trim().toLowerCase().startsWith(ConfigConstants.OPENDISTRO_SECURITY_CONFIG_PREFIX.toLowerCase())) { return true; } } } - + return false; } } diff --git a/src/main/java/org/opensearch/security/support/HeaderHelper.java b/src/main/java/org/opensearch/security/support/HeaderHelper.java index 1a7484a781..af8da305d4 100644 --- a/src/main/java/org/opensearch/security/support/HeaderHelper.java +++ b/src/main/java/org/opensearch/security/support/HeaderHelper.java @@ -39,7 +39,7 @@ public static boolean isInterClusterRequest(final ThreadContext context) { } public static boolean isDirectRequest(final ThreadContext context) { - + return "direct".equals(context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_CHANNEL_TYPE)) || context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_CHANNEL_TYPE) == null; } @@ -49,7 +49,7 @@ public static boolean isExtensionRequest(final ThreadContext context) { return context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_EXTENSION_REQUEST) == Boolean.TRUE; } // CS-ENFORCE-SINGLE - + public static String getSafeFromHeader(final ThreadContext context, final String headerName) { if (context == null || headerName == null || headerName.isEmpty()) { @@ -73,7 +73,7 @@ public static Serializable deserializeSafeFromHeader(final ThreadContext context return null; } - + public static boolean isTrustedClusterRequest(final ThreadContext context) { return context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_TRUSTED_CLUSTER_REQUEST) == Boolean.TRUE; } diff --git a/src/main/java/org/opensearch/security/support/MapUtils.java b/src/main/java/org/opensearch/security/support/MapUtils.java index 65aa6f7f9d..a50505eae9 100644 --- a/src/main/java/org/opensearch/security/support/MapUtils.java +++ b/src/main/java/org/opensearch/security/support/MapUtils.java @@ -32,11 +32,11 @@ import java.util.Map; public class MapUtils { - + public static void deepTraverseMap(final Map map, final Callback cb) { deepTraverseMap(map, cb, null); } - + private static void deepTraverseMap(final Map map, final Callback cb, final List stack) { final List localStack; if(stack == null) { @@ -58,7 +58,7 @@ private static void deepTraverseMap(final Map map, final Callbac } } } - + public static interface Callback { public void call(String key, Map map, List stack); } diff --git a/src/main/java/org/opensearch/security/support/ModuleInfo.java b/src/main/java/org/opensearch/security/support/ModuleInfo.java index 994e49046c..c8558b1c13 100644 --- a/src/main/java/org/opensearch/security/support/ModuleInfo.java +++ b/src/main/java/org/opensearch/security/support/ModuleInfo.java @@ -36,16 +36,16 @@ import org.opensearch.common.io.stream.Writeable; public class ModuleInfo implements Serializable, Writeable{ - + private static final long serialVersionUID = -1077651823194285138L; - + private ModuleType moduleType; private String classname; private String classpath = ""; private String version = ""; private String buildTime = ""; private String gitsha1 = ""; - + public ModuleInfo(ModuleType moduleType, String classname) { assert(moduleType != null); this.moduleType = moduleType; @@ -73,7 +73,7 @@ public void setVersion(String version) { public void setBuildTime(String buildTime) { this.buildTime = buildTime; } - + public String getGitsha1() { return gitsha1; } @@ -85,7 +85,7 @@ public void setGitsha1(String gitsha1) { public ModuleType getModuleType() { return moduleType; } - + public Map getAsMap() { Map infoMap = new HashMap<>(); infoMap.put("type", moduleType.name()); @@ -99,7 +99,7 @@ public Map getAsMap() { infoMap.put("gitsha1", this.gitsha1); return infoMap; } - + @Override public void writeTo(StreamOutput out) throws IOException { out.writeEnum(moduleType); @@ -109,8 +109,8 @@ public void writeTo(StreamOutput out) throws IOException { out.writeString(buildTime); out.writeString(gitsha1); } - - + + /* (non-Javadoc) * @see java.lang.Object#hashCode() @@ -180,5 +180,5 @@ public boolean equals(Object obj) { public String toString() { return "Module [type=" + this.moduleType.name() + ", implementing class=" + this.classname + "]"; } - + } diff --git a/src/main/java/org/opensearch/security/support/PemKeyReader.java b/src/main/java/org/opensearch/security/support/PemKeyReader.java index fb3a595f9e..7d37fee4f3 100644 --- a/src/main/java/org/opensearch/security/support/PemKeyReader.java +++ b/src/main/java/org/opensearch/security/support/PemKeyReader.java @@ -72,12 +72,12 @@ import org.opensearch.env.Environment; public final class PemKeyReader { - + //private static final String[] EMPTY_STRING_ARRAY = new String[0]; protected static final Logger log = LogManager.getLogger(PemKeyReader.class); static final String JKS = "JKS"; static final String PKCS12 = "PKCS12"; - + private static final Pattern KEY_PATTERN = Pattern.compile( "-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header "([a-z0-9+/=\\r\\n]+)" + // Base64 text @@ -147,7 +147,7 @@ private static void safeClose(OutputStream out) { //ignore } } - + public static PrivateKey toPrivateKey(File keyFile, String keyPassword) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, InvalidAlgorithmParameterException, KeyException, IOException { if (keyFile == null) { @@ -155,7 +155,7 @@ public static PrivateKey toPrivateKey(File keyFile, String keyPassword) throws N } return getPrivateKeyFromByteBuffer(PemKeyReader.readPrivateKey(keyFile), keyPassword); } - + public static PrivateKey toPrivateKey(InputStream in, String keyPassword) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, InvalidAlgorithmParameterException, KeyException, IOException { if (in == null) { @@ -182,7 +182,7 @@ private static PrivateKey getPrivateKeyFromByteBuffer(byte[] encodedKey, String } } } - + private static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, InvalidKeyException, InvalidAlgorithmParameterException { @@ -201,27 +201,27 @@ private static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key) return encryptedPrivateKeyInfo.getKeySpec(cipher); } - + public static X509Certificate loadCertificateFromFile(String file) throws Exception { if(file == null) { return null; } - + CertificateFactory fact = CertificateFactory.getInstance("X.509"); try(FileInputStream is = new FileInputStream(file)) { return (X509Certificate) fact.generateCertificate(is); } } - + public static X509Certificate loadCertificateFromStream(InputStream in) throws Exception { if(in == null) { return null; } - + CertificateFactory fact = CertificateFactory.getInstance("X.509"); return (X509Certificate) fact.generateCertificate(in); } - + public static KeyStore loadKeyStore(String storePath, String keyStorePassword, String type) throws Exception { if(storePath == null) { return null; @@ -230,36 +230,36 @@ public static KeyStore loadKeyStore(String storePath, String keyStorePassword, S if(type == null || !type.toUpperCase().equals(JKS) || !type.toUpperCase().equals(PKCS12)) { type = JKS; } - + final KeyStore store = KeyStore.getInstance(type.toUpperCase()); store.load(new FileInputStream(storePath), keyStorePassword==null?null:keyStorePassword.toCharArray()); return store; } public static PrivateKey loadKeyFromFile(String password, String keyFile) throws Exception { - + if(keyFile == null) { return null; } - + return PemKeyReader.toPrivateKey(new File(keyFile), password); } - + public static PrivateKey loadKeyFromStream(String password, InputStream in) throws Exception { - + if(in == null) { return null; } - + return PemKeyReader.toPrivateKey(in, password); } - + public static void checkPath(String keystoreFilePath, String fileNameLogOnly) { - + if (keystoreFilePath == null || keystoreFilePath.length() == 0) { throw new OpenSearchException("Empty file path for "+fileNameLogOnly); } - + if (Files.isDirectory(Paths.get(keystoreFilePath), LinkOption.NOFOLLOW_LINKS)) { throw new OpenSearchException("Is a directory: " + keystoreFilePath+" Expected a file for "+fileNameLogOnly); } @@ -268,34 +268,34 @@ public static void checkPath(String keystoreFilePath, String fileNameLogOnly) { throw new OpenSearchException("Unable to read " + keystoreFilePath + " ("+Paths.get(keystoreFilePath)+"). Please make sure this files exists and is readable regarding to permissions. Property: "+fileNameLogOnly); } } - + public static X509Certificate[] loadCertificatesFromFile(String file) throws Exception { if(file == null) { return null; } - + try(FileInputStream is = new FileInputStream(file)) { return loadCertificatesFromStream(is); } - + } public static X509Certificate[] loadCertificatesFromFile(File file) throws Exception { if(file == null) { return null; } - + try(FileInputStream is = new FileInputStream(file)) { return loadCertificatesFromStream(is); } - + } - + public static X509Certificate[] loadCertificatesFromStream(InputStream in) throws Exception { if(in == null) { return null; } - + CertificateFactory fact = CertificateFactory.getInstance("X.509"); Collection certs = fact.generateCertificates(in); X509Certificate[] x509Certs = new X509Certificate[certs.size()]; @@ -304,21 +304,21 @@ public static X509Certificate[] loadCertificatesFromStream(InputStream in) throw x509Certs[i++] = (X509Certificate) cert; } return x509Certs; - + } - + public static InputStream resolveStream(String propName, Settings settings) { final String content = settings.get(propName, null); - + if(content == null) { return null; } return new ByteArrayInputStream(content.getBytes(StandardCharsets.US_ASCII)); } - - public static String resolve(String propName, Settings settings, Path configPath, boolean mustBeValid) { + + public static String resolve(String propName, Settings settings, Path configPath, boolean mustBeValid) { final String originalPath = settings.get(propName, null); return resolve(originalPath, propName, settings, configPath, mustBeValid); } @@ -327,32 +327,32 @@ public static String resolve(String originalPath, String propName, Settings sett log.debug("Path is is {}", originalPath); String path = originalPath; final Environment env = new Environment(settings, configPath); - + if(env != null && originalPath != null && originalPath.length() > 0) { path = env.configDir().resolve(originalPath).toAbsolutePath().toString(); log.debug("Resolved {} to {} against {}", originalPath, path, env.configDir().toAbsolutePath().toString()); } - + if(mustBeValid) { checkPath(path, propName); } - + if("".equals(path)) { path = null; } - - return path; + + return path; } - + public static KeyStore toTruststore(final String trustCertificatesAliasPrefix, final X509Certificate[] trustCertificates) throws Exception { - + if(trustCertificates == null) { return null; } - + KeyStore ks = KeyStore.getInstance(JKS); ks.load(null); - + if(trustCertificates != null && trustCertificates.length > 0) { for (int i = 0; i < trustCertificates.length; i++) { X509Certificate x509Certificate = trustCertificates[i]; @@ -361,10 +361,10 @@ public static KeyStore toTruststore(final String trustCertificatesAliasPrefix, f } return ks; } - + public static KeyStore toKeystore(final String authenticationCertificateAlias, final char[] password, final X509Certificate authenticationCertificate[], final PrivateKey authenticationKey) throws Exception { - if(authenticationCertificateAlias != null && authenticationCertificate != null && authenticationKey != null) { + if(authenticationCertificateAlias != null && authenticationCertificate != null && authenticationKey != null) { KeyStore ks = KeyStore.getInstance(JKS); ks.load(null, null); ks.setKeyEntry(authenticationCertificateAlias, authenticationKey, password, authenticationCertificate); @@ -374,7 +374,7 @@ public static KeyStore toKeystore(final String authenticationCertificateAlias, f } } - + public static char[] randomChars(int len) { final SecureRandom r = new SecureRandom(); final char[] ret = new char[len]; diff --git a/src/main/java/org/opensearch/security/support/ReflectiveAttributeAccessors.java b/src/main/java/org/opensearch/security/support/ReflectiveAttributeAccessors.java index 8f12ffaf61..8ddc52f428 100644 --- a/src/main/java/org/opensearch/security/support/ReflectiveAttributeAccessors.java +++ b/src/main/java/org/opensearch/security/support/ReflectiveAttributeAccessors.java @@ -28,7 +28,7 @@ public static Function objectAttr(String name) { public static Function objectAttr(String name, Class type) { return new ReflectiveAttributeGetter(name, type); } - + public static Function protectedObjectAttr(String name, Class type) { return new ProtectedReflectiveAttributeGetter(name, type); } @@ -114,7 +114,7 @@ public R apply(O object) { } } - + static class ReflectiveAttributeSetter implements BiFunction { private final String attribute; private final String methodName; diff --git a/src/main/java/org/opensearch/security/support/SecurityJsonNode.java b/src/main/java/org/opensearch/security/support/SecurityJsonNode.java index 472ab2bb86..d1e2f4d34e 100644 --- a/src/main/java/org/opensearch/security/support/SecurityJsonNode.java +++ b/src/main/java/org/opensearch/security/support/SecurityJsonNode.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2018 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.support; @@ -28,22 +28,22 @@ import org.opensearch.security.DefaultObjectMapper; public final class SecurityJsonNode { - + private final JsonNode node; public SecurityJsonNode(JsonNode node) { this.node = node; } - + public SecurityJsonNode get(String name) { if(isNull(node)) { return new SecurityJsonNode(null); } - + JsonNode val = node.get(name); return new SecurityJsonNode(val); } - + public String asString() { if(isNull(node)) { return null; @@ -51,11 +51,11 @@ public String asString() { return node.asText(null); } } - + private static boolean isNull(JsonNode node) { return node == null || node.isNull(); } - + public boolean isNull() { return isNull(this.node); } @@ -73,25 +73,25 @@ public SecurityJsonNode getDotted(String string) { for(String part: string.split("\\.")) { tmp = tmp.get(part); } - + return tmp; - + } public List asList() { if(isNull(node) || node.getNodeType() != JsonNodeType.ARRAY) { return null; } - + List retVal = new ArrayList(); - + for(int i=0; i> map, final String in .findAny() .orElse(null); } - + @SafeVarargs public static Map mapFromArray(T ... keyValues) { if(keyValues == null) { @@ -109,25 +109,25 @@ public static Map mapFromArray(T ... keyValues) { return null; } Map map = new HashMap<>(); - + for(int i = 0; i resolveOriginalIndices(RestoreSnapshotRequest restoreRequest) { final SnapshotInfo snapshotInfo = getSnapshotInfo(restoreRequest); @@ -57,17 +57,17 @@ public static List resolveOriginalIndices(RestoreSnapshotRequest restore return null; } else { return SnapshotUtils.filterIndices(snapshotInfo.indices(), restoreRequest.indices(), restoreRequest.indicesOptions()); - } - - + } + + } - + public static SnapshotInfo getSnapshotInfo(RestoreSnapshotRequest restoreRequest) { final RepositoriesService repositoriesService = Objects.requireNonNull(OpenSearchSecurityPlugin.GuiceHolder.getRepositoriesService(), "RepositoriesService not initialized"); final Repository repository = repositoriesService.repository(restoreRequest.repository()); final String threadName = Thread.currentThread().getName(); SnapshotInfo snapshotInfo = null; - + try { setCurrentThreadName("[" + ThreadPool.Names.GENERIC + "]"); for (SnapshotId snapshotId : PlainActionFuture.get(repository::getRepositoryData).getSnapshotIds()) { @@ -86,7 +86,7 @@ public static SnapshotInfo getSnapshotInfo(RestoreSnapshotRequest restoreRequest } return snapshotInfo; } - + @SuppressWarnings("removal") private static void setCurrentThreadName(final String name) { final SecurityManager sm = System.getSecurityManager(); @@ -94,7 +94,7 @@ private static void setCurrentThreadName(final String name) { if (sm != null) { sm.checkPermission(new SpecialPermission()); } - + AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { @@ -103,5 +103,5 @@ public Object run() { } }); } - + } diff --git a/src/main/java/org/opensearch/security/support/SourceFieldsContext.java b/src/main/java/org/opensearch/security/support/SourceFieldsContext.java index 175415a5b6..8f61bcbaf5 100644 --- a/src/main/java/org/opensearch/security/support/SourceFieldsContext.java +++ b/src/main/java/org/opensearch/security/support/SourceFieldsContext.java @@ -97,7 +97,7 @@ public String[] getExcludes() { public boolean hasIncludesOrExcludes() { return (includes != null && includes.length > 0) || (excludes != null && excludes.length > 0); } - + public boolean isFetchSource() { return fetchSource; } diff --git a/src/main/java/org/opensearch/security/support/WildcardMatcher.java b/src/main/java/org/opensearch/security/support/WildcardMatcher.java index f22f981fd2..302736fe67 100644 --- a/src/main/java/org/opensearch/security/support/WildcardMatcher.java +++ b/src/main/java/org/opensearch/security/support/WildcardMatcher.java @@ -296,7 +296,7 @@ public static List getAllMatchingPatterns(final Collection val = Migration.migrateConfig(SecurityDynamicConfiguration.fromNode(DefaultObjectMapper.YAML_MAPPER.readTree(file), CType.CONFIG, 1, 0, 0)); return backupAndWrite(file, val, backup); @@ -121,12 +121,12 @@ public static boolean migrateFile(File file, CType cType, boolean backup) { boolean roles = backupAndWrite(file, tup.v1(), backup); return roles && backupAndWrite(new File(file.getParent(),"tenants.yml"), tup.v2(), backup); } - + if(cType == CType.ROLESMAPPING) { SecurityDynamicConfiguration val = Migration.migrateRoleMappings(SecurityDynamicConfiguration.fromNode(DefaultObjectMapper.YAML_MAPPER.readTree(file), CType.ROLESMAPPING, 1, 0, 0)); return backupAndWrite(file, val, backup); } - + if(cType == CType.INTERNALUSERS) { SecurityDynamicConfiguration val = Migration.migrateInternalUsers(SecurityDynamicConfiguration.fromNode(DefaultObjectMapper.YAML_MAPPER.readTree(file), CType.INTERNALUSERS, 1, 0, 0)); return backupAndWrite(file, val, backup); @@ -147,11 +147,11 @@ public static boolean migrateFile(File file, CType cType, boolean backup) { } catch (Exception e) { System.out.println("Can not migrate "+file+" due to "+e); } - - + + return false; } - + private static boolean backupAndWrite(File file, SecurityDynamicConfiguration val, boolean backup) { try { if(val == null) { @@ -169,7 +169,7 @@ private static boolean backupAndWrite(File file, SecurityDynamicConfiguration System.out.println(" Details: "+e.getMessage()); e.printStackTrace(); } - + return false; } } diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 2427a48f8a..738e0e6b20 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -162,7 +162,7 @@ public class SecurityAdmin { private static final Settings ENABLE_ALL_ALLOCATIONS_SETTINGS = Settings.builder() .put("cluster.routing.allocation.enable", "all") .build(); - + public static void main(final String[] args) { try { final int returnCode = execute(args); @@ -173,7 +173,7 @@ public static void main(final String[] args) { System.out.println(ExceptionsHelper.stackTrace(e)); System.out.println(); System.exit(-1); - } + } catch (IndexNotFoundException e) { System.out.println("ERR: No OpenSearch Security configuration index found. Please execute securityadmin with different command line parameters"); System.out.println("When you run it for the first time do not specify -us, -era, -dra or -rl"); @@ -181,16 +181,16 @@ public static void main(final String[] args) { System.exit(-1); } catch (Throwable e) { - + if (e instanceof OpenSearchException - && e.getMessage() != null + && e.getMessage() != null && e.getMessage().contains("no permissions")) { - System.out.println("ERR: You try to connect with a TLS node certificate instead of an admin client certificate"); + System.out.println("ERR: You try to connect with a TLS node certificate instead of an admin client certificate"); System.out.println(); System.exit(-1); } - + System.out.println("ERR: An unexpected "+e.getClass().getSimpleName()+" occured: "+e.getMessage()); System.out.println("Trace:"); System.out.println(ExceptionsHelper.stackTrace(e)); @@ -200,7 +200,7 @@ public static void main(final String[] args) { } public static int execute(final String[] args) throws Exception { - + System.out.println("Security Admin v7"); System.setProperty("security.nowarn.client","true"); System.setProperty("jdk.tls.rejectClientInitiatedRenegotiation","true"); @@ -256,16 +256,16 @@ public static int execute(final String[] args) throws Exception { options.addOption(Option.builder("backup").hasArg().argName("folder").desc("Backup configuration to folder").build()); options.addOption(Option.builder("migrate").hasArg().argName("folder").desc("Migrate and use folder to store migrated files").build()); - + options.addOption(Option.builder("rev").longOpt("resolve-env-vars").desc("Resolve/Substitute env vars in config with their value before uploading").build()); options.addOption(Option.builder("vc").numberOfArgs(1).optionalArg(true).argName("version").longOpt("validate-configs").desc("Validate config for version 6 or 7 (default 7)").build()); options.addOption(Option.builder("mo").longOpt("migrate-offline").hasArg().argName("folder").desc("Migrate and use folder to store migrated files").build()); - + //when adding new options also adjust validate(CommandLine line) - + String hostname = "localhost"; int port = 9200; String kspass = System.getenv(OPENDISTRO_SECURITY_KS_PASS); @@ -293,7 +293,7 @@ public static int execute(final String[] args) throws Exception { boolean deleteConfigIndex = false; boolean enableShardAllocation = false; boolean acceptRedCluster = false; - + String keypass = System.getenv(OPENDISTRO_SECURITY_KEYPASS); String cacert = null; String cert = null; @@ -316,28 +316,28 @@ public static int execute(final String[] args) throws Exception { CommandLineParser parser = new DefaultParser(); try { CommandLine line = parser.parse( options, args ); - + validate(line); - + hostname = line.getOptionValue("h", hostname); port = Integer.parseInt(line.getOptionValue("p", String.valueOf(port))); promptForPassword = line.hasOption("prompt"); - + if(kspass == null || kspass.isEmpty()) { kspass = line.getOptionValue("kspass",promptForPassword?null:"changeit"); } - + if(tspass == null || tspass.isEmpty()) { tspass = line.getOptionValue("tspass",promptForPassword?null:kspass); } cd = line.getOptionValue("cd", cd); - + if(!cd.endsWith(File.separator)) { cd += File.separator; } - + ks = line.getOptionValue("ks",ks); ts = line.getOptionValue("ts",ts); kst = line.getOptionValue("kst", kst); @@ -349,87 +349,87 @@ public static int execute(final String[] args) throws Exception { retrieve = line.hasOption("r"); ksAlias = line.getOptionValue("ksalias", ksAlias); index = line.getOptionValue("i", index); - + String enabledCiphersString = line.getOptionValue("ec", null); String enabledProtocolsString = line.getOptionValue("ep", null); - + if(enabledCiphersString != null) { enabledCiphers = enabledCiphersString.split(","); } - + if(enabledProtocolsString != null) { enabledProtocols = enabledProtocolsString.split(","); } - + updateSettings = line.hasOption("us")?Integer.parseInt(line.getOptionValue("us")):null; reload = line.hasOption("rl"); - + if(line.hasOption("era")) { replicaAutoExpand = true; } - + if(line.hasOption("dra")) { replicaAutoExpand = false; } - + failFast = line.hasOption("ff"); diagnose = line.hasOption("dg"); deleteConfigIndex = line.hasOption("dci"); enableShardAllocation = line.hasOption("esa"); acceptRedCluster = line.hasOption("arc"); - + cacert = line.getOptionValue("cacert"); cert = line.getOptionValue("cert"); key = line.getOptionValue("key"); keypass = line.getOptionValue("keypass", keypass); si = line.hasOption("si"); - + whoami = line.hasOption("w"); - + explicitReplicas = line.getOptionValue("er", explicitReplicas); - + backup = line.getOptionValue("backup"); - + migrate = line.getOptionValue("migrate"); - + resolveEnvVars = line.hasOption("rev"); - + validateConfig = !line.hasOption("vc")?null:Integer.parseInt(line.getOptionValue("vc", "7")); - + if(validateConfig != null && validateConfig.intValue() != 6 && validateConfig.intValue() != 7) { throw new ParseException("version must be 6 or 7"); } - + migrateOffline = line.getOptionValue("mo"); - + } catch( ParseException exp ) { System.out.println("ERR: Parsing failed. Reason: " + exp.getMessage()); formatter.printHelp("securityadmin.sh", options, true); return -1; } - + if(validateConfig != null) { System.out.println("Validate configuration for Version "+validateConfig.intValue()); return validateConfig(cd, file, type, validateConfig.intValue()); } - + if(migrateOffline != null) { System.out.println("Migrate "+migrateOffline+" offline"); final boolean retVal = Migrater.migrateDirectory(new File(migrateOffline), true); return retVal?0:-1; } - + System.out.print("Will connect to "+hostname+":"+port); Socket socket = new Socket(); - + try { - + socket.connect(new InetSocketAddress(hostname, port)); - + } catch (java.net.ConnectException ex) { System.out.println(); System.out.println("ERR: Seems there is no OpenSearch running on "+hostname+":"+port+" - Will exit"); @@ -450,13 +450,13 @@ public static int execute(final String[] args) throws Exception { kspass = promptForPassword("Keystore", "kspass", OPENDISTRO_SECURITY_KS_PASS); } } - + if(ts != null) { tst = tst==null?(ts.endsWith(".jks")?"JKS":"PKCS12"):tst; if(tspass == null && promptForPassword) { tspass = promptForPassword("Truststore", "tspass", OPENDISTRO_SECURITY_TS_PASS); } - } + } if(key != null) { @@ -549,7 +549,7 @@ public static int execute(final String[] args) throws Exception { System.out.println("Reload config on all nodes"); return 0; } - + if(si) { return (0); } @@ -558,9 +558,9 @@ public static int execute(final String[] args) throws Exception { System.out.println(whoAmIResNode.toPrettyString()); return (0); } - - - if(replicaAutoExpand != null) { + + + if(replicaAutoExpand != null) { Settings indexSettings = Settings.builder() .put("index.auto_expand_replicas", replicaAutoExpand?"0-all":"false") .build(); @@ -594,10 +594,10 @@ public static int execute(final String[] args) throws Exception { } else { System.out.println("ERR: Unable to enable shard allocation"); } - + return (successful?0:-1); - } - + } + if(failFast) { System.out.println("Fail-fast is activated"); } @@ -620,11 +620,11 @@ public static int execute(final String[] args) throws Exception { } catch (Exception e) { Throwable rootCause = ExceptionUtils.getRootCause(e); - + if(!failFast) { System.out.println("Cannot retrieve cluster state due to: "+e.getMessage()+". This is not an error, will keep on trying ..."); System.out.println(" Root cause: "+rootCause+" ("+e.getClass().getName()+"/"+rootCause.getClass().getName()+")"); - System.out.println(" * Try running securityadmin.sh with -icl (but no -cl) and -nhnv (If that works you need to check your clustername as well as hostnames in your TLS certificates)"); + System.out.println(" * Try running securityadmin.sh with -icl (but no -cl) and -nhnv (If that works you need to check your clustername as well as hostnames in your TLS certificates)"); System.out.println(" * Make sure that your keystore or PEM certificate is a client certificate (not a node certificate) and configured properly in opensearch.yml"); System.out.println(" * If this is not working, try running securityadmin.sh with --diagnose and see diagnose trace log file)"); System.out.println(" * Add --accept-red-cluster to allow securityadmin to operate on a red cluster."); @@ -634,12 +634,12 @@ public static int execute(final String[] args) throws Exception { System.out.println(" Root cause: "+rootCause+" ("+e.getClass().getName()+"/"+rootCause.getClass().getName()+")"); System.out.println(" * Try running securityadmin.sh with -icl (but no -cl) and -nhnv (If that works you need to check your clustername as well as hostnames in your TLS certificates)"); System.out.println(" * Make also sure that your keystore or PEM certificate is a client certificate (not a node certificate) and configured properly in opensearch.yml"); - System.out.println(" * If this is not working, try running securityadmin.sh with --diagnose and see diagnose trace log file)"); + System.out.println(" * If this is not working, try running securityadmin.sh with --diagnose and see diagnose trace log file)"); System.out.println(" * Add --accept-red-cluster to allow securityadmin to operate on a red cluster."); return (-1); } - + Thread.sleep(3000); continue; } @@ -651,7 +651,7 @@ public static int execute(final String[] args) throws Exception { System.out.println("ERR: Timed out while waiting for a green or yellow cluster state."); System.out.println(" * Try running securityadmin.sh with -icl (but no -cl) and -nhnv (If that works you need to check your clustername as well as hostnames in your TLS certificates)"); System.out.println(" * Make also sure that your keystore or PEM certificate is a client certificate (not a node certificate) and configured properly in opensearch.yml"); - System.out.println(" * If this is not working, try running securityadmin.sh with --diagnose and see diagnose trace log file)"); + System.out.println(" * If this is not working, try running securityadmin.sh with --diagnose and see diagnose trace log file)"); System.out.println(" * Add --accept-red-cluster to allow securityadmin to operate on a red cluster."); return (-1); } @@ -680,7 +680,7 @@ public static int execute(final String[] args) throws Exception { if(deleteConfigIndex) { return deleteConfigIndex(restHighLevelClient, index, indexExists); } - + if (!indexExists) { System.out.print(index +" index does not exists, attempt to create it ... "); final int created = createConfigIndex(restHighLevelClient, index, explicitReplicas); @@ -690,7 +690,7 @@ public static int execute(final String[] args) throws Exception { } else { System.out.println(index+" index already exists, so we do not need to create one."); - + try { ClusterHealthResponse clusterHealthResponse = restHighLevelClient.cluster().health(new ClusterHealthRequest(index), RequestOptions.DEFAULT); @@ -705,7 +705,7 @@ public static int execute(final String[] args) throws Exception { if (clusterHealthResponse.getStatus() == ClusterHealthStatus.YELLOW) { System.out.println("INFO: "+index+" index state is YELLOW, it seems you miss some replicas"); } - + } catch (Exception e) { if(!failFast) { System.out.println("Cannot retrieve "+index+" index state state due to "+e.getMessage()+". This is not an error, will keep on trying ..."); @@ -726,7 +726,7 @@ public static int execute(final String[] args) throws Exception { && securityIndex.getMappings() != null && securityIndex.getMappings().get(index) != null && securityIndex.getMappings().get(index).getSourceAsMap().containsKey("security")); - + if(legacy) { System.out.println("Legacy index '"+index+"' (ES 6) detected (or forced). You should migrate the configuration!"); } @@ -765,9 +765,9 @@ public static int execute(final String[] args) throws Exception { } boolean isCdAbs = new File(cd).isAbsolute(); - + System.out.println("Populate config from "+(isCdAbs?cd:new File(".", cd).getCanonicalPath())); - + if(file != null) { if(type != null) { System.out.println("Force type: "+type); @@ -828,7 +828,7 @@ private static boolean checkConfigUpdateResponse(Response response, int expected } else { System.out.println("SUCC: Expected " + expectedConfigCount + " config types for node " + n + " is " + n.get("updated_config_size").asInt() + " (" + n.get("updated_config_types") + ") due to: " + (n.get("message") == null ? "unknown reason" : n.get("message"))); } - + success = success && successNode; } @@ -843,7 +843,7 @@ private static boolean uploadFile(final RestHighLevelClient restHighLevelClient, final boolean populateEmptyIfMissing) { String id = _id; - + if(legacy) { id = _id; @@ -892,12 +892,12 @@ private static boolean retrieveFile(final RestHighLevelClient restHighLevelClien private static boolean retrieveFile(final RestHighLevelClient restHighLevelClient, final String filepath, final String index, final String _id, final boolean legacy, final boolean populateFileIfEmpty) { String id = _id; - + if(legacy) { id = _id; - + } - + System.out.println("Will retrieve '"+"/" +id+"' into "+filepath+" "+(legacy?"(legacy mode)":"")); try (Writer writer = new FileWriter(filepath, StandardCharsets.UTF_8)) { @@ -944,7 +944,7 @@ private static boolean retrieveFile(final RestHighLevelClient restHighLevelClien } catch (Exception e) { System.out.println(" FAIL: Get configuration for '"+_id+"' failed because of "+e.toString()); } - + return false; } @@ -962,23 +962,23 @@ private static BytesReference readXContent(final String content, final MediaType parser.close(); } } - + //validate return retVal; } - + private static String convertToYaml(String type, BytesReference bytes, boolean prettyPrint) throws IOException { - + try (XContentParser parser = JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, bytes.streamInput())) { parser.nextToken(); parser.nextToken(); - + if(!type.equals((parser.currentName()))) { return null; } - + parser.nextToken(); - + XContentBuilder builder = XContentFactory.yamlBuilder(); if (prettyPrint) { builder.prettyPrint(); @@ -991,7 +991,7 @@ private static String convertToYaml(String type, BytesReference bytes, boolean p protected static void generateDiagnoseTrace(final RestHighLevelClient restHighLevelClient) { final String date = DATE_FORMAT.format(new Date()); - + final StringBuilder sb = new StringBuilder(); sb.append("Diagnostic securityadmin trace"+System.lineSeparator()); sb.append("OpenSearch client version: "+Version.CURRENT+System.lineSeparator()); @@ -1061,19 +1061,19 @@ private static void validate(CommandLine line) throws ParseException { if(line.hasOption("ts") && line.hasOption("cacert")) { System.out.println("WARN: It makes no sense to specify -ts as well as -cacert"); } - + if(line.hasOption("ks") && line.hasOption("cert")) { System.out.println("WARN: It makes no sense to specify -ks as well as -cert"); } - + if(line.hasOption("ks") && line.hasOption("key")) { System.out.println("WARN: It makes no sense to specify -ks as well as -key"); } - + if(line.hasOption("cd") && line.hasOption("rl")) { System.out.println("WARN: It makes no sense to specify -cd as well as -r"); } - + if(line.hasOption("cd") && line.hasOption("f")) { System.out.println("WARN: It makes no sense to specify -cd as well as -f"); } @@ -1089,15 +1089,15 @@ private static void validate(CommandLine line) throws ParseException { if(!line.hasOption("vc") && !line.hasOption("ks") && !line.hasOption("cert") /*&& !line.hasOption("simple-auth")*/) { throw new ParseException("Specify at least -ks or -cert"); } - - if(!line.hasOption("vc") && !line.hasOption("mo") + + if(!line.hasOption("vc") && !line.hasOption("mo") && !line.hasOption("ts") && !line.hasOption("cacert")) { throw new ParseException("Specify at least -ts or -cacert"); } - + //TODO add more validation rules } - + private static String promptForPassword(String passwordName, String commandLineOption, String envVarName) throws Exception { final Console console = System.console(); if(console == null) { @@ -1132,7 +1132,7 @@ private static int issueWarnings(RestHighLevelClient restHighLevelClient) throws if(!ALLOW_MIXED) { return -1; } - + } else { System.out.println("OpenSearch Version: "+minVersion.toString()); } @@ -1161,14 +1161,14 @@ private static int deleteConfigIndex(RestHighLevelClient restHighLevelClient, St } else { System.out.print("No index '"+index+"' exists, so no need to delete it"); } - + return (success?0:-1); } private static int createConfigIndex(RestHighLevelClient restHighLevelClient, String index, String explicitReplicas) throws IOException { Map indexSettings = new HashMap<>(); indexSettings.put("index.number_of_shards", 1); - + if(explicitReplicas != null) { if(explicitReplicas.contains("-")) { indexSettings.put("index.auto_expand_replicas", explicitReplicas); @@ -1218,11 +1218,11 @@ private static int upload(RestHighLevelClient tc, String index, String cd, boole boolean success = uploadFile(tc, cd + "config.yml", index, "config", legacy, resolveEnvVars); success = uploadFile(tc, cd+"roles.yml", index, "roles", legacy, resolveEnvVars) && success; success = uploadFile(tc, cd+"roles_mapping.yml", index, "rolesmapping", legacy, resolveEnvVars) && success; - + success = uploadFile(tc, cd+"internal_users.yml", index, "internalusers", legacy, resolveEnvVars) && success; success = uploadFile(tc, cd+"action_groups.yml", index, "actiongroups", legacy, resolveEnvVars) && success; - + if(!legacy) { success = uploadFile(tc, cd+"tenants.yml", index, "tenants", legacy, resolveEnvVars) && success; } @@ -1252,18 +1252,18 @@ private static int migrate(RestHighLevelClient tc, String index, File backupDir, System.out.println("== Migration started =="); System.out.println("======================="); - + System.out.println("-> Backup current configuration to "+backupDir.getAbsolutePath()); - + if(backup(tc, index, backupDir, true) != 0) { return -1; } System.out.println(" done"); - + File v7Dir = new File(backupDir,"v7"); v7Dir.mkdirs(); - + try { System.out.println("-> Migrate configuration to new format and store it here: "+v7Dir.getAbsolutePath()); @@ -1303,13 +1303,13 @@ private static int migrate(RestHighLevelClient tc, String index, File backupDir, e.printStackTrace(); return -1; } - + System.out.println(" done"); - + System.out.println("-> Delete old "+index+" index"); deleteConfigIndex(tc, index, true); System.out.println(" done"); - + System.out.println("-> Upload new configuration into OpenSearch cluster"); int uploadResult = upload(tc, index, v7Dir.getAbsolutePath() + "/", false, expectedNodeCount, resolveEnvVars); @@ -1319,10 +1319,10 @@ private static int migrate(RestHighLevelClient tc, String index, File backupDir, }else { System.out.println(" ERR: unable to upload"); } - + return uploadResult; } - + private static String readTypeFromFile(File file) throws IOException { if(!file.exists() || !file.isFile()) { System.out.println("ERR: No such file "+file.getAbsolutePath()); @@ -1335,16 +1335,16 @@ private static String readTypeFromFile(File file) throws IOException { private static int validateConfig(String cd, String file, String type, int version) { if (file != null) { try { - + if(type == null) { type = readTypeFromFile(new File(file)); } - + if(type == null) { System.out.println("ERR: Unable to read type from "+file); return -1; } - + ConfigHelper.fromYamlFile(file, CType.fromString(type), version==7?2:1, 0, 0); return 0; } catch (Exception e) { @@ -1357,7 +1357,7 @@ private static int validateConfig(String cd, String file, String type, int versi success = validateConfigFile(cd+"roles.yml", CType.ROLES, version) && success; success = validateConfigFile(cd+"roles_mapping.yml", CType.ROLESMAPPING, version) && success; success = validateConfigFile(cd+"config.yml", CType.CONFIG, version) && success; - + if(new File(cd+"tenants.yml").exists() && version != 6) { success = validateConfigFile(cd+"tenants.yml", CType.TENANTS, version) && success; } @@ -1368,10 +1368,10 @@ private static int validateConfig(String cd, String file, String type, int versi return success?0:-1; } - + return -1; } - + private static boolean validateConfigFile(String file, CType cType, int version) { try { ConfigHelper.fromYamlFile(file, cType, version==7?2:1, 0, 0); diff --git a/src/main/java/org/opensearch/security/transport/DefaultInterClusterRequestEvaluator.java b/src/main/java/org/opensearch/security/transport/DefaultInterClusterRequestEvaluator.java index 6a7917b385..84087b5ed9 100644 --- a/src/main/java/org/opensearch/security/transport/DefaultInterClusterRequestEvaluator.java +++ b/src/main/java/org/opensearch/security/transport/DefaultInterClusterRequestEvaluator.java @@ -81,9 +81,9 @@ private WildcardMatcher getNodesDnToEvaluate() { @Override public boolean isInterClusterRequest(TransportRequest request, X509Certificate[] localCerts, X509Certificate[] peerCerts, final String principal) { - + String[] principals = new String[2]; - + if (principal != null && principal.length() > 0) { principals[0] = principal; principals[1] = principal.replace(" ",""); @@ -93,14 +93,14 @@ public boolean isInterClusterRequest(TransportRequest request, X509Certificate[] final boolean isTraceEnabled = log.isTraceEnabled(); if (principals[0] != null && nodesDn.matchAny(principals)) { - + if (isTraceEnabled) { log.trace("Treat certificate with principal {} as other node because of it matches one of {}", Arrays.toString(principals), nodesDn); } - + return true; - + } else { if (isTraceEnabled) { log.trace("Treat certificate with principal {} NOT as other node because we it does not matches one of {}", Arrays.toString(principals), diff --git a/src/main/java/org/opensearch/security/transport/InterClusterRequestEvaluator.java b/src/main/java/org/opensearch/security/transport/InterClusterRequestEvaluator.java index 38f4426420..81cfa9e345 100644 --- a/src/main/java/org/opensearch/security/transport/InterClusterRequestEvaluator.java +++ b/src/main/java/org/opensearch/security/transport/InterClusterRequestEvaluator.java @@ -42,18 +42,18 @@ public interface InterClusterRequestEvaluator { /** * Determine if request is a message from * another node in the cluster - * + * * @param request The transport request to evaluate * @param localCerts Local certs to use for evaluating the request which include criteria * specific to the implementation for confirming intercluster * communication - * + * * @param peerCerts Certs to use for evaluating the request which include criteria * specific to the implementation for confirming intercluster * communication - * + * * @param principal The principal evaluated by the configured principal extractor - * + * * @return True when determined to be intercluster, false otherwise */ boolean isInterClusterRequest(final TransportRequest request, final X509Certificate[] localCerts, final X509Certificate[] peerCerts, diff --git a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java index e17560a000..5456d36d9c 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java +++ b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java @@ -142,7 +142,7 @@ public void sendRequestDecorate(AsyncSender sender || k.equals(ConfigConstants.OPENDISTRO_SECURITY_DOC_ALLOWLIST_HEADER) || k.equals(ConfigConstants.OPENDISTRO_SECURITY_FILTER_LEVEL_DLS_DONE) || k.equals(ConfigConstants.OPENDISTRO_SECURITY_DLS_MODE_HEADER) - || k.equals(ConfigConstants.OPENDISTRO_SECURITY_DLS_FILTER_LEVEL_QUERY_HEADER) + || k.equals(ConfigConstants.OPENDISTRO_SECURITY_DLS_FILTER_LEVEL_QUERY_HEADER) || (k.equals("_opendistro_security_source_field_context") && ! (request instanceof SearchRequest) && !(request instanceof GetRequest)) || k.startsWith("_opendistro_security_trace") || k.startsWith(ConfigConstants.OPENDISTRO_SECURITY_INITIAL_ACTION_CLASS_HEADER) @@ -269,18 +269,18 @@ public T read(StreamInput in) throws IOException { @Override public void handleResponse(T response) { - + ThreadContext threadContext = getThreadContext(); Map> responseHeaders = threadContext.getResponseHeaders(); List flsResponseHeader = responseHeaders.get(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER); List dlsResponseHeader = responseHeaders.get(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER); List maskedFieldsResponseHeader = responseHeaders.get(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER); - + contextToRestore.restore(); final boolean isDebugEnabled = log.isDebugEnabled(); - if (response instanceof ClusterSearchShardsResponse) { + if (response instanceof ClusterSearchShardsResponse) { if (flsResponseHeader != null && !flsResponseHeader.isEmpty()) { if (isDebugEnabled) { log.debug("add flsResponseHeader as transient"); diff --git a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java index c6c80ba50e..4a2919fdb2 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java +++ b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java @@ -296,7 +296,7 @@ else if(!Strings.isNullOrEmpty(injectedUserHeader)) { } } } - + private void putInitialActionClassHeader(String initialActionClassValue, String resolvedActionClass) { if(initialActionClassValue == null) { if(getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_INITIAL_ACTION_CLASS_HEADER) == null) { diff --git a/src/main/java/org/opensearch/security/user/CustomAttributesAware.java b/src/main/java/org/opensearch/security/user/CustomAttributesAware.java index 89d62d3d5d..f5db96db8a 100644 --- a/src/main/java/org/opensearch/security/user/CustomAttributesAware.java +++ b/src/main/java/org/opensearch/security/user/CustomAttributesAware.java @@ -29,6 +29,6 @@ import java.util.Map; public interface CustomAttributesAware { - + Map getCustomAttributesMap(); } diff --git a/src/main/java/org/opensearch/security/user/User.java b/src/main/java/org/opensearch/security/user/User.java index 83c7ea2eb5..86089afd35 100644 --- a/src/main/java/org/opensearch/security/user/User.java +++ b/src/main/java/org/opensearch/security/user/User.java @@ -56,7 +56,7 @@ public class User implements Serializable, Writeable, CustomAttributesAware { // This is to be used in scenarios where some of the nodes do not have security enabled, and therefore do not pass any user information in threadcontext, yet we need the communication to not break between the nodes. // Attach the required permissions to either the user or the backend role. public static final User DEFAULT_TRANSPORT_USER = new User("opendistro_security_default_transport_user", Lists.newArrayList("opendistro_security_default_transport_backendrole"), null); - + private static final long serialVersionUID = -5500938501822658596L; private final String name; /** @@ -76,10 +76,10 @@ public User(final StreamInput in) throws IOException { attributes = Collections.synchronizedMap(in.readMap(StreamInput::readString, StreamInput::readString)); securityRoles.addAll(in.readList(StreamInput::readString)); } - + /** * Create a new authenticated user - * + * * @param name The username (must not be null or empty) * @param roles Roles of which the user is a member off (maybe null) * @param customAttributes Custom attributes associated with this (maybe null) @@ -97,7 +97,7 @@ public User(final String name, final Collection roles, final AuthCredent if (roles != null) { this.addRoles(roles); } - + if(customAttributes != null) { this.attributes.putAll(customAttributes.getAttributes()); } @@ -106,7 +106,7 @@ public User(final String name, final Collection roles, final AuthCredent /** * Create a new authenticated user without roles and attributes - * + * * @param name The username (must not be null or empty) * @throws IllegalArgumentException if name is null or empty */ @@ -119,7 +119,7 @@ public final String getName() { } /** - * + * * @return A unmodifiable set of the backend roles this user is a member of */ public final Set getRoles() { @@ -128,7 +128,7 @@ public final Set getRoles() { /** * Associate this user with a backend role - * + * * @param role The backend role */ public final void addRole(final String role) { @@ -137,7 +137,7 @@ public final void addRole(final String role) { /** * Associate this user with a set of backend roles - * + * * @param roles The backend roles */ public final void addRoles(final Collection roles) { @@ -148,7 +148,7 @@ public final void addRoles(final Collection roles) { /** * Check if this user is a member of a backend role - * + * * @param role The backend role * @return true if this user is a member of the backend role, false otherwise */ @@ -158,7 +158,7 @@ public final boolean isUserInRole(final String role) { /** * Associate this user with a set of backend roles - * + * * @param roles The backend roles */ public final void addAttributes(final Map attributes) { @@ -166,7 +166,7 @@ public final void addAttributes(final Map attributes) { this.attributes.putAll(attributes); } } - + public final String getRequestedTenant() { return requestedTenant; } @@ -174,8 +174,8 @@ public final String getRequestedTenant() { public final void setRequestedTenant(String requestedTenant) { this.requestedTenant = requestedTenant; } - - + + public boolean isInjected() { return isInjected; } @@ -225,7 +225,7 @@ public final boolean equals(final Object obj) { /** * Copy all backend roles from another user - * + * * @param user The user from which the backend roles should be copied over */ public final void copyRolesFrom(final User user) { @@ -245,7 +245,7 @@ public void writeTo(StreamOutput out) throws IOException { /** * Get the custom attributes associated with this user - * + * * @return A modifiable map with all the current custom attributes associated with this user */ public synchronized final Map getCustomAttributesMap() { @@ -254,13 +254,13 @@ public synchronized final Map getCustomAttributesMap() { } return attributes; } - + public final void addSecurityRoles(final Collection securityRoles) { if(securityRoles != null && this.securityRoles != null) { this.securityRoles.addAll(securityRoles); } } - + public final Set getSecurityRoles() { return this.securityRoles == null ? Collections.synchronizedSet(Collections.emptySet()) : Collections.unmodifiableSet(this.securityRoles); } diff --git a/src/main/java/org/opensearch/security/util/ratetracking/HeapBasedRateTracker.java b/src/main/java/org/opensearch/security/util/ratetracking/HeapBasedRateTracker.java index 37a9817b34..537d4d8fc7 100644 --- a/src/main/java/org/opensearch/security/util/ratetracking/HeapBasedRateTracker.java +++ b/src/main/java/org/opensearch/security/util/ratetracking/HeapBasedRateTracker.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.util.ratetracking; @@ -131,7 +131,7 @@ private void shiftFull(long timestamp) { this.startTime = timestamp; return; } - + int shiftOffset = this.timeOffsets[this.timeOffsetStart]; this.startTime += shiftOffset; diff --git a/src/main/java/org/opensearch/security/util/ratetracking/RateTracker.java b/src/main/java/org/opensearch/security/util/ratetracking/RateTracker.java index 5de531f0cb..c90680cd4a 100644 --- a/src/main/java/org/opensearch/security/util/ratetracking/RateTracker.java +++ b/src/main/java/org/opensearch/security/util/ratetracking/RateTracker.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.util.ratetracking; diff --git a/src/main/java/org/opensearch/security/util/ratetracking/SingleTryRateTracker.java b/src/main/java/org/opensearch/security/util/ratetracking/SingleTryRateTracker.java index c56639d04a..4f6bd371ed 100644 --- a/src/main/java/org/opensearch/security/util/ratetracking/SingleTryRateTracker.java +++ b/src/main/java/org/opensearch/security/util/ratetracking/SingleTryRateTracker.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.util.ratetracking; diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticatorTest.java index 0aeb4df082..04a30ba2db 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticatorTest.java @@ -41,7 +41,7 @@ public class HTTPJwtAuthenticatorTest { final static byte[] secretKeyBytes = new byte[1024]; final static SecretKey secretKey; - + static { new SecureRandom().nextBytes(secretKeyBytes); secretKey = Keys.hmacShaKeyFor(secretKeyBytes); @@ -50,29 +50,29 @@ public class HTTPJwtAuthenticatorTest { @Test public void testNoKey() throws Exception { - final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder(), - Jwts.builder().setSubject("Leonard McCoy")); + final AuthCredentials credentials = extractCredentialsFromJwtHeader(Settings.builder(), Jwts.builder().setSubject("Leonard McCoy")); Assert.assertNull(credentials); } @Test public void testEmptyKey() throws Exception { - + final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", ""), - Jwts.builder().setSubject("Leonard McCoy")); + Settings.builder().put("signing_key", ""), + Jwts.builder().setSubject("Leonard McCoy") + ); - Assert.assertNull(credentials); + Assert.assertNull(credentials); } @Test public void testBadKey() throws Exception { final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(new byte[]{1,3,3,4,3,6,7,8,3,10})), - Jwts.builder().setSubject("Leonard McCoy")); + Settings.builder().put("signing_key", BaseEncoding.base64().encode(new byte[] { 1, 3, 3, 4, 3, 6, 7, 8, 3, 10 })), + Jwts.builder().setSubject("Leonard McCoy") + ); Assert.assertNull(credentials); } @@ -99,7 +99,7 @@ public void testInvalid() throws Exception { HTTPJwtAuthenticator jwtAuth = new HTTPJwtAuthenticator(settings, null); Map headers = new HashMap(); - headers.put("Authorization", "Bearer "+jwsToken); + headers.put("Authorization", "Bearer " + jwsToken); AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap()), null); Assert.assertNull(credentials); @@ -110,11 +110,15 @@ public void testBearer() throws Exception { Settings settings = Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).build(); - String jwsToken = Jwts.builder().setSubject("Leonard McCoy").setAudience("myaud").signWith(secretKey, SignatureAlgorithm.HS512).compact(); + String jwsToken = Jwts.builder() + .setSubject("Leonard McCoy") + .setAudience("myaud") + .signWith(secretKey, SignatureAlgorithm.HS512) + .compact(); HTTPJwtAuthenticator jwtAuth = new HTTPJwtAuthenticator(settings, null); Map headers = new HashMap(); - headers.put("Authorization", "Bearer "+jwsToken); + headers.put("Authorization", "Bearer " + jwsToken); AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap()), null); @@ -140,7 +144,6 @@ public void testBearerWrongPosition() throws Exception { Assert.assertNull(credentials); } - @Test public void testBasicAuthHeader() throws Exception { Settings settings = Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).build(); @@ -157,8 +160,9 @@ public void testBasicAuthHeader() throws Exception { public void testRoles() throws Exception { final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("roles_key", "roles"), - Jwts.builder().setSubject("Leonard McCoy").claim("roles", "role1,role2")); + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("roles_key", "roles"), + Jwts.builder().setSubject("Leonard McCoy").claim("roles", "role1,role2") + ); Assert.assertNotNull(credentials); Assert.assertEquals("Leonard McCoy", credentials.getUsername()); @@ -169,8 +173,9 @@ public void testRoles() throws Exception { public void testNullClaim() throws Exception { final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("roles_key", "roles"), - Jwts.builder().setSubject("Leonard McCoy").claim("roles", null)); + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("roles_key", "roles"), + Jwts.builder().setSubject("Leonard McCoy").claim("roles", null) + ); Assert.assertNotNull(credentials); Assert.assertEquals("Leonard McCoy", credentials.getUsername()); @@ -181,21 +186,23 @@ public void testNullClaim() throws Exception { public void testNonStringClaim() throws Exception { final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("roles_key", "roles"), - Jwts.builder().setSubject("Leonard McCoy").claim("roles", 123L)); + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("roles_key", "roles"), + Jwts.builder().setSubject("Leonard McCoy").claim("roles", 123L) + ); Assert.assertNotNull(credentials); Assert.assertEquals("Leonard McCoy", credentials.getUsername()); Assert.assertEquals(1, credentials.getBackendRoles().size()); - Assert.assertTrue( credentials.getBackendRoles().contains("123")); + Assert.assertTrue(credentials.getBackendRoles().contains("123")); } @Test public void testRolesMissing() throws Exception { final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("roles_key", "roles"), - Jwts.builder().setSubject("Leonard McCoy")); + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("roles_key", "roles"), + Jwts.builder().setSubject("Leonard McCoy") + ); Assert.assertNotNull(credentials); Assert.assertEquals("Leonard McCoy", credentials.getUsername()); @@ -206,8 +213,9 @@ public void testRolesMissing() throws Exception { public void testWrongSubjectKey() throws Exception { final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("subject_key", "missing"), - Jwts.builder().claim("roles", "role1,role2").claim("asub", "Dr. Who")); + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("subject_key", "missing"), + Jwts.builder().claim("roles", "role1,role2").claim("asub", "Dr. Who") + ); Assert.assertNull(credentials); } @@ -216,8 +224,9 @@ public void testWrongSubjectKey() throws Exception { public void testAlternativeSubject() throws Exception { final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("subject_key", "asub"), - Jwts.builder().setSubject("Leonard McCoy").claim("roles", "role1,role2").claim("asub", "Dr. Who")); + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("subject_key", "asub"), + Jwts.builder().setSubject("Leonard McCoy").claim("roles", "role1,role2").claim("asub", "Dr. Who") + ); Assert.assertNotNull(credentials); Assert.assertEquals("Dr. Who", credentials.getUsername()); @@ -228,8 +237,9 @@ public void testAlternativeSubject() throws Exception { public void testNonStringAlternativeSubject() throws Exception { final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("subject_key", "asub"), - Jwts.builder().setSubject("Leonard McCoy").claim("roles", "role1,role2").claim("asub", false)); + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("subject_key", "asub"), + Jwts.builder().setSubject("Leonard McCoy").claim("roles", "role1,role2").claim("asub", false) + ); Assert.assertNotNull(credentials); Assert.assertEquals("false", credentials.getUsername()); @@ -239,7 +249,10 @@ public void testNonStringAlternativeSubject() throws Exception { @Test public void testUrlParam() throws Exception { - Settings settings = Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("jwt_url_parameter", "abc").build(); + Settings settings = Settings.builder() + .put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)) + .put("jwt_url_parameter", "abc") + .build(); String jwsToken = Jwts.builder().setSubject("Leonard McCoy").signWith(secretKey, SignatureAlgorithm.HS512).compact(); @@ -259,8 +272,9 @@ public void testUrlParam() throws Exception { public void testExp() throws Exception { final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)), - Jwts.builder().setSubject("Expired").setExpiration(new Date(100))); + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)), + Jwts.builder().setSubject("Expired").setExpiration(new Date(100)) + ); Assert.assertNull(credentials); } @@ -269,9 +283,10 @@ public void testExp() throws Exception { public void testNbf() throws Exception { final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)), - Jwts.builder().setSubject("Expired").setNotBefore(new Date(System.currentTimeMillis()+(1000*36000)))); - + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)), + Jwts.builder().setSubject("Expired").setNotBefore(new Date(System.currentTimeMillis() + (1000 * 36000))) + ); + Assert.assertNull(credentials); } @@ -285,11 +300,16 @@ public void testRS256() throws Exception { PublicKey pub = pair.getPublic(); String jwsToken = Jwts.builder().setSubject("Leonard McCoy").signWith(priv, SignatureAlgorithm.RS256).compact(); - Settings settings = Settings.builder().put("signing_key", "-----BEGIN PUBLIC KEY-----\n"+BaseEncoding.base64().encode(pub.getEncoded())+"-----END PUBLIC KEY-----").build(); + Settings settings = Settings.builder() + .put( + "signing_key", + "-----BEGIN PUBLIC KEY-----\n" + BaseEncoding.base64().encode(pub.getEncoded()) + "-----END PUBLIC KEY-----" + ) + .build(); HTTPJwtAuthenticator jwtAuth = new HTTPJwtAuthenticator(settings, null); Map headers = new HashMap(); - headers.put("Authorization", "Bearer "+jwsToken); + headers.put("Authorization", "Bearer " + jwsToken); AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap()), null); @@ -306,10 +326,10 @@ public void testES512() throws Exception { KeyPair pair = keyGen.generateKeyPair(); PrivateKey priv = pair.getPrivate(); PublicKey pub = pair.getPublic(); - + Settings settings = Settings.builder().put("signing_key", BaseEncoding.base64().encode(pub.getEncoded())).build(); String jwsToken = Jwts.builder().setSubject("Leonard McCoy").signWith(priv, SignatureAlgorithm.ES512).compact(); - + HTTPJwtAuthenticator jwtAuth = new HTTPJwtAuthenticator(settings, null); Map headers = new HashMap(); headers.put("Authorization", jwsToken); @@ -324,16 +344,13 @@ public void testES512() throws Exception { @Test public void testRolesArray() throws Exception { - JwtBuilder builder = Jwts.builder() - .setPayload("{"+ - "\"sub\": \"John Doe\","+ - "\"roles\": [\"a\",\"b\",\"3rd\"]"+ - "}"); + JwtBuilder builder = Jwts.builder().setPayload("{" + "\"sub\": \"John Doe\"," + "\"roles\": [\"a\",\"b\",\"3rd\"]" + "}"); final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("roles_key", "roles"), - builder); - + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("roles_key", "roles"), + builder + ); + Assert.assertNotNull(credentials); Assert.assertEquals("John Doe", credentials.getUsername()); Assert.assertEquals(3, credentials.getBackendRoles().size()); @@ -346,9 +363,10 @@ public void testRolesArray() throws Exception { public void testRequiredAudienceWithCorrectAudience() { final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("required_audience", "test_audience"), - Jwts.builder().setSubject("Leonard McCoy").setAudience("test_audience")); - + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("required_audience", "test_audience"), + Jwts.builder().setSubject("Leonard McCoy").setAudience("test_audience") + ); + Assert.assertNotNull(credentials); Assert.assertEquals("Leonard McCoy", credentials.getUsername()); } @@ -356,19 +374,21 @@ public void testRequiredAudienceWithCorrectAudience() { @Test public void testRequiredAudienceWithIncorrectAudience() { - final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("required_audience", "test_audience"), - Jwts.builder().setSubject("Leonard McCoy").setAudience("wrong_audience")); - + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("required_audience", "test_audience"), + Jwts.builder().setSubject("Leonard McCoy").setAudience("wrong_audience") + ); + Assert.assertNull(credentials); } @Test public void testRequiredIssuerWithCorrectAudience() { - + final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("required_issuer", "test_issuer"), - Jwts.builder().setSubject("Leonard McCoy").setIssuer("test_issuer")); + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("required_issuer", "test_issuer"), + Jwts.builder().setSubject("Leonard McCoy").setIssuer("test_issuer") + ); Assert.assertNotNull(credentials); Assert.assertEquals("Leonard McCoy", credentials.getUsername()); @@ -376,18 +396,17 @@ public void testRequiredIssuerWithCorrectAudience() { @Test public void testRequiredIssuerWithIncorrectAudience() { - - final AuthCredentials credentials = extractCredentialsFromJwtHeader( - Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("required_issuer", "test_issuer"), - Jwts.builder().setSubject("Leonard McCoy").setIssuer("wrong_issuer")); - + + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).put("required_issuer", "test_issuer"), + Jwts.builder().setSubject("Leonard McCoy").setIssuer("wrong_issuer") + ); + Assert.assertNull(credentials); } /** extracts a default user credential from a request header */ - private AuthCredentials extractCredentialsFromJwtHeader( - final Settings.Builder settingsBuilder, - final JwtBuilder jwtBuilder) { + private AuthCredentials extractCredentialsFromJwtHeader(final Settings.Builder settingsBuilder, final JwtBuilder jwtBuilder) { final Settings settings = settingsBuilder.build(); final String jwsToken = jwtBuilder.signWith(secretKey, SignatureAlgorithm.HS512).compact(); final HTTPJwtAuthenticator jwtAuth = new HTTPJwtAuthenticator(settings, null); diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/CxfTestTools.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/CxfTestTools.java index e9920995ac..b2958193c2 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/CxfTestTools.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/CxfTestTools.java @@ -16,7 +16,7 @@ class CxfTestTools { - static String toJson(JsonMapObject jsonMapObject) { - return new JsonMapObjectReaderWriter().toJson(jsonMapObject); - } + static String toJson(JsonMapObject jsonMapObject) { + return new JsonMapObjectReaderWriter().toJson(jsonMapObject); + } } diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java index 3bcaba970f..578fef7202 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java @@ -25,340 +25,369 @@ public class HTTPJwtKeyByOpenIdConnectAuthenticatorTest { - protected static MockIpdServer mockIdpServer; - - @BeforeClass - public static void setUp() throws Exception { - mockIdpServer = new MockIpdServer(TestJwk.Jwks.ALL); - } - - @AfterClass - public static void tearDown() { - if (mockIdpServer != null) { - try { - mockIdpServer.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - @Test - public void basicTest() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( - ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap()), null); - - Assert.assertNotNull(creds); - Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); - Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); - Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(4, creds.getAttributes().size()); - } - - - @Test - public void jwksUriTest() { - Settings settings = Settings.builder() - .put("jwks_uri", mockIdpServer.getJwksUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( - ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()), null); - - Assert.assertNotNull(creds); - Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); - Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); - Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(4, creds.getAttributes().size()); - } - - @Test - public void jwksMissingRequiredIssuerInClaimTest() { - Settings settings = Settings.builder() - .put("jwks_uri", mockIdpServer.getJwksUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( - ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_NO_ISSUER_OCT_1), new HashMap<>()), null); - - Assert.assertNull(creds); - } - - @Test - public void jwksNotMatchingRequiredIssuerInClaimTest() { - Settings settings = Settings.builder() - .put("jwks_uri", mockIdpServer.getJwksUri()) - .put("required_issuer", "Wrong Issuer") - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( - ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()), null); - - Assert.assertNull(creds); - } - - @Test - public void jwksMissingRequiredAudienceInClaimTest() { - Settings settings = Settings.builder() - .put("jwks_uri", mockIdpServer.getJwksUri()) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( - ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_NO_AUDIENCE_OCT_1), new HashMap<>()), null); - - Assert.assertNull(creds); - } - - @Test - public void jwksNotMatchingRequiredAudienceInClaimTest() { - Settings settings = Settings.builder() - .put("jwks_uri", mockIdpServer.getJwksUri()) - .put("required_audience", "Wrong Audience") - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( - ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()), null); - - Assert.assertNull(creds); - } - - @Test - public void jwksUriMissingTest() { - var exception = Assert.assertThrows(Exception.class, () -> { - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(Settings.builder().build(), null); - jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap<>()), - null); - }); - - Assert.assertEquals("Authentication backend failed", exception.getMessage()); - Assert.assertEquals(OpenSearchSecurityException.class, exception.getClass()); - } - - @Test - public void testEscapeKid() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( - ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1_INVALID_KID), new HashMap()), null); - - Assert.assertNotNull(creds); - Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); - Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); - Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(4, creds.getAttributes().size()); - } - - @Test - public void bearerTest() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1), - new HashMap()), - null); - - Assert.assertNotNull(creds); - Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); - Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); - Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(4, creds.getAttributes().size()); - } - - @Test - public void testRoles() throws Exception { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("roles_key", TestJwts.ROLES_CLAIM) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( - ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap()), null); - - Assert.assertNotNull(creds); - Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); - Assert.assertEquals(TestJwts.TEST_ROLES, creds.getBackendRoles()); - } - - @Test - public void testExp() throws Exception { - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_EXPIRED_SIGNED_OCT_1), - new HashMap()), - null); - - Assert.assertNull(creds); - } - - @Test - public void testExpInSkew() throws Exception { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("jwt_clock_skew_tolerance_seconds", "10") - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - long expiringDate = System.currentTimeMillis()/1000-5; - long notBeforeDate = System.currentTimeMillis()/1000-25; - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest( - ImmutableMap.of( - "Authorization", - "Bearer "+TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), - new HashMap()), - null); - - Assert.assertNotNull(creds); - } - - @Test - public void testNbf() throws Exception { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("jwt_clock_skew_tolerance_seconds", "0") - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - long expiringDate = 20+System.currentTimeMillis()/1000; - long notBeforeDate = 5+System.currentTimeMillis()/1000; - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest( - ImmutableMap.of( - "Authorization", - "Bearer "+TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), - new HashMap()), - null); - - Assert.assertNull(creds); - } - - @Test - public void testNbfInSkew() throws Exception { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("jwt_clock_skew_tolerance_seconds", "10") - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - long expiringDate = 20+System.currentTimeMillis()/1000; - long notBeforeDate = 5+System.currentTimeMillis()/1000;; - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest( - ImmutableMap.of("Authorization", "Bearer "+TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), - new HashMap()), - null); - - Assert.assertNotNull(creds); - } - - - @Test - public void testRS256() throws Exception { - - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( - ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_1), new HashMap()), null); - - Assert.assertNotNull(creds); - Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); - Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); - Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(4, creds.getAttributes().size()); - } - - @Test - public void testBadSignature() throws Exception { - - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials(new FakeRestRequest( - ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_X), new HashMap()), null); - - Assert.assertNull(creds); - } - - @Test - public void testPeculiarJsonEscaping() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.PeculiarEscaping.MC_COY_SIGNED_RSA_1), new HashMap()), - null); - - Assert.assertNotNull(creds); - Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); - Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); - Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(4, creds.getAttributes().size()); - } + protected static MockIpdServer mockIdpServer; + + @BeforeClass + public static void setUp() throws Exception { + mockIdpServer = new MockIpdServer(TestJwk.Jwks.ALL); + } + + @AfterClass + public static void tearDown() { + if (mockIdpServer != null) { + try { + mockIdpServer.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Test + public void basicTest() { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap()), + null + ); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); + Assert.assertEquals(0, creds.getBackendRoles().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + } + + @Test + public void jwksUriTest() { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()), + null + ); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); + Assert.assertEquals(0, creds.getBackendRoles().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + } + + @Test + public void jwksMissingRequiredIssuerInClaimTest() { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_NO_ISSUER_OCT_1), new HashMap<>()), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void jwksNotMatchingRequiredIssuerInClaimTest() { + Settings settings = Settings.builder().put("jwks_uri", mockIdpServer.getJwksUri()).put("required_issuer", "Wrong Issuer").build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void jwksMissingRequiredAudienceInClaimTest() { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_NO_AUDIENCE_OCT_1), new HashMap<>()), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void jwksNotMatchingRequiredAudienceInClaimTest() { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_audience", "Wrong Audience") + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void jwksUriMissingTest() { + var exception = Assert.assertThrows(Exception.class, () -> { + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(Settings.builder().build(), null); + jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap<>()), + null + ); + }); + + Assert.assertEquals("Authentication backend failed", exception.getMessage()); + Assert.assertEquals(OpenSearchSecurityException.class, exception.getClass()); + } + + @Test + public void testEscapeKid() { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1_INVALID_KID), + new HashMap() + ), + null + ); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); + Assert.assertEquals(0, creds.getBackendRoles().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + } + + @Test + public void bearerTest() { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1), new HashMap()), + null + ); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); + Assert.assertEquals(0, creds.getBackendRoles().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + } + + @Test + public void testRoles() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("roles_key", TestJwts.ROLES_CLAIM) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap()), + null + ); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_ROLES, creds.getBackendRoles()); + } + + @Test + public void testExp() throws Exception { + Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_EXPIRED_SIGNED_OCT_1), + new HashMap() + ), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void testExpInSkew() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("jwt_clock_skew_tolerance_seconds", "10") + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + long expiringDate = System.currentTimeMillis() / 1000 - 5; + long notBeforeDate = System.currentTimeMillis() / 1000 - 25; + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), + new HashMap() + ), + null + ); + + Assert.assertNotNull(creds); + } + + @Test + public void testNbf() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("jwt_clock_skew_tolerance_seconds", "0") + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + long expiringDate = 20 + System.currentTimeMillis() / 1000; + long notBeforeDate = 5 + System.currentTimeMillis() / 1000; + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), + new HashMap() + ), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void testNbfInSkew() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("jwt_clock_skew_tolerance_seconds", "10") + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + long expiringDate = 20 + System.currentTimeMillis() / 1000; + long notBeforeDate = 5 + System.currentTimeMillis() / 1000; + ; + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), + new HashMap() + ), + null + ); + + Assert.assertNotNull(creds); + } + + @Test + public void testRS256() throws Exception { + + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_1), new HashMap()), + null + ); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); + Assert.assertEquals(0, creds.getBackendRoles().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + } + + @Test + public void testBadSignature() throws Exception { + + Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_X), new HashMap()), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void testPeculiarJsonEscaping() { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", TestJwts.PeculiarEscaping.MC_COY_SIGNED_RSA_1), + new HashMap() + ), + null + ); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); + Assert.assertEquals(0, creds.getBackendRoles().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + } } diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetrieverTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetrieverTest.java index b30a6326b6..07c0250504 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetrieverTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetrieverTest.java @@ -78,12 +78,10 @@ public void cacheTest() { @Test public void clientCertTest() throws Exception { - try (MockIpdServer sslMockIdpServer = new MockIpdServer(TestJwk.Jwks.ALL, SocketUtils.findAvailableTcpPort(), - true) { + try (MockIpdServer sslMockIdpServer = new MockIpdServer(TestJwk.Jwks.ALL, SocketUtils.findAvailableTcpPort(), true) { @Override - protected void handleDiscoverRequest(HttpRequest request, ClassicHttpResponse response, HttpContext context) - throws IOException, HttpException { - + protected void handleDiscoverRequest(HttpRequest request, ClassicHttpResponse response, HttpContext context) throws IOException, + HttpException { SSLSession sslSession = ((HttpCoreContext) context).getSSLSession(); @@ -92,8 +90,7 @@ protected void handleDiscoverRequest(HttpRequest request, ClassicHttpResponse re try { String sha256Fingerprint = Hashing.sha256().hashBytes(peerCert.getEncoded()).toString(); - Assert.assertEquals("04b2b8baea7a0a893f0223d95b72081e9a1e154a0f9b1b4e75998085972b1b68", - sha256Fingerprint); + Assert.assertEquals("04b2b8baea7a0a893f0223d95b72081e9a1e154a0f9b1b4e75998085972b1b68", sha256Fingerprint); } catch (CertificateEncodingException e) { throw new RuntimeException(e); @@ -105,13 +102,11 @@ protected void handleDiscoverRequest(HttpRequest request, ClassicHttpResponse re SSLContextBuilder sslContextBuilder = SSLContexts.custom(); KeyStore trustStore = KeyStore.getInstance("JKS"); - InputStream trustStream = new FileInputStream( - FileHelper.getAbsoluteFilePathFromClassPath("jwt/truststore.jks").toFile()); + InputStream trustStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("jwt/truststore.jks").toFile()); trustStore.load(trustStream, "changeit".toCharArray()); KeyStore keyStore = KeyStore.getInstance("JKS"); - InputStream keyStream = new FileInputStream( - FileHelper.getAbsoluteFilePathFromClassPath("jwt/spock-keystore.jks").toFile()); + InputStream keyStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("jwt/spock-keystore.jks").toFile()); keyStore.load(keyStream, "changeit".toCharArray()); @@ -126,8 +121,19 @@ public String chooseAlias(Map aliases, SSLParameters }); SettingsBasedSSLConfigurator.SSLConfig sslConfig = new SettingsBasedSSLConfigurator.SSLConfig( - sslContextBuilder.build(), new String[] { "TLSv1.2", "TLSv1.1" }, null, null, false, false, false, - trustStore, null, keyStore, null, null); + sslContextBuilder.build(), + new String[] { "TLSv1.2", "TLSv1.1" }, + null, + null, + false, + false, + false, + trustStore, + null, + keyStore, + null, + null + ); KeySetRetriever keySetRetriever = new KeySetRetriever(sslMockIdpServer.getDiscoverUri(), sslConfig, false); diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java index 35e51919cb..68f852da5c 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java @@ -45,127 +45,131 @@ import static com.amazon.dlic.auth.http.jwt.keybyoidc.CxfTestTools.toJson; class MockIpdServer implements Closeable { - final static String CTX_DISCOVER = "/discover"; - final static String CTX_KEYS = "/api/oauth/keys"; - - private final HttpServer httpServer; - private final int port; - private final String uri; - private final boolean ssl; - private final JsonWebKeys jwks; - - MockIpdServer(JsonWebKeys jwks) throws IOException { - this(jwks, SocketUtils.findAvailableTcpPort(), false); - } - - MockIpdServer(JsonWebKeys jwks, int port, boolean ssl) throws IOException { - this.port = port; - this.uri = (ssl ? "https" : "http") + "://localhost:" + port; - this.ssl = ssl; - this.jwks = jwks; - - ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap().setListenerPort(port) - .register(CTX_DISCOVER, new HttpRequestHandler() { - - @Override - public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { - handleDiscoverRequest(request, response, context); - } - }).register(CTX_KEYS, new HttpRequestHandler() { - - @Override - public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { - handleKeysRequest(request, response, context); - } - }); - - if (ssl) { - serverBootstrap = serverBootstrap.setSslContext(createSSLContext()) - .setSslSetupHandler(new Callback() { - @Override - public void execute(SSLParameters object) { - object.setNeedClientAuth(true); - } - }).setConnectionFactory(new HttpConnectionFactory() { - @Override - public DefaultBHttpServerConnection createConnection(final Socket socket) throws IOException { - final DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection(ssl ? "https" : "http", Http1Config.DEFAULT); - conn.bind(socket); - return conn; - } - }); - } - - this.httpServer = serverBootstrap.create(); - - httpServer.start(); - } - - @Override - public void close() throws IOException { - httpServer.stop(); - } - - public HttpServer getHttpServer() { - return httpServer; - } - - public String getUri() { - return uri; - } - - public String getDiscoverUri() { - return uri + CTX_DISCOVER; - } - - public String getJwksUri() { - return uri + CTX_KEYS; - } - - public int getPort() { - return port; - } - - protected void handleDiscoverRequest(HttpRequest request, ClassicHttpResponse response, HttpContext context) - throws HttpException, IOException { - response.setCode(200); - response.setHeader("Cache-Control", "public, max-age=31536000"); - response.setEntity(new StringEntity("{\"jwks_uri\": \"" + uri + CTX_KEYS + "\",\n" + "\"issuer\": \"" + uri - + "\", \"unknownPropertyToBeIgnored\": 42}")); - } - - protected void handleKeysRequest(HttpRequest request, ClassicHttpResponse response, HttpContext context) - throws HttpException, IOException { - response.setCode(200); - response.setEntity(new StringEntity(toJson(jwks))); - } - - private SSLContext createSSLContext() { - if (!this.ssl) { - return null; - } - - try { - final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - final KeyStore trustStore = KeyStore.getInstance("JKS"); - InputStream trustStream = new FileInputStream( - FileHelper.getAbsoluteFilePathFromClassPath("jwt/truststore.jks").toFile()); - trustStore.load(trustStream, "changeit".toCharArray()); - tmf.init(trustStore); - - final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - final KeyStore keyStore = KeyStore.getInstance("JKS"); - InputStream keyStream = new FileInputStream( - FileHelper.getAbsoluteFilePathFromClassPath("jwt/node-0-keystore.jks").toFile()); - - keyStore.load(keyStream, "changeit".toCharArray()); - kmf.init(keyStore, "changeit".toCharArray()); - - SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); - sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - return sslContext; - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException(e); - } - } + final static String CTX_DISCOVER = "/discover"; + final static String CTX_KEYS = "/api/oauth/keys"; + + private final HttpServer httpServer; + private final int port; + private final String uri; + private final boolean ssl; + private final JsonWebKeys jwks; + + MockIpdServer(JsonWebKeys jwks) throws IOException { + this(jwks, SocketUtils.findAvailableTcpPort(), false); + } + + MockIpdServer(JsonWebKeys jwks, int port, boolean ssl) throws IOException { + this.port = port; + this.uri = (ssl ? "https" : "http") + "://localhost:" + port; + this.ssl = ssl; + this.jwks = jwks; + + ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap() + .setListenerPort(port) + .register(CTX_DISCOVER, new HttpRequestHandler() { + + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { + handleDiscoverRequest(request, response, context); + } + }) + .register(CTX_KEYS, new HttpRequestHandler() { + + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { + handleKeysRequest(request, response, context); + } + }); + + if (ssl) { + serverBootstrap = serverBootstrap.setSslContext(createSSLContext()).setSslSetupHandler(new Callback() { + @Override + public void execute(SSLParameters object) { + object.setNeedClientAuth(true); + } + }).setConnectionFactory(new HttpConnectionFactory() { + @Override + public DefaultBHttpServerConnection createConnection(final Socket socket) throws IOException { + final DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection(ssl ? "https" : "http", Http1Config.DEFAULT); + conn.bind(socket); + return conn; + } + }); + } + + this.httpServer = serverBootstrap.create(); + + httpServer.start(); + } + + @Override + public void close() throws IOException { + httpServer.stop(); + } + + public HttpServer getHttpServer() { + return httpServer; + } + + public String getUri() { + return uri; + } + + public String getDiscoverUri() { + return uri + CTX_DISCOVER; + } + + public String getJwksUri() { + return uri + CTX_KEYS; + } + + public int getPort() { + return port; + } + + protected void handleDiscoverRequest(HttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { + response.setCode(200); + response.setHeader("Cache-Control", "public, max-age=31536000"); + response.setEntity( + new StringEntity( + "{\"jwks_uri\": \"" + uri + CTX_KEYS + "\",\n" + "\"issuer\": \"" + uri + "\", \"unknownPropertyToBeIgnored\": 42}" + ) + ); + } + + protected void handleKeysRequest(HttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { + response.setCode(200); + response.setEntity(new StringEntity(toJson(jwks))); + } + + private SSLContext createSSLContext() { + if (!this.ssl) { + return null; + } + + try { + final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + final KeyStore trustStore = KeyStore.getInstance("JKS"); + InputStream trustStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("jwt/truststore.jks").toFile()); + trustStore.load(trustStream, "changeit".toCharArray()); + tmf.init(trustStore); + + final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + final KeyStore keyStore = KeyStore.getInstance("JKS"); + InputStream keyStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("jwt/node-0-keystore.jks").toFile()); + + keyStore.load(keyStream, "changeit".toCharArray()); + kmf.init(keyStore, "changeit".toCharArray()); + + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + return sslContext; + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SelfRefreshingKeySetTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SelfRefreshingKeySetTest.java index 50bd2fcc0e..6bbce7d85d 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SelfRefreshingKeySetTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SelfRefreshingKeySetTest.java @@ -22,100 +22,97 @@ public class SelfRefreshingKeySetTest { - @Test - public void basicTest() throws AuthenticatorUnavailableException, BadCredentialsException { - SelfRefreshingKeySet selfRefreshingKeySet = new SelfRefreshingKeySet(new MockKeySetProvider()); + @Test + public void basicTest() throws AuthenticatorUnavailableException, BadCredentialsException { + SelfRefreshingKeySet selfRefreshingKeySet = new SelfRefreshingKeySet(new MockKeySetProvider()); - JsonWebKey key1 = selfRefreshingKeySet.getKey("kid/a"); - Assert.assertEquals(TestJwk.OCT_1_K, key1.getProperty("k")); - Assert.assertEquals(1, selfRefreshingKeySet.getRefreshCount()); + JsonWebKey key1 = selfRefreshingKeySet.getKey("kid/a"); + Assert.assertEquals(TestJwk.OCT_1_K, key1.getProperty("k")); + Assert.assertEquals(1, selfRefreshingKeySet.getRefreshCount()); - JsonWebKey key2 = selfRefreshingKeySet.getKey("kid/b"); - Assert.assertEquals(TestJwk.OCT_2_K, key2.getProperty("k")); - Assert.assertEquals(1, selfRefreshingKeySet.getRefreshCount()); + JsonWebKey key2 = selfRefreshingKeySet.getKey("kid/b"); + Assert.assertEquals(TestJwk.OCT_2_K, key2.getProperty("k")); + Assert.assertEquals(1, selfRefreshingKeySet.getRefreshCount()); - try { - selfRefreshingKeySet.getKey("kid/X"); - Assert.fail("Expected a BadCredentialsException"); - } catch (BadCredentialsException e) { - Assert.assertEquals(2, selfRefreshingKeySet.getRefreshCount()); - } + try { + selfRefreshingKeySet.getKey("kid/X"); + Assert.fail("Expected a BadCredentialsException"); + } catch (BadCredentialsException e) { + Assert.assertEquals(2, selfRefreshingKeySet.getRefreshCount()); + } - } + } + @Test(timeout = 10000) + public void twoThreadedTest() throws Exception { + BlockingMockKeySetProvider provider = new BlockingMockKeySetProvider(); + final SelfRefreshingKeySet selfRefreshingKeySet = new SelfRefreshingKeySet(provider); - @Test(timeout = 10000) - public void twoThreadedTest() throws Exception { - BlockingMockKeySetProvider provider = new BlockingMockKeySetProvider(); + ExecutorService executorService = Executors.newCachedThreadPool(); - final SelfRefreshingKeySet selfRefreshingKeySet = new SelfRefreshingKeySet(provider); + Future f1 = executorService.submit(() -> selfRefreshingKeySet.getKey("kid/a")); - ExecutorService executorService = Executors.newCachedThreadPool(); + provider.waitForCalled(); - Future f1 = executorService.submit(() -> selfRefreshingKeySet.getKey("kid/a")); + Future f2 = executorService.submit(() -> selfRefreshingKeySet.getKey("kid/b")); - provider.waitForCalled(); + while (selfRefreshingKeySet.getQueuedGetCount() == 0) { + Thread.sleep(10); + } - Future f2 = executorService.submit(() -> selfRefreshingKeySet.getKey("kid/b")); + provider.unblock(); - while (selfRefreshingKeySet.getQueuedGetCount() == 0) { - Thread.sleep(10); - } + Assert.assertEquals(TestJwk.OCT_1_K, f1.get().getProperty("k")); + Assert.assertEquals(TestJwk.OCT_2_K, f2.get().getProperty("k")); - provider.unblock(); + Assert.assertEquals(1, selfRefreshingKeySet.getRefreshCount()); + Assert.assertEquals(1, selfRefreshingKeySet.getQueuedGetCount()); - Assert.assertEquals(TestJwk.OCT_1_K, f1.get().getProperty("k")); - Assert.assertEquals(TestJwk.OCT_2_K, f2.get().getProperty("k")); + } - Assert.assertEquals(1, selfRefreshingKeySet.getRefreshCount()); - Assert.assertEquals(1, selfRefreshingKeySet.getQueuedGetCount()); + static class MockKeySetProvider implements KeySetProvider { - } + @Override + public JsonWebKeys get() throws AuthenticatorUnavailableException { + return TestJwk.OCT_1_2_3; + } - static class MockKeySetProvider implements KeySetProvider { + } - @Override - public JsonWebKeys get() throws AuthenticatorUnavailableException { - return TestJwk.OCT_1_2_3; - } + static class BlockingMockKeySetProvider extends MockKeySetProvider { + private boolean blocked = true; + private boolean called = false; - } + @Override + public synchronized JsonWebKeys get() throws AuthenticatorUnavailableException { - static class BlockingMockKeySetProvider extends MockKeySetProvider { - private boolean blocked = true; - private boolean called = false; + called = true; + notifyAll(); - @Override - public synchronized JsonWebKeys get() throws AuthenticatorUnavailableException { + waitForUnblock(); - called = true; - notifyAll(); + return super.get(); + } - waitForUnblock(); + public synchronized void unblock() { + blocked = false; + notifyAll(); + } - return super.get(); - } + public synchronized void waitForCalled() throws InterruptedException { + while (!called) { + wait(); + } + } - public synchronized void unblock() { - blocked = false; - notifyAll(); - } + private synchronized void waitForUnblock() { + while (blocked) { + try { + wait(); + } catch (InterruptedException e) {} - public synchronized void waitForCalled() throws InterruptedException { - while (!called) { - wait(); - } - } - - private synchronized void waitForUnblock() { - while (blocked) { - try { - wait(); - } catch (InterruptedException e) { - } - - } - } - } + } + } + } } diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest.java index 4285ee07ae..21f0c362d9 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest.java @@ -23,186 +23,185 @@ public class SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest { - @Test - public void basicTest() throws Exception { - MockIpdServer mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_1); - try { - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_1), - new HashMap()), - null); - - Assert.assertNotNull(creds); - Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); - Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); - Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(4, creds.getAttributes().size()); - - } finally { - try { - mockIdpServer.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - @Test - public void wrongSigTest() throws Exception { - MockIpdServer mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_1); - try { - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_X), - new HashMap()), - null); - - Assert.assertNull(creds); - - } finally { - try { - mockIdpServer.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - @Test - public void noAlgTest() throws Exception { - MockIpdServer mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_1_NO_ALG); - try { - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_1), - new HashMap()), - null); - - Assert.assertNotNull(creds); - Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); - Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); - Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(4, creds.getAttributes().size()); - } finally { - try { - mockIdpServer.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - @Test - public void mismatchedAlgTest() throws Exception { - MockIpdServer mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_1_WRONG_ALG); - try { - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_1), - new HashMap()), - null); - - Assert.assertNull(creds); - - } finally { - try { - mockIdpServer.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - - @Test - public void keyExchangeTest() throws Exception { - MockIpdServer mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_1); - - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - try { - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_1), - new HashMap()), - null); - - Assert.assertNotNull(creds); - Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); - Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); - Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(4, creds.getAttributes().size()); - - creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_2), - new HashMap()), - null); - - Assert.assertNull(creds); - - creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_X), - new HashMap()), - null); - - Assert.assertNull(creds); - - creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_1), - new HashMap()), - null); - - Assert.assertNotNull(creds); - Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); - Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); - Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(4, creds.getAttributes().size()); - - } finally { - try { - mockIdpServer.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_2); - settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); //port changed - jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - try { - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_2), - new HashMap()), - null); - - Assert.assertNotNull(creds); - Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); - Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); - Assert.assertEquals(0, creds.getBackendRoles().size()); - Assert.assertEquals(4, creds.getAttributes().size()); - - } finally { - try { - mockIdpServer.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } + @Test + public void basicTest() throws Exception { + MockIpdServer mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_1); + try { + Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_1), new HashMap()), + null + ); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); + Assert.assertEquals(0, creds.getBackendRoles().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + + } finally { + try { + mockIdpServer.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Test + public void wrongSigTest() throws Exception { + MockIpdServer mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_1); + try { + Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_X), new HashMap()), + null + ); + + Assert.assertNull(creds); + + } finally { + try { + mockIdpServer.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Test + public void noAlgTest() throws Exception { + MockIpdServer mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_1_NO_ALG); + try { + Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_1), new HashMap()), + null + ); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); + Assert.assertEquals(0, creds.getBackendRoles().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + } finally { + try { + mockIdpServer.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Test + public void mismatchedAlgTest() throws Exception { + MockIpdServer mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_1_WRONG_ALG); + try { + Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_1), new HashMap()), + null + ); + + Assert.assertNull(creds); + + } finally { + try { + mockIdpServer.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Test + public void keyExchangeTest() throws Exception { + MockIpdServer mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_1); + + Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + + HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + try { + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_1), new HashMap()), + null + ); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); + Assert.assertEquals(0, creds.getBackendRoles().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + + creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_2), new HashMap()), + null + ); + + Assert.assertNull(creds); + + creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_X), new HashMap()), + null + ); + + Assert.assertNull(creds); + + creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_1), new HashMap()), + null + ); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); + Assert.assertEquals(0, creds.getBackendRoles().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + + } finally { + try { + mockIdpServer.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_2); + settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); // port changed + jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + + try { + AuthCredentials creds = jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_2), new HashMap()), + null + ); + + Assert.assertNotNull(creds); + Assert.assertEquals(TestJwts.MCCOY_SUBJECT, creds.getUsername()); + Assert.assertEquals(TestJwts.TEST_AUDIENCE, creds.getAttributes().get("attr.jwt.aud")); + Assert.assertEquals(0, creds.getBackendRoles().size()); + Assert.assertEquals(4, creds.getAttributes().size()); + + } finally { + try { + mockIdpServer.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } } diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwk.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwk.java index b0f996a03f..5b0d5738a3 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwk.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwk.java @@ -20,94 +20,102 @@ class TestJwk { - // Keys generated with https://mkjwk.org/ - - static final String OCT_1_K = "eTDZjSqRD9Abhod9iqeGX_7o93a-eElTeXWAF6FmzQshmRIrPD-C9ET3pFjJ_IBrzmWIZDk8ig-X_PIyGmKsxNMsrU-0BNWF5gJq5xOp4rYTl8z66Tw9wr8tHLxLxgJqkLSuUCRBZvlZlQ7jNdhBBxgM-hdSSzsN1T33qdIwhrUeJ-KXI5yKUXHjoWFYb9tETbYQ4NvONowkCsXK_flp-E3F_OcKe_z5iVUszAV8QfCod1zhbya540kDejXCL6N_XMmhWJqum7UJ3hgf6DEtroPSnVpHt4iR5w9ArKK-IBgluPght03gNcoNqwz7p77TFbdOmUKF_PWy1bcdbaUoSg"; - static final String OCT_2_K = "YP6Q3IF2qJEagV948dsicXKpG43Ci2W7ZxUpiVTBLZr1vFN9ZGUKxeXGgVWuMFYTmoHvv5AOC8BvoNOpcE3rcJNuNOqTMdujxD92CxjOykiLEKQ0Te_7xQ4LnSQjlqdIJ4U3S7qCnJLd1LxhKOGZcUhE_pjhwf7q2RUUpvC3UOyZZLog9yeflnp9nqqDy5yVqRYWZRcPI06kJTh3Z8IFi2JRJV14iUFQtOHQKuyJRMcsldKnfWl7YW3JdQ9IRN-c1lEYSEBmsavEejcqHZkbli2svqLfmCBJVWffXDRxhq0_VafiL83HC0bP9qeNKivhemw6foVmg8UMs7yJ6ao02A"; - static final String OCT_3_K = "r3aeW3OK7-B4Hs3hq9BmlT1D3jRiolH9PL82XUz9xAS7dniAdmvMnN5GkOc1vqibOe2T-CC_103UglDm9D0iU9S9zn6wTuQt1L5wfZIoHd9f5IjJ_YFEzZMvsoUY_-ji_0K_ugVvBPwi9JnBQHHS4zrgmP06dGjmcnZDcIf4W_iFas3lDYSXilL1V2QhNaynpSqTarpfBGSphKv4Zg2JhsX8xB0VSaTlEq4lF8pzvpWSxXCW9CtomhB80daSuTizrmSTEPpdN3XzQ2-Tovo1ieMOfDU4csvjEk7Bwc2ThjpnA8ucKQUYpUv9joBxKuCdUltssthWnetrogjYOn_xGA"; - - static final JsonWebKey OCT_1 = createOct("kid/a", "HS256", OCT_1_K); - static final JsonWebKey OCT_2 = createOct("kid/b", "HS256", OCT_2_K); - static final JsonWebKey OCT_3 = createOct("kid/c", "HS256", OCT_3_K); - static final JsonWebKey ESCAPED_SLASH_KID_OCT_1 = createOct("kid\\/_a", "HS256", OCT_1_K); - static final JsonWebKey FORWARD_SLASH_KID_OCT_1 = createOct("kid/_a", "HS256", OCT_1_K); - - static final JsonWebKeys OCT_1_2_3 = createJwks(OCT_1, OCT_2, OCT_3, FORWARD_SLASH_KID_OCT_1,ESCAPED_SLASH_KID_OCT_1); - - static final String RSA_1_D = "On8XGMmdM5Fm5hvuhQk-qAkIP2CoK5QMx0OH5m_WDzKXZv8lZ2eg89I4ehBiOKGdw1h_mjmWwTah-evpXV-BF5QpejPQqxkXS-8s5r2AvietQq32jl-gwIwZWTvfzjpT9On0YJZ4q01tMDj3r-YOLUW2xrz3za9tl6pPU_5kP63C-hoj1ybTwcC7ujbCPwhY6yAopMA1v10uVmCxsjsNikEjB6YePgHixez51wO3Z8mXNwefWukFWYJ5T7t4kHMSf5P_8FJZ14u5yvYZnngE_tJCyHFdIDb6UWsrgxomtlQU-SdZYK_NY6gw6mCkjjlqOoYqlsrRJ16kJ81Ds269oQ"; - static final String RSA_1_N = "hMSoV74FRtoaU7xpp0llsXbHE4oUseKoSNga-C_YIXuoGc3pajHh1WtJppZQNYM1Xy07nHchLJAdgqL2_q_Lk8cFHmmL1KTjwPflK9zZ9C0-8QTOrrqU9vkp3gT00jWWJ0HJbUvXIGxPGPnxoJoI--ToE0EWsYEWqWyx1TqYol--oUUPlY5r7vXRKIn5UZNz6VGkW8nI4fXaqDUpXH9uVM9A-nJX2B0Xjwu3VOn2zrgkCZeGTHjNgfLISOTFe9m8lHWLKcuxOWPuCZyCN0C6ZdWB1YP2NhxYFQwQfGV8yfnTImgL-DuV4WPSRVj7W_GJr213-oXBrBR0CnQEPbi_3w"; - static final String RSA_1_E = "AQAB"; - - static final String RSA_2_D = "QQ18k_buZHOSVYzkXL1FaqdodZVNZ_hrBtDcmCVUYjm3dfDVQYt70h8LUdLUCSUA2-_VEwqVdQ-L2FTg7NZVvZJXIyQXp3yrdY1vGKebs3oaIB_VQT8jt-64s12r_8V2ksK2myRrvfm2Fgqi32H5QkspuaQYb9s4NJwKSk7mVAz5dRWQdCx9JNVWknWDJxgHzh3Uku1tNwUOyvSYcRnSZ9X7oWNHaHkSGLEYE_mxD7YXs6HEdCDwc3WuvR5AiVKg2OGec0lL1hY_AWX5UxnR00mhAa0qPytFfaPe-Sc5tQ5regQRqRNDyDESVGIvqXsY8ePjZPOFyoxrcJ2wN3bt4Q"; - static final String RSA_2_N = "lt4EID7tbrE9E8l7VfVGhiwSx4O8nLO5AZo5pJNE1fUy4bM56wH_DeU3YspXh0UvH-vcn4uKjhwJdOCjzalBc2wXD0aRd3JXzWwbjveo6oBFz6kU7VnY8nFMYLMlb6FDcl066OZOtW4PIFtAStXj5rX_J94He3sfTClodpNljTi4qeQwoNsrnZ5Eq82pCp20zCgvbdes8HQBq_QgApvzhL3c-PXd2I_4pBnaPoZwAnufthk7-v8V0Zf5CrDuqEczKKr38pvwggnxZqsfUy2X0bXPBvDXh5B2ljWxWl8tHJbKXzOhfV5Nx5rllJnNabFoVxh3hnlxdOZ88zcaslWBLQ"; - static final String RSA_2_E = "AQAB"; - - static final String RSA_X_D = "iXym57VmwbWvcHtf--xSDPTagEJdnceuErjH6lbuabFXeBx42ZpuAICvo6_YpMcqLybD37ArIu2SD5J_ZBALp4v4KecMPFI5lZr7GKlGgqnForvcC7EWA_ZtZ9uY746cKun8NtemcOlAenn2dvc9NP2S4JtE3FHxmqs2MMmz-ki-ar8-zu0j0HLPLl_Wj2SZ9yCeFmmH3eocX5IRRiWwPnudQJM2t0kt9V-M88YzobqzoMEoFjTfi-owa-w6xGAgJxAUKk02vTiTivH3Qmkk-uAXyj-VtcyzYXD74ICN8EplcAEUKegDR59T4-u18GdpDbPU20XzxDaO4lZiQ7TIEQ"; - static final String RSA_X_N = "jDDVUMXOXDVcaRVAT5TtuiAsLxk7XAAwyyECfmySZul7D5XVLMtGe6rP2900q3nM4BaCEiuwXjmTCZDAGlFGs2a3eQ1vbBSv9_0KGHL-gZGFPNiv0v8aR7QzZ-abhGnRy5F52PlTWsypGgG_kQpF2t2TBotvYhvVPagAt4ljllDKvY1siOvS3nh4TqcUtWcbgQZEWPmaXuhx0eLmhQJca7UEw99YlGNew48AEzt7ZnfU0Qkz3JwSz7IcPx-NfIh6BN6LwAg_ASdoM3MR8rDOtLYavmJVhutrfOpE-4-fw1mf3eLYu7xrxIplSiOIsHunTUssnTiBkXAaGqGJs604Pw"; - static final String RSA_X_E = "AQAB"; - - static final JsonWebKey RSA_1 = createRsa("kid/1", "RS256", RSA_1_E, RSA_1_N, RSA_1_D); - static final JsonWebKey RSA_1_PUBLIC = createRsaPublic("kid/1", "RS256", RSA_1_E, RSA_1_N); - static final JsonWebKey RSA_1_PUBLIC_NO_ALG = createRsaPublic("kid/1", null, RSA_1_E, RSA_1_N); + // Keys generated with https://mkjwk.org/ + + static final String OCT_1_K = + "eTDZjSqRD9Abhod9iqeGX_7o93a-eElTeXWAF6FmzQshmRIrPD-C9ET3pFjJ_IBrzmWIZDk8ig-X_PIyGmKsxNMsrU-0BNWF5gJq5xOp4rYTl8z66Tw9wr8tHLxLxgJqkLSuUCRBZvlZlQ7jNdhBBxgM-hdSSzsN1T33qdIwhrUeJ-KXI5yKUXHjoWFYb9tETbYQ4NvONowkCsXK_flp-E3F_OcKe_z5iVUszAV8QfCod1zhbya540kDejXCL6N_XMmhWJqum7UJ3hgf6DEtroPSnVpHt4iR5w9ArKK-IBgluPght03gNcoNqwz7p77TFbdOmUKF_PWy1bcdbaUoSg"; + static final String OCT_2_K = + "YP6Q3IF2qJEagV948dsicXKpG43Ci2W7ZxUpiVTBLZr1vFN9ZGUKxeXGgVWuMFYTmoHvv5AOC8BvoNOpcE3rcJNuNOqTMdujxD92CxjOykiLEKQ0Te_7xQ4LnSQjlqdIJ4U3S7qCnJLd1LxhKOGZcUhE_pjhwf7q2RUUpvC3UOyZZLog9yeflnp9nqqDy5yVqRYWZRcPI06kJTh3Z8IFi2JRJV14iUFQtOHQKuyJRMcsldKnfWl7YW3JdQ9IRN-c1lEYSEBmsavEejcqHZkbli2svqLfmCBJVWffXDRxhq0_VafiL83HC0bP9qeNKivhemw6foVmg8UMs7yJ6ao02A"; + static final String OCT_3_K = + "r3aeW3OK7-B4Hs3hq9BmlT1D3jRiolH9PL82XUz9xAS7dniAdmvMnN5GkOc1vqibOe2T-CC_103UglDm9D0iU9S9zn6wTuQt1L5wfZIoHd9f5IjJ_YFEzZMvsoUY_-ji_0K_ugVvBPwi9JnBQHHS4zrgmP06dGjmcnZDcIf4W_iFas3lDYSXilL1V2QhNaynpSqTarpfBGSphKv4Zg2JhsX8xB0VSaTlEq4lF8pzvpWSxXCW9CtomhB80daSuTizrmSTEPpdN3XzQ2-Tovo1ieMOfDU4csvjEk7Bwc2ThjpnA8ucKQUYpUv9joBxKuCdUltssthWnetrogjYOn_xGA"; + + static final JsonWebKey OCT_1 = createOct("kid/a", "HS256", OCT_1_K); + static final JsonWebKey OCT_2 = createOct("kid/b", "HS256", OCT_2_K); + static final JsonWebKey OCT_3 = createOct("kid/c", "HS256", OCT_3_K); + static final JsonWebKey ESCAPED_SLASH_KID_OCT_1 = createOct("kid\\/_a", "HS256", OCT_1_K); + static final JsonWebKey FORWARD_SLASH_KID_OCT_1 = createOct("kid/_a", "HS256", OCT_1_K); + + static final JsonWebKeys OCT_1_2_3 = createJwks(OCT_1, OCT_2, OCT_3, FORWARD_SLASH_KID_OCT_1, ESCAPED_SLASH_KID_OCT_1); + + static final String RSA_1_D = + "On8XGMmdM5Fm5hvuhQk-qAkIP2CoK5QMx0OH5m_WDzKXZv8lZ2eg89I4ehBiOKGdw1h_mjmWwTah-evpXV-BF5QpejPQqxkXS-8s5r2AvietQq32jl-gwIwZWTvfzjpT9On0YJZ4q01tMDj3r-YOLUW2xrz3za9tl6pPU_5kP63C-hoj1ybTwcC7ujbCPwhY6yAopMA1v10uVmCxsjsNikEjB6YePgHixez51wO3Z8mXNwefWukFWYJ5T7t4kHMSf5P_8FJZ14u5yvYZnngE_tJCyHFdIDb6UWsrgxomtlQU-SdZYK_NY6gw6mCkjjlqOoYqlsrRJ16kJ81Ds269oQ"; + static final String RSA_1_N = + "hMSoV74FRtoaU7xpp0llsXbHE4oUseKoSNga-C_YIXuoGc3pajHh1WtJppZQNYM1Xy07nHchLJAdgqL2_q_Lk8cFHmmL1KTjwPflK9zZ9C0-8QTOrrqU9vkp3gT00jWWJ0HJbUvXIGxPGPnxoJoI--ToE0EWsYEWqWyx1TqYol--oUUPlY5r7vXRKIn5UZNz6VGkW8nI4fXaqDUpXH9uVM9A-nJX2B0Xjwu3VOn2zrgkCZeGTHjNgfLISOTFe9m8lHWLKcuxOWPuCZyCN0C6ZdWB1YP2NhxYFQwQfGV8yfnTImgL-DuV4WPSRVj7W_GJr213-oXBrBR0CnQEPbi_3w"; + static final String RSA_1_E = "AQAB"; + + static final String RSA_2_D = + "QQ18k_buZHOSVYzkXL1FaqdodZVNZ_hrBtDcmCVUYjm3dfDVQYt70h8LUdLUCSUA2-_VEwqVdQ-L2FTg7NZVvZJXIyQXp3yrdY1vGKebs3oaIB_VQT8jt-64s12r_8V2ksK2myRrvfm2Fgqi32H5QkspuaQYb9s4NJwKSk7mVAz5dRWQdCx9JNVWknWDJxgHzh3Uku1tNwUOyvSYcRnSZ9X7oWNHaHkSGLEYE_mxD7YXs6HEdCDwc3WuvR5AiVKg2OGec0lL1hY_AWX5UxnR00mhAa0qPytFfaPe-Sc5tQ5regQRqRNDyDESVGIvqXsY8ePjZPOFyoxrcJ2wN3bt4Q"; + static final String RSA_2_N = + "lt4EID7tbrE9E8l7VfVGhiwSx4O8nLO5AZo5pJNE1fUy4bM56wH_DeU3YspXh0UvH-vcn4uKjhwJdOCjzalBc2wXD0aRd3JXzWwbjveo6oBFz6kU7VnY8nFMYLMlb6FDcl066OZOtW4PIFtAStXj5rX_J94He3sfTClodpNljTi4qeQwoNsrnZ5Eq82pCp20zCgvbdes8HQBq_QgApvzhL3c-PXd2I_4pBnaPoZwAnufthk7-v8V0Zf5CrDuqEczKKr38pvwggnxZqsfUy2X0bXPBvDXh5B2ljWxWl8tHJbKXzOhfV5Nx5rllJnNabFoVxh3hnlxdOZ88zcaslWBLQ"; + static final String RSA_2_E = "AQAB"; + + static final String RSA_X_D = + "iXym57VmwbWvcHtf--xSDPTagEJdnceuErjH6lbuabFXeBx42ZpuAICvo6_YpMcqLybD37ArIu2SD5J_ZBALp4v4KecMPFI5lZr7GKlGgqnForvcC7EWA_ZtZ9uY746cKun8NtemcOlAenn2dvc9NP2S4JtE3FHxmqs2MMmz-ki-ar8-zu0j0HLPLl_Wj2SZ9yCeFmmH3eocX5IRRiWwPnudQJM2t0kt9V-M88YzobqzoMEoFjTfi-owa-w6xGAgJxAUKk02vTiTivH3Qmkk-uAXyj-VtcyzYXD74ICN8EplcAEUKegDR59T4-u18GdpDbPU20XzxDaO4lZiQ7TIEQ"; + static final String RSA_X_N = + "jDDVUMXOXDVcaRVAT5TtuiAsLxk7XAAwyyECfmySZul7D5XVLMtGe6rP2900q3nM4BaCEiuwXjmTCZDAGlFGs2a3eQ1vbBSv9_0KGHL-gZGFPNiv0v8aR7QzZ-abhGnRy5F52PlTWsypGgG_kQpF2t2TBotvYhvVPagAt4ljllDKvY1siOvS3nh4TqcUtWcbgQZEWPmaXuhx0eLmhQJca7UEw99YlGNew48AEzt7ZnfU0Qkz3JwSz7IcPx-NfIh6BN6LwAg_ASdoM3MR8rDOtLYavmJVhutrfOpE-4-fw1mf3eLYu7xrxIplSiOIsHunTUssnTiBkXAaGqGJs604Pw"; + static final String RSA_X_E = "AQAB"; + + static final JsonWebKey RSA_1 = createRsa("kid/1", "RS256", RSA_1_E, RSA_1_N, RSA_1_D); + static final JsonWebKey RSA_1_PUBLIC = createRsaPublic("kid/1", "RS256", RSA_1_E, RSA_1_N); + static final JsonWebKey RSA_1_PUBLIC_NO_ALG = createRsaPublic("kid/1", null, RSA_1_E, RSA_1_N); static final JsonWebKey RSA_1_PUBLIC_WRONG_ALG = createRsaPublic("kid/1", "HS256", RSA_1_E, RSA_1_N); - static final JsonWebKey RSA_2 = createRsa("kid/2", "RS256", RSA_2_E, RSA_2_N, RSA_2_D); - static final JsonWebKey RSA_2_PUBLIC = createRsaPublic("kid/2", "RS256", RSA_2_E, RSA_2_N); - - static final JsonWebKey RSA_X = createRsa("kid/2", "RS256", RSA_X_E, RSA_X_N, RSA_X_D); - static final JsonWebKey RSA_X_PUBLIC = createRsaPublic("kid/2", "RS256", RSA_X_E, RSA_X_N); + static final JsonWebKey RSA_2 = createRsa("kid/2", "RS256", RSA_2_E, RSA_2_N, RSA_2_D); + static final JsonWebKey RSA_2_PUBLIC = createRsaPublic("kid/2", "RS256", RSA_2_E, RSA_2_N); - static final JsonWebKeys RSA_1_2_PUBLIC = createJwks(RSA_1_PUBLIC, RSA_2_PUBLIC); + static final JsonWebKey RSA_X = createRsa("kid/2", "RS256", RSA_X_E, RSA_X_N, RSA_X_D); + static final JsonWebKey RSA_X_PUBLIC = createRsaPublic("kid/2", "RS256", RSA_X_E, RSA_X_N); - static class Jwks { - static final JsonWebKeys ALL = createJwks(OCT_1, OCT_2, OCT_3, FORWARD_SLASH_KID_OCT_1, RSA_1_PUBLIC, RSA_2_PUBLIC); - static final JsonWebKeys RSA_1 = createJwks(RSA_1_PUBLIC); - static final JsonWebKeys RSA_2 = createJwks(RSA_2_PUBLIC); - static final JsonWebKeys RSA_1_NO_ALG = createJwks(RSA_1_PUBLIC_NO_ALG); - static final JsonWebKeys RSA_1_WRONG_ALG = createJwks(RSA_1_PUBLIC_WRONG_ALG); - } + static final JsonWebKeys RSA_1_2_PUBLIC = createJwks(RSA_1_PUBLIC, RSA_2_PUBLIC); + static class Jwks { + static final JsonWebKeys ALL = createJwks(OCT_1, OCT_2, OCT_3, FORWARD_SLASH_KID_OCT_1, RSA_1_PUBLIC, RSA_2_PUBLIC); + static final JsonWebKeys RSA_1 = createJwks(RSA_1_PUBLIC); + static final JsonWebKeys RSA_2 = createJwks(RSA_2_PUBLIC); + static final JsonWebKeys RSA_1_NO_ALG = createJwks(RSA_1_PUBLIC_NO_ALG); + static final JsonWebKeys RSA_1_WRONG_ALG = createJwks(RSA_1_PUBLIC_WRONG_ALG); + } - private static JsonWebKey createOct(String keyId, String algorithm, String k) { - JsonWebKey result = new JsonWebKey(); + private static JsonWebKey createOct(String keyId, String algorithm, String k) { + JsonWebKey result = new JsonWebKey(); - result.setKeyId(keyId); - result.setKeyType(KeyType.OCTET); - result.setAlgorithm(algorithm); - result.setPublicKeyUse(PublicKeyUse.SIGN); - result.setProperty("k", k); + result.setKeyId(keyId); + result.setKeyType(KeyType.OCTET); + result.setAlgorithm(algorithm); + result.setPublicKeyUse(PublicKeyUse.SIGN); + result.setProperty("k", k); - return result; - } + return result; + } - private static JsonWebKey createRsa(String keyId, String algorithm, String e, String n, String d) { - JsonWebKey result = new JsonWebKey(); + private static JsonWebKey createRsa(String keyId, String algorithm, String e, String n, String d) { + JsonWebKey result = new JsonWebKey(); - result.setKeyId(keyId); - result.setKeyType(KeyType.RSA); - result.setAlgorithm(algorithm); - result.setPublicKeyUse(PublicKeyUse.SIGN); + result.setKeyId(keyId); + result.setKeyType(KeyType.RSA); + result.setAlgorithm(algorithm); + result.setPublicKeyUse(PublicKeyUse.SIGN); - if (d != null) { - result.setProperty("d", d); - } + if (d != null) { + result.setProperty("d", d); + } - result.setProperty("e", e); - result.setProperty("n", n); + result.setProperty("e", e); + result.setProperty("n", n); - return result; - } + return result; + } - private static JsonWebKey createRsaPublic(String keyId, String algorithm, String e, String n) { - return createRsa(keyId, algorithm, e, n, null); - } + private static JsonWebKey createRsaPublic(String keyId, String algorithm, String e, String n) { + return createRsa(keyId, algorithm, e, n, null); + } - private static JsonWebKeys createJwks(JsonWebKey... array) { - JsonWebKeys result = new JsonWebKeys(); + private static JsonWebKeys createJwks(JsonWebKey... array) { + JsonWebKeys result = new JsonWebKeys(); - result.setKeys(Arrays.asList(array)); + result.setKeys(Arrays.asList(array)); - return result; - } + return result; + } } diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java index 7d0e91561d..292af6c014 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java @@ -25,113 +25,126 @@ import org.apache.logging.log4j.util.Strings; class TestJwts { - static final String ROLES_CLAIM = "roles"; - static final Set TEST_ROLES = ImmutableSet.of("role1", "role2"); - static final String TEST_ROLES_STRING = Strings.join(TEST_ROLES, ','); + static final String ROLES_CLAIM = "roles"; + static final Set TEST_ROLES = ImmutableSet.of("role1", "role2"); + static final String TEST_ROLES_STRING = Strings.join(TEST_ROLES, ','); - static final String TEST_AUDIENCE = "TestAudience"; + static final String TEST_AUDIENCE = "TestAudience"; - static final String MCCOY_SUBJECT = "Leonard McCoy"; + static final String MCCOY_SUBJECT = "Leonard McCoy"; - static final String TEST_ISSUER = "TestIssuer"; + static final String TEST_ISSUER = "TestIssuer"; - static final JwtToken MC_COY = create(MCCOY_SUBJECT, TEST_AUDIENCE, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); + static final JwtToken MC_COY = create(MCCOY_SUBJECT, TEST_AUDIENCE, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); - static final JwtToken MC_COY_2 = create(MCCOY_SUBJECT, TEST_AUDIENCE, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); + static final JwtToken MC_COY_2 = create(MCCOY_SUBJECT, TEST_AUDIENCE, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); - static final JwtToken MC_COY_NO_AUDIENCE = create(MCCOY_SUBJECT, null, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); + static final JwtToken MC_COY_NO_AUDIENCE = create(MCCOY_SUBJECT, null, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); - static final JwtToken MC_COY_NO_ISSUER = create(MCCOY_SUBJECT, TEST_AUDIENCE, null, ROLES_CLAIM, TEST_ROLES_STRING); + static final JwtToken MC_COY_NO_ISSUER = create(MCCOY_SUBJECT, TEST_AUDIENCE, null, ROLES_CLAIM, TEST_ROLES_STRING); - static final JwtToken MC_COY_EXPIRED = create(MCCOY_SUBJECT, TEST_AUDIENCE, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING, JwtConstants.CLAIM_EXPIRY, 10); + static final JwtToken MC_COY_EXPIRED = create( + MCCOY_SUBJECT, + TEST_AUDIENCE, + TEST_ISSUER, + ROLES_CLAIM, + TEST_ROLES_STRING, + JwtConstants.CLAIM_EXPIRY, + 10 + ); - static final String MC_COY_SIGNED_OCT_1 = createSigned(MC_COY, TestJwk.OCT_1); + static final String MC_COY_SIGNED_OCT_1 = createSigned(MC_COY, TestJwk.OCT_1); - static final String MC_COY_SIGNED_OCT_2 = createSigned(MC_COY_2, TestJwk.OCT_2); + static final String MC_COY_SIGNED_OCT_2 = createSigned(MC_COY_2, TestJwk.OCT_2); - static final String MC_COY_SIGNED_NO_AUDIENCE_OCT_1 = createSigned(MC_COY_NO_AUDIENCE, TestJwk.OCT_1); - static final String MC_COY_SIGNED_NO_ISSUER_OCT_1 = createSigned(MC_COY_NO_ISSUER, TestJwk.OCT_1); + static final String MC_COY_SIGNED_NO_AUDIENCE_OCT_1 = createSigned(MC_COY_NO_AUDIENCE, TestJwk.OCT_1); + static final String MC_COY_SIGNED_NO_ISSUER_OCT_1 = createSigned(MC_COY_NO_ISSUER, TestJwk.OCT_1); - static final String MC_COY_SIGNED_OCT_1_INVALID_KID = createSigned(MC_COY, TestJwk.FORWARD_SLASH_KID_OCT_1); + static final String MC_COY_SIGNED_OCT_1_INVALID_KID = createSigned(MC_COY, TestJwk.FORWARD_SLASH_KID_OCT_1); - static final String MC_COY_SIGNED_RSA_1 = createSigned(MC_COY, TestJwk.RSA_1); + static final String MC_COY_SIGNED_RSA_1 = createSigned(MC_COY, TestJwk.RSA_1); - static final String MC_COY_SIGNED_RSA_X = createSigned(MC_COY, TestJwk.RSA_X); + static final String MC_COY_SIGNED_RSA_X = createSigned(MC_COY, TestJwk.RSA_X); - static final String MC_COY_EXPIRED_SIGNED_OCT_1 = createSigned(MC_COY_EXPIRED, TestJwk.OCT_1); + static final String MC_COY_EXPIRED_SIGNED_OCT_1 = createSigned(MC_COY_EXPIRED, TestJwk.OCT_1); - static class NoKid { - static final String MC_COY_SIGNED_RSA_1 = createSignedWithoutKeyId(MC_COY, TestJwk.RSA_1); - static final String MC_COY_SIGNED_RSA_2 = createSignedWithoutKeyId(MC_COY, TestJwk.RSA_2); - static final String MC_COY_SIGNED_RSA_X = createSignedWithoutKeyId(MC_COY, TestJwk.RSA_X); - } + static class NoKid { + static final String MC_COY_SIGNED_RSA_1 = createSignedWithoutKeyId(MC_COY, TestJwk.RSA_1); + static final String MC_COY_SIGNED_RSA_2 = createSignedWithoutKeyId(MC_COY, TestJwk.RSA_2); + static final String MC_COY_SIGNED_RSA_X = createSignedWithoutKeyId(MC_COY, TestJwk.RSA_X); + } - static class PeculiarEscaping { - static final String MC_COY_SIGNED_RSA_1 = createSignedWithPeculiarEscaping(MC_COY, TestJwk.RSA_1); - } + static class PeculiarEscaping { + static final String MC_COY_SIGNED_RSA_1 = createSignedWithPeculiarEscaping(MC_COY, TestJwk.RSA_1); + } - static JwtToken create(String subject, String audience, String issuer, Object... moreClaims) { - JwtClaims claims = new JwtClaims(); + static JwtToken create(String subject, String audience, String issuer, Object... moreClaims) { + JwtClaims claims = new JwtClaims(); - claims.setSubject(subject); - if (audience != null) { - claims.setAudience(audience); - } - if (issuer != null) { - claims.setIssuer(issuer); - } + claims.setSubject(subject); + if (audience != null) { + claims.setAudience(audience); + } + if (issuer != null) { + claims.setIssuer(issuer); + } - if (moreClaims != null) { - for (int i = 0; i < moreClaims.length; i += 2) { - claims.setClaim(String.valueOf(moreClaims[i]), moreClaims[i + 1]); - } - } + if (moreClaims != null) { + for (int i = 0; i < moreClaims.length; i += 2) { + claims.setClaim(String.valueOf(moreClaims[i]), moreClaims[i + 1]); + } + } - JwtToken result = new JwtToken(claims); + JwtToken result = new JwtToken(claims); - return result; - } + return result; + } - static String createSigned(JwtToken baseJwt, JsonWebKey jwk) { + static String createSigned(JwtToken baseJwt, JsonWebKey jwk) { return createSigned(baseJwt, jwk, JwsUtils.getSignatureProvider(jwk)); } static String createSigned(JwtToken baseJwt, JsonWebKey jwk, JwsSignatureProvider signatureProvider) { - JwsHeaders jwsHeaders = new JwsHeaders(); - JwtToken signedToken = new JwtToken(jwsHeaders, baseJwt.getClaims()); + JwsHeaders jwsHeaders = new JwsHeaders(); + JwtToken signedToken = new JwtToken(jwsHeaders, baseJwt.getClaims()); - jwsHeaders.setKeyId(jwk.getKeyId()); + jwsHeaders.setKeyId(jwk.getKeyId()); return new JoseJwtProducer().processJwt(signedToken, null, signatureProvider); - } - - static String createSignedWithoutKeyId(JwtToken baseJwt, JsonWebKey jwk) { - JwsHeaders jwsHeaders = new JwsHeaders(); - JwtToken signedToken = new JwtToken(jwsHeaders, baseJwt.getClaims()); + } - return new JoseJwtProducer().processJwt(signedToken, null, JwsUtils.getSignatureProvider(jwk)); - } + static String createSignedWithoutKeyId(JwtToken baseJwt, JsonWebKey jwk) { + JwsHeaders jwsHeaders = new JwsHeaders(); + JwtToken signedToken = new JwtToken(jwsHeaders, baseJwt.getClaims()); - static String createSignedWithPeculiarEscaping(JwtToken baseJwt, JsonWebKey jwk) { - JwsSignatureProvider signatureProvider = JwsUtils.getSignatureProvider(jwk); - JwsHeaders jwsHeaders = new JwsHeaders(); - JwtToken signedToken = new JwtToken(jwsHeaders, baseJwt.getClaims()); + return new JoseJwtProducer().processJwt(signedToken, null, JwsUtils.getSignatureProvider(jwk)); + } - // Depends on CXF not escaping the input string. This may fail for other frameworks or versions. - jwsHeaders.setKeyId(jwk.getKeyId().replace("/", "\\/")); + static String createSignedWithPeculiarEscaping(JwtToken baseJwt, JsonWebKey jwk) { + JwsSignatureProvider signatureProvider = JwsUtils.getSignatureProvider(jwk); + JwsHeaders jwsHeaders = new JwsHeaders(); + JwtToken signedToken = new JwtToken(jwsHeaders, baseJwt.getClaims()); - return new JoseJwtProducer().processJwt(signedToken, null, signatureProvider); - } + // Depends on CXF not escaping the input string. This may fail for other frameworks or versions. + jwsHeaders.setKeyId(jwk.getKeyId().replace("/", "\\/")); - static String createMcCoySignedOct1(long nbf, long exp) - { - JwtToken jwt_token = create( - MCCOY_SUBJECT, TEST_AUDIENCE, - TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING, - JwtConstants.CLAIM_NOT_BEFORE, nbf, - JwtConstants.CLAIM_EXPIRY, exp); + return new JoseJwtProducer().processJwt(signedToken, null, signatureProvider); + } - return createSigned(jwt_token, TestJwk.OCT_1); - } + static String createMcCoySignedOct1(long nbf, long exp) { + JwtToken jwt_token = create( + MCCOY_SUBJECT, + TEST_AUDIENCE, + TEST_ISSUER, + ROLES_CLAIM, + TEST_ROLES_STRING, + JwtConstants.CLAIM_NOT_BEFORE, + nbf, + JwtConstants.CLAIM_EXPIRY, + exp + ); + + return createSigned(jwt_token, TestJwk.OCT_1); + } } diff --git a/src/test/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java index bfaf33049d..5a70a963c6 100644 --- a/src/test/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticatorTest.java @@ -62,37 +62,39 @@ public class HTTPSamlAuthenticatorTest { protected MockSamlIdpServer mockSamlIdpServer; - private static final Pattern WWW_AUTHENTICATE_PATTERN = Pattern - .compile("([^\\s]+)\\s*([^\\s=]+)=\"([^\"]+)\"\\s*([^\\s=]+)=\"([^\"]+)\"\\s*([^\\s=]+)=\"([^\"]+)\"\\s*"); + private static final Pattern WWW_AUTHENTICATE_PATTERN = Pattern.compile( + "([^\\s]+)\\s*([^\\s=]+)=\"([^\"]+)\"\\s*([^\\s=]+)=\"([^\"]+)\"\\s*([^\\s=]+)=\"([^\"]+)\"\\s*" + ); private static final String SPOCK_KEY = "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" - + "MIIE6TAbBgkqhkiG9w0BBQMwDgQI0JMa7PyPedwCAggABIIEyLdPL2RXj8jjKqFT\n" - + "p+7vywwyxyUQOQvvIIU6H+lKZPd/y6pxzYtGd1suT2aermrrlh4b/ZXXfj/EcKcw\n" - + "GgcXB60Kr7UHIv7Xr498S4EKa9R7UG0NtWtsA3FVR5ndwXI+CiRSShhkskmpseVH\n" - + "dNWAoUsKQFbZRLnoINMKIw1/lpQBUwAUcYVB7LxLeKSTVHn/h9kvq0tad1kbE5OY\n" - + "GnOLEVW311++XQ3Ep/13tGEZCrxef+QsnmXuYxXBq4RvbyGZOvyM2FC7va8KzJxl\n" - + "P38SPEL1TzqokQB/eLDBMBOCqkhTbP/8lNuoEVm44T6//ijBp6VdBB+YRIFh3NrS\n" - + "1fPuDVgHr1jrRGICe8lzWy/bSa+4FlxYjn5qpEzZQtbC6C+iRzlwtlCiDdKl8zJ1\n" - + "YF80OW9Gr3Kvph2LJukBiODcyWUAsAf5vJH3vfPV4T9kWTNMu2NCy3Ch8u9d906k\n" - + "zojB/tRRdZ/XCftkU05gYU/5ruU1YA49U60s0KWXvSLmecFo2SjkcEoPDI+Y80Uw\n" - + "OB/5kdh1M1uu/qjoJTPWBbZ28L6e0fiMsr7eWSG7PQFwnN6VzY6Oesm8AS8LMe3V\n" - + "Dr4Syec8vVfGg/EDsjNC1yeZTzlO66NQYGkpnHwK1kgX/XXe7fjDfztPyM9crBXj\n" - + "YcYpNULAkMj9QUVDQqQ7L8TjoAFQiSdvNa+kkDhaxnAXoxfqeacTtkpKcHADsAQL\n" - + "azfoyflnpuZ1dIn0noRFsVuguKDp4k990bhXu9RkQ1H5IzIoYqJwypacVdt3m74o\n" - + "jpZvBY6z0EtBNkze6WA0Vj0BSWpy/IzndDwroG4Xf+54hn0R/Tp5K5UNttOaJN8c\n" - + "9U/NTiGJTJg1O4x6xbPD7C5bBdoJ/MH5yJuk/dUc7pVkisLpuH9sAPETjYCdFIjX\n" - + "MSRJCtq2ouT0ZRW1yBIrKIadgHLExhjZjTSQCBXJMbO7r2DjPHMZU23GTiPtC8ua\n" - + "L2BmC+AW7RQ2Fyo3hJDT2TM4XlMMlTtGuFxkWwmjV+FiwfjbiR3cp0+99/X6OFu5\n" - + "ysgZLuTMQsmWNJ8ZARZqBnkGnN92Aw4D5GLCFv3QXO+fqJnOP1PbkPwpjq59Yytf\n" - + "U4XqyTwRYSXRzwPFFb7RcgL9HbmjpRBEnvqEjKYeXxkBnhs+WOWN/PuJzGgP5uAk\n" - + "jAjQbtgLEPd4WpGcwEhkX6S1DBi8NrGapuehCjXsN1axify8Kx4eRuTiPdINlgsq\n" - + "d2MsPIuDgU2+0QXrXjRLwABcMGuKcmmfZjC+zZomj+yr4+Togs3vhSj9yGK3HHMh\n" - + "NgOlPBTibruXXa4AI07c28j3sEry+CMZrUGyYg6o1HLBpBfOmp7V5HJcvkMFWCVy\n" - + "DPFm5LZu0jZMDj9a+oGkv4hfp1xSXSUjhjiGz47xFJb6PH9pOUIkhTEdFCgEXbaR\n" - + "fXcR+kakLOotL4X1cT9cpxdimN3CCTBpr03gCv2NCVYMYhHKHK+CQVngJrY+PzMH\n" - + "q6fw81bUNcixZyeXFfLFN6GK75k51UV7YS/X2H8YkqGeIVNaFjrcqUoVAN8jQOeb\n" - + "XXIa8gT/MdNT0+W3NHKcbE31pDhOI92COZWlhOyp1cLhyo1ytayjxPTl/2RM/Vtj\n" + "T9IKkp7810LOKhrCDQ==\n" - + "-----END ENCRYPTED PRIVATE KEY-----"; + + "MIIE6TAbBgkqhkiG9w0BBQMwDgQI0JMa7PyPedwCAggABIIEyLdPL2RXj8jjKqFT\n" + + "p+7vywwyxyUQOQvvIIU6H+lKZPd/y6pxzYtGd1suT2aermrrlh4b/ZXXfj/EcKcw\n" + + "GgcXB60Kr7UHIv7Xr498S4EKa9R7UG0NtWtsA3FVR5ndwXI+CiRSShhkskmpseVH\n" + + "dNWAoUsKQFbZRLnoINMKIw1/lpQBUwAUcYVB7LxLeKSTVHn/h9kvq0tad1kbE5OY\n" + + "GnOLEVW311++XQ3Ep/13tGEZCrxef+QsnmXuYxXBq4RvbyGZOvyM2FC7va8KzJxl\n" + + "P38SPEL1TzqokQB/eLDBMBOCqkhTbP/8lNuoEVm44T6//ijBp6VdBB+YRIFh3NrS\n" + + "1fPuDVgHr1jrRGICe8lzWy/bSa+4FlxYjn5qpEzZQtbC6C+iRzlwtlCiDdKl8zJ1\n" + + "YF80OW9Gr3Kvph2LJukBiODcyWUAsAf5vJH3vfPV4T9kWTNMu2NCy3Ch8u9d906k\n" + + "zojB/tRRdZ/XCftkU05gYU/5ruU1YA49U60s0KWXvSLmecFo2SjkcEoPDI+Y80Uw\n" + + "OB/5kdh1M1uu/qjoJTPWBbZ28L6e0fiMsr7eWSG7PQFwnN6VzY6Oesm8AS8LMe3V\n" + + "Dr4Syec8vVfGg/EDsjNC1yeZTzlO66NQYGkpnHwK1kgX/XXe7fjDfztPyM9crBXj\n" + + "YcYpNULAkMj9QUVDQqQ7L8TjoAFQiSdvNa+kkDhaxnAXoxfqeacTtkpKcHADsAQL\n" + + "azfoyflnpuZ1dIn0noRFsVuguKDp4k990bhXu9RkQ1H5IzIoYqJwypacVdt3m74o\n" + + "jpZvBY6z0EtBNkze6WA0Vj0BSWpy/IzndDwroG4Xf+54hn0R/Tp5K5UNttOaJN8c\n" + + "9U/NTiGJTJg1O4x6xbPD7C5bBdoJ/MH5yJuk/dUc7pVkisLpuH9sAPETjYCdFIjX\n" + + "MSRJCtq2ouT0ZRW1yBIrKIadgHLExhjZjTSQCBXJMbO7r2DjPHMZU23GTiPtC8ua\n" + + "L2BmC+AW7RQ2Fyo3hJDT2TM4XlMMlTtGuFxkWwmjV+FiwfjbiR3cp0+99/X6OFu5\n" + + "ysgZLuTMQsmWNJ8ZARZqBnkGnN92Aw4D5GLCFv3QXO+fqJnOP1PbkPwpjq59Yytf\n" + + "U4XqyTwRYSXRzwPFFb7RcgL9HbmjpRBEnvqEjKYeXxkBnhs+WOWN/PuJzGgP5uAk\n" + + "jAjQbtgLEPd4WpGcwEhkX6S1DBi8NrGapuehCjXsN1axify8Kx4eRuTiPdINlgsq\n" + + "d2MsPIuDgU2+0QXrXjRLwABcMGuKcmmfZjC+zZomj+yr4+Togs3vhSj9yGK3HHMh\n" + + "NgOlPBTibruXXa4AI07c28j3sEry+CMZrUGyYg6o1HLBpBfOmp7V5HJcvkMFWCVy\n" + + "DPFm5LZu0jZMDj9a+oGkv4hfp1xSXSUjhjiGz47xFJb6PH9pOUIkhTEdFCgEXbaR\n" + + "fXcR+kakLOotL4X1cT9cpxdimN3CCTBpr03gCv2NCVYMYhHKHK+CQVngJrY+PzMH\n" + + "q6fw81bUNcixZyeXFfLFN6GK75k51UV7YS/X2H8YkqGeIVNaFjrcqUoVAN8jQOeb\n" + + "XXIa8gT/MdNT0+W3NHKcbE31pDhOI92COZWlhOyp1cLhyo1ytayjxPTl/2RM/Vtj\n" + + "T9IKkp7810LOKhrCDQ==\n" + + "-----END ENCRYPTED PRIVATE KEY-----"; private static X509Certificate spSigningCertificate; private static PrivateKey spSigningPrivateKey; @@ -121,9 +123,14 @@ public void basicTest() throws Exception { mockSamlIdpServer.setAuthenticateUser("horst"); mockSamlIdpServer.setEndpointQueryString(null); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -137,9 +144,11 @@ public void basicTest() throws Exception { samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); - HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, - new TypeReference>() { - }); + HashMap response = DefaultObjectMapper.objectMapper.readValue( + responseJson, + new TypeReference>() { + } + ); String authorization = (String) response.get("authorization"); Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); @@ -157,11 +166,18 @@ public void decryptAssertionsTest() throws Exception { mockSamlIdpServer.setSpSignatureCertificate(spSigningCertificate); mockSamlIdpServer.setEncryptAssertion(true); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("sp.signature_private_key", "-BEGIN PRIVATE KEY-\n" - + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-") - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put( + "sp.signature_private_key", + "-BEGIN PRIVATE KEY-\n" + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-" + ) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -175,9 +191,11 @@ public void decryptAssertionsTest() throws Exception { samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); - HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, - new TypeReference>() { - }); + HashMap response = DefaultObjectMapper.objectMapper.readValue( + responseJson, + new TypeReference>() { + } + ); String authorization = (String) response.get("authorization"); Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); @@ -196,11 +214,18 @@ public void shouldUnescapeSamlEntitiesTest() throws Exception { mockSamlIdpServer.setEncryptAssertion(true); mockSamlIdpServer.setAuthenticateUserRoles(Arrays.asList("ABC\\Admin")); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("sp.signature_private_key", "-BEGIN PRIVATE KEY-\n" - + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-") - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put( + "sp.signature_private_key", + "-BEGIN PRIVATE KEY-\n" + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-" + ) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -214,9 +239,11 @@ public void shouldUnescapeSamlEntitiesTest() throws Exception { samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); - HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, - new TypeReference>() { - }); + HashMap response = DefaultObjectMapper.objectMapper.readValue( + responseJson, + new TypeReference>() { + } + ); String authorization = (String) response.get("authorization"); Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); @@ -225,7 +252,7 @@ public void shouldUnescapeSamlEntitiesTest() throws Exception { JwtToken jwt = jwtConsumer.getJwtToken(); Assert.assertEquals("ABC\\User1", jwt.getClaim("sub")); - Assert.assertEquals("ABC\\User1", samlAuthenticator.httpJwtAuthenticator.extractSubject(jwt.getClaims())); + Assert.assertEquals("ABC\\User1", samlAuthenticator.httpJwtAuthenticator.extractSubject(jwt.getClaims())); Assert.assertEquals("[ABC\\Admin]", String.valueOf(jwt.getClaim("roles"))); Assert.assertEquals("ABC\\Admin", samlAuthenticator.httpJwtAuthenticator.extractRoles(jwt.getClaims())[0]); } @@ -238,11 +265,18 @@ public void shouldUnescapeSamlEntitiesTest2() throws Exception { mockSamlIdpServer.setEncryptAssertion(true); mockSamlIdpServer.setAuthenticateUserRoles(Arrays.asList("ABC\"Admin")); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("sp.signature_private_key", "-BEGIN PRIVATE KEY-\n" - + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-") - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put( + "sp.signature_private_key", + "-BEGIN PRIVATE KEY-\n" + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-" + ) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -256,9 +290,11 @@ public void shouldUnescapeSamlEntitiesTest2() throws Exception { samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); - HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, - new TypeReference>() { - }); + HashMap response = DefaultObjectMapper.objectMapper.readValue( + responseJson, + new TypeReference>() { + } + ); String authorization = (String) response.get("authorization"); Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); @@ -267,7 +303,7 @@ public void shouldUnescapeSamlEntitiesTest2() throws Exception { JwtToken jwt = jwtConsumer.getJwtToken(); Assert.assertEquals("ABC\"User1", jwt.getClaim("sub")); - Assert.assertEquals("ABC\"User1", samlAuthenticator.httpJwtAuthenticator.extractSubject(jwt.getClaims())); + Assert.assertEquals("ABC\"User1", samlAuthenticator.httpJwtAuthenticator.extractSubject(jwt.getClaims())); Assert.assertEquals("[ABC\"Admin]", String.valueOf(jwt.getClaim("roles"))); Assert.assertEquals("ABC\"Admin", samlAuthenticator.httpJwtAuthenticator.extractRoles(jwt.getClaims())[0]); } @@ -280,11 +316,18 @@ public void shouldNotEscapeSamlEntities() throws Exception { mockSamlIdpServer.setEncryptAssertion(true); mockSamlIdpServer.setAuthenticateUserRoles(Arrays.asList("ABC/Admin")); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("sp.signature_private_key", "-BEGIN PRIVATE KEY-\n" - + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-") - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put( + "sp.signature_private_key", + "-BEGIN PRIVATE KEY-\n" + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-" + ) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -298,9 +341,11 @@ public void shouldNotEscapeSamlEntities() throws Exception { samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); - HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, - new TypeReference>() { - }); + HashMap response = DefaultObjectMapper.objectMapper.readValue( + responseJson, + new TypeReference>() { + } + ); String authorization = (String) response.get("authorization"); Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); @@ -309,7 +354,7 @@ public void shouldNotEscapeSamlEntities() throws Exception { JwtToken jwt = jwtConsumer.getJwtToken(); Assert.assertEquals("ABC/User1", jwt.getClaim("sub")); - Assert.assertEquals("ABC/User1", samlAuthenticator.httpJwtAuthenticator.extractSubject(jwt.getClaims())); + Assert.assertEquals("ABC/User1", samlAuthenticator.httpJwtAuthenticator.extractSubject(jwt.getClaims())); Assert.assertEquals("[ABC/Admin]", String.valueOf(jwt.getClaim("roles"))); Assert.assertEquals("ABC/Admin", samlAuthenticator.httpJwtAuthenticator.extractRoles(jwt.getClaims())[0]); } @@ -322,11 +367,18 @@ public void shouldNotTrimWhitespaceInJwtRoles() throws Exception { mockSamlIdpServer.setEncryptAssertion(true); mockSamlIdpServer.setAuthenticateUserRoles(Arrays.asList(" ABC/Admin ")); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("sp.signature_private_key", "-BEGIN PRIVATE KEY-\n" - + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-") - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put( + "sp.signature_private_key", + "-BEGIN PRIVATE KEY-\n" + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-" + ) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -340,9 +392,11 @@ public void shouldNotTrimWhitespaceInJwtRoles() throws Exception { samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); - HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, - new TypeReference>() { - }); + HashMap response = DefaultObjectMapper.objectMapper.readValue( + responseJson, + new TypeReference>() { + } + ); String authorization = (String) response.get("authorization"); Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); @@ -362,12 +416,16 @@ public void testMetadataBody() throws Exception { // Note: We need to replace endpoint with mockSamlIdpServer endpoint final String metadataBody = FileHelper.loadFile("saml/metadata.xml") - .replaceAll("http://localhost:33667/", mockSamlIdpServer.getMetadataUri()); + .replaceAll("http://localhost:33667/", mockSamlIdpServer.getMetadataUri()); - Settings settings = Settings.builder().put(IDP_METADATA_CONTENT, metadataBody) + Settings settings = Settings.builder() + .put(IDP_METADATA_CONTENT, metadataBody) .put("kibana_url", "http://wherever") .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -381,9 +439,11 @@ public void testMetadataBody() throws Exception { samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); - HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, + HashMap response = DefaultObjectMapper.objectMapper.readValue( + responseJson, new TypeReference>() { - }); + } + ); String authorization = (String) response.get("authorization"); Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); @@ -394,18 +454,21 @@ public void testMetadataBody() throws Exception { Assert.assertEquals("horst", jwt.getClaim("sub")); } - - @Test(expected= RuntimeException.class) + @Test(expected = RuntimeException.class) public void testEmptyMetadataBody() throws Exception { mockSamlIdpServer.setSignResponses(true); mockSamlIdpServer.loadSigningKeys("saml/kirk-keystore.jks", "kirk"); mockSamlIdpServer.setAuthenticateUser("horst"); mockSamlIdpServer.setEndpointQueryString(null); - Settings settings = Settings.builder().put(IDP_METADATA_CONTENT, "") + Settings settings = Settings.builder() + .put(IDP_METADATA_CONTENT, "") .put("kibana_url", "http://wherever") .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); new HTTPSamlAuthenticator(settings, null); } @@ -418,24 +481,34 @@ public void unsolicitedSsoTest() throws Exception { mockSamlIdpServer.setEndpointQueryString(null); mockSamlIdpServer.setDefaultAssertionConsumerService("http://wherever/opendistrosecurity/saml/acs/idpinitiated"); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); String encodedSamlResponse = mockSamlIdpServer.createUnsolicitedSamlResponse(); - RestRequest tokenRestRequest = buildTokenExchangeRestRequest(encodedSamlResponse, null, - "/opendistrosecurity/saml/acs/idpinitiated"); + RestRequest tokenRestRequest = buildTokenExchangeRestRequest( + encodedSamlResponse, + null, + "/opendistrosecurity/saml/acs/idpinitiated" + ); TestRestChannel tokenRestChannel = new TestRestChannel(tokenRestRequest); samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); - HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, - new TypeReference>() { - }); + HashMap response = DefaultObjectMapper.objectMapper.readValue( + responseJson, + new TypeReference>() { + } + ); String authorization = (String) response.get("authorization"); Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); @@ -454,19 +527,29 @@ public void badUnsolicitedSsoTest() throws Exception { mockSamlIdpServer.setEndpointQueryString(null); mockSamlIdpServer.setDefaultAssertionConsumerService("http://wherever/opendistrosecurity/saml/acs/idpinitiated"); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); String encodedSamlResponse = mockSamlIdpServer.createUnsolicitedSamlResponse(); - AuthenticateHeaders authenticateHeaders = new AuthenticateHeaders("http://wherever/opendistrosecurity/saml/acs/", - "wrong_request_id"); + AuthenticateHeaders authenticateHeaders = new AuthenticateHeaders( + "http://wherever/opendistrosecurity/saml/acs/", + "wrong_request_id" + ); - RestRequest tokenRestRequest = buildTokenExchangeRestRequest(encodedSamlResponse, authenticateHeaders, - "/opendistrosecurity/saml/acs/idpinitiated"); + RestRequest tokenRestRequest = buildTokenExchangeRestRequest( + encodedSamlResponse, + authenticateHeaders, + "/opendistrosecurity/saml/acs/idpinitiated" + ); TestRestChannel tokenRestChannel = new TestRestChannel(tokenRestRequest); samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); @@ -481,9 +564,14 @@ public void wrongCertTest() throws Exception { mockSamlIdpServer.setAuthenticateUser("horst"); mockSamlIdpServer.setEndpointQueryString(null); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -507,9 +595,14 @@ public void noSignatureTest() throws Exception { mockSamlIdpServer.setAuthenticateUser("horst"); mockSamlIdpServer.setEndpointQueryString(null); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -534,9 +627,15 @@ public void rolesTest() throws Exception { mockSamlIdpServer.setAuthenticateUserRoles(Arrays.asList("a ,c", "b ,d, e", "f", "g,,h, ,i")); mockSamlIdpServer.setEndpointQueryString(null); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").put("roles_seperator", ",").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .put("roles_seperator", ",") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -550,9 +649,11 @@ public void rolesTest() throws Exception { samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); - HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, - new TypeReference>() { - }); + HashMap response = DefaultObjectMapper.objectMapper.readValue( + responseJson, + new TypeReference>() { + } + ); String authorization = (String) response.get("authorization"); Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); @@ -561,8 +662,10 @@ public void rolesTest() throws Exception { JwtToken jwt = jwtConsumer.getJwtToken(); Assert.assertEquals("horst", jwt.getClaim("sub")); - Assert.assertArrayEquals(new String[] { "a ", "c", "b ", "d", " e", "f", "g", "h", " ", "i" }, - ((List) jwt.getClaim("roles")).toArray(new String[0])); + Assert.assertArrayEquals( + new String[] { "a ", "c", "b ", "d", " e", "f", "g", "h", " ", "i" }, + ((List) jwt.getClaim("roles")).toArray(new String[0]) + ); } @Test @@ -572,9 +675,14 @@ public void idpEndpointWithQueryStringTest() throws Exception { mockSamlIdpServer.setAuthenticateUser("horst"); mockSamlIdpServer.setEndpointQueryString("extra=query"); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -588,9 +696,11 @@ public void idpEndpointWithQueryStringTest() throws Exception { samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); - HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, - new TypeReference>() { - }); + HashMap response = DefaultObjectMapper.objectMapper.readValue( + responseJson, + new TypeReference>() { + } + ); String authorization = (String) response.get("authorization"); Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); @@ -622,9 +732,12 @@ private void commaSeparatedRoles(final String rolesAsString, final Settings.Buil mockSamlIdpServer.setEndpointQueryString(null); Settings settings = settingsBuilder.put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".") - .build(); + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); AuthenticateHeaders authenticateHeaders = getAutenticateHeaders(samlAuthenticator); @@ -637,9 +750,11 @@ private void commaSeparatedRoles(final String rolesAsString, final Settings.Buil samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); - HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, - new TypeReference>() { - }); + HashMap response = DefaultObjectMapper.objectMapper.readValue( + responseJson, + new TypeReference>() { + } + ); String authorization = (String) response.get("authorization"); Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); @@ -648,8 +763,7 @@ private void commaSeparatedRoles(final String rolesAsString, final Settings.Buil JwtToken jwt = jwtConsumer.getJwtToken(); Assert.assertEquals("horst", jwt.getClaim("sub")); - Assert.assertArrayEquals(new String[] { "a", "b" }, - ((List) jwt.getClaim("roles")).toArray(new String[0])); + Assert.assertArrayEquals(new String[] { "a", "b" }, ((List) jwt.getClaim("roles")).toArray(new String[0])); } @Test @@ -660,12 +774,18 @@ public void basicLogoutTest() throws Exception { mockSamlIdpServer.setSpSignatureCertificate(spSigningCertificate); mockSamlIdpServer.setEndpointQueryString(null); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles") - .put("sp.signature_private_key", "-BEGIN PRIVATE KEY-\n" - + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-") - .put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put( + "sp.signature_private_key", + "-BEGIN PRIVATE KEY-\n" + Base64.getEncoder().encodeToString(spSigningPrivateKey.getEncoded()) + "-END PRIVATE KEY-" + ) + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -688,10 +808,16 @@ public void basicLogoutTestEncryptedKey() throws Exception { mockSamlIdpServer.setSpSignatureCertificate(spSigningCertificate); mockSamlIdpServer.setEndpointQueryString(null); - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles").put("sp.signature_private_key", SPOCK_KEY) - .put("sp.signature_private_key_password", "changeit").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("sp.signature_private_key", SPOCK_KEY) + .put("sp.signature_private_key_password", "changeit") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -710,10 +836,15 @@ public void basicLogoutTestEncryptedKey() throws Exception { public void initialConnectionFailureTest() throws Exception { try (MockSamlIdpServer mockSamlIdpServer = new MockSamlIdpServer()) { - Settings settings = Settings.builder().put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) - .put("idp.min_refresh_delay", 100) - .put("kibana_url", "http://wherever").put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) - .put("exchange_key", "abc").put("roles_key", "roles").put("path.home", ".").build(); + Settings settings = Settings.builder() + .put(IDP_METADATA_URL, mockSamlIdpServer.getMetadataUri()) + .put("idp.min_refresh_delay", 100) + .put("kibana_url", "http://wherever") + .put("idp.entity_id", mockSamlIdpServer.getIdpEntityId()) + .put("exchange_key", "abc") + .put("roles_key", "roles") + .put("path.home", ".") + .build(); HTTPSamlAuthenticator samlAuthenticator = new HTTPSamlAuthenticator(settings, null); @@ -742,9 +873,11 @@ public void initialConnectionFailureTest() throws Exception { samlAuthenticator.reRequestAuthentication(tokenRestChannel, null); String responseJson = new String(BytesReference.toBytes(tokenRestChannel.response.content())); - HashMap response = DefaultObjectMapper.objectMapper.readValue(responseJson, - new TypeReference>() { - }); + HashMap response = DefaultObjectMapper.objectMapper.readValue( + responseJson, + new TypeReference>() { + } + ); String authorization = (String) response.get("authorization"); Assert.assertNotNull("Expected authorization attribute in JSON: " + responseJson, authorization); @@ -765,8 +898,7 @@ private AuthenticateHeaders getAutenticateHeaders(HTTPSamlAuthenticator samlAuth List wwwAuthenticateHeaders = restChannel.response.getHeaders().get("WWW-Authenticate"); Assert.assertNotNull(wwwAuthenticateHeaders); - Assert.assertEquals("More than one WWW-Authenticate header: " + wwwAuthenticateHeaders, 1, - wwwAuthenticateHeaders.size()); + Assert.assertEquals("More than one WWW-Authenticate header: " + wwwAuthenticateHeaders, 1, wwwAuthenticateHeaders.size()); String wwwAuthenticateHeader = wwwAuthenticateHeaders.get(0); @@ -786,26 +918,36 @@ private AuthenticateHeaders getAutenticateHeaders(HTTPSamlAuthenticator samlAuth return new AuthenticateHeaders(location, requestId); } - private RestRequest buildTokenExchangeRestRequest(String encodedSamlResponse, - AuthenticateHeaders authenticateHeaders) { + private RestRequest buildTokenExchangeRestRequest(String encodedSamlResponse, AuthenticateHeaders authenticateHeaders) { return buildTokenExchangeRestRequest(encodedSamlResponse, authenticateHeaders, "/opendistrosecurity/saml/acs"); } - private RestRequest buildTokenExchangeRestRequest(String encodedSamlResponse, - AuthenticateHeaders authenticateHeaders, String acsEndpoint) { + private RestRequest buildTokenExchangeRestRequest( + String encodedSamlResponse, + AuthenticateHeaders authenticateHeaders, + String acsEndpoint + ) { String authtokenPostJson; if (authenticateHeaders != null) { - authtokenPostJson = "{\"SAMLResponse\": \"" + encodedSamlResponse + "\", \"RequestId\": \"" - + authenticateHeaders.requestId + "\"}"; + authtokenPostJson = "{\"SAMLResponse\": \"" + + encodedSamlResponse + + "\", \"RequestId\": \"" + + authenticateHeaders.requestId + + "\"}"; } else { - authtokenPostJson = "{\"SAMLResponse\": \"" + encodedSamlResponse - + "\", \"RequestId\": null, \"acsEndpoint\": \"" + acsEndpoint + "\" }"; + authtokenPostJson = "{\"SAMLResponse\": \"" + + encodedSamlResponse + + "\", \"RequestId\": null, \"acsEndpoint\": \"" + + acsEndpoint + + "\" }"; } - return new FakeRestRequest.Builder().withPath("/_opendistro/_security/api/authtoken").withMethod(Method.POST) - .withContent(new BytesArray(authtokenPostJson)) - .withHeaders(ImmutableMap.of("Content-Type", "application/json")).build(); + return new FakeRestRequest.Builder().withPath("/_opendistro/_security/api/authtoken") + .withMethod(Method.POST) + .withContent(new BytesArray(authtokenPostJson)) + .withHeaders(ImmutableMap.of("Content-Type", "application/json")) + .build(); } @BeforeClass @@ -814,8 +956,7 @@ public static void initSpSigningKeys() { KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); KeyStore keyStore = KeyStore.getInstance("JKS"); - InputStream keyStream = new FileInputStream( - FileHelper.getAbsoluteFilePathFromClassPath("saml/spock-keystore.jks").toFile()); + InputStream keyStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("saml/spock-keystore.jks").toFile()); keyStore.load(keyStream, "changeit".toCharArray()); kmf.init(keyStore, "changeit".toCharArray()); @@ -824,8 +965,7 @@ public static void initSpSigningKeys() { spSigningPrivateKey = (PrivateKey) keyStore.getKey("spock", "changeit".toCharArray()); - } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException - | UnrecoverableKeyException e) { + } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException | UnrecoverableKeyException e) { throw new RuntimeException(e); } } @@ -876,7 +1016,8 @@ public void sendResponse(RestResponse response) { } @Override - public XContentBuilder newBuilder(XContentType xContentType, XContentType responseContentType, boolean useFiltering) throws IOException { + public XContentBuilder newBuilder(XContentType xContentType, XContentType responseContentType, boolean useFiltering) + throws IOException { return null; } diff --git a/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java b/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java index 4f4a8c9640..ef54e3e833 100644 --- a/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java +++ b/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java @@ -193,46 +193,50 @@ class MockSamlIdpServer implements Closeable { this.loadSigningKeys("saml/kirk-keystore.jks", "kirk"); - ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap().setListenerPort(port) - .register(CTX_METADATA, new HttpRequestHandler() { + ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap() + .setListenerPort(port) + .register(CTX_METADATA, new HttpRequestHandler() { - @Override - public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { - handleMetadataRequest(request, response, context); + handleMetadataRequest(request, response, context); - } - }).register(CTX_SAML_SSO, new HttpRequestHandler() { + } + }) + .register(CTX_SAML_SSO, new HttpRequestHandler() { - @Override - public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { - handleSsoRequest(request, response, context); - } - }).register(CTX_SAML_SLO, new HttpRequestHandler() { + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { + handleSsoRequest(request, response, context); + } + }) + .register(CTX_SAML_SLO, new HttpRequestHandler() { - @Override - public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { - handleSloRequest(request, response, context); - } - }); + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { + handleSloRequest(request, response, context); + } + }); if (ssl) { - serverBootstrap = serverBootstrap.setSslContext(createSSLContext()) - .setSslSetupHandler(new Callback() { - @Override - public void execute(SSLParameters object) { - object.setNeedClientAuth(true); - } - }) - .setConnectionFactory(new HttpConnectionFactory() { - @Override - public DefaultBHttpServerConnection createConnection(final Socket socket) throws IOException { - final DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection(ssl ? "https" : "http", Http1Config.DEFAULT); - conn.bind(socket); - return conn; - } - }); + serverBootstrap = serverBootstrap.setSslContext(createSSLContext()).setSslSetupHandler(new Callback() { + @Override + public void execute(SSLParameters object) { + object.setNeedClientAuth(true); + } + }).setConnectionFactory(new HttpConnectionFactory() { + @Override + public DefaultBHttpServerConnection createConnection(final Socket socket) throws IOException { + final DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection(ssl ? "https" : "http", Http1Config.DEFAULT); + conn.bind(socket); + return conn; + } + }); } this.httpServer = serverBootstrap.create(); @@ -289,16 +293,15 @@ public int getPort() { return port; } - protected void handleMetadataRequest(HttpRequest request, ClassicHttpResponse response, HttpContext context) - throws HttpException, IOException { + protected void handleMetadataRequest(HttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { response.setCode(200); response.setHeader("Cache-Control", "public, max-age=31536000"); response.setHeader("Content-Type", "application/xml"); response.setEntity(new StringEntity(createMetadata())); } - protected void handleSsoRequest(HttpRequest request, HttpResponse response, HttpContext context) - throws HttpException, IOException { + protected void handleSsoRequest(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { if ("GET".equalsIgnoreCase(request.getMethod())) { handleSsoGetRequestBase(request); @@ -308,8 +311,7 @@ protected void handleSsoRequest(HttpRequest request, HttpResponse response, Http } - protected void handleSloRequest(HttpRequest request, HttpResponse response, HttpContext context) - throws HttpException, IOException { + protected void handleSloRequest(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { if ("GET".equalsIgnoreCase(request.getMethod())) { handleSloGetRequestBase(request); @@ -375,10 +377,10 @@ public void handleSloGetRequestBase(HttpRequest request) { LogoutRequest logoutRequest = (LogoutRequest) messageContext.getMessage(); - SAML2HTTPRedirectDeflateSignatureSecurityHandler signatureSecurityHandler = new SAML2HTTPRedirectDeflateSignatureSecurityHandler(); + SAML2HTTPRedirectDeflateSignatureSecurityHandler signatureSecurityHandler = + new SAML2HTTPRedirectDeflateSignatureSecurityHandler(); SignatureValidationParameters validationParams = new SignatureValidationParameters(); - SecurityParametersContext securityParametersContext = messageContext - .getSubcontext(SecurityParametersContext.class, true); + SecurityParametersContext securityParametersContext = messageContext.getSubcontext(SecurityParametersContext.class, true); SAMLPeerEntityContext peerEntityContext = messageContext.getSubcontext(SAMLPeerEntityContext.class, true); peerEntityContext.setEntityId(idpEntityId); @@ -397,8 +399,7 @@ public void handleSloGetRequestBase(HttpRequest request) { throw new RuntimeException("Unexpected NameID in LogoutRequest: " + logoutRequest); } - } catch (URISyntaxException | ComponentInitializationException | MessageDecodingException - | MessageHandlerException e) { + } catch (URISyntaxException | ComponentInitializationException | MessageDecodingException | MessageHandlerException e) { throw new RuntimeException(e); } } @@ -436,12 +437,24 @@ private String createSamlAuthResponse(AuthnRequest authnRequest) { if (authnRequest != null) { subject.getSubjectConfirmations() - .add(createSubjectConfirmation("urn:oasis:names:tc:SAML:2.0:cm:bearer", - new DateTime().plusMinutes(1), authnRequest.getID(), - authnRequest.getAssertionConsumerServiceURL())); + .add( + createSubjectConfirmation( + "urn:oasis:names:tc:SAML:2.0:cm:bearer", + new DateTime().plusMinutes(1), + authnRequest.getID(), + authnRequest.getAssertionConsumerServiceURL() + ) + ); } else { - subject.getSubjectConfirmations().add(createSubjectConfirmation("urn:oasis:names:tc:SAML:2.0:cm:bearer", - new DateTime().plusMinutes(1), null, defaultAssertionConsumerService)); + subject.getSubjectConfirmations() + .add( + createSubjectConfirmation( + "urn:oasis:names:tc:SAML:2.0:cm:bearer", + new DateTime().plusMinutes(1), + null, + defaultAssertionConsumerService + ) + ); } Conditions conditions = createSamlElement(Conditions.class); @@ -464,7 +477,7 @@ private String createSamlAuthResponse(AuthnRequest authnRequest) { attribute.getAttributeValues().add(createXSAny(AttributeValue.DEFAULT_ELEMENT_NAME, role)); } } - + if (signResponses) { Signature signature = createSamlElement(Signature.class); assertion.setSignature(signature); @@ -478,7 +491,7 @@ private String createSamlAuthResponse(AuthnRequest authnRequest) { Signer.signObject(signature); } - if (this.encryptAssertion){ + if (this.encryptAssertion) { Encrypter encrypter = getEncrypter(); EncryptedAssertion encryptedAssertion = encrypter.encrypt(assertion); response.getEncryptedAssertions().add(encryptedAssertion); @@ -486,7 +499,6 @@ private String createSamlAuthResponse(AuthnRequest authnRequest) { response.getAssertions().add(assertion); } - String marshalledXml = marshallSamlXml(response); return Base64Support.encode(marshalledXml.getBytes("UTF-8"), Base64Support.UNCHUNKED); @@ -498,10 +510,11 @@ private String createSamlAuthResponse(AuthnRequest authnRequest) { private Encrypter getEncrypter() { KeyEncryptionParameters kek = new KeyEncryptionParameters(); - // Algorithm from https://santuario.apache.org/Java/api/constant-values.html#org.apache.xml.security.utils.EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSA15 + // Algorithm from + // https://santuario.apache.org/Java/api/constant-values.html#org.apache.xml.security.utils.EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSA15 kek.setAlgorithm("http://www.w3.org/2001/04/xmlenc#rsa-1_5"); kek.setEncryptionCredential(new BasicX509Credential(spSignatureCertificate)); - Encrypter encrypter = new Encrypter( new DataEncryptionParameters(),kek); + Encrypter encrypter = new Encrypter(new DataEncryptionParameters(), kek); encrypter.setKeyPlacement(Encrypter.KeyPlacement.INLINE); return encrypter; } @@ -554,8 +567,7 @@ private NameID createNameID(String format, String value) { return nameID; } - private SubjectConfirmation createSubjectConfirmation(String method, DateTime notOnOrAfter, String inResponseTo, - String recipient) { + private SubjectConfirmation createSubjectConfirmation(String method, DateTime notOnOrAfter, String inResponseTo, String recipient) { SubjectConfirmation result = createSamlElement(SubjectConfirmation.class); result.setMethod(method); @@ -601,8 +613,7 @@ private String createMetadata() { redirectSingleLogoutService.setBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"); redirectSingleLogoutService.setLocation(getSamlSloUri()); - idpSsoDescriptor.getNameIDFormats() - .add(createNameIDFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified")); + idpSsoDescriptor.getNameIDFormats().add(createNameIDFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified")); SingleSignOnService redirectSingleSignOnService = createSamlElement(SingleSignOnService.class); idpSsoDescriptor.getSingleSignOnServices().add(redirectSingleSignOnService); @@ -619,8 +630,7 @@ private String createMetadata() { signingKeyDescriptor.setUse(UsageType.SIGNING); - signingKeyDescriptor - .setKeyInfo(keyInfoGenerator.generate(new BasicX509Credential(this.signingCertificate))); + signingKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(new BasicX509Credential(this.signingCertificate))); return marshallSamlXml(idpEntityDescriptor); } catch (org.opensaml.security.SecurityException e) { @@ -640,16 +650,14 @@ private String marshallSamlXml(XMLObject xmlObject) { transformer.transform(source, new StreamResult(stringWriter)); return stringWriter.toString(); - } catch (ParserConfigurationException | MarshallingException | TransformerFactoryConfigurationError - | TransformerException e) { + } catch (ParserConfigurationException | MarshallingException | TransformerFactoryConfigurationError | TransformerException e) { throw new RuntimeException(e); } } private SignatureTrustEngine buildSignatureTrustEngine(X509Certificate certificate) { CredentialResolver credentialResolver = new StaticCredentialResolver(new BasicX509Credential(certificate)); - KeyInfoCredentialResolver keyInfoCredentialResolver = new StaticKeyInfoCredentialResolver( - new BasicX509Credential(certificate)); + KeyInfoCredentialResolver keyInfoCredentialResolver = new StaticKeyInfoCredentialResolver(new BasicX509Credential(certificate)); return new ExplicitKeySignatureTrustEngine(credentialResolver, keyInfoCredentialResolver); } @@ -666,11 +674,12 @@ void loadSigningKeys(String path, String alias) { this.signingCertificate = (X509Certificate) keyStore.getCertificate(alias); - this.signingCredential = new BasicX509Credential(this.signingCertificate, - (PrivateKey) keyStore.getKey(alias, "changeit".toCharArray())); + this.signingCredential = new BasicX509Credential( + this.signingCertificate, + (PrivateKey) keyStore.getKey(alias, "changeit".toCharArray()) + ); - } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException - | UnrecoverableKeyException e) { + } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException | UnrecoverableKeyException e) { throw new RuntimeException(e); } } @@ -683,15 +692,13 @@ private SSLContext createSSLContext() { try { final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); final KeyStore trustStore = KeyStore.getInstance("JKS"); - InputStream trustStream = new FileInputStream( - FileHelper.getAbsoluteFilePathFromClassPath("jwt/truststore.jks").toFile()); + InputStream trustStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("jwt/truststore.jks").toFile()); trustStore.load(trustStream, "changeit".toCharArray()); tmf.init(trustStore); final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); final KeyStore keyStore = KeyStore.getInstance("JKS"); - InputStream keyStream = new FileInputStream( - FileHelper.getAbsoluteFilePathFromClassPath("jwt/node-0-keystore.jks").toFile()); + InputStream keyStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("jwt/node-0-keystore.jks").toFile()); keyStore.load(keyStream, "changeit".toCharArray()); kmf.init(keyStore, "changeit".toCharArray()); @@ -709,14 +716,26 @@ private String nextId() { } static class SSLTestHttpServerConnection extends DefaultBHttpServerConnection { - public SSLTestHttpServerConnection(final String scheme, Http1Config http1Config, - final CharsetDecoder charDecoder, final CharsetEncoder charEncoder, - final ContentLengthStrategy incomingContentStrategy, - final ContentLengthStrategy outgoingContentStrategy, - final HttpMessageParserFactory requestParserFactory, - final HttpMessageWriterFactory responseWriterFactory) { - super(scheme, http1Config, charDecoder, charEncoder, incomingContentStrategy, - outgoingContentStrategy, requestParserFactory, responseWriterFactory); + public SSLTestHttpServerConnection( + final String scheme, + Http1Config http1Config, + final CharsetDecoder charDecoder, + final CharsetEncoder charEncoder, + final ContentLengthStrategy incomingContentStrategy, + final ContentLengthStrategy outgoingContentStrategy, + final HttpMessageParserFactory requestParserFactory, + final HttpMessageWriterFactory responseWriterFactory + ) { + super( + scheme, + http1Config, + charDecoder, + charEncoder, + incomingContentStrategy, + outgoingContentStrategy, + requestParserFactory, + responseWriterFactory + ); } } @@ -729,8 +748,9 @@ static class FakeHttpServletRequest implements HttpServletRequest { this.delegate = delegate; String uri = delegate.getRequestUri(); this.uriBuilder = new URIBuilder(uri); - this.queryParams = uriBuilder.getQueryParams().stream() - .collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue)); + this.queryParams = uriBuilder.getQueryParams() + .stream() + .collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue)); } @Override @@ -959,8 +979,7 @@ public String getHeader(String name) { @SuppressWarnings("rawtypes") @Override public Enumeration getHeaderNames() { - return Collections.enumeration( - Arrays.asList(delegate.getHeaders()).stream().map(Header::getName).collect(Collectors.toSet())); + return Collections.enumeration(Arrays.asList(delegate.getHeaders()).stream().map(Header::getName).collect(Collectors.toSet())); } @SuppressWarnings("rawtypes") @@ -969,8 +988,7 @@ public Enumeration getHeaders(String name) { Header[] headers = delegate.getHeaders(name); if (headers != null) { - return Collections - .enumeration(Arrays.asList(headers).stream().map(Header::getName).collect(Collectors.toSet())); + return Collections.enumeration(Arrays.asList(headers).stream().map(Header::getName).collect(Collectors.toSet())); } else { return null; } diff --git a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendIntegTest.java b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendIntegTest.java index 81654d4c19..b2ba079dff 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendIntegTest.java +++ b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendIntegTest.java @@ -74,13 +74,19 @@ public void testAttributesWithImpersonation() throws Exception { String securityConfigAsYamlString = FileHelper.loadFile("ldap/config.yml"); securityConfigAsYamlString = securityConfigAsYamlString.replace("${ldapsPort}", String.valueOf(ldapsPort)); final Settings settings = Settings.builder() - .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".cn=Captain Spock,ou=people,o=TEST", "*") - .build(); + .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".cn=Captain Spock,ou=people,o=TEST", "*") + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setConfigAsYamlString(securityConfigAsYamlString), settings); final RestHelper rh = nonSslRestHelper(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as", "jacksonm") - ,encodeBasicHeader("spock", "spocksecret"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader("opendistro_security_impersonate_as", "jacksonm"), + encodeBasicHeader("spock", "spocksecret") + )).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("ldap.dn")); Assert.assertTrue(res.getBody().contains("attr.ldap.entryDN")); @@ -88,7 +94,6 @@ public void testAttributesWithImpersonation() throws Exception { } - @AfterClass public static void tearDownLdap() throws Exception { diff --git a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTest.java b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTest.java index db961eb9a4..3cc5006198 100755 --- a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTest.java +++ b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTest.java @@ -61,128 +61,138 @@ public static void startLdapServer() throws Exception { @Test public void testLdapAuthentication() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } - @Test(expected=OpenSearchSecurityException.class) + @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationFakeLogin() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, true) - .build(); - - new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("unknown", "unknown" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, true) + .build(); + + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("unknown", "unknown".getBytes(StandardCharsets.UTF_8)) + ); } - @Test(expected=OpenSearchSecurityException.class) + @Test(expected = OpenSearchSecurityException.class) public void testLdapInjection() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); String injectString = "*jack*"; - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials(injectString, "secret" - .getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials(injectString, "secret".getBytes(StandardCharsets.UTF_8)) + ); } @Test public void testLdapAuthenticationBindDn() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") - .put(ConfigConstants.LDAP_PASSWORD, "spocksecret") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") + .put(ConfigConstants.LDAP_PASSWORD, "spocksecret") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } - @Test(expected=OpenSearchSecurityException.class) + @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationWrongBindDn() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") - .put(ConfigConstants.LDAP_PASSWORD, "wrong") - .build(); - - new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") + .put(ConfigConstants.LDAP_PASSWORD, "wrong") + .build(); + + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); } - @Test(expected=OpenSearchSecurityException.class) + @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationBindFail() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "wrong".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "wrong".getBytes(StandardCharsets.UTF_8)) + ); } - @Test(expected=OpenSearchSecurityException.class) + @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationNoUser() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("UNKNOWN", "UNKNOWN".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("UNKNOWN", "UNKNOWN".getBytes(StandardCharsets.UTF_8)) + ); } @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationFail() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "xxxxx".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "xxxxx".getBytes(StandardCharsets.UTF_8)) + ); } @Test public void testLdapAuthenticationSSL() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false) - .put("path.home",".") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -190,36 +200,34 @@ public void testLdapAuthenticationSSL() throws Exception { @Test public void testLdapAuthenticationSSLPEMFile() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").toFile().getName()) - .put("verify_hostnames", false) - .put("path.home",".") - .put("path.conf",FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").getParent()) - .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, Paths.get("src/test/resources/ldap")).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").toFile().getName() + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .put("path.conf", FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").getParent()) + .build(); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, Paths.get("src/test/resources/ldap")).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @Test public void testLdapAuthenticationSSLPEMText() throws Exception { - final Settings settingsFromFile = Settings - .builder() - .loadFromPath( - Paths - .get(FileHelper - .getAbsoluteFilePathFromClassPath("ldap/test1.yml") - .toFile() - .getAbsolutePath())) - .build(); - Settings settings = Settings.builder().put(settingsFromFile).putList("hosts", "localhost:"+ldapsPort).build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + final Settings settingsFromFile = Settings.builder() + .loadFromPath(Paths.get(FileHelper.getAbsoluteFilePathFromClassPath("ldap/test1.yml").toFile().getAbsolutePath())) + .build(); + Settings settings = Settings.builder().put(settingsFromFile).putList("hosts", "localhost:" + ldapsPort).build(); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -227,20 +235,23 @@ public void testLdapAuthenticationSSLPEMText() throws Exception { @Test public void testLdapAuthenticationSSLSSLv3() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false) - .putList("enabled_ssl_protocols", "SSLv3") - .put("path.home",".") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .putList("enabled_ssl_protocols", "SSLv3") + .put("path.home", ".") + .build(); try { - new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); } catch (Exception e) { Assert.assertEquals(e.getCause().getClass(), org.ldaptive.LdapException.class); Assert.assertTrue(e.getCause().getMessage().contains("Unable to connec")); @@ -251,20 +262,23 @@ public void testLdapAuthenticationSSLSSLv3() throws Exception { @Test public void testLdapAuthenticationSSLUnknowCipher() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false) - .putList("enabled_ssl_ciphers", "AAA") - .put("path.home",".") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .putList("enabled_ssl_ciphers", "AAA") + .put("path.home", ".") + .build(); try { - new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); } catch (Exception e) { Assert.assertEquals(e.getCause().getClass(), org.ldaptive.LdapException.class); Assert.assertTrue(e.getCause().getMessage().contains("Unable to connec")); @@ -275,20 +289,23 @@ public void testLdapAuthenticationSSLUnknowCipher() throws Exception { @Test public void testLdapAuthenticationSpecialCipherProtocol() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false) - .putList("enabled_ssl_protocols", "TLSv1.2") - .putList("enabled_ssl_ciphers", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA") - .put("path.home",".") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .putList("enabled_ssl_protocols", "TLSv1.2") + .putList("enabled_ssl_ciphers", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA") + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); @@ -297,18 +314,21 @@ public void testLdapAuthenticationSpecialCipherProtocol() throws Exception { @Test public void testLdapAuthenticationSSLNoKeystore() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false) - .put("path.home",".") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -316,15 +336,16 @@ public void testLdapAuthenticationSSLNoKeystore() throws Exception { @Test public void testLdapAuthenticationSSLFailPlain() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true).build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .build(); try { - new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); } catch (final Exception e) { Assert.assertEquals(org.ldaptive.LdapException.class, e.getCause().getClass()); } @@ -333,10 +354,10 @@ public void testLdapAuthenticationSSLFailPlain() throws Exception { @Test public void testLdapExists() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); final LDAPAuthenticationBackend lbe = new LDAPAuthenticationBackend(settings, null); Assert.assertTrue(lbe.exists(new User("jacksonm"))); @@ -346,20 +367,20 @@ public void testLdapExists() throws Exception { @Test public void testLdapAuthorization() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - // .put("plugins.security.authentication.authorization.ldap.userrolename", - // "(uniqueMember={0})") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + // .put("plugins.security.authentication.authorization.ldap.userrolename", + // "(uniqueMember={0})") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -373,19 +394,19 @@ public void testLdapAuthorization() throws Exception { @Test public void testLdapAuthenticationReturnAttributes() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .putList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, "mail", "cn", "uid") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, "mail", "cn", "uid") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -401,10 +422,10 @@ public void testLdapAuthenticationReturnAttributes() throws Exception { @Test public void testLdapAuthenticationReferral() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); try { @@ -418,17 +439,21 @@ public void testLdapAuthenticationReferral() throws Exception { @Test public void testLdapDontFollowReferrals() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.FOLLOW_REFERRALS, false).build(); - + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.FOLLOW_REFERRALS, false) + .build(); final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); try { - //If following is off then should fail to return the result provided by following - final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT)); + // If following is off then should fail to return the result provided by following + final LdapEntry ref1 = LdapHelper.lookup( + con, + "cn=Ref1,ou=people,o=TEST", + ReturnAttributes.ALL.value(), + settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT) + ); Assert.assertNull(ref1); } finally { con.close(); @@ -438,20 +463,20 @@ public void testLdapDontFollowReferrals() throws Exception { @Test public void testLdapEscape() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("ssign", "ssignsecret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("ssign", "ssignsecret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Special\\, Sign,ou=people,o=TEST", user.getName()); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -463,18 +488,18 @@ public void testLdapEscape() throws Exception { @Test public void testLdapAuthorizationRoleSearchUsername() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(cn={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember=cn={1},ou=people,o=TEST)") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("Michael Jackson", "secret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(cn={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember=cn={1},ou=people,o=TEST)") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("Michael Jackson", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -489,15 +514,14 @@ public void testLdapAuthorizationRoleSearchUsername() throws Exception { @Test public void testLdapAuthorizationOnly() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .build(); final User user = new User("jacksonm"); @@ -509,19 +533,17 @@ public void testLdapAuthorizationOnly() throws Exception { Assert.assertEquals("ceo", new ArrayList(new TreeSet(user.getRoles())).get(0)); } - - @Test public void testLdapAuthorizationNonDNEntry() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "description") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "description") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .build(); final User user = new User("jacksonm"); @@ -533,20 +555,18 @@ public void testLdapAuthorizationNonDNEntry() throws Exception { Assert.assertEquals("ceo-ceo", new ArrayList(new TreeSet(user.getRoles())).get(0)); } - @Test public void testLdapAuthorizationNested() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .build(); final User user = new User("spock"); @@ -561,17 +581,16 @@ public void testLdapAuthorizationNested() throws Exception { @Test public void testLdapAuthorizationNestedFilter() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=nested2,ou=groups,o=TEST") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=nested2,ou=groups,o=TEST") + .build(); final User user = new User("spock"); @@ -587,16 +606,15 @@ public void testLdapAuthorizationNestedFilter() throws Exception { @Test public void testLdapAuthorizationDnNested() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .build(); final User user = new User("spock"); @@ -611,17 +629,16 @@ public void testLdapAuthorizationDnNested() throws Exception { @Test public void testLdapAuthorizationDn() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "UID") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "UID") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .build(); final User user = new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret".getBytes())); @@ -636,13 +653,16 @@ public void testLdapAuthorizationDn() throws Exception { @Test public void testLdapAuthenticationUserNameAttribute() throws Exception { - - final Settings settings = Settings.builder().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST").put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + final Settings settings = Settings.builder() + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("jacksonm", user.getName()); } @@ -650,17 +670,21 @@ public void testLdapAuthenticationUserNameAttribute() throws Exception { @Test public void testLdapAuthenticationStartTLS() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_START_TLS, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).put("path.home", ".") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_START_TLS, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -668,19 +692,19 @@ public void testLdapAuthenticationStartTLS() throws Exception { @Test public void testLdapAuthorizationSkipUsers() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .putList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS, "cn=Michael Jackson,ou*people,o=TEST") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS, "cn=Michael Jackson,ou*people,o=TEST") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -694,17 +718,18 @@ public void testLdapAuthorizationSkipUsers() throws Exception { public void testLdapAuthorizationSkipUsersNoDn() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .putList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS, "jacksonm") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS, "jacksonm") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -717,18 +742,17 @@ public void testLdapAuthorizationSkipUsersNoDn() throws Exception { @Test public void testLdapAuthorizationNestedAttr() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .build(); final User user = new User("spock"); @@ -744,19 +768,18 @@ public void testLdapAuthorizationNestedAttr() throws Exception { @Test public void testLdapAuthorizationNestedAttrFilter() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) - .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=rolemo4*") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=rolemo4*") + .build(); final User user = new User("spock"); @@ -773,19 +796,18 @@ public void testLdapAuthorizationNestedAttrFilter() throws Exception { @Test public void testLdapAuthorizationNestedAttrFilterAll() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) - .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "*") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "*") + .build(); final User user = new User("spock"); @@ -800,18 +822,18 @@ public void testLdapAuthorizationNestedAttrFilterAll() throws Exception { @Test public void testLdapAuthorizationNestedAttrFilterAllEqualsNestedFalse() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) //-> same like putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "*") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) // -> same like + // putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "*") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .build(); final User user = new User("spock"); @@ -826,18 +848,17 @@ public void testLdapAuthorizationNestedAttrFilterAllEqualsNestedFalse() throws E @Test public void testLdapAuthorizationNestedAttrNoRoleSearch() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "unused") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(((unused") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, false) - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "unused") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(((unused") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, false) + .build(); final User user = new User("spock"); @@ -854,35 +875,42 @@ public void testLdapAuthorizationNestedAttrNoRoleSearch() throws Exception { public void testCustomAttributes() throws Exception { Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); Assert.assertEquals(user.getCustomAttributesMap().toString(), 16, user.getCustomAttributesMap().size()); - Assert.assertFalse(user.getCustomAttributesMap().toString(), user.getCustomAttributesMap().keySet().contains("attr.ldap.userpassword")); + Assert.assertFalse( + user.getCustomAttributesMap().toString(), + user.getCustomAttributesMap().keySet().contains("attr.ldap.userpassword") + ); settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 0) - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 0) + .build(); - user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertEquals(user.getCustomAttributesMap().toString(), 2, user.getCustomAttributesMap().size()); settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .putList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, "*objectclass*","entryParentId") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .putList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, "*objectclass*", "entryParentId") + .build(); - user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertEquals(user.getCustomAttributesMap().toString(), 2, user.getCustomAttributesMap().size()); @@ -892,16 +920,16 @@ public void testCustomAttributes() throws Exception { public void testLdapAuthorizationNonDNRoles() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description, ou") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description, ou") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .build(); final User user = new User("nondnroles"); @@ -914,25 +942,28 @@ public void testLdapAuthorizationNonDNRoles() throws Exception { Assert.assertTrue("Roles do not contain non-LDAP role 'humanresources'", user.getRoles().contains("humanresources")); Assert.assertTrue("Roles do not contain LDAP role 'dummyempty'", user.getRoles().contains("dummyempty")); Assert.assertTrue("Roles do not contain non-LDAP role 'role2'", user.getRoles().contains("role2")); - Assert.assertTrue("Roles do not contain non-LDAP role 'anotherrole' from second role name", user.getRoles().contains("anotherrole")); + Assert.assertTrue( + "Roles do not contain non-LDAP role 'anotherrole' from second role name", + user.getRoles().contains("anotherrole") + ); } - @Test public void testLdapSpecial186() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "description") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("spec186", "spec186" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "description") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("spec186", "spec186".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST", user.getName()); Assert.assertEquals("AA BB/CC (DD) my, company end=with=whitespace ", user.getUserEntry().getAttribute("cn").getStringValue()); @@ -948,12 +979,18 @@ public void testLdapSpecial186() throws Exception { Assert.assertTrue(user.getRoles().toString().contains("ROLEx(186n) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186nn) consists of\\, special=")); - new LDAPAuthorizationBackend(settings, null).fillRoles(new User("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), null); + new LDAPAuthorizationBackend(settings, null).fillRoles( + new User("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), + null + ); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLEx(186n) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186nn) consists of\\, special=")); - new LDAPAuthorizationBackend(settings, null).fillRoles(new User("CN=AA BB\\/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), null); + new LDAPAuthorizationBackend(settings, null).fillRoles( + new User("CN=AA BB\\/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), + null + ); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLEx(186n) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186nn) consists of\\, special=")); @@ -963,17 +1000,18 @@ public void testLdapSpecial186() throws Exception { public void testLdapSpecial186_2() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("spec186", "spec186" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("spec186", "spec186".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST", user.getName()); Assert.assertEquals("AA BB/CC (DD) my, company end=with=whitespace ", user.getUserEntry().getAttribute("cn").getStringValue()); @@ -989,13 +1027,18 @@ public void testLdapSpecial186_2() throws Exception { Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186n) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186nn) consists of\\, special\\=chars\\ ")); - - new LDAPAuthorizationBackend(settings, null).fillRoles(new User("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), null); + new LDAPAuthorizationBackend(settings, null).fillRoles( + new User("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), + null + ); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186n) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186nn) consists of\\, special\\=chars\\ ")); - new LDAPAuthorizationBackend(settings, null).fillRoles(new User("CN=AA BB\\/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), null); + new LDAPAuthorizationBackend(settings, null).fillRoles( + new User("CN=AA BB\\/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), + null + ); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186n) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186nn) consists of\\, special\\=chars\\ ")); @@ -1004,13 +1047,14 @@ public void testLdapSpecial186_2() throws Exception { @Test public void testOperationalAttributes() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); LdapAttribute operationAttribute = user.getUserEntry().getAttribute("entryUUID"); Assert.assertNotNull(operationAttribute); @@ -1023,23 +1067,23 @@ public void testOperationalAttributes() throws Exception { public void testMultiCn() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("multi", "multi" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("multi", "multi".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=cabc,ou=people,o=TEST", user.getName()); System.out.println(user.getUserEntry().getAttribute("cn")); } - @AfterClass public static void tearDown() throws Exception { diff --git a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestClientCert.java b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestClientCert.java index 6f62cd48dd..1765b5fd26 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestClientCert.java +++ b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestClientCert.java @@ -39,106 +39,124 @@ public class LdapBackendTestClientCert { @Test public void testNoAuth() throws Exception { - //no auth + // no auth final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put("path.home",".") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks" + ) + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put("path.home", ".") + .build(); LdapUser user; try { - user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail(); } catch (Exception e) { - Assert.assertTrue(ExceptionUtils.getRootCause(e).getMessage(), ExceptionUtils.getRootCause(e).getMessage().contains("authentication required")); + Assert.assertTrue( + ExceptionUtils.getRootCause(e).getMessage(), + ExceptionUtils.getRootCause(e).getMessage().contains("authentication required") + ); } } @Test public void testNoAuthX() throws Exception { - //no auth + // no auth final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "kdc.dummy.com:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks") - .put(ConfigConstants.LDAPS_VERIFY_HOSTNAMES, false) - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put("path.home",".") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "kdc.dummy.com:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks" + ) + .put(ConfigConstants.LDAPS_VERIFY_HOSTNAMES, false) + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put("path.home", ".") + .build(); LdapUser user; try { - user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail(); } catch (Exception e) { - Assert.assertTrue(ExceptionUtils.getRootCause(e).getMessage(), ExceptionUtils.getRootCause(e).getMessage().contains("authentication required")); + Assert.assertTrue( + ExceptionUtils.getRootCause(e).getMessage(), + ExceptionUtils.getRootCause(e).getMessage().contains("authentication required") + ); } } @Test public void testNoAuthY() throws Exception { - //no auth + // no auth final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "kdc.dummy.com:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/wrong/truststore.jks") - .put(ConfigConstants.LDAPS_VERIFY_HOSTNAMES, false) - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put("path.home",".") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "kdc.dummy.com:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/wrong/truststore.jks" + ) + .put(ConfigConstants.LDAPS_VERIFY_HOSTNAMES, false) + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put("path.home", ".") + .build(); LdapUser user; try { - user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail(); } catch (Exception e) { - Assert.assertTrue(ExceptionUtils.getRootCause(e).getMessage(), ExceptionUtils.getRootCause(e).getMessage().contains("Unable to connect to any")); + Assert.assertTrue( + ExceptionUtils.getRootCause(e).getMessage(), + ExceptionUtils.getRootCause(e).getMessage().contains("Unable to connect to any") + ); } } - - - @Test public void testBindDnAuthLocalhost() throws Exception { - //bin dn auth + // bin dn auth final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put(ConfigConstants.LDAP_BIND_DN, "cn=ldapbinder,ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_PASSWORD, "ldapbinder") - .put("path.home",".") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks" + ) + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put(ConfigConstants.LDAP_BIND_DN, "cn=ldapbinder,ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_PASSWORD, "ldapbinder") + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("ldap_hr_employee", user.getName()); } @@ -147,21 +165,27 @@ public void testBindDnAuthLocalhost() throws Exception { public void testLdapSslAuth() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put("plugins.security.ssl.transport.keystore_filepath", "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/spock-keystore.jks") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks") - .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) - .put(ConfigConstants.LDAPS_JKS_CERT_ALIAS, "spock") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put("path.home",".") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + "plugins.security.ssl.transport.keystore_filepath", + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/spock-keystore.jks" + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks" + ) + .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) + .put(ConfigConstants.LDAPS_JKS_CERT_ALIAS, "spock") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("ldap_hr_employee", user.getName()); } @@ -170,24 +194,30 @@ public void testLdapSslAuth() throws Exception { public void testLdapSslAuthPem() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/ca/root-ca.pem") - .put(ConfigConstants.LDAPS_PEMCERT_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/spock.crtfull.pem") - .put(ConfigConstants.LDAPS_PEMKEY_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/spock.key.pem") - //.put(ConfigConstants.LDAPS_PEMKEY_PASSWORD, "changeit") - .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put("path.home",".") - //.put(ConfigConstants.LDAP_BIND_DN, "cn=ldapbinder,ou=people,dc=example,dc=com") - // .put(ConfigConstants.LDAP_PASSWORD, "ldapbinder") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/ca/root-ca.pem" + ) + .put( + ConfigConstants.LDAPS_PEMCERT_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/spock.crtfull.pem" + ) + .put(ConfigConstants.LDAPS_PEMKEY_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/spock.key.pem") + // .put(ConfigConstants.LDAPS_PEMKEY_PASSWORD, "changeit") + .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put("path.home", ".") + // .put(ConfigConstants.LDAP_BIND_DN, "cn=ldapbinder,ou=people,dc=example,dc=com") + // .put(ConfigConstants.LDAP_PASSWORD, "ldapbinder") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("ldap_hr_employee", user.getName()); } @@ -196,59 +226,66 @@ public void testLdapSslAuthPem() throws Exception { public void testLdapSslAuthNo() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put("plugins.security.ssl.transport.keystore_filepath", "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/kirk-keystore.jks") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks") - .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) - .put(ConfigConstants.LDAPS_JKS_CERT_ALIAS, "kirk") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put("path.home",".") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + "plugins.security.ssl.transport.keystore_filepath", + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/kirk-keystore.jks" + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks" + ) + .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) + .put(ConfigConstants.LDAPS_JKS_CERT_ALIAS, "kirk") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("ldap_hr_employee", user.getName()); } - public void testLdapAuthenticationSSL() throws Exception { - //startLDAPServer(); + // startLDAPServer(); final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "kdc.dummy.com:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - //.put("plugins.security.ssl.transport.keystore_filepath", "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/cn=ldapbinder,ou=people,dc=example,dc=com-keystore.jks") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks") - //.put("verify_hostnames", false) - //.put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) - //.put(ConfigConstants.LDAPS_JKS_CERT_ALIAS, "cn=ldapbinder,ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - //.put(ConfigConstants.LDAP_BIND_DN, "cn=ldapbinder,ou=people,dc=example,dc=com") - //.put(ConfigConstants.LDAP_PASSWORD, "ldapbinder") - - //.putList(ConfigConstants.LDAPS_ENABLED_SSL_CIPHERS, "TLS_RSA_WITH_AES_128_CBC_SHA") - //.putList(ConfigConstants.LDAPS_ENABLED_SSL_PROTOCOLS, "TLSv1") - //TLS_RSA_AES_128_CBC_SHA1 - .put("path.home",".") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "kdc.dummy.com:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + // .put("plugins.security.ssl.transport.keystore_filepath", + // "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/cn=ldapbinder,ou=people,dc=example,dc=com-keystore.jks") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks" + ) + // .put("verify_hostnames", false) + // .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) + // .put(ConfigConstants.LDAPS_JKS_CERT_ALIAS, "cn=ldapbinder,ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + // .put(ConfigConstants.LDAP_BIND_DN, "cn=ldapbinder,ou=people,dc=example,dc=com") + // .put(ConfigConstants.LDAP_PASSWORD, "ldapbinder") + + // .putList(ConfigConstants.LDAPS_ENABLED_SSL_CIPHERS, "TLS_RSA_WITH_AES_128_CBC_SHA") + // .putList(ConfigConstants.LDAPS_ENABLED_SSL_PROTOCOLS, "TLSv1") + // TLS_RSA_AES_128_CBC_SHA1 + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("ldap_hr_employee", user.getName()); } - - public static File getAbsoluteFilePathFromClassPath(final String fileNameFromClasspath) { File file = null; final URL fileUrl = LdapBackendTestClientCert.class.getClassLoader().getResource(fileNameFromClasspath); diff --git a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestNewStyleConfig.java b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestNewStyleConfig.java index 3e21a223e2..8bc13eec48 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestNewStyleConfig.java +++ b/src/test/java/com/amazon/dlic/auth/ldap/LdapBackendTestNewStyleConfig.java @@ -52,7 +52,7 @@ public class LdapBackendTestNewStyleConfig { public static void startLdapServer() throws Exception { ldapServer = new EmbeddedLDAPServer(); ldapServer.start(); - ldapServer.applyLdif("base.ldif","base2.ldif"); + ldapServer.applyLdif("base.ldif", "base2.ldif"); ldapPort = ldapServer.getLdapPort(); ldapsPort = ldapServer.getLdapsPort(); } @@ -61,11 +61,13 @@ public static void startLdapServer() throws Exception { public void testLdapAuthentication() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -74,38 +76,46 @@ public void testLdapAuthentication() throws Exception { public void testLdapAuthenticationFakeLogin() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, true).build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, true) + .build(); - new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("unknown", "unknown".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("unknown", "unknown".getBytes(StandardCharsets.UTF_8)) + ); } @Test(expected = OpenSearchSecurityException.class) public void testLdapInjection() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); String injectString = "*jack*"; @SuppressWarnings("unused") - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials(injectString, "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials(injectString, "secret".getBytes(StandardCharsets.UTF_8)) + ); } @Test public void testLdapAuthenticationBindDn() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") - .put(ConfigConstants.LDAP_PASSWORD, "spocksecret").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") + .put(ConfigConstants.LDAP_PASSWORD, "spocksecret") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -114,60 +124,75 @@ public void testLdapAuthenticationBindDn() throws Exception { public void testLdapAuthenticationWrongBindDn() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") - .put(ConfigConstants.LDAP_PASSWORD, "wrong").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") + .put(ConfigConstants.LDAP_PASSWORD, "wrong") + .build(); - new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); } @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationBindFail() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); - new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "wrong".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "wrong".getBytes(StandardCharsets.UTF_8)) + ); } @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationNoUser() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); - new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("UNKNOWN", "UNKNOWN".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("UNKNOWN", "UNKNOWN".getBytes(StandardCharsets.UTF_8)) + ); } @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationFail() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); - new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "xxxxx".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "xxxxx".getBytes(StandardCharsets.UTF_8)) + ); } @Test public void testLdapAuthenticationSSL() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -176,14 +201,20 @@ public void testLdapAuthenticationSSL() throws Exception { public void testLdapAuthenticationSSLPEMFile() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").toFile().getName()) - .put("verify_hostnames", false).put("path.home", ".") - .put("path.conf", FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").getParent()).build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, Paths.get("src/test/resources/ldap")) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").toFile().getName() + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .put("path.conf", FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").getParent()) + .build(); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, Paths.get("src/test/resources/ldap")).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -191,18 +222,13 @@ public void testLdapAuthenticationSSLPEMFile() throws Exception { @Test public void testLdapAuthenticationSSLPEMText() throws Exception { - final Settings settingsFromFile = Settings - .builder() - .loadFromPath( - Paths - .get(FileHelper - .getAbsoluteFilePathFromClassPath("ldap/test1.yml") - .toFile() - .getAbsolutePath())) - .build(); - Settings settings = Settings.builder().put(settingsFromFile).putList("hosts", "localhost:"+ldapsPort).build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settingsFromFile = Settings.builder() + .loadFromPath(Paths.get(FileHelper.getAbsoluteFilePathFromClassPath("ldap/test1.yml").toFile().getAbsolutePath())) + .build(); + Settings settings = Settings.builder().put(settingsFromFile).putList("hosts", "localhost:" + ldapsPort).build(); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -211,15 +237,22 @@ public void testLdapAuthenticationSSLPEMText() throws Exception { public void testLdapAuthenticationSSLSSLv3() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).putList("enabled_ssl_protocols", "SSLv3").put("path.home", ".").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .putList("enabled_ssl_protocols", "SSLv3") + .put("path.home", ".") + .build(); try { - new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); } catch (Exception e) { Assert.assertEquals(e.getCause().getClass(), org.ldaptive.LdapException.class); Assert.assertTrue(e.getCause().getMessage().contains("Unable to connec")); @@ -231,15 +264,22 @@ public void testLdapAuthenticationSSLSSLv3() throws Exception { public void testLdapAuthenticationSSLUnknownCipher() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).putList("enabled_ssl_ciphers", "AAA").put("path.home", ".").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .putList("enabled_ssl_ciphers", "AAA") + .put("path.home", ".") + .build(); try { - new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); } catch (Exception e) { Assert.assertEquals(e.getCause().getClass(), org.ldaptive.LdapException.class); Assert.assertTrue(e.getCause().getMessage().contains("Unable to connec")); @@ -251,15 +291,22 @@ public void testLdapAuthenticationSSLUnknownCipher() throws Exception { public void testLdapAuthenticationSpecialCipherProtocol() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).putList("enabled_ssl_protocols", "TLSv1.2") - .putList("enabled_ssl_ciphers", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA").put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .putList("enabled_ssl_protocols", "TLSv1.2") + .putList("enabled_ssl_ciphers", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA") + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); @@ -269,14 +316,20 @@ public void testLdapAuthenticationSpecialCipherProtocol() throws Exception { public void testLdapAuthenticationSSLNoKeystore() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -285,12 +338,15 @@ public void testLdapAuthenticationSSLNoKeystore() throws Exception { public void testLdapAuthenticationSSLFailPlain() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true).build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .build(); try { - new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); } catch (final Exception e) { Assert.assertEquals(org.ldaptive.LdapException.class, e.getCause().getClass()); } @@ -300,8 +356,9 @@ public void testLdapAuthenticationSSLFailPlain() throws Exception { public void testLdapExists() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); final LDAPAuthenticationBackend lbe = new LDAPAuthenticationBackend(settings, null); Assert.assertTrue(lbe.exists(new User("jacksonm"))); @@ -312,16 +369,19 @@ public void testLdapExists() throws Exception { public void testLdapAuthorization() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.search", "(uniqueMember={0})") - // .put("plugins.security.authentication.authorization.ldap.userrolename", - // "(uniqueMember={0})") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.search", "(uniqueMember={0})") + // .put("plugins.security.authentication.authorization.ldap.userrolename", + // "(uniqueMember={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -336,8 +396,9 @@ public void testLdapAuthorization() throws Exception { public void testLdapAuthenticationReferral() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); try { @@ -352,17 +413,21 @@ public void testLdapAuthenticationReferral() throws Exception { @Test public void testLdapDontFollowReferrals() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.FOLLOW_REFERRALS, false).build(); - + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.FOLLOW_REFERRALS, false) + .build(); final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); try { - //If following is off then should fail to return the result provided by following - final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT)); + // If following is off then should fail to return the result provided by following + final LdapEntry ref1 = LdapHelper.lookup( + con, + "cn=Ref1,ou=people,o=TEST", + ReturnAttributes.ALL.value(), + settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT) + ); Assert.assertNull(ref1); } finally { con.close(); @@ -373,15 +438,19 @@ public void testLdapDontFollowReferrals() throws Exception { public void testLdapEscape() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.search", "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true).build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.search", "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("ssign", "ssignsecret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("ssign", "ssignsecret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Special\\, Sign,ou=people,o=TEST", user.getName()); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -394,13 +463,17 @@ public void testLdapEscape() throws Exception { public void testLdapAuthorizationRoleSearchUsername() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(cn={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.search", "(uniqueMember=cn={1},ou=people,o=TEST)").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(cn={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.search", "(uniqueMember=cn={1},ou=people,o=TEST)") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("Michael Jackson", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("Michael Jackson", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -416,10 +489,13 @@ public void testLdapAuthorizationRoleSearchUsername() throws Exception { public void testLdapAuthorizationOnly() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.search", "(uniqueMember={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.search", "(uniqueMember={0})") + .build(); final User user = new User("jacksonm"); @@ -435,11 +511,14 @@ public void testLdapAuthorizationOnly() throws Exception { public void testLdapAuthorizationNested() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put("roles.g1.search", "(uniqueMember={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .build(); final User user = new User("spock"); @@ -455,12 +534,15 @@ public void testLdapAuthorizationNested() throws Exception { public void testLdapAuthorizationNestedFilter() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put("roles.g1.search", "(uniqueMember={0})") - .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=nested2,ou=groups,o=TEST").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=nested2,ou=groups,o=TEST") + .build(); final User user = new User("spock"); @@ -477,11 +559,14 @@ public void testLdapAuthorizationNestedFilter() throws Exception { public void testLdapAuthorizationDnNested() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put("roles.g1.search", "(uniqueMember={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .build(); final User user = new User("spock"); @@ -497,15 +582,17 @@ public void testLdapAuthorizationDnNested() throws Exception { public void testLdapAuthorizationDn() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "UID") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) - .put("roles.g1.search", "(uniqueMember={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "UID") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) + .put("roles.g1.search", "(uniqueMember={0})") + .build(); - final User user = new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes())); + final User user = new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("jacksonm", "secret".getBytes())); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -519,12 +606,15 @@ public void testLdapAuthorizationDn() throws Exception { public void testLdapAuthenticationUserNameAttribute() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.base", "ou=people,o=TEST").put("users.u1.search", "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.base", "ou=people,o=TEST") + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("jacksonm", user.getName()); } @@ -533,14 +623,20 @@ public void testLdapAuthenticationUserNameAttribute() throws Exception { public void testLdapAuthenticationStartTLS() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_START_TLS, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_START_TLS, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -549,14 +645,18 @@ public void testLdapAuthenticationStartTLS() throws Exception { public void testLdapAuthorizationSkipUsers() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.search", "(uniqueMember={0})") - .putList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS, "cn=Michael Jackson,ou*people,o=TEST").build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.search", "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS, "cn=Michael Jackson,ou*people,o=TEST") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -570,13 +670,16 @@ public void testLdapAuthorizationSkipUsers() throws Exception { public void testLdapAuthorizationNestedAttr() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put("roles.g1.search", "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true).build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .build(); final User user = new User("spock"); @@ -593,14 +696,17 @@ public void testLdapAuthorizationNestedAttr() throws Exception { public void testLdapAuthorizationNestedAttrFilter() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put("roles.g1.search", "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) - .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=rolemo4*").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=rolemo4*") + .build(); final User user = new User("spock"); @@ -618,14 +724,17 @@ public void testLdapAuthorizationNestedAttrFilter() throws Exception { public void testLdapAuthorizationNestedAttrFilterAll() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put("roles.g1.search", "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) - .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "*").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "*") + .build(); final User user = new User("spock"); @@ -641,15 +750,18 @@ public void testLdapAuthorizationNestedAttrFilterAll() throws Exception { public void testLdapAuthorizationNestedAttrFilterAllEqualsNestedFalse() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) // -> same like - // putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, - // "*") - .put("roles.g1.search", "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true).build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) // -> same like + // putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, + // "*") + .put("roles.g1.search", "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .build(); final User user = new User("spock"); @@ -665,12 +777,16 @@ public void testLdapAuthorizationNestedAttrFilterAllEqualsNestedFalse() throws E public void testLdapAuthorizationNestedAttrNoRoleSearch() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "unused").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true).put("roles.g1.search", "(((unused") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, false).build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "unused") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(((unused") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, false) + .build(); final User user = new User("spock"); @@ -687,33 +803,39 @@ public void testLdapAuthorizationNestedAttrNoRoleSearch() throws Exception { public void testCustomAttributes() throws Exception { Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); - LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); Assert.assertEquals(user.getCustomAttributesMap().toString(), 16, user.getCustomAttributesMap().size()); - Assert.assertFalse(user.getCustomAttributesMap().toString(), - user.getCustomAttributesMap().containsKey("attr.ldap.userpassword")); + Assert.assertFalse(user.getCustomAttributesMap().toString(), user.getCustomAttributesMap().containsKey("attr.ldap.userpassword")); settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 0).build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 0) + .build(); - user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertEquals(user.getCustomAttributesMap().toString(), 2, user.getCustomAttributesMap().size()); settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})") - .putList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, "*objectclass*", "entryParentId").build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .putList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, "*objectclass*", "entryParentId") + .build(); - user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertEquals(user.getCustomAttributesMap().toString(), 2, user.getCustomAttributesMap().size()); @@ -723,13 +845,16 @@ public void testCustomAttributes() throws Exception { public void testLdapAuthorizationNonDNRoles() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put("roles.g1.search", "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description, ou") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true).build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description, ou") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .build(); final User user = new User("nondnroles"); @@ -739,24 +864,29 @@ public void testLdapAuthorizationNonDNRoles() throws Exception { Assert.assertEquals("nondnroles", user.getName()); Assert.assertEquals(5, user.getRoles().size()); Assert.assertTrue("Roles do not contain non-LDAP role 'kibanauser'", user.getRoles().contains("kibanauser")); - Assert.assertTrue("Roles do not contain non-LDAP role 'humanresources'", - user.getRoles().contains("humanresources")); + Assert.assertTrue("Roles do not contain non-LDAP role 'humanresources'", user.getRoles().contains("humanresources")); Assert.assertTrue("Roles do not contain LDAP role 'dummyempty'", user.getRoles().contains("dummyempty")); Assert.assertTrue("Roles do not contain non-LDAP role 'role2'", user.getRoles().contains("role2")); - Assert.assertTrue("Roles do not contain non-LDAP role 'anotherrole' from second role name", - user.getRoles().contains("anotherrole")); + Assert.assertTrue( + "Roles do not contain non-LDAP role 'anotherrole' from second role name", + user.getRoles().contains("anotherrole") + ); } @Test public void testChainedLdapAuthentication1() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("users.u2.search", "(uid={0})").put("users.u2.base", "ou=people2,o=TEST").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("users.u2.search", "(uid={0})") + .put("users.u2.base", "ou=people2,o=TEST") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -765,12 +895,16 @@ public void testChainedLdapAuthentication1() throws Exception { public void testChainedLdapAuthentication2() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("users.u2.search", "(uid={0})").put("users.u2.base", "ou=people2,o=TEST").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("users.u2.search", "(uid={0})") + .put("users.u2.base", "ou=people2,o=TEST") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("presleye", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("presleye", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Elvis Presley,ou=people2,o=TEST", user.getName()); } @@ -779,13 +913,17 @@ public void testChainedLdapAuthentication2() throws Exception { public void testChainedLdapAuthenticationDuplicate() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_SEARCH_ALL_BASES, true).put("users.u1.search", "(uid={0})") - .put("users.u1.base", "ou=people,o=TEST").put("users.u2.search", "(uid={0})") - .put("users.u2.base", "ou=people2,o=TEST").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_SEARCH_ALL_BASES, true) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("users.u2.search", "(uid={0})") + .put("users.u2.base", "ou=people2,o=TEST") + .build(); - new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); // Fails with OpenSearchSecurityException because two possible instances are // found @@ -795,9 +933,11 @@ public void testChainedLdapAuthenticationDuplicate() throws Exception { public void testChainedLdapExists() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u2.search", "(uid={0})") - .put("users.u2.base", "ou=people2,o=TEST").build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u2.search", "(uid={0})") + .put("users.u2.base", "ou=people2,o=TEST") + .build(); final LDAPAuthenticationBackend lbe = new LDAPAuthenticationBackend(settings, null); Assert.assertTrue(lbe.exists(new User("jacksonm"))); @@ -809,17 +949,19 @@ public void testChainedLdapExists() throws Exception { public void testChainedLdapAuthorization() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.base", "ou=groups,o=TEST") - .put("roles.g1.search", "(uniqueMember={0})") - .put("roles.g2.base", "ou=groups2,o=TEST") - .put("roles.g2.search", "(uniqueMember={0})") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.base", "ou=groups,o=TEST") + .put("roles.g1.search", "(uniqueMember={0})") + .put("roles.g2.base", "ou=groups2,o=TEST") + .put("roles.g2.search", "(uniqueMember={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -838,17 +980,19 @@ public void testChainedLdapAuthorization() throws Exception { public void testCrossChainedLdapAuthorization() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people2,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.base", "ou=groups,o=TEST") - .put("roles.g1.search", "(uniqueMember={0})") - .put("roles.g2.base", "ou=groups2,o=TEST") - .put("roles.g2.search", "(uniqueMember={0})") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people2,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.base", "ou=groups,o=TEST") + .put("roles.g1.search", "(uniqueMember={0})") + .put("roles.g2.base", "ou=groups2,o=TEST") + .put("roles.g2.search", "(uniqueMember={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null) - .authenticate(new AuthCredentials("mercuryf", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("mercuryf", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); diff --git a/src/test/java/com/amazon/dlic/auth/ldap/UtilsTest.java b/src/test/java/com/amazon/dlic/auth/ldap/UtilsTest.java index cbfa40f850..ce22bf6036 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap/UtilsTest.java +++ b/src/test/java/com/amazon/dlic/auth/ldap/UtilsTest.java @@ -19,39 +19,38 @@ public class UtilsTest { - @Test public void testLDAPName() throws Exception { - //same ldapname - Assert.assertEquals(new LdapName("CN=1,OU=2,O=3,C=4"),new LdapName("CN=1,OU=2,O=3,C=4")); + // same ldapname + Assert.assertEquals(new LdapName("CN=1,OU=2,O=3,C=4"), new LdapName("CN=1,OU=2,O=3,C=4")); - //case differ - Assert.assertEquals(new LdapName("CN=1,OU=2,O=3,C=4".toLowerCase()),new LdapName("CN=1,OU=2,O=3,C=4".toUpperCase())); + // case differ + Assert.assertEquals(new LdapName("CN=1,OU=2,O=3,C=4".toLowerCase()), new LdapName("CN=1,OU=2,O=3,C=4".toUpperCase())); - //case differ - Assert.assertEquals(new LdapName("CN=abc,OU=xyz,O=3,C=4".toLowerCase()),new LdapName("CN=abc,OU=xyz,O=3,C=4".toUpperCase())); + // case differ + Assert.assertEquals(new LdapName("CN=abc,OU=xyz,O=3,C=4".toLowerCase()), new LdapName("CN=abc,OU=xyz,O=3,C=4".toUpperCase())); - //same ldapname - Assert.assertEquals(new LdapName("CN=a,OU=2,O=3,C=xxx"),new LdapName("CN=A,OU=2,O=3,C=XxX")); + // same ldapname + Assert.assertEquals(new LdapName("CN=a,OU=2,O=3,C=xxx"), new LdapName("CN=A,OU=2,O=3,C=XxX")); - //case differ and spaces - Assert.assertEquals(new LdapName("Cn =1 ,OU=2, O = 3,C=4"),new LdapName("CN= 1,Ou=2,O=3,c=4")); + // case differ and spaces + Assert.assertEquals(new LdapName("Cn =1 ,OU=2, O = 3,C=4"), new LdapName("CN= 1,Ou=2,O=3,c=4")); - //same components, different order - Assert.assertNotEquals(new LdapName("CN=1,OU=2,C=4,O=3"),new LdapName("CN=1,OU=2,O=3,C=4")); + // same components, different order + Assert.assertNotEquals(new LdapName("CN=1,OU=2,C=4,O=3"), new LdapName("CN=1,OU=2,O=3,C=4")); - //last component missing - Assert.assertNotEquals(new LdapName("CN=1,OU=2,O=3"),new LdapName("CN=1,OU=2,O=3,C=4")); + // last component missing + Assert.assertNotEquals(new LdapName("CN=1,OU=2,O=3"), new LdapName("CN=1,OU=2,O=3,C=4")); - //first component missing - Assert.assertNotEquals(new LdapName("OU=2,O=3,C=4"),new LdapName("CN=1,OU=2,O=3,C=4")); + // first component missing + Assert.assertNotEquals(new LdapName("OU=2,O=3,C=4"), new LdapName("CN=1,OU=2,O=3,C=4")); - //parse exception + // parse exception try { new LdapName("OU2,O=3,C=4"); Assert.fail(); } catch (InvalidNameException e) { - //expected + // expected } } } diff --git a/src/test/java/com/amazon/dlic/auth/ldap/srv/EmbeddedLDAPServer.java b/src/test/java/com/amazon/dlic/auth/ldap/srv/EmbeddedLDAPServer.java index 2dd3fc8284..b56f432e6e 100755 --- a/src/test/java/com/amazon/dlic/auth/ldap/srv/EmbeddedLDAPServer.java +++ b/src/test/java/com/amazon/dlic/auth/ldap/srv/EmbeddedLDAPServer.java @@ -11,7 +11,6 @@ package com.amazon.dlic.auth.ldap.srv; - public class EmbeddedLDAPServer { LdapServer s = new LdapServer(); diff --git a/src/test/java/com/amazon/dlic/auth/ldap/srv/LdapServer.java b/src/test/java/com/amazon/dlic/auth/ldap/srv/LdapServer.java index 3ce93e8761..bb7738d3fd 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap/srv/LdapServer.java +++ b/src/test/java/com/amazon/dlic/auth/ldap/srv/LdapServer.java @@ -49,11 +49,13 @@ final class LdapServer { private static final int LOCK_TIMEOUT = 60; private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS; - private static final String LOCK_TIMEOUT_MSG = "Unable to obtain lock due to timeout after " + LOCK_TIMEOUT + " " + TIME_UNIT.toString(); + private static final String LOCK_TIMEOUT_MSG = "Unable to obtain lock due to timeout after " + + LOCK_TIMEOUT + + " " + + TIME_UNIT.toString(); private static final String SERVER_NOT_STARTED = "The LDAP server is not started."; private static final String SERVER_ALREADY_STARTED = "The LDAP server is already started."; - private InMemoryDirectoryServer server; private final AtomicBoolean isStarted = new AtomicBoolean(Boolean.FALSE); private final ReentrantLock serverStateLock = new ReentrantLock(); @@ -61,9 +63,7 @@ final class LdapServer { private int ldapPort = -1; private int ldapsPort = -1; - - public LdapServer() { - } + public LdapServer() {} public boolean isStarted() { return this.isStarted.get(); @@ -77,7 +77,7 @@ public int getLdapsPort() { return ldapsPort; } - public int start(String... ldifFiles) throws Exception { + public int start(String... ldifFiles) throws Exception { boolean hasLock = false; try { hasLock = serverStateLock.tryLock(LdapServer.LOCK_TIMEOUT, LdapServer.TIME_UNIT); @@ -89,7 +89,7 @@ public int start(String... ldifFiles) throws Exception { throw new IllegalStateException(LdapServer.LOCK_TIMEOUT_MSG); } } catch (InterruptedException ioe) { - //lock interrupted + // lock interrupted LOG.error(ioe.getMessage(), ioe); } finally { if (hasLock) { @@ -112,8 +112,10 @@ private Collection getInMemoryListenerConfigs() throws E String serverKeyStorePath = FileHelper.getAbsoluteFilePathFromClassPath("ldap/node-0-keystore.jks").toFile().getAbsolutePath(); final SSLUtil serverSSLUtil = new SSLUtil( - new KeyStoreKeyManager(serverKeyStorePath, "changeit".toCharArray()), new TrustStoreTrustManager(serverKeyStorePath)); - //final SSLUtil clientSSLUtil = new SSLUtil(new TrustStoreTrustManager(serverKeyStorePath)); + new KeyStoreKeyManager(serverKeyStorePath, "changeit".toCharArray()), + new TrustStoreTrustManager(serverKeyStorePath) + ); + // final SSLUtil clientSSLUtil = new SSLUtil(new TrustStoreTrustManager(serverKeyStorePath)); ldapPort = SocketUtils.findAvailableTcpPort(); ldapsPort = SocketUtils.findAvailableTcpPort(); @@ -127,11 +129,10 @@ private Collection getInMemoryListenerConfigs() throws E private final String loadFile(final String file) throws IOException { String ldif; - try (final Reader reader = new InputStreamReader(this.getClass().getResourceAsStream("/ldap/" + file),StandardCharsets.UTF_8)) { + try (final Reader reader = new InputStreamReader(this.getClass().getResourceAsStream("/ldap/" + file), StandardCharsets.UTF_8)) { ldif = CharStreams.toString(reader); } - ldif = ldif.replace("${hostname}", "localhost"); ldif = ldif.replace("${port}", String.valueOf(ldapPort)); return ldif; @@ -146,14 +147,14 @@ private synchronized int configureAndStartServer(String... ldifFiles) throws Exc final String rootObjectDN = "o=TEST"; InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(new DN(rootObjectDN)); - config.setSchema(schema); //schema can be set on the rootDN too, per javadoc. + config.setSchema(schema); // schema can be set on the rootDN too, per javadoc. config.setListenerConfigs(listenerConfigs); config.setEnforceAttributeSyntaxCompliance(false); config.setEnforceSingleStructuralObjectClass(false); - //config.setLDAPDebugLogHandler(DEBUG_HANDLER); - //config.setAccessLogHandler(DEBUG_HANDLER); - //config.addAdditionalBindCredentials(configuration.getBindDn(), configuration.getPassword()); + // config.setLDAPDebugLogHandler(DEBUG_HANDLER); + // config.setAccessLogHandler(DEBUG_HANDLER); + // config.addAdditionalBindCredentials(configuration.getBindDn(), configuration.getPassword()); server = new InMemoryDirectoryServer(config); @@ -185,7 +186,7 @@ public void stop() { throw new IllegalStateException(LdapServer.LOCK_TIMEOUT_MSG); } } catch (InterruptedException ioe) { - //lock interrupted + // lock interrupted LOG.debug(ExceptionUtils.getStackTrace(ioe)); } finally { if (hasLock) { @@ -198,13 +199,13 @@ private int loadLdifFiles(String... ldifFiles) throws Exception { int ldifLoadCount = 0; for (String ldif : ldifFiles) { ldifLoadCount++; - try (LDIFReader r = new LDIFReader(new BufferedReader(new StringReader(loadFile(ldif))))){ + try (LDIFReader r = new LDIFReader(new BufferedReader(new StringReader(loadFile(ldif))))) { Entry entry = null; while ((entry = r.readEntry()) != null) { server.add(entry); ldifLoadCount++; } - } catch(Exception e) { + } catch (Exception e) { LOG.error(e.toString(), e); throw e; } diff --git a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendIntegTest2.java b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendIntegTest2.java index 0ce9d0c857..e88491e994 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendIntegTest2.java +++ b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendIntegTest2.java @@ -74,13 +74,19 @@ public void testAttributesWithImpersonation() throws Exception { String securityConfigAsYamlString = FileHelper.loadFile("ldap/config_ldap2.yml"); securityConfigAsYamlString = securityConfigAsYamlString.replace("${ldapsPort}", String.valueOf(ldapsPort)); final Settings settings = Settings.builder() - .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".cn=Captain Spock,ou=people,o=TEST", "*") - .build(); + .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".cn=Captain Spock,ou=people,o=TEST", "*") + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setConfigAsYamlString(securityConfigAsYamlString), settings); final RestHelper rh = nonSslRestHelper(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as", "jacksonm") - ,encodeBasicHeader("spock", "spocksecret"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader("opendistro_security_impersonate_as", "jacksonm"), + encodeBasicHeader("spock", "spocksecret") + )).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("ldap.dn")); Assert.assertTrue(res.getBody().contains("attr.ldap.entryDN")); @@ -88,7 +94,6 @@ public void testAttributesWithImpersonation() throws Exception { } - @AfterClass public static void tearDownLdap() throws Exception { diff --git a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestClientCert2.java b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestClientCert2.java index ac8aca7d15..6ba7a84b4a 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestClientCert2.java +++ b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestClientCert2.java @@ -39,109 +39,127 @@ public class LdapBackendTestClientCert2 { @Test public void testNoAuth() throws Exception { - //no auth + // no auth final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put("path.home",".") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks" + ) + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put("path.home", ".") + .build(); @SuppressWarnings("unused") LdapUser user; try { - user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail(); } catch (Exception e) { - Assert.assertTrue(ExceptionUtils.getRootCause(e).getMessage(), ExceptionUtils.getRootCause(e).getMessage().contains("authentication required")); + Assert.assertTrue( + ExceptionUtils.getRootCause(e).getMessage(), + ExceptionUtils.getRootCause(e).getMessage().contains("authentication required") + ); } } @Test public void testNoAuthX() throws Exception { - //no auth + // no auth final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "kdc.dummy.com:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks") - .put(ConfigConstants.LDAPS_VERIFY_HOSTNAMES, false) - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put("path.home",".") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "kdc.dummy.com:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks" + ) + .put(ConfigConstants.LDAPS_VERIFY_HOSTNAMES, false) + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put("path.home", ".") + .build(); @SuppressWarnings("unused") LdapUser user; try { - user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail(); } catch (Exception e) { - Assert.assertTrue(ExceptionUtils.getRootCause(e).getMessage(), ExceptionUtils.getRootCause(e).getMessage().contains("authentication required")); + Assert.assertTrue( + ExceptionUtils.getRootCause(e).getMessage(), + ExceptionUtils.getRootCause(e).getMessage().contains("authentication required") + ); } } @Test public void testNoAuthY() throws Exception { - //no auth + // no auth final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "kdc.dummy.com:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/wrong/truststore.jks") - .put(ConfigConstants.LDAPS_VERIFY_HOSTNAMES, false) - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put("path.home",".") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "kdc.dummy.com:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/wrong/truststore.jks" + ) + .put(ConfigConstants.LDAPS_VERIFY_HOSTNAMES, false) + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put("path.home", ".") + .build(); @SuppressWarnings("unused") LdapUser user; try { - user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail(); } catch (Exception e) { - Assert.assertTrue(ExceptionUtils.getRootCause(e).getMessage(), ExceptionUtils.getRootCause(e).getMessage().contains("Unable to connect to any")); + Assert.assertTrue( + ExceptionUtils.getRootCause(e).getMessage(), + ExceptionUtils.getRootCause(e).getMessage().contains("Unable to connect to any") + ); } } - - - @Test public void testBindDnAuthLocalhost() throws Exception { - //bin dn auth + // bin dn auth final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put(ConfigConstants.LDAP_BIND_DN, "cn=ldapbinder,ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_PASSWORD, "ldapbinder") - .put("path.home",".") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks" + ) + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put(ConfigConstants.LDAP_BIND_DN, "cn=ldapbinder,ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_PASSWORD, "ldapbinder") + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("ldap_hr_employee", user.getName()); } @@ -150,21 +168,27 @@ public void testBindDnAuthLocalhost() throws Exception { public void testLdapSslAuth() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put("plugins.security.ssl.transport.keystore_filepath", "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/spock-keystore.jks") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks") - .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) - .put(ConfigConstants.LDAPS_JKS_CERT_ALIAS, "spock") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put("path.home",".") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + "plugins.security.ssl.transport.keystore_filepath", + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/spock-keystore.jks" + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks" + ) + .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) + .put(ConfigConstants.LDAPS_JKS_CERT_ALIAS, "spock") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("ldap_hr_employee", user.getName()); } @@ -173,24 +197,30 @@ public void testLdapSslAuth() throws Exception { public void testLdapSslAuthPem() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/ca/root-ca.pem") - .put(ConfigConstants.LDAPS_PEMCERT_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/spock.crtfull.pem") - .put(ConfigConstants.LDAPS_PEMKEY_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/spock.key.pem") - //.put(ConfigConstants.LDAPS_PEMKEY_PASSWORD, "changeit") - .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put("path.home",".") - //.put(ConfigConstants.LDAP_BIND_DN, "cn=ldapbinder,ou=people,dc=example,dc=com") - // .put(ConfigConstants.LDAP_PASSWORD, "ldapbinder") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/ca/root-ca.pem" + ) + .put( + ConfigConstants.LDAPS_PEMCERT_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/spock.crtfull.pem" + ) + .put(ConfigConstants.LDAPS_PEMKEY_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/spock.key.pem") + // .put(ConfigConstants.LDAPS_PEMKEY_PASSWORD, "changeit") + .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put("path.home", ".") + // .put(ConfigConstants.LDAP_BIND_DN, "cn=ldapbinder,ou=people,dc=example,dc=com") + // .put(ConfigConstants.LDAP_PASSWORD, "ldapbinder") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("ldap_hr_employee", user.getName()); } @@ -199,59 +229,66 @@ public void testLdapSslAuthPem() throws Exception { public void testLdapSslAuthNo() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put("plugins.security.ssl.transport.keystore_filepath", "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/kirk-keystore.jks") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks") - .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) - .put(ConfigConstants.LDAPS_JKS_CERT_ALIAS, "kirk") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - .put("path.home",".") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + "plugins.security.ssl.transport.keystore_filepath", + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/kirk-keystore.jks" + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks" + ) + .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) + .put(ConfigConstants.LDAPS_JKS_CERT_ALIAS, "kirk") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("ldap_hr_employee", user.getName()); } - public void testLdapAuthenticationSSL() throws Exception { - //startLDAPServer(); + // startLDAPServer(); final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "kdc.dummy.com:636") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_SSL, true) - //.put("plugins.security.ssl.transport.keystore_filepath", "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/cn=ldapbinder,ou=people,dc=example,dc=com-keystore.jks") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks") - //.put("verify_hostnames", false) - //.put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) - //.put(ConfigConstants.LDAPS_JKS_CERT_ALIAS, "cn=ldapbinder,ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") - //.put(ConfigConstants.LDAP_BIND_DN, "cn=ldapbinder,ou=people,dc=example,dc=com") - //.put(ConfigConstants.LDAP_PASSWORD, "ldapbinder") - - //.putList(ConfigConstants.LDAPS_ENABLED_SSL_CIPHERS, "TLS_RSA_WITH_AES_128_CBC_SHA") - //.putList(ConfigConstants.LDAPS_ENABLED_SSL_PROTOCOLS, "TLSv1") - //TLS_RSA_AES_128_CBC_SHA1 - .put("path.home",".") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate(new AuthCredentials("ldap_hr_employee" - , "ldap_hr_employee" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "kdc.dummy.com:636") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + // .put("plugins.security.ssl.transport.keystore_filepath", + // "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/cn=ldapbinder,ou=people,dc=example,dc=com-keystore.jks") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + "/Users/temp/opendistro_security_integration_tests/ldap/ssl-root-ca/truststore.jks" + ) + // .put("verify_hostnames", false) + // .put(ConfigConstants.LDAPS_ENABLE_SSL_CLIENT_AUTH, true) + // .put(ConfigConstants.LDAPS_JKS_CERT_ALIAS, "cn=ldapbinder,ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,dc=example,dc=com") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + // .put(ConfigConstants.LDAP_BIND_DN, "cn=ldapbinder,ou=people,dc=example,dc=com") + // .put(ConfigConstants.LDAP_PASSWORD, "ldapbinder") + + // .putList(ConfigConstants.LDAPS_ENABLED_SSL_CIPHERS, "TLS_RSA_WITH_AES_128_CBC_SHA") + // .putList(ConfigConstants.LDAPS_ENABLED_SSL_PROTOCOLS, "TLSv1") + // TLS_RSA_AES_128_CBC_SHA1 + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("ldap_hr_employee", "ldap_hr_employee".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("ldap_hr_employee", user.getName()); } - - public static File getAbsoluteFilePathFromClassPath(final String fileNameFromClasspath) { File file = null; final URL fileUrl = LdapBackendTestClientCert2.class.getClassLoader().getResource(fileNameFromClasspath); diff --git a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestNewStyleConfig2.java b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestNewStyleConfig2.java index dfa3296a25..a53e73772d 100644 --- a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestNewStyleConfig2.java +++ b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestNewStyleConfig2.java @@ -62,7 +62,7 @@ public class LdapBackendTestNewStyleConfig2 { public static void startLdapServer() throws Exception { ldapServer = new EmbeddedLDAPServer(); ldapServer.start(); - ldapServer.applyLdif("base.ldif","base2.ldif"); + ldapServer.applyLdif("base.ldif", "base2.ldif"); ldapPort = ldapServer.getLdapPort(); ldapsPort = ldapServer.getLdapsPort(); } @@ -86,12 +86,13 @@ protected Settings.Builder createBaseSettings() { @Test public void testLdapAuthentication() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -99,39 +100,44 @@ public void testLdapAuthentication() throws Exception { @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationFakeLogin() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, true).build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, true) + .build(); - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("unknown", "unknown".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("unknown", "unknown".getBytes(StandardCharsets.UTF_8)) + ); } @Test(expected = OpenSearchSecurityException.class) public void testLdapInjection() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); String injectString = "*jack*"; @SuppressWarnings("unused") - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials(injectString, "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials(injectString, "secret".getBytes(StandardCharsets.UTF_8)) + ); } @Test public void testLdapAuthenticationBindDn() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") - .put(ConfigConstants.LDAP_PASSWORD, "spocksecret").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") + .put(ConfigConstants.LDAP_PASSWORD, "spocksecret") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -140,14 +146,16 @@ public void testLdapAuthenticationBindDn() throws Exception { public void testLdapAuthenticationWrongBindDn() throws Exception { try { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") - .put(ConfigConstants.LDAP_PASSWORD, "wrong").build(); - - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") + .put(ConfigConstants.LDAP_PASSWORD, "wrong") + .build(); + + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail("Expected exception"); } catch (Exception e) { Assert.assertTrue(ExceptionUtils.getStackTrace(e), ExceptionUtils.getStackTrace(e).contains("password was incorrect")); @@ -157,48 +165,56 @@ public void testLdapAuthenticationWrongBindDn() throws Exception { @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationBindFail() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "wrong".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "wrong".getBytes(StandardCharsets.UTF_8)) + ); } @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationNoUser() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("UNKNOWN", "UNKNOWN".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("UNKNOWN", "UNKNOWN".getBytes(StandardCharsets.UTF_8)) + ); } @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationFail() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "xxxxx".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "xxxxx".getBytes(StandardCharsets.UTF_8)) + ); } @Test public void testLdapAuthenticationSSL() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -206,15 +222,20 @@ public void testLdapAuthenticationSSL() throws Exception { @Test public void testLdapAuthenticationSSLPEMFile() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").toFile().getName()) - .put("verify_hostnames", false).put("path.home", ".") - .put("path.conf", FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").getParent()).build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, Paths.get("src/test/resources/ldap")) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").toFile().getName() + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .put("path.conf", FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").getParent()) + .build(); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, Paths.get("src/test/resources/ldap")).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -222,18 +243,13 @@ public void testLdapAuthenticationSSLPEMFile() throws Exception { @Test public void testLdapAuthenticationSSLPEMText() throws Exception { - final Settings settingsFromFile = Settings - .builder() - .loadFromPath( - Paths - .get(FileHelper - .getAbsoluteFilePathFromClassPath("ldap/test1.yml") - .toFile() - .getAbsolutePath())) - .build(); - Settings settings = Settings.builder().put(settingsFromFile).putList("hosts", "localhost:"+ldapsPort).build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settingsFromFile = Settings.builder() + .loadFromPath(Paths.get(FileHelper.getAbsoluteFilePathFromClassPath("ldap/test1.yml").toFile().getAbsolutePath())) + .build(); + Settings settings = Settings.builder().put(settingsFromFile).putList("hosts", "localhost:" + ldapsPort).build(); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -241,16 +257,22 @@ public void testLdapAuthenticationSSLPEMText() throws Exception { @Test public void testLdapAuthenticationSSLSSLv3() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).putList("enabled_ssl_protocols", "SSLv3").put("path.home", ".").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .putList("enabled_ssl_protocols", "SSLv3") + .put("path.home", ".") + .build(); try { - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail("Expected Exception"); } catch (Exception e) { Assert.assertEquals(org.ldaptive.provider.ConnectionException.class, e.getCause().getClass()); @@ -262,20 +284,29 @@ public void testLdapAuthenticationSSLSSLv3() throws Exception { @Test public void testLdapAuthenticationSSLUnknownCipher() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).putList("enabled_ssl_ciphers", "AAA").put("path.home", ".").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .putList("enabled_ssl_ciphers", "AAA") + .put("path.home", ".") + .build(); try { - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail("Expected Exception"); } catch (Exception e) { Assert.assertEquals(org.ldaptive.provider.ConnectionException.class, e.getCause().getClass()); - Assert.assertTrue(ExceptionUtils.getStackTrace(e), WildcardMatcher.from("*unsupported*ciphersuite*aaa*").test(ExceptionUtils.getStackTrace(e).toLowerCase())); + Assert.assertTrue( + ExceptionUtils.getStackTrace(e), + WildcardMatcher.from("*unsupported*ciphersuite*aaa*").test(ExceptionUtils.getStackTrace(e).toLowerCase()) + ); } } @@ -283,16 +314,22 @@ public void testLdapAuthenticationSSLUnknownCipher() throws Exception { @Test public void testLdapAuthenticationSpecialCipherProtocol() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).putList("enabled_ssl_protocols", "TLSv1.2") - .putList("enabled_ssl_ciphers", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA").put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .putList("enabled_ssl_protocols", "TLSv1.2") + .putList("enabled_ssl_ciphers", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA") + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); @@ -301,15 +338,20 @@ public void testLdapAuthenticationSpecialCipherProtocol() throws Exception { @Test public void testLdapAuthenticationSSLNoKeystore() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -317,13 +359,15 @@ public void testLdapAuthenticationSSLNoKeystore() throws Exception { @Test public void testLdapAuthenticationSSLFailPlain() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true).build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .build(); try { - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail("Expected exception"); } catch (final Exception e) { Assert.assertEquals(IllegalStateException.class, e.getCause().getClass()); @@ -333,9 +377,9 @@ public void testLdapAuthenticationSSLFailPlain() throws Exception { @Test public void testLdapExists() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); final LDAPAuthenticationBackend2 lbe = new LDAPAuthenticationBackend2(settings, null); Assert.assertTrue(lbe.exists(new User("jacksonm"))); @@ -345,17 +389,19 @@ public void testLdapExists() throws Exception { @Test public void testLdapAuthorization() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.search", "(uniqueMember={0})") - // .put("plugins.security.authentication.authorization.ldap.userrolename", - // "(uniqueMember={0})") - .build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.search", "(uniqueMember={0})") + // .put("plugins.security.authentication.authorization.ldap.userrolename", + // "(uniqueMember={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -369,16 +415,18 @@ public void testLdapAuthorization() throws Exception { @Test public void testLdapAuthorizationReturnAttributes() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.search", "(uniqueMember={0})") - .putList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, "mail", "cn", "uid") - .build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.search", "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_RETURN_ATTRIBUTES, "mail", "cn", "uid") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend2(settings, null).fillRoles(user, null); @@ -394,12 +442,11 @@ public void testLdapAuthorizationReturnAttributes() throws Exception { @Test public void testLdapAuthenticationReferral() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); - final Connection con = new LDAPConnectionFactoryFactory(settings, null).createBasicConnectionFactory() - .getConnection(); + final Connection con = new LDAPConnectionFactoryFactory(settings, null).createBasicConnectionFactory().getConnection(); try { con.open(); final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), true); @@ -413,17 +460,21 @@ public void testLdapAuthenticationReferral() throws Exception { @Test public void testLdapDontFollowReferrals() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.FOLLOW_REFERRALS, false).build(); - + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.FOLLOW_REFERRALS, false) + .build(); final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); try { - //If following is off then should fail to return the result provided by following - final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT)); + // If following is off then should fail to return the result provided by following + final LdapEntry ref1 = LdapHelper.lookup( + con, + "cn=Ref1,ou=people,o=TEST", + ReturnAttributes.ALL.value(), + settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT) + ); Assert.assertNull(ref1); } finally { con.close(); @@ -433,16 +484,19 @@ public void testLdapDontFollowReferrals() throws Exception { @Test public void testLdapEscape() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.search", "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true).build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("ssign", "ssignsecret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.search", "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("ssign", "ssignsecret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Special\\, Sign,ou=people,o=TEST", user.getName()); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -454,14 +508,17 @@ public void testLdapEscape() throws Exception { @Test public void testLdapAuthorizationRoleSearchUsername() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(cn={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.search", "(uniqueMember=cn={1},ou=people,o=TEST)").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(cn={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.search", "(uniqueMember=cn={1},ou=people,o=TEST)") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("Michael Jackson", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("Michael Jackson", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -476,11 +533,13 @@ public void testLdapAuthorizationRoleSearchUsername() throws Exception { @Test public void testLdapAuthorizationOnly() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.search", "(uniqueMember={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.search", "(uniqueMember={0})") + .build(); final User user = new User("jacksonm"); @@ -495,12 +554,14 @@ public void testLdapAuthorizationOnly() throws Exception { @Test public void testLdapAuthorizationNested() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true).put("roles.g1.search", "(uniqueMember={0})") - .build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .build(); final User user = new User("spock"); @@ -515,12 +576,15 @@ public void testLdapAuthorizationNested() throws Exception { @Test public void testLdapAuthorizationNestedFilter() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true).put("roles.g1.search", "(uniqueMember={0})") - .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=nested2,ou=groups,o=TEST").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=nested2,ou=groups,o=TEST") + .build(); final User user = new User("spock"); @@ -536,12 +600,14 @@ public void testLdapAuthorizationNestedFilter() throws Exception { @Test public void testLdapAuthorizationDnNested() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true).put("roles.g1.search", "(uniqueMember={0})") - .build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .build(); final User user = new User("spock"); @@ -556,16 +622,17 @@ public void testLdapAuthorizationDnNested() throws Exception { @Test public void testLdapAuthorizationDn() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "UID") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) - .put("roles.g1.search", "(uniqueMember={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "UID") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) + .put("roles.g1.search", "(uniqueMember={0})") + .build(); - final User user = new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes())); + final User user = new LDAPAuthenticationBackend2(settings, null).authenticate(new AuthCredentials("jacksonm", "secret".getBytes())); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -578,13 +645,15 @@ public void testLdapAuthorizationDn() throws Exception { @Test public void testLdapAuthenticationUserNameAttribute() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.base", "ou=people,o=TEST").put("users.u1.search", "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.base", "ou=people,o=TEST") + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("jacksonm", user.getName()); } @@ -592,15 +661,20 @@ public void testLdapAuthenticationUserNameAttribute() throws Exception { @Test public void testLdapAuthenticationStartTLS() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_START_TLS, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_START_TLS, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -608,15 +682,18 @@ public void testLdapAuthenticationStartTLS() throws Exception { @Test public void testLdapAuthorizationSkipUsers() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put("roles.g1.search", "(uniqueMember={0})") - .putList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS, "cn=Michael Jackson,ou*people,o=TEST").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.search", "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS, "cn=Michael Jackson,ou*people,o=TEST") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -629,13 +706,16 @@ public void testLdapAuthorizationSkipUsers() throws Exception { @Test public void testLdapAuthorizationNestedAttr() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true).put("roles.g1.search", "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true).build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .build(); final User user = new User("spock"); @@ -651,14 +731,17 @@ public void testLdapAuthorizationNestedAttr() throws Exception { @Test public void testLdapAuthorizationNestedAttrFilter() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true).put("roles.g1.search", "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) - .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=rolemo4*").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=rolemo4*") + .build(); final User user = new User("spock"); @@ -675,14 +758,17 @@ public void testLdapAuthorizationNestedAttrFilter() throws Exception { @Test public void testLdapAuthorizationNestedAttrFilterAll() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true).put("roles.g1.search", "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) - .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "*").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "*") + .build(); final User user = new User("spock"); @@ -697,16 +783,18 @@ public void testLdapAuthorizationNestedAttrFilterAll() throws Exception { @Test public void testLdapAuthorizationNestedAttrFilterAllEqualsNestedFalse() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) // -> same like - // putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, - // "*") - .put("roles.g1.search", "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true).build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) // -> same like + // putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, + // "*") + .put("roles.g1.search", "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .build(); final User user = new User("spock"); @@ -721,13 +809,16 @@ public void testLdapAuthorizationNestedAttrFilterAllEqualsNestedFalse() throws E @Test public void testLdapAuthorizationNestedAttrNoRoleSearch() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "unused").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true).put("roles.g1.search", "(((unused") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, false).build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "unused") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(((unused") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, false) + .build(); final User user = new User("spock"); @@ -743,34 +834,40 @@ public void testLdapAuthorizationNestedAttrNoRoleSearch() throws Exception { @Test public void testCustomAttributes() throws Exception { - Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").build(); + Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .build(); - LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); Assert.assertEquals(user.getCustomAttributesMap().toString(), 16, user.getCustomAttributesMap().size()); - Assert.assertFalse(user.getCustomAttributesMap().toString(), - user.getCustomAttributesMap().keySet().contains("attr.ldap.userpassword")); + Assert.assertFalse( + user.getCustomAttributesMap().toString(), + user.getCustomAttributesMap().keySet().contains("attr.ldap.userpassword") + ); - settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 0).build(); + settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 0) + .build(); - user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertEquals(user.getCustomAttributesMap().toString(), 2, user.getCustomAttributesMap().size()); - settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})") - .putList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, "*objectclass*", "entryParentId").build(); + settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .putList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, "*objectclass*", "entryParentId") + .build(); - user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertEquals(user.getCustomAttributesMap().toString(), 2, user.getCustomAttributesMap().size()); @@ -779,13 +876,16 @@ public void testCustomAttributes() throws Exception { @Test public void testLdapAuthorizationNonDNRoles() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("roles.g1.base", "ou=groups,o=TEST").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true).put("roles.g1.search", "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description, ou") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true).build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("roles.g1.base", "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put("roles.g1.search", "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description, ou") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .build(); final User user = new User("nondnroles"); @@ -795,24 +895,28 @@ public void testLdapAuthorizationNonDNRoles() throws Exception { Assert.assertEquals("nondnroles", user.getName()); Assert.assertEquals(5, user.getRoles().size()); Assert.assertTrue("Roles do not contain non-LDAP role 'kibanauser'", user.getRoles().contains("kibanauser")); - Assert.assertTrue("Roles do not contain non-LDAP role 'humanresources'", - user.getRoles().contains("humanresources")); + Assert.assertTrue("Roles do not contain non-LDAP role 'humanresources'", user.getRoles().contains("humanresources")); Assert.assertTrue("Roles do not contain LDAP role 'dummyempty'", user.getRoles().contains("dummyempty")); Assert.assertTrue("Roles do not contain non-LDAP role 'role2'", user.getRoles().contains("role2")); - Assert.assertTrue("Roles do not contain non-LDAP role 'anotherrole' from second role name", - user.getRoles().contains("anotherrole")); + Assert.assertTrue( + "Roles do not contain non-LDAP role 'anotherrole' from second role name", + user.getRoles().contains("anotherrole") + ); } @Test public void testChainedLdapAuthentication1() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("users.u2.search", "(uid={0})").put("users.u2.base", "ou=people2,o=TEST").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("users.u2.search", "(uid={0})") + .put("users.u2.base", "ou=people2,o=TEST") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -820,13 +924,16 @@ public void testChainedLdapAuthentication1() throws Exception { @Test public void testChainedLdapAuthentication2() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put("users.u2.search", "(uid={0})").put("users.u2.base", "ou=people2,o=TEST").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("users.u2.search", "(uid={0})") + .put("users.u2.base", "ou=people2,o=TEST") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("presleye", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("presleye", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Elvis Presley,ou=people2,o=TEST", user.getName()); } @@ -834,14 +941,17 @@ public void testChainedLdapAuthentication2() throws Exception { @Test(expected = OpenSearchSecurityException.class) public void testChainedLdapAuthenticationDuplicate() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_SEARCH_ALL_BASES, true).put("users.u1.search", "(uid={0})") - .put("users.u1.base", "ou=people,o=TEST").put("users.u2.search", "(uid={0})") - .put("users.u2.base", "ou=people2,o=TEST").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_SEARCH_ALL_BASES, true) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put("users.u2.search", "(uid={0})") + .put("users.u2.base", "ou=people2,o=TEST") + .build(); - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); // Fails with OpenSearchSecurityException because two possible instances are // found @@ -850,10 +960,11 @@ public void testChainedLdapAuthenticationDuplicate() throws Exception { @Test public void testChainedLdapExists() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u2.search", "(uid={0})") - .put("users.u2.base", "ou=people2,o=TEST").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u2.search", "(uid={0})") + .put("users.u2.base", "ou=people2,o=TEST") + .build(); final LDAPAuthenticationBackend2 lbe = new LDAPAuthenticationBackend2(settings, null); Assert.assertTrue(lbe.exists(new User("jacksonm"))); @@ -864,15 +975,19 @@ public void testChainedLdapExists() throws Exception { @Test public void testChainedLdapAuthorization() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn").put("roles.g1.base", "ou=groups,o=TEST") - .put("roles.g1.search", "(uniqueMember={0})").put("roles.g2.base", "ou=groups2,o=TEST") - .put("roles.g2.search", "(uniqueMember={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.base", "ou=groups,o=TEST") + .put("roles.g1.search", "(uniqueMember={0})") + .put("roles.g2.base", "ou=groups2,o=TEST") + .put("roles.g2.search", "(uniqueMember={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -890,15 +1005,19 @@ public void testChainedLdapAuthorization() throws Exception { @Test public void testCrossChainedLdapAuthorization() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put("users.u1.search", "(uid={0})").put("users.u1.base", "ou=people2,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn").put("roles.g1.base", "ou=groups,o=TEST") - .put("roles.g1.search", "(uniqueMember={0})").put("roles.g2.base", "ou=groups2,o=TEST") - .put("roles.g2.search", "(uniqueMember={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put("users.u1.search", "(uid={0})") + .put("users.u1.base", "ou=people2,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put("roles.g1.base", "ou=groups,o=TEST") + .put("roles.g1.search", "(uniqueMember={0})") + .put("roles.g2.base", "ou=groups2,o=TEST") + .put("roles.g2.search", "(uniqueMember={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("mercuryf", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("mercuryf", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -914,13 +1033,13 @@ public void testCrossChainedLdapAuthorization() throws Exception { public void testLdapAuthorizationNonDNEntry() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "description") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "description") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .build(); final User user = new User("jacksonm"); @@ -936,17 +1055,18 @@ public void testLdapAuthorizationNonDNEntry() throws Exception { public void testLdapSpecial186() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "description") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("spec186", "spec186" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "description") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("spec186", "spec186".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST", user.getName()); Assert.assertEquals("AA BB/CC (DD) my, company end=with=whitespace ", user.getUserEntry().getAttribute("cn").getStringValue()); @@ -962,12 +1082,18 @@ public void testLdapSpecial186() throws Exception { Assert.assertTrue(user.getRoles().toString().contains("ROLEx(186n) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186nn) consists of\\, special=")); - new LDAPAuthorizationBackend(settings, null).fillRoles(new User("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), null); + new LDAPAuthorizationBackend(settings, null).fillRoles( + new User("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), + null + ); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLEx(186n) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186nn) consists of\\, special=")); - new LDAPAuthorizationBackend(settings, null).fillRoles(new User("CN=AA BB\\/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), null); + new LDAPAuthorizationBackend(settings, null).fillRoles( + new User("CN=AA BB\\/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), + null + ); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLEx(186n) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186nn) consists of\\, special=")); @@ -977,17 +1103,18 @@ public void testLdapSpecial186() throws Exception { public void testLdapSpecial186_2() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("spec186", "spec186" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("spec186", "spec186".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST", user.getName()); Assert.assertEquals("AA BB/CC (DD) my, company end=with=whitespace ", user.getUserEntry().getAttribute("cn").getStringValue()); @@ -1003,13 +1130,18 @@ public void testLdapSpecial186_2() throws Exception { Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186n) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186nn) consists of\\, special\\=chars\\ ")); - - new LDAPAuthorizationBackend(settings, null).fillRoles(new User("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), null); + new LDAPAuthorizationBackend(settings, null).fillRoles( + new User("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), + null + ); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186n) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186nn) consists of\\, special\\=chars\\ ")); - new LDAPAuthorizationBackend(settings, null).fillRoles(new User("CN=AA BB\\/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), null); + new LDAPAuthorizationBackend(settings, null).fillRoles( + new User("CN=AA BB\\/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), + null + ); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186n) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186nn) consists of\\, special\\=chars\\ ")); @@ -1018,13 +1150,14 @@ public void testLdapSpecial186_2() throws Exception { @Test public void testOperationalAttributes() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); LdapAttribute operationAttribute = user.getUserEntry().getAttribute("entryUUID"); Assert.assertNotNull(operationAttribute); diff --git a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestOldStyleConfig2.java b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestOldStyleConfig2.java index 8a4afee5da..8c6f4dc85d 100755 --- a/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestOldStyleConfig2.java +++ b/src/test/java/com/amazon/dlic/auth/ldap2/LdapBackendTestOldStyleConfig2.java @@ -88,12 +88,13 @@ protected Settings.Builder createBaseSettings() { @Test public void testLdapAuthentication() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -101,13 +102,14 @@ public void testLdapAuthentication() throws Exception { @Test public void testLdapAuthenticationPooled() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").put(ConfigConstants.LDAP_POOL_ENABLED, true) - .build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_POOL_ENABLED, true) + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -115,41 +117,44 @@ public void testLdapAuthenticationPooled() throws Exception { @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationFakeLogin() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, true).build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_FAKE_LOGIN_ENABLED, true) + .build(); - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("unknown", "unknown".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("unknown", "unknown".getBytes(StandardCharsets.UTF_8)) + ); } @Test(expected = OpenSearchSecurityException.class) public void testLdapInjection() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); String injectString = "*jack*"; @SuppressWarnings("unused") - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials(injectString, "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials(injectString, "secret".getBytes(StandardCharsets.UTF_8)) + ); } @Test public void testLdapAuthenticationBindDn() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") - .put(ConfigConstants.LDAP_PASSWORD, "spocksecret").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") + .put(ConfigConstants.LDAP_PASSWORD, "spocksecret") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -157,15 +162,16 @@ public void testLdapAuthenticationBindDn() throws Exception { @Test public void testLdapAuthenticationWrongBindDn() throws Exception { try { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") - .put(ConfigConstants.LDAP_PASSWORD, "wrong").build(); - - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST") + .put(ConfigConstants.LDAP_PASSWORD, "wrong") + .build(); + + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail("Expected exception"); } catch (Exception e) { Assert.assertTrue(ExceptionUtils.getStackTrace(e), ExceptionUtils.getStackTrace(e).contains("password was incorrect")); @@ -175,60 +181,69 @@ public void testLdapAuthenticationWrongBindDn() throws Exception { @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationBindFail() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "wrong".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "wrong".getBytes(StandardCharsets.UTF_8)) + ); } @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationNoUser() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("UNKNOWN", "UNKNOWN".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("UNKNOWN", "UNKNOWN".getBytes(StandardCharsets.UTF_8)) + ); } @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationFail() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "xxxxx".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "xxxxx".getBytes(StandardCharsets.UTF_8)) + ); } @Test(expected = OpenSearchSecurityException.class) public void testLdapAuthenticationFailPooled() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").put(ConfigConstants.LDAP_POOL_ENABLED, true) - .build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_POOL_ENABLED, true) + .build(); - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "xxxxx".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "xxxxx".getBytes(StandardCharsets.UTF_8)) + ); } @Test public void testLdapAuthenticationSSL() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -236,16 +251,21 @@ public void testLdapAuthenticationSSL() throws Exception { @Test public void testLdapAuthenticationSSLPooled() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(ConfigConstants.LDAP_POOL_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put(ConfigConstants.LDAP_POOL_ENABLED, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -253,15 +273,20 @@ public void testLdapAuthenticationSSLPooled() throws Exception { @Test public void testLdapAuthenticationSSLPEMFile() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").toFile().getName()) - .put("verify_hostnames", false).put("path.home", ".") - .put("path.conf", FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").getParent()).build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, Paths.get("src/test/resources/ldap")) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + ConfigConstants.LDAPS_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").toFile().getName() + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .put("path.conf", FileHelper.getAbsoluteFilePathFromClassPath("ldap/root-ca.pem").getParent()) + .build(); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, Paths.get("src/test/resources/ldap")).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -269,18 +294,13 @@ public void testLdapAuthenticationSSLPEMFile() throws Exception { @Test public void testLdapAuthenticationSSLPEMText() throws Exception { - final Settings settingsFromFile = Settings - .builder() - .loadFromPath( - Paths - .get(FileHelper - .getAbsoluteFilePathFromClassPath("ldap/test1.yml") - .toFile() - .getAbsolutePath())) - .build(); - Settings settings = Settings.builder().put(settingsFromFile).putList("hosts", "localhost:"+ldapsPort).build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settingsFromFile = Settings.builder() + .loadFromPath(Paths.get(FileHelper.getAbsoluteFilePathFromClassPath("ldap/test1.yml").toFile().getAbsolutePath())) + .build(); + Settings settings = Settings.builder().put(settingsFromFile).putList("hosts", "localhost:" + ldapsPort).build(); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -288,16 +308,22 @@ public void testLdapAuthenticationSSLPEMText() throws Exception { @Test public void testLdapAuthenticationSSLSSLv3() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).putList("enabled_ssl_protocols", "SSLv3").put("path.home", ".").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .putList("enabled_ssl_protocols", "SSLv3") + .put("path.home", ".") + .build(); try { - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail("Expected Exception"); } catch (Exception e) { Assert.assertEquals(org.ldaptive.provider.ConnectionException.class, e.getCause().getClass()); @@ -309,20 +335,30 @@ public void testLdapAuthenticationSSLSSLv3() throws Exception { @Test public void testLdapAuthenticationSSLUnknowCipher() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).putList("enabled_ssl_ciphers", "AAA").put("path.home", ".").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .putList("enabled_ssl_ciphers", "AAA") + .put("path.home", ".") + .build(); try { - new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail("Expected Exception"); } catch (Exception e) { - Assert.assertEquals(e.getCause().getClass().toString(), org.ldaptive.provider.ConnectionException.class, e.getCause().getClass()); - Assert.assertTrue(ExceptionUtils.getStackTrace(e), EXCEPTION_MATCHER.test( ExceptionUtils.getStackTrace(e).toLowerCase())); + Assert.assertEquals( + e.getCause().getClass().toString(), + org.ldaptive.provider.ConnectionException.class, + e.getCause().getClass() + ); + Assert.assertTrue(ExceptionUtils.getStackTrace(e), EXCEPTION_MATCHER.test(ExceptionUtils.getStackTrace(e).toLowerCase())); } } @@ -330,16 +366,22 @@ public void testLdapAuthenticationSSLUnknowCipher() throws Exception { @Test public void testLdapAuthenticationSpecialCipherProtocol() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).putList("enabled_ssl_protocols", "TLSv1.2") - .putList("enabled_ssl_ciphers", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA").put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .putList("enabled_ssl_protocols", "TLSv1.2") + .putList("enabled_ssl_ciphers", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA") + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); @@ -348,15 +390,20 @@ public void testLdapAuthenticationSpecialCipherProtocol() throws Exception { @Test public void testLdapAuthenticationSSLNoKeystore() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapsPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -364,14 +411,15 @@ public void testLdapAuthenticationSSLNoKeystore() throws Exception { @Test public void testLdapAuthenticationSSLFailPlain() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").put(ConfigConstants.LDAPS_ENABLE_SSL, true) - .build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_SSL, true) + .build(); try { - new LDAPAuthenticationBackend2(settings, new File("").toPath()) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + new LDAPAuthenticationBackend2(settings, new File("").toPath()).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.fail("Expected exception"); } catch (final Exception e) { Assert.assertEquals(IllegalStateException.class, e.getCause().getClass()); @@ -381,9 +429,9 @@ public void testLdapAuthenticationSSLFailPlain() throws Exception { @Test public void testLdapExists() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); final LDAPAuthenticationBackend2 lbe = new LDAPAuthenticationBackend2(settings, null); Assert.assertTrue(lbe.exists(new User("jacksonm"))); @@ -393,19 +441,19 @@ public void testLdapExists() throws Exception { @Test public void testLdapAuthorization() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - // .put("plugins.security.authentication.authorization.ldap.userrolename", - // "(uniqueMember={0})") - .build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + // .put("plugins.security.authentication.authorization.ldap.userrolename", + // "(uniqueMember={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -419,20 +467,20 @@ public void testLdapAuthorization() throws Exception { @Test public void testLdapAuthorizationPooled() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_POOL_ENABLED, true) - // .put("plugins.security.authentication.authorization.ldap.userrolename", - // "(uniqueMember={0})") - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_POOL_ENABLED, true) + // .put("plugins.security.authentication.authorization.ldap.userrolename", + // "(uniqueMember={0})") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -446,12 +494,11 @@ public void testLdapAuthorizationPooled() throws Exception { @Test public void testLdapAuthenticationReferral() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - final Connection con = new LDAPConnectionFactoryFactory(settings, null).createBasicConnectionFactory() - .getConnection(); + final Connection con = new LDAPConnectionFactoryFactory(settings, null).createBasicConnectionFactory().getConnection(); try { con.open(); final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), true); @@ -465,17 +512,21 @@ public void testLdapAuthenticationReferral() throws Exception { @Test public void testLdapDontFollowReferrals() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.FOLLOW_REFERRALS, false).build(); - + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.FOLLOW_REFERRALS, false) + .build(); final Connection con = LDAPAuthorizationBackend.getConnection(settings, null); try { - //If following is off then should fail to return the result provided by following - final LdapEntry ref1 = LdapHelper.lookup(con, "cn=Ref1,ou=people,o=TEST", ReturnAttributes.ALL.value(), settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT)); + // If following is off then should fail to return the result provided by following + final LdapEntry ref1 = LdapHelper.lookup( + con, + "cn=Ref1,ou=people,o=TEST", + ReturnAttributes.ALL.value(), + settings.getAsBoolean(ConfigConstants.FOLLOW_REFERRALS, ConfigConstants.FOLLOW_REFERRALS_DEFAULT) + ); Assert.assertNull(ref1); } finally { con.close(); @@ -485,18 +536,19 @@ public void testLdapDontFollowReferrals() throws Exception { @Test public void testLdapEscape() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true).build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("ssign", "ssignsecret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("ssign", "ssignsecret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Special\\, Sign,ou=people,o=TEST", user.getName()); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -508,16 +560,17 @@ public void testLdapEscape() throws Exception { @Test public void testLdapAuthorizationRoleSearchUsername() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(cn={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember=cn={1},ou=people,o=TEST)").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(cn={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember=cn={1},ou=people,o=TEST)") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("Michael Jackson", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("Michael Jackson", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -532,13 +585,13 @@ public void testLdapAuthorizationRoleSearchUsername() throws Exception { @Test public void testLdapAuthorizationOnly() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .build(); final User user = new User("jacksonm"); @@ -553,14 +606,14 @@ public void testLdapAuthorizationOnly() throws Exception { @Test public void testLdapAuthorizationNested() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .build(); final User user = new User("spock"); @@ -575,15 +628,15 @@ public void testLdapAuthorizationNested() throws Exception { @Test public void testLdapAuthorizationNestedFilter() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=nested2,ou=groups,o=TEST").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=nested2,ou=groups,o=TEST") + .build(); final User user = new User("spock"); @@ -599,14 +652,14 @@ public void testLdapAuthorizationNestedFilter() throws Exception { @Test public void testLdapAuthorizationDnNested() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .build(); final User user = new User("spock"); @@ -621,18 +674,17 @@ public void testLdapAuthorizationDnNested() throws Exception { @Test public void testLdapAuthorizationDn() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "UID") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "UID") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .build(); - final User user = new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes())); + final User user = new LDAPAuthenticationBackend2(settings, null).authenticate(new AuthCredentials("jacksonm", "secret".getBytes())); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -645,14 +697,15 @@ public void testLdapAuthorizationDn() throws Exception { @Test public void testLdapAuthenticationUserNameAttribute() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERNAME_ATTRIBUTE, "uid") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("jacksonm", user.getName()); } @@ -660,16 +713,20 @@ public void testLdapAuthenticationUserNameAttribute() throws Exception { @Test public void testLdapAuthenticationStartTLS() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAPS_ENABLE_START_TLS, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks")) - .put("verify_hostnames", false).put("path.home", ".").build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAPS_ENABLE_START_TLS, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ldap/truststore.jks") + ) + .put("verify_hostnames", false) + .put("path.home", ".") + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); } @@ -677,17 +734,18 @@ public void testLdapAuthenticationStartTLS() throws Exception { @Test public void testLdapAuthorizationSkipUsers() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .putList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS, "cn=Michael Jackson,ou*people,o=TEST").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .putList(ConfigConstants.LDAP_AUTHZ_SKIP_USERS, "cn=Michael Jackson,ou*people,o=TEST") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); new LDAPAuthorizationBackend(settings, null).fillRoles(user, null); @@ -700,16 +758,16 @@ public void testLdapAuthorizationSkipUsers() throws Exception { @Test public void testLdapAuthorizationNestedAttr() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true).build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .build(); final User user = new User("spock"); @@ -725,17 +783,17 @@ public void testLdapAuthorizationNestedAttr() throws Exception { @Test public void testLdapAuthorizationNestedAttrFilter() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) - .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=rolemo4*").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "cn=rolemo4*") + .build(); final User user = new User("spock"); @@ -752,17 +810,17 @@ public void testLdapAuthorizationNestedAttrFilter() throws Exception { @Test public void testLdapAuthorizationNestedAttrFilterAll() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) - .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "*").build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, "*") + .build(); final User user = new User("spock"); @@ -777,18 +835,18 @@ public void testLdapAuthorizationNestedAttrFilterAll() throws Exception { @Test public void testLdapAuthorizationNestedAttrFilterAllEqualsNestedFalse() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) // -> same like - // putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, - // "*") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true).build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, false) // -> same like + // putList(ConfigConstants.LDAP_AUTHZ_NESTEDROLEFILTER, + // "*") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .build(); final User user = new User("spock"); @@ -803,15 +861,16 @@ public void testLdapAuthorizationNestedAttrFilterAllEqualsNestedFalse() throws E @Test public void testLdapAuthorizationNestedAttrNoRoleSearch() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "unused").put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(((unused") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, false).build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "unused") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(((unused") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, false) + .build(); final User user = new User("spock"); @@ -827,35 +886,37 @@ public void testLdapAuthorizationNestedAttrNoRoleSearch() throws Exception { @Test public void testCustomAttributes() throws Exception { - Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("cn=Michael Jackson,ou=people,o=TEST", user.getName()); Assert.assertEquals(user.getCustomAttributesMap().toString(), 16, user.getCustomAttributesMap().size()); - Assert.assertFalse(user.getCustomAttributesMap().toString(), - user.getCustomAttributesMap().containsKey("attr.ldap.userpassword")); + Assert.assertFalse(user.getCustomAttributesMap().toString(), user.getCustomAttributesMap().containsKey("attr.ldap.userpassword")); - settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 0).build(); + settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_CUSTOM_ATTR_MAXVAL_LEN, 0) + .build(); - user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertEquals(user.getCustomAttributesMap().toString(), 2, user.getCustomAttributesMap().size()); - settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .putList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, "*objectclass*", "entryParentId").build(); + settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "127.0.0.1:4", "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .putList(ConfigConstants.LDAP_CUSTOM_ATTR_WHITELIST, "*objectclass*", "entryParentId") + .build(); - user = (LdapUser) new LDAPAuthenticationBackend2(settings, null) - .authenticate(new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8))); + user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertEquals(user.getCustomAttributesMap().toString(), 2, user.getCustomAttributesMap().size()); @@ -864,16 +925,16 @@ public void testCustomAttributes() throws Exception { @Test public void testLdapAuthorizationNonDNRoles() throws Exception { - final Settings settings = createBaseSettings() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description, ou") // no memberOf OID - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true).build(); + final Settings settings = createBaseSettings().putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "cn") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_USERROLENAME, "description, ou") // no memberOf OID + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH_ENABLED, true) + .build(); final User user = new User("nondnroles"); @@ -883,26 +944,26 @@ public void testLdapAuthorizationNonDNRoles() throws Exception { Assert.assertEquals("nondnroles", user.getName()); Assert.assertEquals(5, user.getRoles().size()); Assert.assertTrue("Roles do not contain non-LDAP role 'kibanauser'", user.getRoles().contains("kibanauser")); - Assert.assertTrue("Roles do not contain non-LDAP role 'humanresources'", - user.getRoles().contains("humanresources")); + Assert.assertTrue("Roles do not contain non-LDAP role 'humanresources'", user.getRoles().contains("humanresources")); Assert.assertTrue("Roles do not contain LDAP role 'dummyempty'", user.getRoles().contains("dummyempty")); Assert.assertTrue("Roles do not contain non-LDAP role 'role2'", user.getRoles().contains("role2")); - Assert.assertTrue("Roles do not contain non-LDAP role 'anotherrole' from second role name", - user.getRoles().contains("anotherrole")); + Assert.assertTrue( + "Roles do not contain non-LDAP role 'anotherrole' from second role name", + user.getRoles().contains("anotherrole") + ); } - @Test public void testLdapAuthorizationNonDNEntry() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "description") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "description") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .build(); final User user = new User("jacksonm"); @@ -918,17 +979,18 @@ public void testLdapAuthorizationNonDNEntry() throws Exception { public void testLdapSpecial186() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "description") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("spec186", "spec186" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "description") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("spec186", "spec186".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST", user.getName()); Assert.assertEquals("AA BB/CC (DD) my, company end=with=whitespace ", user.getUserEntry().getAttribute("cn").getStringValue()); @@ -944,12 +1006,18 @@ public void testLdapSpecial186() throws Exception { Assert.assertTrue(user.getRoles().toString().contains("ROLEx(186n) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186nn) consists of\\, special=")); - new LDAPAuthorizationBackend(settings, null).fillRoles(new User("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), null); + new LDAPAuthorizationBackend(settings, null).fillRoles( + new User("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), + null + ); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLEx(186n) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186nn) consists of\\, special=")); - new LDAPAuthorizationBackend(settings, null).fillRoles(new User("CN=AA BB\\/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), null); + new LDAPAuthorizationBackend(settings, null).fillRoles( + new User("CN=AA BB\\/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), + null + ); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLEx(186n) consists of\\, special=")); Assert.assertTrue(user.getRoles().toString().contains("ROLE/(186nn) consists of\\, special=")); @@ -959,17 +1027,18 @@ public void testLdapSpecial186() throws Exception { public void testLdapSpecial186_2() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") - .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") - .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") - .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") - .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) - .build(); - - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate(new AuthCredentials("spec186", "spec186" - .getBytes(StandardCharsets.UTF_8))); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLEBASE, "ou=groups,o=TEST") + .put(ConfigConstants.LDAP_AUTHZ_ROLENAME, "dn") + .put(ConfigConstants.LDAP_AUTHZ_ROLESEARCH, "(uniqueMember={0})") + .put(ConfigConstants.LDAP_AUTHZ_RESOLVE_NESTED_ROLES, true) + .build(); + + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend(settings, null).authenticate( + new AuthCredentials("spec186", "spec186".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); Assert.assertEquals("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST", user.getName()); Assert.assertEquals("AA BB/CC (DD) my, company end=with=whitespace ", user.getUserEntry().getAttribute("cn").getStringValue()); @@ -985,13 +1054,18 @@ public void testLdapSpecial186_2() throws Exception { Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186n) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186nn) consists of\\, special\\=chars\\ ")); - - new LDAPAuthorizationBackend(settings, null).fillRoles(new User("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), null); + new LDAPAuthorizationBackend(settings, null).fillRoles( + new User("CN=AA BB/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), + null + ); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186n) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186nn) consists of\\, special\\=chars\\ ")); - new LDAPAuthorizationBackend(settings, null).fillRoles(new User("CN=AA BB\\/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), null); + new LDAPAuthorizationBackend(settings, null).fillRoles( + new User("CN=AA BB\\/CC (DD) my\\, company end\\=with\\=whitespace\\ ,ou=people,o=TEST"), + null + ); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186n) consists of\\, special\\=chars\\ ")); Assert.assertTrue(user.getRoles().toString().contains("cn=ROLE/(186nn) consists of\\, special\\=chars\\ ")); @@ -1000,13 +1074,14 @@ public void testLdapSpecial186_2() throws Exception { @Test public void testOperationalAttributes() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) - .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})").build(); + .putList(ConfigConstants.LDAP_HOSTS, "localhost:" + ldapPort) + .put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})") + .build(); - final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate(new AuthCredentials("jacksonm", "secret" - .getBytes(StandardCharsets.UTF_8))); + final LdapUser user = (LdapUser) new LDAPAuthenticationBackend2(settings, null).authenticate( + new AuthCredentials("jacksonm", "secret".getBytes(StandardCharsets.UTF_8)) + ); Assert.assertNotNull(user); LdapAttribute operationAttribute = user.getUserEntry().getAttribute("entryUUID"); Assert.assertNotNull(operationAttribute); @@ -1015,7 +1090,6 @@ public void testOperationalAttributes() throws Exception { Assert.assertTrue(operationAttribute.getStringValue().split("-").length == 5); } - @AfterClass public static void tearDown() throws Exception { diff --git a/src/test/java/org/opensearch/node/PluginAwareNode.java b/src/test/java/org/opensearch/node/PluginAwareNode.java index 39c93e6b8e..35dcd8b699 100644 --- a/src/test/java/org/opensearch/node/PluginAwareNode.java +++ b/src/test/java/org/opensearch/node/PluginAwareNode.java @@ -33,7 +33,7 @@ import org.opensearch.plugins.Plugin; public class PluginAwareNode extends Node { - + private final boolean clusterManagerEligible; @SafeVarargs @@ -41,7 +41,7 @@ public PluginAwareNode(boolean clusterManagerEligible, final Settings preparedSe super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, Collections.emptyMap(), null, () -> System.getenv("HOSTNAME")), Arrays.asList(plugins), true); this.clusterManagerEligible = clusterManagerEligible; } - + public boolean isClusterManagerEligible() { return clusterManagerEligible; diff --git a/src/test/java/org/opensearch/security/AggregationTests.java b/src/test/java/org/opensearch/security/AggregationTests.java index c2feddd6b6..a8a0f94078 100644 --- a/src/test/java/org/opensearch/security/AggregationTests.java +++ b/src/test/java/org/opensearch/security/AggregationTests.java @@ -48,33 +48,33 @@ public class AggregationTests extends SingleClusterTest { public void testBasicAggregations() throws Exception { final Settings settings = Settings.builder() .build(); - + setup(settings); final RestHelper rh = nonSslRestHelper(); - + try (Client tc = getClient()) { - tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("xyz").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("starfleet","starfleet_academy","starfleet_library").alias("sf"))).actionGet(); tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire","vulcangov").alias("nonsf"))).actionGet(); tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))).actionGet(); tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("xyz").alias("alias1"))).actionGet(); } - + HttpResponse res; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_search?pretty", "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":40}}}}",encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); System.out.println(res.getBody()); @@ -88,7 +88,7 @@ public void testBasicAggregations() throws Exception { assertContains(res, "*xyz*"); assertContains(res, "*role01_role02*"); assertContains(res, "*\"failed\" : 0*"); - + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("*/_search?pretty", "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":40}}}}",encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); System.out.println(res.getBody()); assertNotContains(res, "*xception*"); @@ -101,7 +101,7 @@ public void testBasicAggregations() throws Exception { assertContains(res, "*xyz*"); assertContains(res, "*role01_role02*"); assertContains(res, "*\"failed\" : 0*"); - + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_search?pretty", "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":40}}}}",encodeBasicHeader("worf", "worf"))).getStatusCode()); System.out.println(res.getBody()); assertNotContains(res, "*xception*"); @@ -114,9 +114,9 @@ public void testBasicAggregations() throws Exception { assertContains(res, "*public*"); assertContains(res, "*xyz*"); assertContains(res, "*\"failed\" : 0*"); - + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePostRequest("_search?pretty", "{\"size\":0,\"aggs\":{\"myindices\":{\"terms\":{\"field\":\"_index\",\"size\":40}}}}",encodeBasicHeader("worf", "worf"))).getStatusCode()); - + } - + } diff --git a/src/test/java/org/opensearch/security/AlwaysFalseInterClusterRequestEvaluator.java b/src/test/java/org/opensearch/security/AlwaysFalseInterClusterRequestEvaluator.java index 7fdf2d8bfc..a0a1b8bb7b 100644 --- a/src/test/java/org/opensearch/security/AlwaysFalseInterClusterRequestEvaluator.java +++ b/src/test/java/org/opensearch/security/AlwaysFalseInterClusterRequestEvaluator.java @@ -42,12 +42,12 @@ public AlwaysFalseInterClusterRequestEvaluator(Settings settings) { @Override public boolean isInterClusterRequest(TransportRequest request, X509Certificate[] localCerts, X509Certificate[] peerCerts, String principal) { - + if(localCerts == null || peerCerts == null || principal == null || localCerts.length == 0 || peerCerts.length == 0 || principal.length() == 0) { return true; } - + return false; } diff --git a/src/test/java/org/opensearch/security/ConfigTests.java b/src/test/java/org/opensearch/security/ConfigTests.java index c5b8c089e4..3c083ac153 100644 --- a/src/test/java/org/opensearch/security/ConfigTests.java +++ b/src/test/java/org/opensearch/security/ConfigTests.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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; @@ -46,24 +46,24 @@ import org.opensearch.security.test.SingleClusterTest; public class ConfigTests { - + private static final ObjectMapper YAML = new ObjectMapper(new YAMLFactory()); - + @Test public void testEmptyConfig() throws Exception { Assert.assertTrue(SecurityDynamicConfiguration.empty().deepClone() != SecurityDynamicConfiguration.empty()); } - + @Test public void testMigrate() throws Exception { Tuple, SecurityDynamicConfiguration> rolesResult = Migration.migrateRoles((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/roles.yml", CType.ROLES), (SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/roles_mapping.yml", CType.ROLESMAPPING)); - + System.out.println(Strings.toString(XContentType.JSON, rolesResult.v2(), true, false)); System.out.println(Strings.toString(XContentType.JSON, rolesResult.v1(), true, false)); - - + + SecurityDynamicConfiguration actionGroupsResult = Migration.migrateActionGroups((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/action_groups.yml", CType.ACTIONGROUPS)); System.out.println(Strings.toString(XContentType.JSON, actionGroupsResult, true, false)); SecurityDynamicConfiguration configResult =Migration.migrateConfig((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/config.yml", CType.CONFIG)); @@ -73,29 +73,29 @@ public void testMigrate() throws Exception { SecurityDynamicConfiguration rolemappingsResult = Migration.migrateRoleMappings((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/roles_mapping.yml", CType.ROLESMAPPING)); System.out.println(Strings.toString(XContentType.JSON, rolemappingsResult, true, false)); } - + @Test public void testParseSg67Config() throws Exception { check("./legacy/securityconfig_v6/action_groups.yml", CType.ACTIONGROUPS); check("./action_groups.yml", CType.ACTIONGROUPS); - + check("./legacy/securityconfig_v6/config.yml", CType.CONFIG); check("./config.yml", CType.CONFIG); - + check("./legacy/securityconfig_v6/roles.yml", CType.ROLES); check("./roles.yml", CType.ROLES); - + check("./legacy/securityconfig_v6/internal_users.yml", CType.INTERNALUSERS); check("./internal_users.yml", CType.INTERNALUSERS); - + check("./legacy/securityconfig_v6/roles_mapping.yml", CType.ROLESMAPPING); check("./roles_mapping.yml", CType.ROLESMAPPING); - + check("./tenants.yml", CType.TENANTS); - + } - + private void check(String file, CType cType) throws Exception { final String adjustedFilePath = SingleClusterTest.TEST_RESOURCE_RELATIVE_PATH + file; JsonNode jsonNode = YAML.readTree(FileUtils.readFileToString(new File(adjustedFilePath), "UTF-8")); @@ -108,16 +108,16 @@ private void check(String file, CType cType) throws Exception { System.out.println("%%%%%%%% THIS IS A LINE OF INTEREST: CONFIG VERSION: "+ configVersion + "%%%%%%%"); - + SecurityDynamicConfiguration dc = load(file, cType); Assert.assertNotNull(dc); //Assert.assertTrue(dc.getCEntries().size() > 0); String jsonSerialize = DefaultObjectMapper.objectMapper.writeValueAsString(dc); SecurityDynamicConfiguration conf = SecurityDynamicConfiguration.fromJson(jsonSerialize, cType, configVersion, 0, 0); SecurityDynamicConfiguration.fromJson(Strings.toString(XContentType.JSON, conf), cType, configVersion, 0, 0); - + } - + private SecurityDynamicConfiguration load(String file, CType cType) throws Exception { final String adjustedFilePath = SingleClusterTest.TEST_RESOURCE_RELATIVE_PATH + file; JsonNode jsonNode = YAML.readTree(FileUtils.readFileToString(new File(adjustedFilePath), "UTF-8")); diff --git a/src/test/java/org/opensearch/security/HealthTests.java b/src/test/java/org/opensearch/security/HealthTests.java index 4cba4030e6..0785fd620f 100644 --- a/src/test/java/org/opensearch/security/HealthTests.java +++ b/src/test/java/org/opensearch/security/HealthTests.java @@ -37,11 +37,11 @@ import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; public class HealthTests extends SingleClusterTest { - + @Test public void testHealth() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY); - + RestHelper rh = nonSslRestHelper(); HttpResponse res; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty&mode=lenient")).getStatusCode()); @@ -49,18 +49,18 @@ public void testHealth() throws Exception { assertContains(res, "*UP*"); assertNotContains(res, "*DOWN*"); assertNotContains(res, "*strict*"); - + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); System.out.println(res.getBody()); assertContains(res, "*UP*"); assertContains(res, "*strict*"); assertNotContains(res, "*DOWN*"); } - + @Test public void testHealthUnitialized() throws Exception { setup(Settings.EMPTY, null, Settings.EMPTY, false); - + RestHelper rh = nonSslRestHelper(); HttpResponse res; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty&mode=lenient")).getStatusCode()); @@ -68,7 +68,7 @@ public void testHealthUnitialized() throws Exception { assertContains(res, "*UP*"); assertNotContains(res, "*DOWN*"); assertNotContains(res, "*strict*"); - + Assert.assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); System.out.println(res.getBody()); assertContains(res, "*DOWN*"); diff --git a/src/test/java/org/opensearch/security/HttpIntegrationTests.java b/src/test/java/org/opensearch/security/HttpIntegrationTests.java index d9ed9c34df..bf185e0972 100644 --- a/src/test/java/org/opensearch/security/HttpIntegrationTests.java +++ b/src/test/java/org/opensearch/security/HttpIntegrationTests.java @@ -68,9 +68,9 @@ public void testHTTPBasic() throws Exception { .build(); setup(settings); final RestHelper rh = nonSslRestHelper(); - + try (Client tc = getClient()) { - tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); + tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); @@ -79,17 +79,17 @@ public void testHTTPBasic() throws Exception { tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("v2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("v3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("starfleet","starfleet_academy","starfleet_library").alias("sf"))).actionGet(); tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire","vulcangov").alias("nonsf"))).actionGet(); tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))).actionGet(); } - + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("_search").getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("worf", "worf")).getStatusCode()); @@ -108,11 +108,11 @@ public void testHTTPBasic() throws Exception { Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "Basic")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("picard", "picard")).getStatusCode()); - + for(int i=0; i< 10; i++) { Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrongpasswd")).getStatusCode()); } - + Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("/theindex","{}",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_CREATED, rh.executePutRequest("/theindex/_doc/1?refresh=true","{\"a\":0}",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); //Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("/theindex/_analyze?text=this+is+a+test",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); @@ -126,80 +126,80 @@ public void testHTTPBasic() throws Exception { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("/.opendistro_security/_close", null,encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("/.opendistro_security/_upgrade", null,encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest("/.opendistro_security/_mapping","{}",encodeBasicHeader("worf", "worf")).getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest(".opendistro_security/", encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest(".opendistro_security/_doc/2", "{}",encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest(".opendistro_security/_doc/0",encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeDeleteRequest(".opendistro_security/_doc/0",encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest(".opendistro_security/_doc/0","{}",encodeBasicHeader("worf", "worf")).getStatusCode()); - + HttpResponse resc = rh.executeGetRequest("_cat/indices/public?v",encodeBasicHeader("bug108", "nagilum")); Assert.assertTrue(resc.getBody().contains("green")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("role01_role02/_search?pretty",encodeBasicHeader("user_role01_role02_role03", "user_role01_role02_role03")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("role01_role02/_search?pretty",encodeBasicHeader("user_role01", "user_role01")).getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("spock/_search?pretty",encodeBasicHeader("spock", "spock")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("spock/_search?pretty",encodeBasicHeader("kirk", "kirk")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("kirk/_search?pretty",encodeBasicHeader("kirk", "kirk")).getStatusCode()); - //all + //all Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest(".opendistro_security/_mget","{\"ids\" : [\"0\"]}",encodeBasicHeader("worf", "worf")).getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode()); - + try (Client tc = getClient()) { tc.index(new IndexRequest(".opendistro_security").id("roles").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("roles", FileHelper.readYamlContent("roles_deny.yml"))).actionGet(); ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"roles"})).actionGet(); Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); } - + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode()); - + try (Client tc = getClient()) { tc.index(new IndexRequest(".opendistro_security").id("roles").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("roles", FileHelper.readYamlContent("roles.yml"))).actionGet(); ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"roles"})).actionGet(); Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); } - + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode()); HttpResponse res = rh.executeGetRequest("_search?pretty", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("\"value\" : 11")); Assert.assertTrue(!res.getBody().contains(".opendistro_security")); - + res = rh.executeGetRequest("_nodes/stats?pretty", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("total_in_bytes")); Assert.assertTrue(res.getBody().contains("max_file_descriptors")); Assert.assertTrue(res.getBody().contains("buffer_pools")); Assert.assertFalse(res.getBody().contains("\"nodes\" : { }")); - + res = rh.executePostRequest("*/_upgrade", "", encodeBasicHeader("nagilum", "nagilum")); System.out.println(res.getBody()); System.out.println(res.getStatusReason()); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - - String bulkBody = + + String bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator()+ "{ \"field2\" : \"value2\" }"+System.lineSeparator(); - + res = rh.executePostRequest("_bulk", bulkBody, encodeBasicHeader("writer", "writer")); System.out.println(res.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("\"errors\":false")); - Assert.assertTrue(res.getBody().contains("\"status\":201")); - + Assert.assertTrue(res.getBody().contains("\"status\":201")); + res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("security_tenant", "unittesttenant"), encodeBasicHeader("worf", "worf")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("tenant")); Assert.assertTrue(res.getBody().contains("unittesttenant")); Assert.assertTrue(res.getBody().contains("\"kltentrw\":true")); Assert.assertTrue(res.getBody().contains("\"user_name\":\"worf\"")); - + res = rh.executeGetRequest("_opendistro/_security/authinfo", encodeBasicHeader("worf", "worf")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("tenant")); @@ -208,7 +208,7 @@ public void testHTTPBasic() throws Exception { Assert.assertTrue(res.getBody().contains("\"user_name\":\"worf\"")); Assert.assertTrue(res.getBody().contains("\"custom_attribute_names\":[]")); Assert.assertFalse(res.getBody().contains("attributes=")); - + res = rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("custattr", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("tenants")); @@ -217,38 +217,38 @@ public void testHTTPBasic() throws Exception { Assert.assertTrue(res.getBody().contains("\"custom_attribute_names\" : [")); Assert.assertTrue(res.getBody().contains("attr.internal.c3")); Assert.assertTrue(res.getBody().contains("attr.internal.c1")); - + res = rh.executeGetRequest("v2/_search", encodeBasicHeader("custattr", "nagilum")); Assert.assertEquals(res.getBody(), HttpStatus.SC_OK, res.getStatusCode()); - + res = rh.executeGetRequest("v3/_search", encodeBasicHeader("custattr", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - + final String reindex = "{"+ - "\"source\": {"+ + "\"source\": {"+ "\"index\": \"starfleet\""+ "},"+ "\"dest\": {"+ "\"index\": \"copysf\""+ "}"+ "}"; - + res = rh.executePostRequest("_reindex?pretty", reindex, encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("\"total\" : 1")); Assert.assertTrue(res.getBody().contains("\"batches\" : 1")); Assert.assertTrue(res.getBody().contains("\"failures\" : [ ]")); - + //rest impersonation res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as","knuddel"), encodeBasicHeader("worf", "worf")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("name=knuddel")); Assert.assertTrue(res.getBody().contains("attr.internal.test1")); Assert.assertFalse(res.getBody().contains("worf")); - + res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as","nonexists"), encodeBasicHeader("worf", "worf")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - + res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as","notallowed"), encodeBasicHeader("worf", "worf")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); } @@ -274,7 +274,7 @@ public void testHTTPSCompressionEnabled() throws Exception { assertNotContains(res, "*\"compression\":\"false\"*"); assertContains(res, "*\"compression\":\"true\"*"); } - + @Test public void testHTTPSCompression() throws Exception { final Settings settings = Settings.builder() @@ -298,30 +298,30 @@ public void testHTTPSCompression() throws Exception { @Test public void testHTTPAnon() throws Exception { - + setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_anon.yml"), Settings.EMPTY, true); - + RestHelper rh = nonSslRestHelper(); - + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("").getStatusCode()); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrong")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - + HttpResponse resc = rh.executeGetRequest("_opendistro/_security/authinfo"); Assert.assertTrue(resc.getBody().contains("opendistro_security_anonymous")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - + resc = rh.executeGetRequest("_opendistro/_security/authinfo?pretty=true"); System.out.println(resc.getBody()); Assert.assertTrue(resc.getBody().contains("\"remote_address\" : \"")); //check pretty print Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - + resc = rh.executeGetRequest("_opendistro/_security/authinfo", encodeBasicHeader("nagilum", "nagilum")); System.out.println(resc.getBody()); Assert.assertTrue(resc.getBody().contains("nagilum")); Assert.assertFalse(resc.getBody().contains("opendistro_security_anonymous")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - + try (Client tc = getClient()) { tc.index(new IndexRequest(".opendistro_security").id("config").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("config", FileHelper.readYamlContent("config.yml"))).actionGet(); tc.index(new IndexRequest(".opendistro_security").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("internalusers").source("internalusers", FileHelper.readYamlContent("internal_users.yml"))).actionGet(); @@ -329,8 +329,8 @@ public void testHTTPAnon() throws Exception { Assert.assertFalse(cur.hasFailures()); Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); } - - + + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("_opendistro/_security/authinfo").getStatusCode()); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrong")).getStatusCode()); @@ -349,27 +349,27 @@ public void testHTTPClientCert() throws Exception { .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.1","TLSv1.2") .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256") .build(); - + setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_clientcert.yml"), settings, true); - + try (Client tc = getClient()) { tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config","roles","rolesmapping","internalusers","actiongroups"})).actionGet(); Assert.assertFalse(cur.hasFailures()); Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); } - + RestHelper rh = restHelper(); - + rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; rh.keystore = "spock-keystore.jks"; Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_search").getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest(".opendistro_security/_doc/x", "{}").getStatusCode()); - + rh.keystore = "kirk-keystore.jks"; Assert.assertEquals(HttpStatus.SC_CREATED, rh.executePutRequest(".opendistro_security/_doc/y", "{}").getStatusCode()); HttpResponse res; @@ -380,7 +380,7 @@ public void testHTTPClientCert() throws Exception { @Test @Ignore public void testHTTPPlaintextErrMsg() throws Exception { - + try { final Settings settings = Settings.builder() .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) @@ -405,7 +405,7 @@ public void testHTTPPlaintextErrMsg() throws Exception { public void testHTTPProxyDefault() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_proxy.yml"), Settings.EMPTY, true); RestHelper rh = nonSslRestHelper(); - + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("x-proxy-user", "scotty"), encodeBasicHeader("nagilum-wrong", "nagilum-wrong")).getStatusCode()); @@ -415,7 +415,7 @@ public void testHTTPProxyDefault() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("x-proxy-user", "scotty")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("X-Proxy-User", "scotty")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("x-proxy-user", "scotty"),new BasicHeader("x-proxy-roles", "starfleet,engineer")).getStatusCode()); - + } @Test @@ -424,45 +424,45 @@ public void testHTTPProxyRolesSeparator() throws Exception { RestHelper rh = nonSslRestHelper(); // separator is configured as ";" so separating roles with "," leads to one (wrong) backend role HttpResponse res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("user", "scotty"),new BasicHeader("roles", "starfleet,engineer")); - Assert.assertTrue("Expected one backend role since separator is incorrect", res.getBody().contains("\"backend_roles\":[\"starfleet,engineer\"]")); + Assert.assertTrue("Expected one backend role since separator is incorrect", res.getBody().contains("\"backend_roles\":[\"starfleet,engineer\"]")); // correct separator, now we should see two backend roles res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("user", "scotty"),new BasicHeader("roles", "starfleet;engineer")); - Assert.assertTrue("Expected two backend roles string since separator is correct: " + res.getBody(), res.getBody().contains("\"backend_roles\":[\"starfleet\",\"engineer\"]")); - + Assert.assertTrue("Expected two backend roles string since separator is correct: " + res.getBody(), res.getBody().contains("\"backend_roles\":[\"starfleet\",\"engineer\"]")); + } @Test public void testHTTPBasic2() throws Exception { - + setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY); - + try (Client tc = getClient()) { - + tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("starfleet","starfleet_academy","starfleet_library").alias("sf"))).actionGet(); tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire","vulcangov").alias("nonsf"))).actionGet(); tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))).actionGet(); } - + RestHelper rh = nonSslRestHelper(); - + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); @@ -480,11 +480,11 @@ public void testHTTPBasic2() throws Exception { Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "Basic")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("picard", "picard")).getStatusCode()); - + for(int i=0; i< 10; i++) { Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrongpasswd")).getStatusCode()); } - + Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("/theindex","{}",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_CREATED, rh.executePutRequest("/theindex/_doc/1?refresh=true","{\"a\":0}",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); //Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("/theindex/_analyze?text=this+is+a+test",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); @@ -498,31 +498,31 @@ public void testHTTPBasic2() throws Exception { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("/.opendistro_security/_close", null,encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("/.opendistro_security/_upgrade", null,encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest("/.opendistro_security/_mapping","{}",encodeBasicHeader("worf", "worf")).getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest(".opendistro_security/", encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest(".opendistro_security/_doc/2", "{}",encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest(".opendistro_security/_doc/0",encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeDeleteRequest(".opendistro_security/_doc/0",encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest(".opendistro_security/_doc/0","{}",encodeBasicHeader("worf", "worf")).getStatusCode()); - + HttpResponse resc = rh.executeGetRequest("_cat/indices/public",encodeBasicHeader("bug108", "nagilum")); System.out.println(resc.getBody()); //Assert.assertTrue(resc.getBody().contains("green")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("role01_role02/_search?pretty",encodeBasicHeader("user_role01_role02_role03", "user_role01_role02_role03")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("role01_role02/_search?pretty",encodeBasicHeader("user_role01", "user_role01")).getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("spock/_search?pretty",encodeBasicHeader("spock", "spock")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("spock/_search?pretty",encodeBasicHeader("kirk", "kirk")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("kirk/_search?pretty",encodeBasicHeader("kirk", "kirk")).getStatusCode()); - + System.out.println("ok"); //all - - + + } - + @Test public void testBulk() throws Exception { final Settings settings = Settings.builder() @@ -530,16 +530,16 @@ public void testBulk() throws Exception { .build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityRoles("roles_bulk.yml"), settings); final RestHelper rh = nonSslRestHelper(); - - String bulkBody = + + String bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator()+ "{ \"field2\" : \"value2\" }"+System.lineSeparator(); - + HttpResponse res = rh.executePostRequest("_bulk", bulkBody, encodeBasicHeader("bulk", "nagilum")); System.out.println(res.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("\"errors\":false")); Assert.assertTrue(res.getBody().contains("\"status\":201")); } @@ -573,40 +573,40 @@ public void test557() throws Exception { .put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH") .build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings); - + try (Client tc = getClient()) { - + tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + } - + final RestHelper rh = nonSslRestHelper(); HttpResponse res = rh.executePostRequest("/*/_search", "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":10}}}}", encodeBasicHeader("nagilum", "nagilum")); System.out.println(res.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("starfleet_academy")); res = rh.executePostRequest("/*/_search", "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":10}}}}", encodeBasicHeader("557", "nagilum")); System.out.println(res.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody().contains("starfleet_academy")); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertTrue(res.getBody().contains("starfleet_academy")); } - + @Test public void testITT1635() throws Exception { final Settings settings = Settings.builder() .put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH") .build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_dnfof.yml").setSecurityRoles("roles_itt1635.yml"), settings); - + try (Client tc = getClient()) { - + tc.index(new IndexRequest("esb-prod-1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("esb-prod-2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("esb-prod-3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); @@ -621,12 +621,12 @@ public void testITT1635() throws Exception { tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-5").alias("esb-alias-5"))).actionGet(); } - + final RestHelper rh = nonSslRestHelper(); System.out.println("###1"); HttpResponse res = rh.executeGetRequest("/esb-prod-*/_search?pretty", encodeBasicHeader("itt1635", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); System.out.println("###2"); res = rh.executeGetRequest("/esb-alias-*/_search?pretty", encodeBasicHeader("itt1635", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); @@ -634,15 +634,15 @@ public void testITT1635() throws Exception { res = rh.executeGetRequest("/esb-prod-all/_search?pretty", encodeBasicHeader("itt1635", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); } - + @Test public void testTenantInfo() throws Exception { final Settings settings = Settings.builder() .build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings); - + /* - + [admin_1, praxisrw, abcdef_2_2, kltentro, praxisro, kltentrw] admin_1==.kibana_-1139640511_admin1 praxisrw==.kibana_-1386441176_praxisrw @@ -650,11 +650,11 @@ public void testTenantInfo() throws Exception { kltentro==.kibana_-2014056171_kltentro praxisro==.kibana_-1386441184_praxisro kltentrw==.kibana_-2014056163_kltentrw - + */ - + try (Client tc = getClient()) { - + tc.index(new IndexRequest(".kibana-6").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest(".kibana_-1139640511_admin1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest(".kibana_-1386441176_praxisrw").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); @@ -664,7 +664,7 @@ public void testTenantInfo() throws Exception { tc.index(new IndexRequest(".kibana_9876_xxx_ccc").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest(".kibana_fff_eee").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("esb-prod-5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":5}", XContentType.JSON)).actionGet(); tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices(".kibana-6").alias(".kibana"))).actionGet(); @@ -672,11 +672,11 @@ public void testTenantInfo() throws Exception { tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-5").alias("esb-alias-5"))).actionGet(); } - + final RestHelper rh = nonSslRestHelper(); HttpResponse res = rh.executeGetRequest("_opendistro/_security/tenantinfo?pretty", encodeBasicHeader("itt1635", "nagilum")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); res = rh.executeGetRequest("_opendistro/_security/tenantinfo?pretty", encodeBasicHeader("kibanaserver", "kibanaserver")); System.out.println(res.getBody()); @@ -691,7 +691,7 @@ public void testTenantInfo() throws Exception { Assert.assertFalse(res.getBody().contains("xxx")); Assert.assertFalse(res.getBody().contains(".kibana2")); } - + @Test public void testRestImpersonation() throws Exception { final Settings settings = Settings.builder() @@ -699,7 +699,7 @@ public void testRestImpersonation() throws Exception { .build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_rest_impersonation.yml"), settings); final RestHelper rh = nonSslRestHelper(); - + //rest impersonation HttpResponse res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as","someotherusernotininternalusersfile"), encodeBasicHeader("worf", "worf")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); diff --git a/src/test/java/org/opensearch/security/IndexIntegrationTests.java b/src/test/java/org/opensearch/security/IndexIntegrationTests.java index 8f2ee960bd..7dcef21483 100644 --- a/src/test/java/org/opensearch/security/IndexIntegrationTests.java +++ b/src/test/java/org/opensearch/security/IndexIntegrationTests.java @@ -59,46 +59,46 @@ public class IndexIntegrationTests extends SingleClusterTest { @Test public void testComposite() throws Exception { - + setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("composite_config.yml").setSecurityRoles("roles_composite.yml"), Settings.EMPTY, true); final RestHelper rh = nonSslRestHelper(); - + try (Client tc = getClient()) { tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); } - - String msearchBody = + + String msearchBody = "{\"index\":\"starfleet\", \"ignore_unavailable\": true}"+System.lineSeparator()+ "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ "{\"index\":\"klingonempire\", \"ignore_unavailable\": true}"+System.lineSeparator()+ "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ "{\"index\":\"public\", \"ignore_unavailable\": true}"+System.lineSeparator()+ "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); - - + + HttpResponse resc = rh.executePostRequest("_msearch", msearchBody, encodeBasicHeader("worf", "worf")); Assert.assertEquals(200, resc.getStatusCode()); Assert.assertTrue(resc.getBody(), resc.getBody().contains("\"_index\":\"klingonempire\"")); Assert.assertTrue(resc.getBody(), resc.getBody().contains("hits")); Assert.assertTrue(resc.getBody(), resc.getBody().contains("no permissions for [indices:data/read/search]")); - + } - + @Test public void testBulkShards() throws Exception { - + setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityRoles("roles_bs.yml"), Settings.EMPTY, true); final RestHelper rh = nonSslRestHelper(); - + try (Client tc = getClient()) { //create indices and mapping upfront tc.index(new IndexRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("lorem").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON)).actionGet(); } - - String bulkBody = + + String bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ "{ \"field2\" : \"value1\" }" +System.lineSeparator()+ "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator()+ @@ -120,7 +120,7 @@ public void testBulkShards() throws Exception { "{ \"index\" : { \"_index\" : \"lorem\", \"_id\" : \"5\" } }"+System.lineSeparator()+ "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ "{ \"delete\" : { \"_index\" : \"lorem\", \"_id\" : \"5\" } }"+System.lineSeparator(); - + System.out.println("############ _bulk"); HttpResponse res = rh.executePostRequest("_bulk?refresh=true&pretty=true", bulkBody, encodeBasicHeader("worf", "worf")); System.out.println(res.getBody()); @@ -128,43 +128,43 @@ public void testBulkShards() throws Exception { Assert.assertTrue(res.getBody().contains("\"errors\" : true")); Assert.assertTrue(res.getBody().contains("\"status\" : 201")); Assert.assertTrue(res.getBody().contains("no permissions for")); - + System.out.println("############ check shards"); System.out.println(rh.executeGetRequest("_cat/shards?v", encodeBasicHeader("nagilum", "nagilum"))); - + } @Test public void testCreateIndex() throws Exception { - + setup(); RestHelper rh = nonSslRestHelper(); - + HttpResponse res; Assert.assertEquals("Unable to create index 'nag'", HttpStatus.SC_OK, rh.executePutRequest("nag1", null, encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); Assert.assertEquals("Unable to create index 'starfleet_library'", HttpStatus.SC_OK, rh.executePutRequest("starfleet_library", null, encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - + clusterHelper.waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(10), clusterInfo.numNodes); - + Assert.assertEquals("Unable to close index 'starfleet_library'", HttpStatus.SC_OK, rh.executePostRequest("starfleet_library/_close", null, encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - + Assert.assertEquals("Unable to open index 'starfleet_library'", HttpStatus.SC_OK, (res = rh.executePostRequest("starfleet_library/_open", null, encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); Assert.assertTrue("open index 'starfleet_library' not acknowledged", res.getBody().contains("acknowledged")); Assert.assertFalse("open index 'starfleet_library' not acknowledged", res.getBody().contains("false")); - + clusterHelper.waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(10), clusterInfo.numNodes); - + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest("public", null, encodeBasicHeader("spock", "spock")).getStatusCode()); - - + + } @Test public void testFilteredAlias() throws Exception { - + setup(); - + try (Client tc = getClient()) { tc.index(new IndexRequest("theindex").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); @@ -173,30 +173,30 @@ public void testFilteredAlias() throws Exception { tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().alias("alias2").filter(QueryBuilders.termQuery("_type", "type2")).index("theindex"))).actionGet(); tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().alias("alias3").filter(QueryBuilders.termQuery("_type", "type2")).index("otherindex"))).actionGet(); } - - + + RestHelper rh = nonSslRestHelper(); - + //opendistro_security_user1 -> worf //opendistro_security_user2 -> picard - + HttpResponse resc = rh.executeGetRequest("alias*/_search", encodeBasicHeader("worf", "worf")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - + resc = rh.executeGetRequest("theindex/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - + resc = rh.executeGetRequest("alias3/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); resc = rh.executeGetRequest("_cat/indices", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - + } - + @Test public void testIndexTypeEvaluation() throws Exception { - + setup(); try (Client tc = getClient()) { @@ -212,45 +212,45 @@ public void testIndexTypeEvaluation() throws Exception { //expected } } - + RestHelper rh = nonSslRestHelper(); - + HttpResponse resc = rh.executeGetRequest("/foo1/_search?pretty", encodeBasicHeader("baz", "worf")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("\"content\" : 1")); - + resc = rh.executeGetRequest("/foo2/_search?pretty", encodeBasicHeader("baz", "worf")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("\"content\" : 2")); - + resc = rh.executeGetRequest("/foo/_search?pretty", encodeBasicHeader("baz", "worf")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("\"content\" : 3")); - + //resc = rh.executeGetRequest("/fooba/z/_search?pretty", encodeBasicHeader("baz", "worf")); - //Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - + //Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + resc = rh.executeGetRequest("/foo1/_doc/1?pretty", encodeBasicHeader("baz", "worf")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("\"found\" : true")); Assert.assertTrue(resc.getBody().contains("\"content\" : 1")); - + resc = rh.executeGetRequest("/foo2/_doc/2?pretty", encodeBasicHeader("baz", "worf")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("\"content\" : 2")); Assert.assertTrue(resc.getBody().contains("\"found\" : true")); - + resc = rh.executeGetRequest("/foo/_doc/3?pretty", encodeBasicHeader("baz", "worf")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("\"content\" : 3")); Assert.assertTrue(resc.getBody().contains("\"found\" : true")); - + //resc = rh.executeGetRequest("/fooba/z/4?pretty", encodeBasicHeader("baz", "worf")); //Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - + //resc = rh.executeGetRequest("/foo*/_search?pretty", encodeBasicHeader("baz", "worf")); //Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - + resc = rh.executeGetRequest("/foo*,-fooba/_search?pretty", encodeBasicHeader("baz", "worf")); Assert.assertEquals(200, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("\"content\" : 1")); @@ -259,32 +259,32 @@ public void testIndexTypeEvaluation() throws Exception { @Test public void testIndices() throws Exception { - + setup(); - + try (Client tc = getClient()) { tc.index(new IndexRequest("nopermindex").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("logstash-1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("logstash-2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("logstash-3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("logstash-4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd", SecurityUtils.EN_Locale); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - + String date = sdf.format(new Date()); tc.index(new IndexRequest("logstash-"+date).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); } - + RestHelper rh = nonSslRestHelper(); - + HttpResponse res = null; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-1/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - + //nonexistent index with permissions Assert.assertEquals(HttpStatus.SC_NOT_FOUND, (res = rh.executeGetRequest("/logstash-nonex/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - + //existent index without permissions Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/nopermindex/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); @@ -298,25 +298,25 @@ public void testIndices() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-1/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); //nonexistent index with failed login - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, (res = rh.executeGetRequest("/logstash-nonex/_search", encodeBasicHeader("nouser", "nosuer"))).getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, (res = rh.executeGetRequest("/logstash-nonex/_search", encodeBasicHeader("nouser", "nosuer"))).getStatusCode()); + //nonexistent index with no login - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, (res = rh.executeGetRequest("/logstash-nonex/_search")).getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, (res = rh.executeGetRequest("/logstash-nonex/_search")).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/_all/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/*/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - + + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/*/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/nopermindex,logstash-1,nonexist/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/logstash-1,nonexist/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/nonexist/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/%3Clogstash-%7Bnow%2Fd%7D%3E/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/%3Cnonex-%7Bnow%2Fd%7D%3E/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/%3Clogstash-%7Bnow%2Fd%7D%3E,logstash-*/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); @@ -337,7 +337,7 @@ public void testIndices() throws Exception { Assert.assertTrue(res.getBody().contains("logstash-cnew-20")); Assert.assertFalse(res.getBody().contains("<")); } - + @Test public void testAliases() throws Exception { @@ -346,10 +346,10 @@ public void testAliases() throws Exception { .build(); setup(settings); - + try (Client tc = getClient()) { tc.index(new IndexRequest("nopermindex").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.index(new IndexRequest("logstash-1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("logstash-2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("logstash-3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); @@ -360,28 +360,28 @@ public void testAliases() throws Exception { String date = new SimpleDateFormat("YYYY.MM.dd").format(new Date()); tc.index(new IndexRequest("logstash-"+date).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("nopermindex").alias("nopermalias"))).actionGet(); tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices(".opendistro_security").alias("mysgi"))).actionGet(); } - + RestHelper rh = nonSslRestHelper(); - + HttpResponse res = null; - + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePostRequest("/mysgi/_doc", "{}",encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/mysgi/_search?pretty", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); assertContains(res, "*\"hits\" : {*\"value\" : 0,*\"hits\" : [ ]*"); - + System.out.println("#### add alias to allowed index"); Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePutRequest("/logstash-1/_alias/alog1", "",encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); System.out.println("#### add alias to not existing (no perm)"); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePutRequest("/nonexitent/_alias/alnp", "",encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); - + System.out.println("#### add alias to not existing (with perm)"); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, (res = rh.executePutRequest("/logstash-nonex/_alias/alnp", "",encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); - + System.out.println("#### add alias to not allowed index"); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePutRequest("/nopermindex/_alias/alnp", "",encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); @@ -391,32 +391,32 @@ public void testAliases() throws Exception { "{ \"remove_index\": { \"index\": \"logstash-del\" } } "+ "]"+ "}"; - + System.out.println("#### remove_index"); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePostRequest("/_aliases", aliasRemoveIndex,encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); - + System.out.println("#### get alias for permitted index"); Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-1/_alias/alog1", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); - + System.out.println("#### get alias for all indices"); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/_alias/alog1", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); - + System.out.println("#### get alias no perm"); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/_alias/nopermalias", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); - + String alias = "{"+ "\"aliases\": {"+ "\"alias1\": {}"+ "}"+ "}"; - - + + System.out.println("#### create alias along with index"); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePutRequest("/beats-withalias", alias,encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePutRequest("/beats-withalias", alias,encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); } @Test @@ -429,10 +429,10 @@ public void testIndexResolveInvalidIndexName() throws Exception { Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("invalid_index_name_exception")); } - + @Test public void testCCSIndexResolve() throws Exception { - + setup(); final RestHelper rh = nonSslRestHelper(); @@ -452,7 +452,7 @@ public void testCCSIndexResolve() throws Exception { @Test @Ignore public void testCCSIndexResolve2() throws Exception { - + setup(); final RestHelper rh = nonSslRestHelper(); @@ -462,46 +462,46 @@ public void testCCSIndexResolve2() throws Exception { tc.index(new IndexRequest("noperm").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); } - + HttpResponse res = rh.executeGetRequest("/*:.abc,.abc/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":1")); - + res = rh.executeGetRequest("/ba*bcuzh/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":12")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - + res = rh.executeGetRequest("/*:.abc/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":1")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - + res = rh.executeGetRequest("/*:xyz,xyz/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":2")); - + //res = rh.executeGetRequest("/*noexist/_search", encodeBasicHeader("nagilum", "nagilum")); - //Assert.assertEquals(HttpStatus.SC_NOT_FOUND, res.getStatusCode()); - + //Assert.assertEquals(HttpStatus.SC_NOT_FOUND, res.getStatusCode()); + res = rh.executeGetRequest("/*:.abc/_search", encodeBasicHeader("nagilum", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":1")); - + res = rh.executeGetRequest("/*:xyz/_search", encodeBasicHeader("nagilum", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":2")); - + res = rh.executeGetRequest("/.abc/_search", encodeBasicHeader("ccsresolv", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); res = rh.executeGetRequest("/xyz/_search", encodeBasicHeader("ccsresolv", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); res = rh.executeGetRequest("/*:.abc,.abc/_search", encodeBasicHeader("ccsresolv", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); res = rh.executeGetRequest("/*:xyz,xyz/_search", encodeBasicHeader("ccsresolv", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); res = rh.executeGetRequest("/*:.abc/_search", encodeBasicHeader("ccsresolv", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); res = rh.executeGetRequest("/*:xyz/_search", encodeBasicHeader("ccsresolv", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); res = rh.executeGetRequest("/*:noperm/_search", encodeBasicHeader("ccsresolv", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); res = rh.executeGetRequest("/*:noperm/_search", encodeBasicHeader("ccsresolv", "nagilum")); @@ -532,7 +532,7 @@ public void testIndexResolveIgnoreUnavailable() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody(), resc.getBody().contains("\"total\":{\"value\":1")); } - + @Test public void testIndexResolveIndicesAlias() throws Exception { @@ -545,19 +545,19 @@ public void testIndexResolveIndicesAlias() throws Exception { tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("foo-index").alias("foo-alias"))).actionGet(); tc.admin().indices().delete(new DeleteIndexRequest("foo-index")).actionGet(); } - + HttpResponse resc = rh.executeGetRequest("/_cat/aliases", encodeBasicHeader("nagilum", "nagilum")); Assert.assertFalse(resc.getBody().contains("foo")); resc = rh.executeGetRequest("/foo-alias/_search", encodeBasicHeader("foo_index", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - + resc = rh.executeGetRequest("/foo-index/_search", encodeBasicHeader("foo_index", "nagilum")); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, resc.getStatusCode()); - + resc = rh.executeGetRequest("/foo-alias/_search", encodeBasicHeader("foo_all", "nagilum")); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, resc.getStatusCode()); - + } @Test @@ -576,30 +576,30 @@ public void testIndexResolveMinus() throws Exception { resc = rh.executeGetRequest("/*/_search", encodeBasicHeader("foo_all", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - + resc = rh.executeGetRequest("/_search", encodeBasicHeader("foo_all", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - + resc = rh.executeGetRequest("/**,-foo*/_search", encodeBasicHeader("foo_all", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - + resc = rh.executeGetRequest("/*,-foo*/_search", encodeBasicHeader("foo_all", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - + resc = rh.executeGetRequest("/*,-*security/_search", encodeBasicHeader("foo_all", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); resc = rh.executeGetRequest("/*,-*security/_search", encodeBasicHeader("foo_all", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - + resc = rh.executeGetRequest("/*,-*security,-foo*/_search", encodeBasicHeader("foo_all", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - + resc = rh.executeGetRequest("/_all,-*security/_search", encodeBasicHeader("foo_all", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - + resc = rh.executeGetRequest("/_all,-*security/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, resc.getStatusCode()); - + } } diff --git a/src/test/java/org/opensearch/security/InitializationIntegrationTests.java b/src/test/java/org/opensearch/security/InitializationIntegrationTests.java index 17ced9e325..87241ee110 100644 --- a/src/test/java/org/opensearch/security/InitializationIntegrationTests.java +++ b/src/test/java/org/opensearch/security/InitializationIntegrationTests.java @@ -65,7 +65,7 @@ public class InitializationIntegrationTests extends SingleClusterTest { @Test public void testEnsureInitViaRestDoesWork() throws Exception { - + final Settings settings = Settings.builder() .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") .put("plugins.security.ssl.http.enabled",true) @@ -80,11 +80,11 @@ public void testEnsureInitViaRestDoesWork() throws Exception { rh.sendAdminCertificate = true; Assert.assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, rh.executePutRequest(".opendistro_security/_doc/0", "{}", encodeBasicHeader("___", "")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, rh.executePutRequest(".opendistro_security/_doc/config", "{}", encodeBasicHeader("___", "")).getStatusCode()); - - + + rh.keystore = "kirk-keystore.jks"; Assert.assertEquals(HttpStatus.SC_CREATED, rh.executePutRequest(".opendistro_security/_doc/config", "{}", encodeBasicHeader("___", "")).getStatusCode()); - + Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"tx_size_in_bytes\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_count\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_size_in_bytes\" : 0")); @@ -157,11 +157,11 @@ public void testWhoAmIForceHttp1() throws Exception { @Test public void testConfigHotReload() throws Exception { - + setup(); RestHelper rh = nonSslRestHelper(); Header spock = encodeBasicHeader("spock", "spock"); - + for (Iterator iterator = clusterInfo.httpAdresses.iterator(); iterator.hasNext();) { TransportAddress TransportAddress = (TransportAddress) iterator.next(); HttpResponse res = rh.executeRequest(new HttpGet("http://"+TransportAddress.getAddress()+":"+TransportAddress.getPort() + "/" + "_opendistro/_security/authinfo?pretty=true"), spock); @@ -169,14 +169,14 @@ public void testConfigHotReload() throws Exception { Assert.assertFalse(res.getBody().contains("additionalrole")); Assert.assertTrue(res.getBody().contains("vulcan")); } - + try (Client tc = getClient()) { Assert.assertEquals(clusterInfo.numNodes, tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); tc.index(new IndexRequest(".opendistro_security").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("internalusers").source("internalusers", FileHelper.readYamlContent("internal_users_spock_add_roles.yml"))).actionGet(); ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config","roles","rolesmapping","internalusers","actiongroups"})).actionGet(); - Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); - } - + Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); + } + for (Iterator iterator = clusterInfo.httpAdresses.iterator(); iterator.hasNext();) { TransportAddress TransportAddress = (TransportAddress) iterator.next(); log.debug("http://"+TransportAddress.getAddress()+":"+TransportAddress.getPort()); @@ -186,14 +186,14 @@ public void testConfigHotReload() throws Exception { Assert.assertTrue(res.getBody().contains("additionalrole2")); Assert.assertFalse(res.getBody().contains("starfleet")); } - + try (Client tc = getClient()) { Assert.assertEquals(clusterInfo.numNodes, tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); tc.index(new IndexRequest(".opendistro_security").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("config").source("config", FileHelper.readYamlContent("config_anon.yml"))).actionGet(); ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config"})).actionGet(); - Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); + Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); } - + for (Iterator iterator = clusterInfo.httpAdresses.iterator(); iterator.hasNext();) { TransportAddress TransportAddress = (TransportAddress) iterator.next(); HttpResponse res = rh.executeRequest(new HttpGet("http://"+TransportAddress.getAddress()+":"+TransportAddress.getPort() + "/" + "_opendistro/_security/authinfo?pretty=true")); @@ -214,7 +214,7 @@ public void testDefaultConfig() throws Exception { setup(Settings.EMPTY, null, settings, false); RestHelper rh = nonSslRestHelper(); Thread.sleep(10000); - + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("admin", "admin")).getStatusCode()); HttpResponse res = rh.executeGetRequest("/_cluster/health", encodeBasicHeader("admin", "admin")); Assert.assertEquals(res.getBody(), HttpStatus.SC_OK, res.getStatusCode()); @@ -244,19 +244,19 @@ public void testInvalidDefaultConfig() throws Exception { @Test public void testDisabled() throws Exception { - + final Settings settings = Settings.builder().put("plugins.security.disabled", true).build(); - + setup(Settings.EMPTY, null, settings, false); RestHelper rh = nonSslRestHelper(); - + HttpResponse resc = rh.executeGetRequest("_search"); Assert.assertEquals(200, resc.getStatusCode()); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("hits")); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("hits")); } @Test - public void testDiscoveryWithoutInitialization() throws Exception { + public void testDiscoveryWithoutInitialization() throws Exception { setup(Settings.EMPTY, null, Settings.EMPTY, false); Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); diff --git a/src/test/java/org/opensearch/security/IntegrationTests.java b/src/test/java/org/opensearch/security/IntegrationTests.java index 226551a5ae..8f2b0f7282 100644 --- a/src/test/java/org/opensearch/security/IntegrationTests.java +++ b/src/test/java/org/opensearch/security/IntegrationTests.java @@ -62,7 +62,7 @@ public class IntegrationTests extends SingleClusterTest { @Test - public void testSearchScroll() throws Exception { + public void testSearchScroll() throws Exception { final Settings settings = Settings.builder() .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".worf", "knuddel","nonexists") .build(); @@ -73,12 +73,12 @@ public void testSearchScroll() throws Exception { for(int i=0; i<3; i++) tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); } - - + + System.out.println("########search"); HttpResponse res; Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executeGetRequest("vulcangov/_search?scroll=1m&pretty=true", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); - + System.out.println(res.getBody()); int start = res.getBody().indexOf("_scroll_id") + 15; String scrollid = res.getBody().substring(start, res.getBody().indexOf("\"", start+1)); @@ -88,8 +88,8 @@ public void testSearchScroll() throws Exception { System.out.println("########search done"); - - + + } @Test @@ -101,21 +101,21 @@ public void testDnParsingCertAuth() throws Exception { HTTPClientCertAuthenticator auth = new HTTPClientCertAuthenticator(settings, null); Assert.assertEquals("abc", auth.extractCredentials(null, newThreadContext("cn=abc,cn=xxx,l=ert,st=zui,c=qwe")).getUsername()); Assert.assertEquals("abc", auth.extractCredentials(null, newThreadContext("cn=abc,l=ert,st=zui,c=qwe")).getUsername()); - Assert.assertEquals("abc", auth.extractCredentials(null, newThreadContext("CN=abc,L=ert,st=zui,c=qwe")).getUsername()); + Assert.assertEquals("abc", auth.extractCredentials(null, newThreadContext("CN=abc,L=ert,st=zui,c=qwe")).getUsername()); Assert.assertEquals("abc", auth.extractCredentials(null, newThreadContext("l=ert,cn=abc,st=zui,c=qwe")).getUsername()); Assert.assertNull(auth.extractCredentials(null, newThreadContext("L=ert,CN=abc,c,st=zui,c=qwe"))); Assert.assertEquals("abc", auth.extractCredentials(null, newThreadContext("l=ert,st=zui,c=qwe,cn=abc")).getUsername()); - Assert.assertEquals("abc", auth.extractCredentials(null, newThreadContext("L=ert,st=zui,c=qwe,CN=abc")).getUsername()); - Assert.assertEquals("L=ert,st=zui,c=qwe", auth.extractCredentials(null, newThreadContext("L=ert,st=zui,c=qwe")).getUsername()); + Assert.assertEquals("abc", auth.extractCredentials(null, newThreadContext("L=ert,st=zui,c=qwe,CN=abc")).getUsername()); + Assert.assertEquals("L=ert,st=zui,c=qwe", auth.extractCredentials(null, newThreadContext("L=ert,st=zui,c=qwe")).getUsername()); Assert.assertArrayEquals(new String[] {"ert"}, auth.extractCredentials(null, newThreadContext("cn=abc,l=ert,st=zui,c=qwe")).getBackendRoles().toArray(new String[0])); Assert.assertArrayEquals(new String[] {"bleh", "ert"}, new TreeSet<>(auth.extractCredentials(null, newThreadContext("cn=abc,l=ert,L=bleh,st=zui,c=qwe")).getBackendRoles()).toArray(new String[0])); - + settings = Settings.builder() .build(); auth = new HTTPClientCertAuthenticator(settings, null); Assert.assertEquals("cn=abc,l=ert,st=zui,c=qwe", auth.extractCredentials(null, newThreadContext("cn=abc,l=ert,st=zui,c=qwe")).getUsername()); } - + private ThreadContext newThreadContext(String sslPrincipal) { ThreadContext threadContext = new ThreadContext(Settings.EMPTY); threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PRINCIPAL, sslPrincipal); @@ -124,7 +124,7 @@ private ThreadContext newThreadContext(String sslPrincipal) { @Test public void testDNSpecials() throws Exception { - + final Settings settings = Settings.builder() .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("node-untspec5-keystore.p12")) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "1") @@ -133,24 +133,24 @@ public void testDNSpecials() throws Exception { .putList(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, "EMAILADDRESS=unt@xxx.com,CN=node-untspec6.example.com,OU=SSL,O=Te\\, st,L=Test,C=DE") .put(ConfigConstants.SECURITY_CERT_OID,"1.2.3.4.5.6") .build(); - - + + Settings tcSettings = Settings.builder() .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("node-untspec6-keystore.p12")) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "PKCS12") .build(); - + setup(tcSettings, new DynamicSecurityConfig(), settings, true); RestHelper rh = nonSslRestHelper(); - + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("worf", "worf")).getStatusCode()); - + } - + @Test public void testDNSpecials1() throws Exception { - + final Settings settings = Settings.builder() .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("node-untspec5-keystore.p12")) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "1") @@ -159,16 +159,16 @@ public void testDNSpecials1() throws Exception { .putList("plugins.security.authcz.admin_dn", "EMAILADDREss=unt@xxx.com, cn=node-untspec6.example.com, OU=SSL,O=Te\\, st,L=Test, c=DE") .put("plugins.security.cert.oid","1.2.3.4.5.6") .build(); - - + + Settings tcSettings = Settings.builder() .put("plugins.security.ssl.transport.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-untspec6-keystore.p12")) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "PKCS12") .build(); - + setup(tcSettings, new DynamicSecurityConfig(), settings, true); RestHelper rh = nonSslRestHelper(); - + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("worf", "worf")).getStatusCode()); } @@ -181,17 +181,17 @@ public void testEnsureOpenSSLAvailability() { @Test public void testMultiget() throws Exception { - + setup(); - + try (Client tc = getClient()) { tc.index(new IndexRequest("mindex1").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("mindex2").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); } - + //opendistro_security_multiget -> picard - - + + String mgetBody = "{"+ "\"docs\" : ["+ "{"+ @@ -204,86 +204,86 @@ public void testMultiget() throws Exception { "}"+ "]"+ "}"; - + RestHelper rh = nonSslRestHelper(); HttpResponse resc = rh.executePostRequest("_mget?refresh=true", mgetBody, encodeBasicHeader("picard", "picard")); System.out.println(resc.getBody()); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertFalse(resc.getBody().contains("type2")); - + } @Test public void testRestImpersonation() throws Exception { - + final Settings settings = Settings.builder() .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".spock", "knuddel","userwhonotexists").build(); - + setup(settings); - + RestHelper rh = nonSslRestHelper(); - + //knuddel: // hash: _rest_impersonation_only_ - + HttpResponse resp; resp = rh.executeGetRequest("/_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as", "knuddel"), encodeBasicHeader("worf", "worf")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resp.getStatusCode()); - + resp = rh.executeGetRequest("/_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as", "knuddel"), encodeBasicHeader("spock", "spock")); Assert.assertEquals(HttpStatus.SC_OK, resp.getStatusCode()); Assert.assertTrue(resp.getBody().contains("name=knuddel")); Assert.assertFalse(resp.getBody().contains("spock")); - + resp = rh.executeGetRequest("/_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as", "userwhonotexists"), encodeBasicHeader("spock", "spock")); System.out.println(resp.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resp.getStatusCode()); - + resp = rh.executeGetRequest("/_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as", "invalid"), encodeBasicHeader("spock", "spock")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resp.getStatusCode()); } @Test public void testSingle() throws Exception { - + setup(); - + try (Client tc = getClient()) { tc.index(new IndexRequest("shakespeare").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - + ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config","roles","rolesmapping","internalusers","actiongroups"})).actionGet(); Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); } - + RestHelper rh = nonSslRestHelper(); //opendistro_security_shakespeare -> picard - + HttpResponse resc = rh.executeGetRequest("shakespeare/_search", encodeBasicHeader("picard", "picard")); System.out.println(resc.getBody()); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("\"content\":1")); - + resc = rh.executeHeadRequest("shakespeare", encodeBasicHeader("picard", "picard")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - + } @Test public void testSpecialUsernames() throws Exception { - - setup(); + + setup(); RestHelper rh = nonSslRestHelper(); - + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("bug.99", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("a", "b")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("\"'+-,;_?*@<>!$%&/()=#", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("§ÄÖÜäöüß", "nagilum")).getStatusCode()); - + } @Test public void testXff() throws Exception { - + setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_xff.yml"), Settings.EMPTY, true); RestHelper rh = nonSslRestHelper(); HttpResponse resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("x-forwarded-for", "10.0.0.7"), encodeBasicHeader("worf", "worf")); @@ -293,7 +293,7 @@ public void testXff() throws Exception { @Test public void testRegexExcludes() throws Exception { - + setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY); try (Client tc = getClient()) { @@ -303,7 +303,7 @@ public void testRegexExcludes() throws Exception { tc.index(new IndexRequest("special").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"special\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("alsonotallowed").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"alsonotallowed\":1}", XContentType.JSON)).actionGet(); } - + RestHelper rh = nonSslRestHelper(); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("index*/_search",encodeBasicHeader("rexclude", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("indexa/_search",encodeBasicHeader("rexclude", "nagilum")).getStatusCode()); @@ -311,10 +311,10 @@ public void testRegexExcludes() throws Exception { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("special/_search",encodeBasicHeader("rexclude", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("alsonotallowed/_search",encodeBasicHeader("rexclude", "nagilum")).getStatusCode()); } - + @Test public void testMultiRoleSpan() throws Exception { - + setup(); final RestHelper rh = nonSslRestHelper(); @@ -322,31 +322,31 @@ public void testMultiRoleSpan() throws Exception { tc.index(new IndexRequest("mindex_1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("mindex_2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); } - + HttpResponse res = rh.executeGetRequest("/mindex_1,mindex_2/_search", encodeBasicHeader("mindex12", "nagilum")); System.out.println(res.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); Assert.assertFalse(res.getBody().contains("\"content\":1")); Assert.assertFalse(res.getBody().contains("\"content\":2")); - + try (Client tc = getClient()) { tc.index(new IndexRequest(".opendistro_security").id("config").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("config", FileHelper.readYamlContent("config_multirolespan.yml"))).actionGet(); - + ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config"})).actionGet(); Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); } - + res = rh.executeGetRequest("/mindex_1,mindex_2/_search", encodeBasicHeader("mindex12", "nagilum")); System.out.println(res.getBody()); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("\"content\":1")); Assert.assertTrue(res.getBody().contains("\"content\":2")); - + } - + @Test public void testMultiRoleSpan2() throws Exception { - + setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_multirolespan.yml"), Settings.EMPTY); final RestHelper rh = nonSslRestHelper(); @@ -356,26 +356,26 @@ public void testMultiRoleSpan2() throws Exception { tc.index(new IndexRequest("mindex_3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("mindex_4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); } - + HttpResponse res = rh.executeGetRequest("/mindex_1,mindex_2/_search", encodeBasicHeader("mindex12", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - + res = rh.executeGetRequest("/mindex_1,mindex_3/_search", encodeBasicHeader("mindex12", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); res = rh.executeGetRequest("/mindex_1,mindex_4/_search", encodeBasicHeader("mindex12", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - + } - + @Test public void testSecurityUnderscore() throws Exception { - + setup(); final RestHelper rh = nonSslRestHelper(); - + HttpResponse res = rh.executePostRequest("abc_xyz_2018_05_24/_doc/1", "{\"content\":1}", encodeBasicHeader("underscore", "nagilum")); - + res = rh.executeGetRequest("abc_xyz_2018_05_24/_doc/1", encodeBasicHeader("underscore", "nagilum")); Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":1")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); @@ -573,10 +573,10 @@ public void testDnfof() throws Exception { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("notexists/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); System.out.println(resc.getBody()); - + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, (resc=rh.executeGetRequest("permitnotexistentindex/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); System.out.println(resc.getBody()); - + Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("permitnotexistentindex*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); System.out.println(resc.getBody()); diff --git a/src/test/java/org/opensearch/security/ResolveAPITests.java b/src/test/java/org/opensearch/security/ResolveAPITests.java index 2c297e3bbe..a27c338dd1 100644 --- a/src/test/java/org/opensearch/security/ResolveAPITests.java +++ b/src/test/java/org/opensearch/security/ResolveAPITests.java @@ -34,7 +34,7 @@ public class ResolveAPITests extends SingleClusterTest { - + protected final Logger log = LogManager.getLogger(this.getClass()); @Test diff --git a/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java b/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java index d14f8d6600..af71d590bf 100644 --- a/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java @@ -84,7 +84,7 @@ public void testRolesInject() throws Exception { Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster(). health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") diff --git a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java index 1b4aee3e51..9a8278804a 100644 --- a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java @@ -75,7 +75,7 @@ public Collection createComponents(Client client, ClusterService cluster public void testRolesValidation() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityRoles("roles.yml"), Settings.EMPTY); - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") diff --git a/src/test/java/org/opensearch/security/SecurityAdminTests.java b/src/test/java/org/opensearch/security/SecurityAdminTests.java index bd032fc332..553ab0a5f2 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminTests.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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; @@ -43,7 +43,7 @@ import static org.junit.Assert.assertThrows; public class SecurityAdminTests extends SingleClusterTest { - + @Test public void testSecurityAdmin() throws Exception { final Settings settings = Settings.builder() @@ -52,9 +52,9 @@ public void testSecurityAdmin() throws Exception { .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) .build(); setup(Settings.EMPTY, null, settings, false); - + final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; - + List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); @@ -66,11 +66,11 @@ public void testSecurityAdmin() throws Exception { argsAsList.add(clusterInfo.clustername); addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-nhnv"); - - + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - + RestHelper rh = restHelper(); Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); @@ -211,9 +211,9 @@ public void testSecurityAdminV6Update() throws Exception { .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) .build(); setup(Settings.EMPTY, null, settings, false); - + final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; - + List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); @@ -225,11 +225,11 @@ public void testSecurityAdminV6Update() throws Exception { argsAsList.add(clusterInfo.clustername); addDirectoryPath(argsAsList, new File("./legacy/securityconfig_v6").getAbsolutePath()); argsAsList.add("-nhnv"); - - + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); - + RestHelper rh = restHelper(); Assert.assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, rh.executeGetRequest("_opendistro/_security/health?pretty").getStatusCode()); @@ -238,7 +238,7 @@ public void testSecurityAdminV6Update() throws Exception { //assertContains(res, "*strict*"); //assertNotContains(res, "*DOWN*"); } - + @Test public void testSecurityAdminRegularUpdate() throws Exception { final Settings settings = Settings.builder() @@ -247,9 +247,9 @@ public void testSecurityAdminRegularUpdate() throws Exception { .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) .build(); setup(Settings.EMPTY, null, settings, true); - + final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; - + List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); @@ -261,21 +261,21 @@ public void testSecurityAdminRegularUpdate() throws Exception { argsAsList.add(clusterInfo.clustername); addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-nhnv"); - - + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - + RestHelper rh = restHelper(); HttpResponse res; - + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); System.out.println(res.getBody()); assertContains(res, "*UP*"); assertContains(res, "*strict*"); assertNotContains(res, "*DOWN*"); } - + @Test public void testSecurityAdminSingularV7Updates() throws Exception { final Settings settings = Settings.builder() @@ -284,9 +284,9 @@ public void testSecurityAdminSingularV7Updates() throws Exception { .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) .build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings, true); - + final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; - + List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); @@ -301,11 +301,11 @@ public void testSecurityAdminSingularV7Updates() throws Exception { argsAsList.add("-t"); argsAsList.add("config"); argsAsList.add("-nhnv"); - - + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - + argsAsList = new ArrayList<>(); argsAsList.add("-ts"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); @@ -320,11 +320,11 @@ public void testSecurityAdminSingularV7Updates() throws Exception { argsAsList.add("-t"); argsAsList.add("rolesmapping"); argsAsList.add("-nhnv"); - - + + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - + argsAsList = new ArrayList<>(); argsAsList.add("-ts"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); @@ -339,21 +339,21 @@ public void testSecurityAdminSingularV7Updates() throws Exception { argsAsList.add("-t"); argsAsList.add("tenants"); argsAsList.add("-nhnv"); - - + + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - + RestHelper rh = restHelper(); HttpResponse res; - + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); System.out.println(res.getBody()); assertContains(res, "*UP*"); assertContains(res, "*strict*"); assertNotContains(res, "*DOWN*"); } - + @Test public void testSecurityAdminSingularV6Updates() throws Exception { final Settings settings = Settings.builder() @@ -364,7 +364,7 @@ public void testSecurityAdminSingularV6Updates() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig(), settings, true); final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; - + List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); @@ -379,21 +379,21 @@ public void testSecurityAdminSingularV6Updates() throws Exception { argsAsList.add("-t"); argsAsList.add("config"); argsAsList.add("-nhnv"); - + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); - + RestHelper rh = restHelper(); HttpResponse res; - + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); System.out.println(res.getBody()); assertContains(res, "*UP*"); assertContains(res, "*strict*"); assertNotContains(res, "*DOWN*"); } - + @Test public void testSecurityAdminInvalidYml() throws Exception { final Settings settings = Settings.builder() @@ -404,7 +404,7 @@ public void testSecurityAdminInvalidYml() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig(), settings, true); final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; - + List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); @@ -419,21 +419,21 @@ public void testSecurityAdminInvalidYml() throws Exception { argsAsList.add("-t"); argsAsList.add("roles"); argsAsList.add("-nhnv"); - - + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); - + RestHelper rh = restHelper(); HttpResponse res; - + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); System.out.println(res.getBody()); assertContains(res, "*UP*"); assertContains(res, "*strict*"); assertNotContains(res, "*DOWN*"); } - + @Test public void testSecurityAdminReloadInvalidConfig() throws Exception { final Settings settings = Settings.builder() @@ -451,10 +451,10 @@ public void testSecurityAdminReloadInvalidConfig() throws Exception { rh.keystore = "kirk-keystore.jks"; System.out.println(rh.executePutRequest(".opendistro_security/_doc/roles", FileHelper.loadFile("roles_invalidxcontent.yml")).getBody());; Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest(".opendistro_security/_doc/roles", "{\"roles\":\"dummy\"}").getStatusCode()); - - + + final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; - + List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); @@ -466,98 +466,98 @@ public void testSecurityAdminReloadInvalidConfig() throws Exception { argsAsList.add(clusterInfo.clustername); argsAsList.add("-rl"); argsAsList.add("-nhnv"); - - + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); - + HttpResponse res; - + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); assertContains(res, "*UP*"); assertContains(res, "*strict*"); assertNotContains(res, "*DOWN*"); } - + @Test public void testSecurityAdminValidateConfig() throws Exception { List argsAsList = new ArrayList<>(); addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-vc"); - + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - + argsAsList = new ArrayList<>(); argsAsList.add("-f"); argsAsList.add(new File(PROJECT_ROOT_RELATIVE_PATH + "src/test/resources/roles.yml").getAbsolutePath()); argsAsList.add("-vc"); - + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - + argsAsList = new ArrayList<>(); argsAsList.add("-f"); argsAsList.add(new File(PROJECT_ROOT_RELATIVE_PATH + "src/main/resources/static_config/static_roles.yml").getAbsolutePath()); argsAsList.add("-vc"); - + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - + argsAsList = new ArrayList<>(); argsAsList.add("-f"); argsAsList.add(new File(PROJECT_ROOT_RELATIVE_PATH + "src/main/resources/static_config/static_action_groups.yml").getAbsolutePath()); argsAsList.add("-vc"); - + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - + argsAsList = new ArrayList<>(); argsAsList.add("-f"); argsAsList.add(new File(PROJECT_ROOT_RELATIVE_PATH + "src/main/resources/static_config/static_tenants.yml").getAbsolutePath()); argsAsList.add("-vc"); - + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - + argsAsList = new ArrayList<>(); argsAsList.add("-f"); argsAsList.add(TEST_RESOURCE_ABSOLUTE_PATH + "roles.yml"); argsAsList.add("-vc"); argsAsList.add("-t"); argsAsList.add("config"); - + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); - + argsAsList = new ArrayList<>(); argsAsList.add("-ks"); argsAsList.add(TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-vc"); - + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); - + argsAsList = new ArrayList<>(); addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH + "legacy/securityconfig_v6"); argsAsList.add("-vc"); - + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); - + argsAsList = new ArrayList<>(); addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH + "legacy/securityconfig_v6"); argsAsList.add("-vc"); argsAsList.add("6"); - + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - + argsAsList = new ArrayList<>(); addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-vc"); argsAsList.add("8"); - + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); } @@ -570,9 +570,9 @@ public void testIsLegacySecurityIndexOnV7Index() throws Exception { .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) .build(); setup(Settings.EMPTY, null, settings, false); - + final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; - + List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); @@ -588,7 +588,7 @@ public void testIsLegacySecurityIndexOnV7Index() throws Exception { // Execute first time to create the index int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - + ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos); PrintStream old = System.out; diff --git a/src/test/java/org/opensearch/security/SlowIntegrationTests.java b/src/test/java/org/opensearch/security/SlowIntegrationTests.java index 6a90ef8e71..b2efada0d8 100644 --- a/src/test/java/org/opensearch/security/SlowIntegrationTests.java +++ b/src/test/java/org/opensearch/security/SlowIntegrationTests.java @@ -53,7 +53,7 @@ public class SlowIntegrationTests extends SingleClusterTest { @Test public void testCustomInterclusterRequestEvaluator() throws Exception { - + final Settings settings = Settings.builder() .put(ConfigConstants.SECURITY_INTERCLUSTER_REQUEST_EVALUATOR_CLASS, "org.opensearch.security.AlwaysFalseInterClusterRequestEvaluator") .put("discovery.initial_state_timeout","8s") @@ -69,9 +69,9 @@ public void testNodeClientAllowedWithServerCertificate() throws Exception { setup(); Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - - - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + + + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") @@ -81,24 +81,24 @@ public void testNodeClientAllowedWithServerCertificate() throws Exception { .put("discovery.initial_state_timeout","8s") .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost+":"+clusterInfo.nodePort) .build(); - + log.debug("Start node client"); - + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { Assert.assertFalse(node.client().admin().cluster().health(new ClusterHealthRequest().waitForNodes(String.valueOf(clusterInfo.numNodes+1))).actionGet().isTimedOut()); - Assert.assertEquals(clusterInfo.numNodes+1, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); + Assert.assertEquals(clusterInfo.numNodes+1, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); } } - + @SuppressWarnings("resource") @Test public void testNodeClientDisallowedWithNonServerCertificate() throws Exception { setup(); Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - - - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + + + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") @@ -110,26 +110,26 @@ public void testNodeClientDisallowedWithNonServerCertificate() throws Exception .put("plugins.security.ssl.transport.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("kirk-keystore.jks")) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS,"kirk") .build(); - + log.debug("Start node client"); try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { Thread.sleep(10000); - Assert.assertEquals(1, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); + Assert.assertEquals(1, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); } catch (Exception e) { Assert.fail(e.toString()); } - + } - + @SuppressWarnings("resource") @Test public void testNodeClientDisallowedWithNonServerCertificate2() throws Exception { setup(); Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") @@ -141,12 +141,12 @@ public void testNodeClientDisallowedWithNonServerCertificate2() throws Exception .put("plugins.security.ssl.transport.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("spock-keystore.jks")) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS,"spock") .build(); - + log.debug("Start node client"); - + try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { Thread.sleep(10000); - Assert.assertEquals(1, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); + Assert.assertEquals(1, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); } catch (Exception e) { Assert.fail(e.toString()); } diff --git a/src/test/java/org/opensearch/security/SystemIntegratorsTests.java b/src/test/java/org/opensearch/security/SystemIntegratorsTests.java index 4e647a6324..6ccc11104a 100644 --- a/src/test/java/org/opensearch/security/SystemIntegratorsTests.java +++ b/src/test/java/org/opensearch/security/SystemIntegratorsTests.java @@ -40,31 +40,31 @@ import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; public class SystemIntegratorsTests extends SingleClusterTest { - + @Test public void testInjectedUserMalformed() throws Exception { - - final Settings settings = Settings.builder() + + final Settings settings = Settings.builder() .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") .build(); - + setup(settings, ClusterConfiguration.USERINJECTOR); - + final RestHelper rh = nonSslRestHelper(); // username|role1,role2|remoteIP|attributes - + HttpResponse resc; - + resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, null)); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "|||")); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); - + resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "||127.0.0:80|")); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); - + resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "username||ip|")); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); @@ -82,24 +82,24 @@ public void testInjectedUserMalformed() throws Exception { resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "||127.0.0:80|key1,value1,key2,value2")); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); - + } @Test public void testInjectedUser() throws Exception { - - final Settings settings = Settings.builder() + + final Settings settings = Settings.builder() .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") .build(); - + setup(settings, ClusterConfiguration.USERINJECTOR); - + final RestHelper rh = nonSslRestHelper(); // username|role1,role2|remoteIP|attributes - + HttpResponse resc; - + resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "admin||127.0.0:80|")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("User [name=admin, backend_roles=[], requestedTenant=null]")); @@ -139,7 +139,7 @@ public void testInjectedUser() throws Exception { // mapped by username Assert.assertTrue(resc.getBody().contains("\"roles\":[\"opendistro_security_all_access\"")); Assert.assertTrue(resc.getBody().contains("\"custom_attribute_names\":[\"key1\",\"key2\"]")); - + resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "myuser|role1,vulcanadmin|8.8.8.8:8|key1,value1,key2,value2")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("User [name=myuser, backend_roles=[role1, vulcanadmin], requestedTenant=null]")); @@ -149,7 +149,7 @@ public void testInjectedUser() throws Exception { // mapped by backend role "twitter" Assert.assertTrue(resc.getBody().contains("\"roles\":[\"public\",\"role_vulcans_admin\"]")); Assert.assertTrue(resc.getBody().contains("\"custom_attribute_names\":[\"key1\",\"key2\"]")); - + // add requested tenant resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "myuser|role1,vulcanadmin|8.8.8.8:8|key1,value1,key2,value2|")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); @@ -179,43 +179,43 @@ public void testInjectedUser() throws Exception { Assert.assertTrue(resc.getBody().contains("\"backend_roles\":[\"role1\",\"vulcanadmin\"]")); // mapped by backend role "twitter" Assert.assertTrue(resc.getBody().contains("\"roles\":[\"public\",\"role_vulcans_admin\"]")); - - } + + } @Test public void testInjectedUserDisabled() throws Exception { - - final Settings settings = Settings.builder() + + final Settings settings = Settings.builder() .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") .build(); - + setup(settings, ClusterConfiguration.USERINJECTOR); - + final RestHelper rh = nonSslRestHelper(); // username|role1,role2|remoteIP|attributes - + HttpResponse resc; - + resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "admin|role1|127.0.0:80|key1,value1")); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); } @Test public void testInjectedAdminUser() throws Exception { - - final Settings settings = Settings.builder() + + final Settings settings = Settings.builder() .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_ADMIN_USER_ENABLED, true) .putList(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, Lists.newArrayList("CN=kirk,OU=client,O=client,L=Test,C=DE","injectedadmin")) .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") .build(); - + setup(settings, ClusterConfiguration.USERINJECTOR); - + final RestHelper rh = nonSslRestHelper(); HttpResponse resc; - + // injected user is admin, access to Security index must be allowed resc = rh.executeGetRequest(".opendistro_security/_search?pretty", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "injectedadmin|role1|127.0.0:80|key1,value1")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); @@ -223,26 +223,26 @@ public void testInjectedAdminUser() throws Exception { Assert.assertTrue(resc.getBody().contains("\"_id\" : \"roles\"")); Assert.assertTrue(resc.getBody().contains("\"_id\" : \"internalusers\"")); Assert.assertTrue(resc.getBody().contains("\"total\" : 5")); - + resc = rh.executeGetRequest(".opendistro_security/_search?pretty", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "wrongadmin|role1|127.0.0:80|key1,value1")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - + } @Test public void testInjectedAdminUserAdminInjectionDisabled() throws Exception { - - final Settings settings = Settings.builder() + + final Settings settings = Settings.builder() .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) .putList(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, Lists.newArrayList("CN=kirk,OU=client,O=client,L=Test,C=DE","injectedadmin")) .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") .build(); - + setup(settings, ClusterConfiguration.USERINJECTOR); - + final RestHelper rh = nonSslRestHelper(); HttpResponse resc; - + // injected user is admin, access to Security index must be allowed resc = rh.executeGetRequest(".opendistro_security/_search?pretty", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "injectedadmin|role1|127.0.0:80|key1,value1")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); @@ -251,7 +251,7 @@ public void testInjectedAdminUserAdminInjectionDisabled() throws Exception { Assert.assertFalse(resc.getBody().contains("\"_id\" : \"internalusers\"")); Assert.assertFalse(resc.getBody().contains("\"_id\" : \"tattr\"")); Assert.assertFalse(resc.getBody(), resc.getBody().contains("\"total\" : 6")); - - } + + } } diff --git a/src/test/java/org/opensearch/security/TaskTests.java b/src/test/java/org/opensearch/security/TaskTests.java index 0ec671af27..3a86b7e2bf 100644 --- a/src/test/java/org/opensearch/security/TaskTests.java +++ b/src/test/java/org/opensearch/security/TaskTests.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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; @@ -30,11 +30,11 @@ import org.opensearch.tasks.Task; public class TaskTests extends SingleClusterTest { - + @Test public void testXOpaqueIdHeader() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY); - + RestHelper rh = nonSslRestHelper(); HttpResponse res; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_tasks?group_by=parents&pretty" diff --git a/src/test/java/org/opensearch/security/UtilTests.java b/src/test/java/org/opensearch/security/UtilTests.java index 16baf932cc..83728e165d 100644 --- a/src/test/java/org/opensearch/security/UtilTests.java +++ b/src/test/java/org/opensearch/security/UtilTests.java @@ -110,31 +110,31 @@ public void testWildcardMatchers() { public void testMapFromArray() { Map map = SecurityUtils.mapFromArray((Object)null); assertTrue(map == null); - + map = SecurityUtils.mapFromArray("key"); assertTrue(map == null); map = SecurityUtils.mapFromArray("key", "value", "otherkey"); assertTrue(map == null); - + map = SecurityUtils.mapFromArray("key", "value"); - assertNotNull(map); + assertNotNull(map); assertEquals(1, map.size()); assertEquals("value", map.get("key")); map = SecurityUtils.mapFromArray("key", "value", "key", "value"); - assertNotNull(map); + assertNotNull(map); assertEquals(1, map.size()); assertEquals("value", map.get("key")); map = SecurityUtils.mapFromArray("key1", "value1", "key2", "value2"); - assertNotNull(map); + assertNotNull(map); assertEquals(2, map.size()); assertEquals("value1", map.get("key1")); assertEquals("value2", map.get("key2")); } - + @Test public void testEnvReplace() { Settings settings = Settings.EMPTY; @@ -150,7 +150,7 @@ public void testEnvReplace() { Map env = System.getenv(); assertTrue(env.size() > 0); - + boolean checked = false; for(String k: env.keySet()) { @@ -166,10 +166,10 @@ public void testEnvReplace() { assertTrue(OpenBSDBCrypt.checkPassword(SecurityUtils.replaceEnvVars("${envbc."+k+"}",settings), val.toCharArray())); checked = true; } - + assertTrue(checked); } - + @Test public void testNoEnvReplace() { Settings settings = Settings.builder().put(ConfigConstants.SECURITY_DISABLE_ENVVAR_REPLACEMENT, true).build(); @@ -182,7 +182,7 @@ public void testNoEnvReplace() { assertEquals("abv${env.MYENV-tTt}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV-tTt}xyz",settings)); Map env = System.getenv(); assertTrue(env.size() > 0); - + for(String k: env.keySet()) { assertEquals("abv${env."+k+"}xyz", SecurityUtils.replaceEnvVars("abv${env."+k+"}xyz",settings)); assertEquals("abv${"+k+"}xyz", SecurityUtils.replaceEnvVars("abv${"+k+"}xyz",settings)); 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 dd53cd16a8..96773dfee4 100644 --- a/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java @@ -136,7 +136,7 @@ public void testComplianceEnable() throws Exception { 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)); 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 1a99e8cdb0..afb102293b 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java @@ -83,7 +83,7 @@ public void testSimpleAuthenticatedSetting() throws Exception { final Settings settings = Settings.builder() .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) .put(FilterEntries.DISABLE_TRANSPORT_CATEGORIES.getKeyWithNamespace(), "NONE") - .build(); + .build(); verifyAuthenticated(settings); } @@ -133,7 +133,7 @@ public void testSSLPlainText() throws Exception { final RuntimeException ex = Assert.assertThrows(RuntimeException.class, () -> nonSslRestHelper().executeGetRequest("_search", encodeBasicHeader("admin", "admin"))); Assert.assertEquals("org.apache.hc.core5.http.NoHttpResponseException", ex.getCause().getClass().getName()); - }, 1); /* no retry on NotSslRecordException exceptions */ + }, 1); /* no retry on NotSslRecordException exceptions */ // All of the messages should be the same as the http client is attempting multiple times. messages.stream().forEach((message) -> { diff --git a/src/test/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistryTest.java b/src/test/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistryTest.java index 391b48ed70..3adc7a577f 100644 --- a/src/test/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistryTest.java +++ b/src/test/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistryTest.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.auth.blocking; @@ -23,33 +23,33 @@ import static org.junit.Assert.assertTrue; public class HeapBasedClientBlockRegistryTest { - + @Test - public void simpleTest() throws Exception { + public void simpleTest() throws Exception { HeapBasedClientBlockRegistry registry = new HeapBasedClientBlockRegistry<>(50, 3, String.class); - + assertFalse(registry.isBlocked("a")); registry.block("a"); assertTrue(registry.isBlocked("a")); - + registry.block("b"); assertTrue(registry.isBlocked("a")); assertTrue(registry.isBlocked("b")); - + registry.block("c"); assertTrue(registry.isBlocked("a")); assertTrue(registry.isBlocked("b")); assertTrue(registry.isBlocked("c")); - + registry.block("d"); assertFalse(registry.isBlocked("a")); assertTrue(registry.isBlocked("b")); assertTrue(registry.isBlocked("c")); assertTrue(registry.isBlocked("d")); } - + @Test - public void expiryTest() throws Exception { + public void expiryTest() throws Exception { HeapBasedClientBlockRegistry registry = new HeapBasedClientBlockRegistry<>(50, 3, String.class); assertFalse(registry.isBlocked("a")); diff --git a/src/test/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiterTest.java b/src/test/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiterTest.java index bd0cadfa6c..827bfa24b6 100644 --- a/src/test/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiterTest.java +++ b/src/test/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiterTest.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.auth.limiting; 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 d3383f2dbe..2e8ddec15b 100644 --- a/src/test/java/org/opensearch/security/auth/limiting/HeapBasedRateTrackerTest.java +++ b/src/test/java/org/opensearch/security/auth/limiting/HeapBasedRateTrackerTest.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.auth.limiting; @@ -26,11 +26,11 @@ import static org.junit.Assert.assertTrue; public class HeapBasedRateTrackerTest { - + @Test - public void simpleTest() throws Exception { + public void simpleTest() throws Exception { HeapBasedRateTracker tracker = new HeapBasedRateTracker<>(100, 5, 100_000); - + assertFalse(tracker.track("a")); assertFalse(tracker.track("a")); assertFalse(tracker.track("a")); @@ -38,12 +38,12 @@ public void simpleTest() throws Exception { assertTrue(tracker.track("a")); } - + @Test @Ignore // https://github.com/opensearch-project/security/issues/2193 - public void expiryTest() throws Exception { + public void expiryTest() throws Exception { HeapBasedRateTracker tracker = new HeapBasedRateTracker<>(100, 5, 100_000); - + assertFalse(tracker.track("a")); assertFalse(tracker.track("a")); assertFalse(tracker.track("a")); @@ -55,38 +55,38 @@ public void expiryTest() throws Exception { assertFalse(tracker.track("b")); assertFalse(tracker.track("b")); assertTrue(tracker.track("b")); - - assertFalse(tracker.track("c")); - + + assertFalse(tracker.track("c")); + Thread.sleep(50); - - assertFalse(tracker.track("c")); - assertFalse(tracker.track("c")); - assertFalse(tracker.track("c")); - - Thread.sleep(55); - - assertFalse(tracker.track("c")); - assertTrue(tracker.track("c")); - - assertFalse(tracker.track("a")); - + + assertFalse(tracker.track("c")); + assertFalse(tracker.track("c")); + assertFalse(tracker.track("c")); + + Thread.sleep(55); + + assertFalse(tracker.track("c")); + assertTrue(tracker.track("c")); + + assertFalse(tracker.track("a")); + Thread.sleep(55); - assertFalse(tracker.track("c")); - assertFalse(tracker.track("c")); - assertTrue(tracker.track("c")); + assertFalse(tracker.track("c")); + assertFalse(tracker.track("c")); + assertTrue(tracker.track("c")); + - } - + @Test @Ignore // https://github.com/opensearch-project/security/issues/2193 - public void maxTwoTriesTest() throws Exception { + public void maxTwoTriesTest() throws Exception { HeapBasedRateTracker tracker = new HeapBasedRateTracker<>(100, 2, 100_000); - + assertFalse(tracker.track("a")); assertTrue(tracker.track("a")); - + assertFalse(tracker.track("b")); Thread.sleep(50); assertTrue(tracker.track("b")); diff --git a/src/test/java/org/opensearch/security/auth/limiting/UserNameBasedRateLimiterTest.java b/src/test/java/org/opensearch/security/auth/limiting/UserNameBasedRateLimiterTest.java index 9e7d95fd00..e42d2bd1b8 100644 --- a/src/test/java/org/opensearch/security/auth/limiting/UserNameBasedRateLimiterTest.java +++ b/src/test/java/org/opensearch/security/auth/limiting/UserNameBasedRateLimiterTest.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2019 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.auth.limiting; diff --git a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java index 64e73202c7..b9b4d22d49 100644 --- a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java +++ b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java @@ -67,7 +67,7 @@ import static org.hamcrest.Matchers.not; public class CrossClusterSearchTests extends AbstractSecurityUnitTest { - + private final ClusterHelper cl1 = new ClusterHelper("crl1_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); private final ClusterHelper cl2 = new ClusterHelper("crl2_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); private ClusterInfo cl1Info; @@ -143,13 +143,13 @@ private Tuple setupCluster(ClusterHelper ch, ClusterTra System.out.println("### " + ch.getClusterName() + " complete ###"); return new Tuple<>(clusterInfo, rh); } - + @After public void tearDown() throws Exception { cl1.stopCluster(); cl2.stopCluster(); } - + @Test public void testCcs() throws Exception { setupCcs(); @@ -1004,7 +1004,7 @@ public void testCcsWithRoleInjection() throws Exception { } final Settings.Builder clusterClientSettings = Settings.builder().putList("node.roles", "remote_cluster_client"); - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(clusterClientSettings, false, false) + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(clusterClientSettings, false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", cl1Info.clustername) .put("path.data", "./target/data/" + cl1Info.clustername + "/cert/data") diff --git a/src/test/java/org/opensearch/security/ccstest/RemoteReindexTests.java b/src/test/java/org/opensearch/security/ccstest/RemoteReindexTests.java index 0d6efe1bb9..ad449aa20b 100644 --- a/src/test/java/org/opensearch/security/ccstest/RemoteReindexTests.java +++ b/src/test/java/org/opensearch/security/ccstest/RemoteReindexTests.java @@ -45,57 +45,57 @@ import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; public class RemoteReindexTests extends AbstractSecurityUnitTest { - + private final ClusterHelper cl1 = new ClusterHelper("crl1_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); private final ClusterHelper cl2 = new ClusterHelper("crl2_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); private ClusterInfo cl1Info; private ClusterInfo cl2Info; - - private void setupReindex() throws Exception { - + + private void setupReindex() throws Exception { + System.setProperty("security.display_lic_none","true"); - + cl2Info = cl2.startCluster(minimumSecuritySettings(Settings.EMPTY), ClusterConfiguration.DEFAULT); initialize(cl2, cl2Info); - + cl1Info = cl1.startCluster(minimumSecuritySettings(crossClusterNodeSettings(cl2Info)), ClusterConfiguration.DEFAULT); initialize(cl1, cl1Info); } - + @After public void tearDown() throws Exception { cl1.stopCluster(); cl2.stopCluster(); } - + private Settings crossClusterNodeSettings(ClusterInfo remote) { Settings.Builder builder = Settings.builder() .putList("reindex.remote.whitelist", remote.httpHost+":"+remote.httpPort); return builder.build(); } - + //TODO add ssl tests //https://github.com/elastic/elasticsearch/issues/27267 - + @Test public void testNonSSLReindex() throws Exception { setupReindex(); - + final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("nagilum","nagilum")).getBody(); Assert.assertTrue(cl1BodyMain.contains("crl1")); - + try (Client tc = cl1.nodeClient()) { tc.admin().indices().create(new CreateIndexRequest("twutter")).actionGet(); } - + final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("nagilum","nagilum")).getBody(); Assert.assertTrue(cl2BodyMain.contains("crl2")); - + try (Client tc = cl2.nodeClient()) { tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); } - + String reindex = "{"+ "\"source\": {"+ "\"remote\": {"+ @@ -110,11 +110,11 @@ public void testNonSSLReindex() throws Exception { "\"index\": \"twutter\""+ "}"+ "}"; - + System.out.println(reindex); - + HttpResponse ccs = null; - + System.out.println("###################### reindex"); ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("_reindex?pretty", reindex, encodeBasicHeader("nagilum","nagilum")); System.out.println(ccs.getBody()); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/AbstractDlsFlsTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/AbstractDlsFlsTest.java index 06d428a483..2e1eedcf57 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/AbstractDlsFlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/AbstractDlsFlsTest.java @@ -64,7 +64,7 @@ protected final void setup(Settings override, DynamicSecurityConfig dynamicSecur rh = nonSslRestHelper(); } - + protected SearchResponse executeSearch(String indexName, String user, String password) throws Exception { HttpResponse response = rh.executeGetRequest("/"+indexName+"/_search?from=0&size=50&pretty", encodeBasicHeader(user, password)); @@ -80,15 +80,15 @@ protected GetResponse executeGet(String indexName, String id, String user, Strin LoggingDeprecationHandler.INSTANCE, response.getBody()); return GetResponse.fromXContent(xcp); } - + protected MultiSearchResponse executeMSearchMatchAll(String user, String password, String ... indexName) throws Exception { StringBuilder body = new StringBuilder(); - + for (String index : indexName) { body.append("{\"index\": \"").append(index).append("\"}\n"); body.append("{\"query\" : {\"match_all\" : {}}}\n"); } - + HttpResponse response = rh.executePostRequest("/_msearch?pretty", body.toString(), encodeBasicHeader(user, password)); Assert.assertEquals(200, response.getStatusCode()); @@ -98,13 +98,13 @@ protected MultiSearchResponse executeMSearchMatchAll(String user, String passwor } protected MultiGetResponse executeMGet(String user, String password, Map indicesAndIds) throws Exception { - + Set indexAndIdJson = new HashSet<>(); for (Map.Entry indexAndId : indicesAndIds.entrySet()) { indexAndIdJson.add("{ \"_index\": \""+indexAndId.getKey()+"\", \"_id\": \""+indexAndId.getValue()+"\" }"); } String body = "{ \"docs\": ["+ String.join(",", indexAndIdJson) +"] }"; - + HttpResponse response = rh.executePostRequest("/_mget?pretty", body,encodeBasicHeader(user, password)); Assert.assertEquals(200, response.getStatusCode()); XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java index 4cd6987b5a..4ac8077c34 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java @@ -185,7 +185,7 @@ public void testReplication() throws Exception { Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster(). health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsDateMathTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsDateMathTest.java index c4105c11e9..bfd0773f44 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsDateMathTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsDateMathTest.java @@ -39,7 +39,7 @@ protected void populateData(Client tc) { LocalDateTime today = LocalDateTime.now(ZoneId.of("UTC")); LocalDateTime tomorrow = LocalDateTime.now(ZoneId.of("UTC")).plusDays(1); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); - + tc.index(new IndexRequest("logstash").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) .source("{\"@timestamp\": \""+formatter.format(yesterday)+"\"}", XContentType.JSON)).actionGet(); tc.index(new IndexRequest("logstash").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) @@ -48,7 +48,7 @@ protected void populateData(Client tc) { .source("{\"@timestamp\": \""+formatter.format(tomorrow)+"\"}", XContentType.JSON)).actionGet(); } - + @Test public void testDlsDateMathQuery() throws Exception { final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_ALLOW_NOW_IN_DLS,true).build(); @@ -60,13 +60,13 @@ public void testDlsDateMathQuery() throws Exception { System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 3,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); } - + @Test public void testDlsDateMathQueryNotAllowed() throws Exception { setup(); @@ -77,7 +77,7 @@ public void testDlsDateMathQueryNotAllowed() throws Exception { System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("'now' is not allowed in DLS queries")); Assert.assertTrue(res.getBody().contains("error")); - + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 3,\n \"relation")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTermLookupQueryTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTermLookupQueryTest.java index b4a0d1f129..098d659d88 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTermLookupQueryTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTermLookupQueryTest.java @@ -545,7 +545,7 @@ public void testSimpleAggregation_tlqdocuments_AccessCode_1337() throws Exceptio setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") .setSecurityRolesMapping("roles_mapping_tlq.yml")); - + String body = "" + " {\n" + " \"aggs\": {\n" @@ -580,7 +580,7 @@ public void testSimpleAggregation_tlqdocuments_AccessCode_1337() throws Exceptio Assert.assertTrue("Expected doc count in bucket " + bucketName + " to be 2", bucket.getDocCount() == 2); } // expect FFF to be absent - Assert.assertNull("Expected bucket FFF to be absent", agg.getBucketByKey("FFF")); + Assert.assertNull("Expected bucket FFF to be absent", agg.getBucketByKey("FFF")); } diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java index bbac74e6eb..a3b5d2809e 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java @@ -55,7 +55,7 @@ public void testSingleIndexFlsApplied() throws Exception { setup(new DynamicSecurityConfig() .setSecurityRoles("roles_fls_indexing.yml") .setSecurityRolesMapping("roles_mapping_fls_indexing.yml")); - + final HttpResponse phoneOneFilteredResponse = rh.executeGetRequest(searchQuery, asPhoneOneUser); assertThat(phoneOneFilteredResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(phoneOneFilteredResponse.getBody(), not(containsString("1003"))); @@ -76,7 +76,7 @@ public void testSingleIndexFlsAppliedForLimitedResults() throws Exception { setup(new DynamicSecurityConfig() .setSecurityRoles("roles_fls_indexing.yml") .setSecurityRolesMapping("roles_mapping_fls_indexing.yml")); - + final HttpResponse phoneOneFilteredResponse = rh.executeGetRequest("/yellow-pages/_search?filter_path=hits.hits&pretty", asPhoneOneUser); assertThat(phoneOneFilteredResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(phoneOneFilteredResponse.getBody(), not(containsString("1003"))); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java index 9f6bcb65c9..25974e322f 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java @@ -29,8 +29,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class AccountApiTest extends AbstractRestApiUnitTest { - private final String BASE_ENDPOINT; - private final String ENDPOINT; + private final String BASE_ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java index 926ee23f28..8030703197 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java @@ -32,7 +32,7 @@ import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; public class ActionGroupsApiTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java index 450a5de83b..018af18293 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java @@ -51,7 +51,7 @@ public class AuditApiActionTest extends AbstractRestApiUnitTest { // non-admin final Header nonAdminCredsHeader = encodeBasicHeader("random", "random"); - private final String ENDPOINT; + private final String ENDPOINT; private final String CONFIG_ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java index c17e997dc3..7d2396ecb0 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java @@ -22,7 +22,7 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class DashboardsInfoActionTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpoint() { return PLUGINS_PREFIX + "/dashboardsinfo"; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java index c998bf5a19..1d25b7dee2 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java @@ -23,7 +23,7 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class FlushCacheApiTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java index ea5e96d37e..9f767fd7a6 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java @@ -24,7 +24,7 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class GetConfigurationApiTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java index c2313fe434..1b403760f8 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java @@ -24,7 +24,7 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class IndexMissingTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java index ef1042682b..1f752bd0b2 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java @@ -42,7 +42,7 @@ public class NodesDnApiTest extends AbstractRestApiUnitTest { private HttpResponse response; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java index 5adac7ca78..9b5c7dc8c5 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java @@ -25,7 +25,7 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class RoleBasedAccessTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java index 3bc647bf12..c15651fcc8 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java @@ -32,7 +32,7 @@ import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; public class RolesMappingApiTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java index 83630c036a..53c6ff2e96 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java @@ -18,7 +18,7 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class SecurityApiAccessTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java index d717dcbf6c..50014993c1 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java @@ -25,7 +25,7 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class SecurityConfigApiTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } @@ -57,7 +57,7 @@ public void testSecurityConfigApiRead() throws Exception { response = rh.executeDeleteRequest(ENDPOINT + "/securityconfig", new Header[0]); Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode()); } - + @Test public void testSecurityConfigApiWrite() throws Exception { diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java index 13dc4ee885..93371b548a 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java @@ -22,7 +22,7 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class SecurityHealthActionTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java index 506ea3bdd2..654e0c6230 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java @@ -22,7 +22,7 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class SecurityInfoActionTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java index ab7e807153..00e983cc4f 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java @@ -27,7 +27,7 @@ public class TenantInfoActionTest extends AbstractRestApiUnitTest { "\"backend_roles\":[\"starfleet*\",\"ambassador\"],\"and_backend_roles\":[],\"description\":\"Migrated " + "from v6\"}"; private final String BASE_ENDPOINT; - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } @@ -60,7 +60,7 @@ public void testTenantInfoAPIAccess() throws Exception { public void testTenantInfoAPIUpdate() throws Exception { Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build(); setup(settings); - + rh.keystore = "restapi/kirk-keystore.jks"; rh.sendHTTPClientCredentials = true; rh.sendAdminCertificate = true; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java index e4fca1e99b..cc148393c1 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java @@ -51,7 +51,7 @@ public class WhitelistApiTest extends AbstractRestApiUnitTest { */ private final Header adminCredsHeader = encodeBasicHeader("admin_all_access", "admin_all_access"); private final Header nonAdminCredsHeader = encodeBasicHeader("sarek", "sarek"); - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } diff --git a/src/test/java/org/opensearch/security/filter/SecurityFilterTest.java b/src/test/java/org/opensearch/security/filter/SecurityFilterTest.java index 9430450875..161e8aab72 100644 --- a/src/test/java/org/opensearch/security/filter/SecurityFilterTest.java +++ b/src/test/java/org/opensearch/security/filter/SecurityFilterTest.java @@ -119,7 +119,7 @@ public void testUnexepectedCausesAreNotSendToCallers() { final ArgumentCaptor cap = ArgumentCaptor.forClass(OpenSearchSecurityException.class); verify(listener).onFailure(cap.capture()); - assertThat("The cause should never be included as it will leak to callers", cap.getValue().getCause(), nullValue()); + assertThat("The cause should never be included as it will leak to callers", cap.getValue().getCause(), nullValue()); assertThat("Make sure the cause exception wasn't toStringed in the method", cap.getValue().getMessage(), not(containsString("ABC!"))); verifyNoMoreInteractions(auditLog, listener); diff --git a/src/test/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticatorTest.java b/src/test/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticatorTest.java index 111a07bf40..487da55767 100644 --- a/src/test/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticatorTest.java +++ b/src/test/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticatorTest.java @@ -90,11 +90,11 @@ public void testReturnsNullWhenUserHeaderIsUnconfigured() { @Test public void testReturnsNullWhenUserHeaderIsMissing() { - + assertNull(authenticator.extractCredentials(new TestRestRequest(), context)); } @Test - + public void testReturnsCredentials() { headers.put("user", new ArrayList<>()); headers.put("proxy_uid", new ArrayList<>()); @@ -103,7 +103,7 @@ public void testReturnsCredentials() { headers.get("proxy_uid").add("123"); headers.get("proxy_uid").add("456"); headers.get("proxy_other").add("someothervalue"); - + settings = Settings.builder().put(settings).put("attr_header_prefix","proxy_").build(); authenticator = new HTTPExtendedProxyAuthenticator(settings,null); AuthCredentials creds = authenticator.extractCredentials(new TestRestRequest(headers), context); @@ -113,14 +113,14 @@ public void testReturnsCredentials() { assertEquals("someothervalue", creds.getAttributes().get("attr.proxy.other")); assertTrue(creds.isComplete()); } - + @Test public void testTrimOnRoles() { headers.put("user", new ArrayList<>()); headers.put("roles", new ArrayList<>()); headers.get("user").add("aValidUser"); headers.get("roles").add("role1, role2,\t"); - + settings = Settings.builder().put(settings) .put("roles_header","roles") .put("roles_separator", ",") @@ -134,7 +134,7 @@ public void testTrimOnRoles() { } static class TestRestRequest extends RestRequest { - + public TestRestRequest() { super(NamedXContentRegistry.EMPTY, new HashMap<>(), "", new HashMap<>(),new HttpRequestImpl(),new HttpChannelImpl()); } @@ -162,7 +162,7 @@ public boolean hasContent() { } } - + static class HttpRequestImpl implements HttpRequest { @Override @@ -228,19 +228,19 @@ public Exception getInboundException() { return null; } } - + static class HttpChannelImpl implements HttpChannel { @Override public void close() { // TODO Auto-generated method stub - + } @Override public void addCloseListener(ActionListener listener) { // TODO Auto-generated method stub - + } @Override @@ -252,7 +252,7 @@ public boolean isOpen() { @Override public void sendResponse(HttpResponse response, ActionListener listener) { // TODO Auto-generated method stub - + } @Override @@ -266,6 +266,6 @@ public InetSocketAddress getRemoteAddress() { // TODO Auto-generated method stub return null; } - + } } diff --git a/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java b/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java index bd9664a84c..4839155232 100644 --- a/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java +++ b/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java @@ -29,7 +29,7 @@ public class TenancyMultitenancyEnabledTests extends SingleClusterTest { private static final Header AS_REST_API_USER = encodeBasicHeader("user_rest_api_access", "user_rest_api_access"); private static final Header AS_USER = encodeBasicHeader("admin", "admin"); private static final Header ON_USER_TENANT = new BasicHeader("securitytenant", "__user__"); - + private static String createIndexPatternDoc(final String title) { return "{"+ "\"type\" : \"index-pattern\","+ @@ -37,7 +37,7 @@ private static String createIndexPatternDoc(final String title) { "\"index-pattern\" : {"+ "\"title\" : \"" + title + "\""+ "}}"; - } + } @Override protected String getResourceFolder() { diff --git a/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java b/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java index 6d81c3b1da..d84d62c6b4 100644 --- a/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java +++ b/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java @@ -58,8 +58,8 @@ public class SecurityIndexAccessEvaluatorTest { private SecurityIndexAccessEvaluator evaluator; - private static final String UNPROTECTED_ACTION = "indices:data/read"; - private static final String PROTECTED_ACTION = "indices:data/write"; + private static final String UNPROTECTED_ACTION = "indices:data/read"; + private static final String PROTECTED_ACTION = "indices:data/write"; @Before public void before() { @@ -79,7 +79,7 @@ public void before() { public void after() { verifyNoMoreInteractions(auditLog, irr, request, task, presponse, log); } - + @Test public void actionIsNotProtected_noSystemIndexInvolved() { final Resolved resolved = createResolved(".test"); diff --git a/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java b/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java index be7c2da7b0..856f026d72 100644 --- a/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java +++ b/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java @@ -68,7 +68,7 @@ public void before() { @After public void after() { verifyNoMoreInteractions(user, resolver, clusterService); - } + } @Test public void testCtor() { @@ -78,7 +78,7 @@ public void testCtor() { /** Ensure that concreteIndexNames sends correct parameters are sent to getResolvedIndexPattern */ @Test public void testConcreteIndexNamesOverload() { - doReturn(ImmutableSet.of("darn")).when(ip).getResolvedIndexPattern(user, resolver, clusterService, false); + doReturn(ImmutableSet.of("darn")).when(ip).getResolvedIndexPattern(user, resolver, clusterService, false); final Set results = ip.concreteIndexNames(user, resolver, clusterService); @@ -93,7 +93,7 @@ public void testConcreteIndexNamesOverload() { @Test public void testAttemptResolveIndexNamesOverload() { doReturn(ImmutableSet.of("yarn")).when(ip).getResolvedIndexPattern(user, resolver, clusterService, true); - + final Set results = ip.attemptResolveIndexNames(user, resolver, clusterService); assertThat(results, contains("yarn")); @@ -172,7 +172,7 @@ public void testMultipleConcreteIndicesWithOneAlias() { verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-100")); verify(resolver).concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*")); } - + /** Verify attemptResolveIndexNames with multiple aliases */ @Test public void testMultipleConcreteAliasedAndUnresolved() { @@ -200,7 +200,7 @@ private ClusterState createClusterState(final IndexShorthand... indices) { Arrays.stream(indices).forEach(indexShorthand -> { final IndexAbstraction indexAbstraction = mock(IndexAbstraction.class); when(indexAbstraction.getType()).thenReturn(indexShorthand.type); - indexMap.put(indexShorthand.name, indexAbstraction); + indexMap.put(indexShorthand.name, indexAbstraction); }); final Metadata mockMetadata = mock(Metadata.class, withSettings().strictness(Strictness.LENIENT)); diff --git a/src/test/java/org/opensearch/security/setting/DeprecatedSettingsTest.java b/src/test/java/org/opensearch/security/setting/DeprecatedSettingsTest.java index 72c1bc3741..143efe9b11 100644 --- a/src/test/java/org/opensearch/security/setting/DeprecatedSettingsTest.java +++ b/src/test/java/org/opensearch/security/setting/DeprecatedSettingsTest.java @@ -29,7 +29,7 @@ public class DeprecatedSettingsTest { @Mock private DeprecationLogger logger; - private DeprecationLogger original; + private DeprecationLogger original; @Before public void before() { diff --git a/src/test/java/org/opensearch/security/ssl/CertificateValidatorTest.java b/src/test/java/org/opensearch/security/ssl/CertificateValidatorTest.java index f1f7f9ea84..b619c2707f 100644 --- a/src/test/java/org/opensearch/security/ssl/CertificateValidatorTest.java +++ b/src/test/java/org/opensearch/security/ssl/CertificateValidatorTest.java @@ -1,10 +1,10 @@ /* * Copyright 2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl; @@ -41,28 +41,28 @@ import org.opensearch.security.test.helper.file.FileHelper; public class CertificateValidatorTest { - + public static final Date CRL_DATE = new Date(1525546426000L); protected final Logger log = LogManager.getLogger(this.getClass()); - + @Test public void testStaticCRL() throws Exception { - + File staticCrl = FileHelper.getAbsoluteFilePathFromClassPath("ssl/crl/revoked.crl").toFile(); Collection crls = null; try(FileInputStream crlin = new FileInputStream(staticCrl)) { crls = CertificateFactory.getInstance("X.509").generateCRLs(crlin); } - + Assert.assertEquals(crls.size(), 1); - + //trust chain incl intermediate certificates (root + intermediates) Collection rootCas; final File trustedCas = FileHelper.getAbsoluteFilePathFromClassPath("ssl/chain-ca.pem").toFile(); try(FileInputStream trin = new FileInputStream(trustedCas)) { rootCas = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } - + Assert.assertEquals(rootCas.size(), 2); //certificate chain to validate (client cert + intermediates but without root) @@ -71,9 +71,9 @@ public void testStaticCRL() throws Exception { try(FileInputStream trin = new FileInputStream(certs)) { certsToValidate = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } - + Assert.assertEquals(certsToValidate.size(), 2); - + CertificateValidator validator = new CertificateValidator(rootCas.toArray(new X509Certificate[0]), crls); validator.setDate(CRL_DATE); try { @@ -83,25 +83,25 @@ public void testStaticCRL() throws Exception { Assert.assertTrue(ExceptionUtils.getRootCause(e) instanceof CertificateRevokedException); } } - + @Test public void testStaticCRLOk() throws Exception { - + File staticCrl = FileHelper.getAbsoluteFilePathFromClassPath("ssl/crl/revoked.crl").toFile(); Collection crls = null; try(FileInputStream crlin = new FileInputStream(staticCrl)) { crls = CertificateFactory.getInstance("X.509").generateCRLs(crlin); } - + Assert.assertEquals(crls.size(), 1); - + //trust chain incl intermediate certificates (root + intermediates) Collection rootCas; final File trustedCas = FileHelper.getAbsoluteFilePathFromClassPath("ssl/chain-ca.pem").toFile(); try(FileInputStream trin = new FileInputStream(trustedCas)) { rootCas = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } - + Assert.assertEquals(rootCas.size(), 2); //certificate chain to validate (client cert + intermediates but without root) @@ -110,9 +110,9 @@ public void testStaticCRLOk() throws Exception { try(FileInputStream trin = new FileInputStream(certs)) { certsToValidate = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } - + Assert.assertEquals(certsToValidate.size(), 3); - + CertificateValidator validator = new CertificateValidator(rootCas.toArray(new X509Certificate[0]), crls); validator.setDate(CRL_DATE); try { @@ -121,7 +121,7 @@ public void testStaticCRLOk() throws Exception { Assert.fail(ExceptionsHelper.stackTrace(ExceptionUtils.getRootCause(e))); } } - + @Test public void testNoValidationPossible() throws Exception { @@ -131,7 +131,7 @@ public void testNoValidationPossible() throws Exception { try(FileInputStream trin = new FileInputStream(trustedCas)) { rootCas = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } - + Assert.assertEquals(rootCas.size(), 2); //certificate chain to validate (client cert + intermediates but without root) @@ -140,7 +140,7 @@ public void testNoValidationPossible() throws Exception { try(FileInputStream trin = new FileInputStream(certs)) { certsToValidate = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } - + Assert.assertEquals(certsToValidate.size(), 2); CertificateValidator validator = new CertificateValidator(rootCas.toArray(new X509Certificate[0]), Collections.emptyList()); @@ -153,7 +153,7 @@ public void testNoValidationPossible() throws Exception { Assert.assertTrue(e.getCause().getMessage().contains("unable to find valid certification path to requested target")); } } - + @Test public void testCRLDP() throws Exception { @@ -163,7 +163,7 @@ public void testCRLDP() throws Exception { try(FileInputStream trin = new FileInputStream(trustedCas)) { rootCas = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } - + Assert.assertEquals(rootCas.size(), 1); //certificate chain to validate (client cert + intermediates but without root) @@ -173,9 +173,9 @@ public void testCRLDP() throws Exception { try(FileInputStream trin = new FileInputStream(certs)) { certsToValidate = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } - + Assert.assertEquals(certsToValidate.size(), 2); - + CertificateValidator validator = new CertificateValidator(rootCas.toArray(new X509Certificate[0]), Collections.emptyList()); validator.setEnableCRLDP(true); validator.setEnableOCSP(true); diff --git a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java index 6d473c0160..4334d9a91c 100644 --- a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl; @@ -111,14 +111,14 @@ public void testNodeClientSSL() throws Exception { Assume.assumeTrue(OpenSearchSecuritySSLPlugin.OPENSSL_SUPPORTED && OpenSsl.isAvailable()); super.testNodeClientSSL(); } - + @Override @Test public void testHttpsOptionalAuth() throws Exception { Assume.assumeTrue(OpenSearchSecuritySSLPlugin.OPENSSL_SUPPORTED && OpenSsl.isAvailable()); super.testHttpsOptionalAuth(); } - + @Test public void testAvailCiphersOpenSSL() throws Exception { Assume.assumeTrue(OpenSearchSecuritySSLPlugin.OPENSSL_SUPPORTED && OpenSsl.isAvailable()); @@ -139,7 +139,7 @@ public void testAvailCiphersOpenSSL() throws Exception { System.out.println("OpenSSL secure ciphers: " + openSSLSecureCiphers); Assert.assertTrue(openSSLSecureCiphers.size() > 0); } - + @Test public void testHttpsEnforceFail() throws Exception { Assume.assumeTrue(OpenSearchSecuritySSLPlugin.OPENSSL_SUPPORTED && OpenSsl.isAvailable()); @@ -157,22 +157,22 @@ public void testHttpsAndNodeSSLFailedCipher() throws Exception { Assume.assumeTrue(OpenSearchSecuritySSLPlugin.OPENSSL_SUPPORTED && OpenSsl.isAvailable()); super.testHttpsAndNodeSSLFailedCipher(); } - + @Test public void testHttpsAndNodeSSLPem() throws Exception { Assume.assumeTrue(OpenSearchSecuritySSLPlugin.OPENSSL_SUPPORTED && OpenSsl.isAvailable()); super.testHttpsAndNodeSSLPKCS8Pem(); } - + @Test public void testHttpsAndNodeSSLPemEnc() throws Exception { Assume.assumeTrue(OpenSearchSecuritySSLPlugin.OPENSSL_SUPPORTED && OpenSsl.isAvailable()); super.testHttpsAndNodeSSLPemEnc(); } - + @Test public void testNodeClientSSLwithOpenSslTLSv13() throws Exception { - + Assume.assumeTrue(OpenSearchSecuritySSLPlugin.OPENSSL_SUPPORTED && OpenSsl.isAvailable() && OpenSsl.version() > 0x10101009L); final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) @@ -190,10 +190,10 @@ public void testNodeClientSSLwithOpenSslTLSv13() throws Exception { .build(); setupSslOnlyMode(settings); - + RestHelper rh = nonSslRestHelper(); - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put("cluster.name", clusterInfo.clustername).put("path.home", "/tmp") .put("node.name", "client_node_" + new Random().nextInt()) .put("path.data", "./target/data/" + clusterInfo.clustername + "/ssl/data") diff --git a/src/test/java/org/opensearch/security/ssl/TestPrincipalExtractor.java b/src/test/java/org/opensearch/security/ssl/TestPrincipalExtractor.java index 6102327fe4..0dfaa557e1 100644 --- a/src/test/java/org/opensearch/security/ssl/TestPrincipalExtractor.java +++ b/src/test/java/org/opensearch/security/ssl/TestPrincipalExtractor.java @@ -1,10 +1,10 @@ /* * Copyright 2017 floragunn GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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.ssl; @@ -25,7 +25,7 @@ public class TestPrincipalExtractor implements PrincipalExtractor { private static int transportCount = 0; private static int httpCount = 0; - + public TestPrincipalExtractor() { } @@ -34,11 +34,11 @@ public String extractPrincipal(X509Certificate x509Certificate, Type type) { if(type == Type.HTTP) { httpCount++; } - + if(type == Type.TRANSPORT) { transportCount++; } - + return "testdn"; } @@ -49,7 +49,7 @@ public static int getTransportCount() { public static int getHttpCount() { return httpCount; } - + public static void reset() { httpCount = 0; transportCount = 0; diff --git a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java index 72b05abb91..950409a3e3 100644 --- a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java +++ b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java @@ -95,11 +95,11 @@ import org.opensearch.threadpool.ThreadPool; /* - * There are real thread leaks during test execution, not all threads are + * There are real thread leaks during test execution, not all threads are * properly waited on or interrupted. While this normally doesn't create test * failures, retries mitigate this. Remove this attribute to explore these * issues. - */ + */ @ThreadLeakScope(Scope.NONE) public abstract class AbstractSecurityUnitTest extends RandomizedTest { diff --git a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterConfiguration.java b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterConfiguration.java index 871cf5a59d..5a0c41fd33 100644 --- a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterConfiguration.java +++ b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterConfiguration.java @@ -47,7 +47,7 @@ public enum ClusterConfiguration { //first one needs to be a cluster manager //HUGE(new NodeSettings(true, false, false), new NodeSettings(true, false, false), new NodeSettings(true, false, false), new NodeSettings(false, true,false), new NodeSettings(false, true, false)), - + //3 nodes (1m, 2d) DEFAULT(new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true)), @@ -65,7 +65,7 @@ public enum ClusterConfiguration { //1 node (1md) SINGLENODE(new NodeSettings(true, true)), - + //4 node (1m, 2d, 1c) CLIENTNODE(new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true), new NodeSettings(false, false)), @@ -73,50 +73,50 @@ public enum ClusterConfiguration { USERINJECTOR(new NodeSettings(true, false, Lists.newArrayList(UserInjectorPlugin.class)), new NodeSettings(false, true, Lists.newArrayList(UserInjectorPlugin.class)), new NodeSettings(false, true, Lists.newArrayList(UserInjectorPlugin.class))); private List nodeSettings = new LinkedList<>(); - + private ClusterConfiguration(NodeSettings ... settings) { nodeSettings.addAll(Arrays.asList(settings)); } - + public List getNodeSettings() { return Collections.unmodifiableList(nodeSettings); } - + public List getClusterManagerNodeSettings() { return Collections.unmodifiableList(nodeSettings.stream().filter(a->a.clusterManagerNode).collect(Collectors.toList())); } - + public List getNonClusterManagerNodeSettings() { return Collections.unmodifiableList(nodeSettings.stream().filter(a->!a.clusterManagerNode).collect(Collectors.toList())); } - + public int getNodes() { return nodeSettings.size(); } - + public int getClusterManagerNodes() { return (int) nodeSettings.stream().filter(a->a.clusterManagerNode).count(); } - + public int getDataNodes() { return (int) nodeSettings.stream().filter(a->a.dataNode).count(); } - + public int getClientNodes() { return (int) nodeSettings.stream().filter(a->!a.clusterManagerNode && !a.dataNode).count(); } - + public static class NodeSettings { public boolean clusterManagerNode; public boolean dataNode; public List> plugins = Lists.newArrayList(Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, MatrixAggregationModulePlugin.class, MustacheModulePlugin.class, ParentJoinModulePlugin.class, PercolatorModulePlugin.class, ReindexModulePlugin.class); - + public NodeSettings(boolean clusterManagerNode, boolean dataNode) { super(); this.clusterManagerNode = clusterManagerNode; this.dataNode = dataNode; } - + public NodeSettings(boolean clusterManagerNode, boolean dataNode, List> additionalPlugins) { this(clusterManagerNode, dataNode); this.plugins.addAll(additionalPlugins); @@ -126,7 +126,7 @@ public NodeSettings removePluginIfPresent(Class pluginToRemove this.plugins.remove(pluginToRemove); return this; } - + public Class[] getPlugins() { return plugins.toArray(new Class[0] ); } diff --git a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java index 5aea8f7dfe..efa0a8f89d 100644 --- a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java +++ b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java @@ -74,7 +74,7 @@ public final class ClusterHelper { static { resetSystemProperties(); } - + /** Resets all system properties associated with a cluster */ public static void resetSystemProperties() { System.setProperty("opensearch.enforce.bootstrap.checks", "true"); @@ -84,7 +84,7 @@ public static void resetSystemProperties() { /** * Update the default directory used by the security plugin * NOTE: this setting is system wide, use ClusterHelper.resetSystemProperties() to restore the original state - * + * * @return the previous value if one was set, otherwise null */ public static String updateDefaultDirectory(final String newValue) { diff --git a/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java b/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java index 6aadd2acbb..803088771a 100644 --- a/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java +++ b/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java @@ -64,14 +64,14 @@ public static KeyStore getKeystoreFromClassPath(final String fileNameFromClasspa if(path==null) { return null; } - + KeyStore ks = KeyStore.getInstance("JKS"); try (FileInputStream fin = new FileInputStream(path.toFile())) { ks.load(fin, password==null||password.isEmpty()?null:password.toCharArray()); } return ks; } - + public static Path getAbsoluteFilePathFromClassPath(final String fileNameFromClasspath) { File file = null; final URL fileUrl = FileHelper.class.getClassLoader().getResource(fileNameFromClasspath); @@ -99,9 +99,9 @@ public static final String loadFile(final String file) throws IOException { IOUtils.copy(FileHelper.class.getResourceAsStream("/" + file), sw, StandardCharsets.UTF_8); return sw.toString(); } - + public static BytesReference readYamlContent(final String file) { - + XContentParser parser = null; try { parser = XContentFactory.xContent(XContentType.YAML).createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, new StringReader(loadFile(file))); @@ -122,9 +122,9 @@ public static BytesReference readYamlContent(final String file) { } } } - + public static BytesReference readYamlContentFromString(final String yaml) { - + XContentParser parser = null; try { parser = XContentFactory.xContent(XContentType.YAML).createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, new StringReader(yaml)); 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 367332f160..7ed23b9c73 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 @@ -95,7 +95,7 @@ public class RestHelper { protected final Logger log = LogManager.getLogger(RestHelper.class); - + public boolean enableHTTPClientSSL = true; public boolean enableHTTPClientSSLv3Only = false; public boolean sendAdminCertificate = false; @@ -105,12 +105,12 @@ public class RestHelper { public final String prefix; //public String truststore = "truststore.jks"; private ClusterInfo clusterInfo; - + public RestHelper(ClusterInfo clusterInfo, String prefix) { this.clusterInfo = clusterInfo; this.prefix = prefix; } - + public RestHelper(ClusterInfo clusterInfo, boolean enableHTTPClientSSL, boolean trustHTTPServerCertificate, String prefix) { this.clusterInfo = clusterInfo; this.enableHTTPClientSSL = enableHTTPClientSSL; @@ -191,11 +191,11 @@ public HttpResponse executeGetRequest(final String request, String body, Header. getRequest.addHeader(HttpHeaders.CONTENT_TYPE, "application/json"); return executeRequest(getRequest, header); } - + public HttpResponse executeHeadRequest(final String request, Header... header) { return executeRequest(new HttpHead(getRequestUri(request)), header); } - + public HttpResponse executeOptionsRequest(final String request) { return executeRequest(new HttpOptions(getRequestUri(request))); } @@ -228,15 +228,15 @@ public HttpResponse executePostRequest(final String request, String body, Header return executeRequest(uriRequest, header); } - + public HttpResponse executePatchRequest(final String request, String body, Header... header) { HttpPatch uriRequest = new HttpPatch(getRequestUri(request)); if (body != null && !body.isEmpty()) { uriRequest.setEntity(createStringEntity(body)); } return executeRequest(uriRequest, header); - } - + } + public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... header) { CloseableHttpAsyncClient httpClient = null; @@ -255,7 +255,7 @@ public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... header) if (!uriRequest.containsHeader("Content-Type")) { uriRequest.addHeader("Content-Type","application/json"); } - + final CompletableFuture future = new CompletableFuture<>(); final SimpleHttpRequest simpleRequest = SimpleRequestBuilder.copy(uriRequest).build(); if (uriRequest.getEntity() != null) { @@ -283,7 +283,7 @@ public void cancelled() { if (enableHTTPClientSSL && !res.getProtocolVersion().equals(HttpVersion.HTTP_2)) { throw new IllegalStateException("HTTP/2 expected for HTTPS communication but " + res.getProtocolVersion() + " was used"); } - + log.debug(res.getBody()); return res; } catch (final CompletionException e) { @@ -313,7 +313,7 @@ public void cancelled() { private HttpEntity createStringEntity(String body) { return new StringEntity(body); } - + protected final String getHttpServerUri() { final String address = "http" + (enableHTTPClientSSL ? "s" : "") + "://" + clusterInfo.httpHost + ":" + clusterInfo.httpPort; log.debug("Connect to {}", address); @@ -323,7 +323,7 @@ protected final String getHttpServerUri() { protected final String getRequestUri(String request) { return getHttpServerUri() + "/" + StringUtils.strip(request, "/"); } - + protected final CloseableHttpAsyncClient getHTTPClient() throws Exception { final HttpAsyncClientBuilder hcb = HttpAsyncClients.custom(); @@ -338,13 +338,13 @@ protected final CloseableHttpAsyncClient getHTTPClient() throws Exception { if (enableHTTPClientSSL) { log.debug("Configure HTTP client with SSL"); - + if(prefix != null && !keystore.contains("/")) { keystore = prefix+"/"+keystore; } - + final String keyStorePath = FileHelper.getAbsoluteFilePathFromClassPath(keystore).toFile().getParent(); - + final KeyStore myTrustStore = KeyStore.getInstance("JKS"); myTrustStore.load(new FileInputStream(keyStorePath+"/truststore.jks"), "changeit".toCharArray()); @@ -399,7 +399,7 @@ public TlsDetails create(final SSLEngine sslEngine) { return hcb.setDefaultRequestConfig(requestConfigBuilder.build()).disableAutomaticRetries().build(); } - + public static class HttpResponse { private final SimpleHttpResponse inner; private final String body; @@ -473,7 +473,7 @@ public String toString() { } /** - * Given a json path with dots delimiated returns the object at the leaf + * Given a json path with dots delimiated returns the object at the leaf */ public String findValueInJson(final String jsonDotPath) { // Make sure its json / then parse it @@ -539,5 +539,5 @@ private static HttpResponse from(Future future) { } } - + } diff --git a/src/test/java/org/opensearch/security/test/helper/rules/SecurityTestWatcher.java b/src/test/java/org/opensearch/security/test/helper/rules/SecurityTestWatcher.java index b0ee40384d..c194d7e6de 100644 --- a/src/test/java/org/opensearch/security/test/helper/rules/SecurityTestWatcher.java +++ b/src/test/java/org/opensearch/security/test/helper/rules/SecurityTestWatcher.java @@ -30,7 +30,7 @@ import org.junit.runner.Description; public class SecurityTestWatcher extends TestWatcher{ - + @Override protected void starting(final Description description) { final String methodName = description.getMethodName(); diff --git a/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java b/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java index ae6f5116a1..f0059d50fb 100644 --- a/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java +++ b/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java @@ -58,12 +58,12 @@ * @author jkressin */ public class UserInjectorPlugin extends Plugin implements NetworkPlugin { - + Settings settings; private final SharedGroupFactory sharedGroupFactory; ThreadPool threadPool; - - public UserInjectorPlugin(final Settings settings, final Path configPath) { + + public UserInjectorPlugin(final Settings settings, final Path configPath) { this.settings = settings; sharedGroupFactory = new SharedGroupFactory(settings); } @@ -77,17 +77,17 @@ public Map> getHttpTransports(Settings set return ImmutableMap.of("org.opensearch.security.http.UserInjectingServerTransport", () -> new UserInjectingServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, validatingDispatcher, clusterSettings, sharedGroupFactory)); } - + class UserInjectingServerTransport extends Netty4HttpServerTransport { - + public UserInjectingServerTransport(final Settings settings, final NetworkService networkService, final BigArrays bigArrays, final ThreadPool threadPool, final NamedXContentRegistry namedXContentRegistry, final Dispatcher dispatcher, ClusterSettings clusterSettings, SharedGroupFactory sharedGroupFactory) { super(settings, networkService, bigArrays, threadPool, namedXContentRegistry, dispatcher, clusterSettings, sharedGroupFactory); } } - + class UserInjectingDispatcher implements Dispatcher { - + private Dispatcher originalDispatcher; public UserInjectingDispatcher(final Dispatcher originalDispatcher) { @@ -99,7 +99,7 @@ public UserInjectingDispatcher(final Dispatcher originalDispatcher) { public void dispatchRequest(RestRequest request, RestChannel channel, ThreadContext threadContext) { threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, request.header(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER)); originalDispatcher.dispatchRequest(request, channel, threadContext); - + } @Override From abcb3a2124b3bb8f5b8bd81c83c3fb5ddc5edd50 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Mon, 5 Jun 2023 09:34:27 -0400 Subject: [PATCH 196/369] Match version of zstd-jni from core (#2832) * Match version of zstd-jni from core Signed-off-by: Craig Perkins * Add zstd version from core to force resolutions section Signed-off-by: Craig Perkins --------- Signed-off-by: Craig Perkins --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 94f62ab567..82c60040fb 100644 --- a/build.gradle +++ b/build.gradle @@ -338,6 +338,7 @@ configurations { force "io.netty:netty-transport:${versions.netty}" force "io.netty:netty-transport-native-unix-common:${versions.netty}" force "org.apache.bcel:bcel:6.6.0" // This line should be removed once Spotbugs is upgraded to 4.7.4 + force "com.github.luben:zstd-jni:${versions.zstd}" } } @@ -460,7 +461,7 @@ dependencies { runtimeOnly 'com.fasterxml.woodstox:woodstox-core:6.4.0' runtimeOnly 'org.apache.ws.xmlschema:xmlschema-core:2.2.5' runtimeOnly 'org.apache.santuario:xmlsec:2.2.3' - runtimeOnly 'com.github.luben:zstd-jni:1.5.2-1' + runtimeOnly "com.github.luben:zstd-jni:${versions.zstd}" runtimeOnly 'org.checkerframework:checker-qual:3.5.0' runtimeOnly "org.bouncycastle:bcpkix-jdk15on:${versions.bouncycastle}" From cb8818c621135268bd13f6fb7f6d447fb7515e65 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Mon, 5 Jun 2023 10:41:05 -0400 Subject: [PATCH 197/369] Updates all java files under security/* and test/security/a* -> test/security/m* (#2826) * Update Sec/action to Sec/Filter Signed-off-by: Stephen Crawford * Update Security/* Signed-off-by: Stephen Crawford * Update Security/* and test/opensearch/security/* Signed-off-by: Stephen Crawford * Half sec test changes Signed-off-by: Stephen Crawford * Half sec test changes Signed-off-by: Stephen Crawford --------- Signed-off-by: Stephen Crawford --- build.gradle | 20 +- .../multitenancy/test/MultitenancyTests.java | 612 +++++++++++------- .../test/TenancyMultitenancyEnabledTests.java | 78 ++- .../TenancyPrivateTenantEnabledTests.java | 76 ++- 4 files changed, 525 insertions(+), 261 deletions(-) diff --git a/build.gradle b/build.gradle index 82c60040fb..943bdea29b 100644 --- a/build.gradle +++ b/build.gradle @@ -70,13 +70,21 @@ apply plugin: 'opensearch.opensearchplugin' apply plugin: 'opensearch.pluginzip' apply plugin: 'opensearch.rest-test' apply plugin: 'opensearch.testclusters' -//apply from: 'gradle/formatting.gradle' +// apply from: 'gradle/formatting.gradle' spotless { java { // Normally this isn't necessary, but we have Java sources in // non-standard places target '**/com/amazon/dlic/**/*.java' + target '**/com/amazon/security/**/*.java' + target '**/test/java/org/opensearch/security/a*/**/*.java' + target '**/test/java/org/opensearch/security/b*/**/*.java' + target '**/test/java/org/opensearch/security/c*/**/*.java' + target '**/test/java/org/opensearch/security/d*/**/*.java' + target '**/test/java/org/opensearch/security/f*/**/*.java' + target '**/test/java/org/opensearch/security/h*/**/*.java' + target '**/test/java/org/opensearch/security/m*/**/*.java' removeUnusedImports() eclipse().configFile rootProject.file('formatter/formatterConfig.xml') @@ -109,7 +117,15 @@ spotless { importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') target '**/*.java' targetExclude '**/com/amazon/dlic/**/*.java' - targetExclude('src/integrationTest/**') + targetExclude '**/com/amazon/security/**/*.java' + targetExclude '**/test/java/org/opensearch/security/a*/**/*.java' + targetExclude '**/test/java/org/opensearch/security/b*/**/*.java' + targetExclude '**/test/java/org/opensearch/security/c*/**/*.java' + targetExclude '**/test/java/org/opensearch/security/d*/**/*.java' + targetExclude '**/test/java/org/opensearch/security/f*/**/*.java' + targetExclude '**/test/java/org/opensearch/security/h*/**/*.java' + targetExclude '**/test/java/org/opensearch/security/m*/**/*.java' + targetExclude 'src/integrationTest/**' trimTrailingWhitespace() endWithNewline(); diff --git a/src/test/java/org/opensearch/security/multitenancy/test/MultitenancyTests.java b/src/test/java/org/opensearch/security/multitenancy/test/MultitenancyTests.java index 5177dbae10..648f5df4a0 100644 --- a/src/test/java/org/opensearch/security/multitenancy/test/MultitenancyTests.java +++ b/src/test/java/org/opensearch/security/multitenancy/test/MultitenancyTests.java @@ -50,210 +50,321 @@ protected String getResourceFolder() { @Test public void testNoDnfof() throws Exception { - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH") - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH").build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_nodnfof.yml"), settings); final RestHelper rh = nonSslRestHelper(); - try (Client tc = getClient()) { - tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - - tc.index(new IndexRequest("indexa").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":\"indexa\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("indexb").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":\"indexb\"}", XContentType.JSON)).actionGet(); - - - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("starfleet","starfleet_academy","starfleet_library").alias("sf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire","vulcangov").alias("nonsf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))).actionGet(); - - } - - HttpResponse resc; - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("indexa,indexb/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); - System.out.println(resc.getBody()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("indexa,indexb/_search?pretty", encodeBasicHeader("user_b", "user_b"))).getStatusCode()); - System.out.println(resc.getBody()); - - String msearchBody = - "{\"index\":\"indexa\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"indexb\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); - System.out.println("#### msearch a"); - resc = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("user_a", "user_a")); - Assert.assertEquals(200, resc.getStatusCode()); - System.out.println(resc.getBody()); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("indexa")); - Assert.assertFalse(resc.getBody(), resc.getBody().contains("indexb")); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("permission")); - - System.out.println("#### msearch b"); - resc = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("user_b", "user_b")); - Assert.assertEquals(200, resc.getStatusCode()); - System.out.println(resc.getBody()); - Assert.assertFalse(resc.getBody(), resc.getBody().contains("indexa")); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("indexb")); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("permission")); - - msearchBody = - "{\"index\":\"indexc\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"indexd\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); - - System.out.println("#### msearch b2"); - resc = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("user_b", "user_b")); - System.out.println(resc.getBody()); - Assert.assertEquals(200, resc.getStatusCode()); - Assert.assertFalse(resc.getBody(), resc.getBody().contains("indexc")); - Assert.assertFalse(resc.getBody(), resc.getBody().contains("indexd")); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("permission")); - int count = resc.getBody().split("\"status\" : 403").length; - Assert.assertEquals(3, count); - - String mgetBody = "{"+ - "\"docs\" : ["+ - "{"+ - "\"_index\" : \"indexa\","+ - "\"_id\" : \"0\""+ - " },"+ - " {"+ - "\"_index\" : \"indexb\","+ - " \"_id\" : \"0\""+ - "}"+ - "]"+ - "}"; - - resc = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("user_b", "user_b")); - Assert.assertEquals(200, resc.getStatusCode()); - Assert.assertFalse(resc.getBody(), resc.getBody().contains("\"content\" : \"indexa\"")); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("indexb")); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("permission")); - - mgetBody = "{"+ - "\"docs\" : ["+ - "{"+ - "\"_index\" : \"indexx\","+ - "\"_id\" : \"0\""+ - " },"+ - " {"+ - "\"_index\" : \"indexy\","+ - " \"_id\" : \"0\""+ - "}"+ - "]"+ - "}"; - - resc = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("user_b", "user_b")); - Assert.assertEquals(200, resc.getStatusCode()); - Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); - count = resc.getBody().split("root_cause").length; - Assert.assertEquals(3, count); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("index*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); - - - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("indexa/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); - System.out.println(resc.getBody()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("indexb/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); - System.out.println(resc.getBody()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); - System.out.println(resc.getBody()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("_all/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); - System.out.println(resc.getBody()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("notexists/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); - System.out.println(resc.getBody()); - - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, (resc=rh.executeGetRequest("indexanbh,indexabb*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); - System.out.println(resc.getBody()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); - System.out.println(resc.getBody()); - - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode()); - System.out.println(resc.getBody()); + try (Client tc = getClient()) { + tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); + + tc.index( + new IndexRequest("indexa").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":\"indexa\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("indexb").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":\"indexb\"}", XContentType.JSON) + ).actionGet(); + + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add().indices("starfleet", "starfleet_academy", "starfleet_library").alias("sf") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire", "vulcangov").alias("nonsf")) + ) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))) + .actionGet(); + + } + + HttpResponse resc; + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("indexa,indexb/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); + System.out.println(resc.getBody()); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("indexa,indexb/_search?pretty", encodeBasicHeader("user_b", "user_b"))).getStatusCode() + ); + System.out.println(resc.getBody()); + + String msearchBody = "{\"index\":\"indexa\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"indexb\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); + System.out.println("#### msearch a"); + resc = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("user_a", "user_a")); + Assert.assertEquals(200, resc.getStatusCode()); + System.out.println(resc.getBody()); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("indexa")); + Assert.assertFalse(resc.getBody(), resc.getBody().contains("indexb")); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("permission")); + + System.out.println("#### msearch b"); + resc = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("user_b", "user_b")); + Assert.assertEquals(200, resc.getStatusCode()); + System.out.println(resc.getBody()); + Assert.assertFalse(resc.getBody(), resc.getBody().contains("indexa")); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("indexb")); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("permission")); + + msearchBody = "{\"index\":\"indexc\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"indexd\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); + + System.out.println("#### msearch b2"); + resc = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("user_b", "user_b")); + System.out.println(resc.getBody()); + Assert.assertEquals(200, resc.getStatusCode()); + Assert.assertFalse(resc.getBody(), resc.getBody().contains("indexc")); + Assert.assertFalse(resc.getBody(), resc.getBody().contains("indexd")); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("permission")); + int count = resc.getBody().split("\"status\" : 403").length; + Assert.assertEquals(3, count); + + String mgetBody = "{" + + "\"docs\" : [" + + "{" + + "\"_index\" : \"indexa\"," + + "\"_id\" : \"0\"" + + " }," + + " {" + + "\"_index\" : \"indexb\"," + + " \"_id\" : \"0\"" + + "}" + + "]" + + "}"; + + resc = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("user_b", "user_b")); + Assert.assertEquals(200, resc.getStatusCode()); + Assert.assertFalse(resc.getBody(), resc.getBody().contains("\"content\" : \"indexa\"")); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("indexb")); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("permission")); + + mgetBody = "{" + + "\"docs\" : [" + + "{" + + "\"_index\" : \"indexx\"," + + "\"_id\" : \"0\"" + + " }," + + " {" + + "\"_index\" : \"indexy\"," + + " \"_id\" : \"0\"" + + "}" + + "]" + + "}"; + + resc = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("user_b", "user_b")); + Assert.assertEquals(200, resc.getStatusCode()); + Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); + count = resc.getBody().split("root_cause").length; + Assert.assertEquals(3, count); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("index*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("indexa/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); + System.out.println(resc.getBody()); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("indexb/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); + System.out.println(resc.getBody()); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); + System.out.println(resc.getBody()); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("_all/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); + System.out.println(resc.getBody()); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("notexists/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); + System.out.println(resc.getBody()); + + Assert.assertEquals( + HttpStatus.SC_NOT_FOUND, + (resc = rh.executeGetRequest("indexanbh,indexabb*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); + System.out.println(resc.getBody()); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); + System.out.println(resc.getBody()); + + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode() + ); + System.out.println(resc.getBody()); } @Test public void testMt() throws Exception { - final Settings settings = Settings.builder() - .build(); + final Settings settings = Settings.builder().build(); setup(settings); final RestHelper rh = nonSslRestHelper(); HttpResponse res; String body = "{\"buildNum\": 15460, \"defaultIndex\": \"humanresources\", \"tenant\": \"human_resources\"}"; - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePutRequest(".kibana/_doc/5.6.0?pretty",body, new BasicHeader("securitytenant", "blafasel"), encodeBasicHeader("hr_employee", "hr_employee"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executePutRequest( + ".kibana/_doc/5.6.0?pretty", + body, + new BasicHeader("securitytenant", "blafasel"), + encodeBasicHeader("hr_employee", "hr_employee") + )).getStatusCode() + ); body = "{\"buildNum\": 15460, \"defaultIndex\": \"humanresources\", \"tenant\": \"human_resources\"}"; - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePutRequest(".kibana/_doc/5.6.0?pretty",body, new BasicHeader("securitytenant", "business_intelligence"), encodeBasicHeader("hr_employee", "hr_employee"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executePutRequest( + ".kibana/_doc/5.6.0?pretty", + body, + new BasicHeader("securitytenant", "business_intelligence"), + encodeBasicHeader("hr_employee", "hr_employee") + )).getStatusCode() + ); body = "{\"buildNum\": 15460, \"defaultIndex\": \"humanresources\", \"tenant\": \"human_resources\"}"; - Assert.assertEquals(HttpStatus.SC_CREATED, (res = rh.executePutRequest(".kibana/_doc/5.6.0?pretty",body, new BasicHeader("securitytenant", "human_resources"), encodeBasicHeader("hr_employee", "hr_employee"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_CREATED, + (res = rh.executePutRequest( + ".kibana/_doc/5.6.0?pretty", + body, + new BasicHeader("securitytenant", "human_resources"), + encodeBasicHeader("hr_employee", "hr_employee") + )).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertEquals(".kibana_1592542611_humanresources_1", DefaultObjectMapper.readTree(res.getBody()).get("_index").asText()); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest(".kibana/_doc/5.6.0?pretty",new BasicHeader("securitytenant", "human_resources"), encodeBasicHeader("hr_employee", "hr_employee"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + ".kibana/_doc/5.6.0?pretty", + new BasicHeader("securitytenant", "human_resources"), + encodeBasicHeader("hr_employee", "hr_employee") + )).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(WildcardMatcher.from("*human_resources*").test(res.getBody())); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest(".kibana_1592542611_humanresources_1/_alias", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest(".kibana_1592542611_humanresources_1/_alias", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); - Assert.assertNotNull(DefaultObjectMapper.readTree(res.getBody()).get(".kibana_1592542611_humanresources_1").get("aliases").get(".kibana_1592542611_humanresources")); + Assert.assertNotNull( + DefaultObjectMapper.readTree(res.getBody()) + .get(".kibana_1592542611_humanresources_1") + .get("aliases") + .get(".kibana_1592542611_humanresources") + ); } - @Test public void testMtMulti() throws Exception { - final Settings settings = Settings.builder() - .build(); + final Settings settings = Settings.builder().build(); setup(settings); final String dashboardsIndex = ".kibana_92668751_admin_1"; try (Client tc = getClient()) { - String body = "{"+ - "\"type\" : \"index-pattern\","+ - "\"updated_at\" : \"2018-09-29T08:56:59.066Z\","+ - "\"index-pattern\" : {"+ - "\"title\" : \"humanresources\""+ - "}}"; + String body = "{" + + "\"type\" : \"index-pattern\"," + + "\"updated_at\" : \"2018-09-29T08:56:59.066Z\"," + + "\"index-pattern\" : {" + + "\"title\" : \"humanresources\"" + + "}}"; Map indexSettings = new HashMap(); indexSettings.put("number_of_shards", 1); indexSettings.put("number_of_replicas", 0); - tc.admin().indices().create(new CreateIndexRequest(dashboardsIndex) - .settings(indexSettings) - .alias(new Alias(".kibana_92668751_admin"))) + tc.admin() + .indices() + .create(new CreateIndexRequest(dashboardsIndex).settings(indexSettings).alias(new Alias(".kibana_92668751_admin"))) .actionGet(); - tc.index(new IndexRequest(dashboardsIndex) - .id("index-pattern:9fbbd1a0-c3c5-11e8-a13f-71b8ea5a4f7b") + tc.index( + new IndexRequest(dashboardsIndex).id("index-pattern:9fbbd1a0-c3c5-11e8-a13f-71b8ea5a4f7b") .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(body, XContentType.JSON)).actionGet(); + .source(body, XContentType.JSON) + ).actionGet(); } final RestHelper rh = nonSslRestHelper(); @@ -261,28 +372,52 @@ public void testMtMulti() throws Exception { System.out.println("#### search"); HttpResponse res; String body = "{\"query\" : {\"term\" : { \"_id\" : \"index-pattern:9fbbd1a0-c3c5-11e8-a13f-71b8ea5a4f7b\"}}}"; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest(".kibana/_search/?pretty",body, new BasicHeader("securitytenant", "__user__"), encodeBasicHeader("admin", "admin"))).getStatusCode()); - //System.out.println(res.getBody()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest( + ".kibana/_search/?pretty", + body, + new BasicHeader("securitytenant", "__user__"), + encodeBasicHeader("admin", "admin") + )).getStatusCode() + ); + // System.out.println(res.getBody()); Assert.assertFalse(res.getBody().contains("exception")); Assert.assertTrue(res.getBody().contains("humanresources")); Assert.assertTrue(res.getBody().contains("\"value\" : 1")); Assert.assertTrue(res.getBody().contains(dashboardsIndex)); System.out.println("#### msearch"); - body = - "{\"index\":\".kibana\", \"ignore_unavailable\": false}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_msearch/?pretty",body, new BasicHeader("securitytenant", "__user__"), encodeBasicHeader("admin", "admin"))).getStatusCode()); - //System.out.println(res.getBody()); + body = "{\"index\":\".kibana\", \"ignore_unavailable\": false}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest( + "_msearch/?pretty", + body, + new BasicHeader("securitytenant", "__user__"), + encodeBasicHeader("admin", "admin") + )).getStatusCode() + ); + // System.out.println(res.getBody()); Assert.assertFalse(res.getBody().contains("exception")); Assert.assertTrue(res.getBody().contains("humanresources")); Assert.assertTrue(res.getBody().contains("\"value\" : 1")); Assert.assertTrue(res.getBody().contains(dashboardsIndex)); System.out.println("#### get"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest(".kibana/_doc/index-pattern:9fbbd1a0-c3c5-11e8-a13f-71b8ea5a4f7b?pretty", new BasicHeader("securitytenant", "__user__"), encodeBasicHeader("admin", "admin"))).getStatusCode()); - //System.out.println(res.getBody()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + ".kibana/_doc/index-pattern:9fbbd1a0-c3c5-11e8-a13f-71b8ea5a4f7b?pretty", + new BasicHeader("securitytenant", "__user__"), + encodeBasicHeader("admin", "admin") + )).getStatusCode() + ); + // System.out.println(res.getBody()); Assert.assertFalse(res.getBody().contains("exception")); Assert.assertTrue(res.getBody().contains("humanresources")); Assert.assertTrue(res.getBody().contains("\"found\" : true")); @@ -290,40 +425,70 @@ public void testMtMulti() throws Exception { System.out.println("#### mget"); body = "{\"docs\" : [{\"_index\" : \".kibana\",\"_id\" : \"index-pattern:9fbbd1a0-c3c5-11e8-a13f-71b8ea5a4f7b\"}]}"; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_mget/?pretty",body, new BasicHeader("securitytenant", "__user__"), encodeBasicHeader("admin", "admin"))).getStatusCode()); - //System.out.println(res.getBody()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest( + "_mget/?pretty", + body, + new BasicHeader("securitytenant", "__user__"), + encodeBasicHeader("admin", "admin") + )).getStatusCode() + ); + // System.out.println(res.getBody()); Assert.assertFalse(res.getBody().contains("exception")); Assert.assertTrue(res.getBody().contains("humanresources")); Assert.assertTrue(res.getBody().contains(dashboardsIndex)); System.out.println("#### index"); - body = "{"+ - "\"type\" : \"index-pattern\","+ - "\"updated_at\" : \"2017-09-29T08:56:59.066Z\","+ - "\"index-pattern\" : {"+ - "\"title\" : \"xyz\""+ - "}}"; - Assert.assertEquals(HttpStatus.SC_CREATED, (res = rh.executePutRequest(".kibana/_doc/abc?pretty",body, new BasicHeader("securitytenant", "__user__"), encodeBasicHeader("admin", "admin"))).getStatusCode()); - //System.out.println(res.getBody()); + body = "{" + + "\"type\" : \"index-pattern\"," + + "\"updated_at\" : \"2017-09-29T08:56:59.066Z\"," + + "\"index-pattern\" : {" + + "\"title\" : \"xyz\"" + + "}}"; + Assert.assertEquals( + HttpStatus.SC_CREATED, + (res = rh.executePutRequest( + ".kibana/_doc/abc?pretty", + body, + new BasicHeader("securitytenant", "__user__"), + encodeBasicHeader("admin", "admin") + )).getStatusCode() + ); + // System.out.println(res.getBody()); Assert.assertFalse(res.getBody().contains("exception")); Assert.assertTrue(res.getBody().contains("\"result\" : \"created\"")); Assert.assertTrue(res.getBody().contains(dashboardsIndex)); System.out.println("#### bulk"); - body = - "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"b1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"b2\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator(); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePutRequest("_bulk?pretty",body, new BasicHeader("securitytenant", "__user__"), encodeBasicHeader("admin", "admin"))).getStatusCode()); - //System.out.println(res.getBody()); + body = "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"b1\" } }" + + System.lineSeparator() + + "{ \"field1\" : \"value1\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"b2\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator(); + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePutRequest( + "_bulk?pretty", + body, + new BasicHeader("securitytenant", "__user__"), + encodeBasicHeader("admin", "admin") + )).getStatusCode() + ); + // System.out.println(res.getBody()); Assert.assertFalse(res.getBody().contains("exception")); Assert.assertTrue(res.getBody().contains(dashboardsIndex)); Assert.assertTrue(res.getBody().contains("\"errors\" : false")); Assert.assertTrue(res.getBody().contains("\"result\" : \"created\"")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_cat/indices", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("_cat/indices", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertEquals(2, res.getBody().split(".kibana").length); Assert.assertTrue(res.getBody().contains(dashboardsIndex)); @@ -331,8 +496,7 @@ public void testMtMulti() throws Exception { @Test public void testDashboardsAlias() throws Exception { - final Settings settings = Settings.builder() - .build(); + final Settings settings = Settings.builder().build(); setup(settings); try (Client tc = getClient()) { @@ -340,19 +504,26 @@ public void testDashboardsAlias() throws Exception { Map indexSettings = new HashMap(); indexSettings.put("number_of_shards", 1); indexSettings.put("number_of_replicas", 0); - tc.admin().indices().create(new CreateIndexRequest(".kibana-6") - .alias(new Alias(".kibana")) - .settings(indexSettings)) + tc.admin() + .indices() + .create(new CreateIndexRequest(".kibana-6").alias(new Alias(".kibana")).settings(indexSettings)) .actionGet(); - tc.index(new IndexRequest(".kibana-6").id("6.2.2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(body, XContentType.JSON)).actionGet(); + tc.index(new IndexRequest(".kibana-6").id("6.2.2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(body, XContentType.JSON)) + .actionGet(); } final RestHelper rh = nonSslRestHelper(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest(".kibana-6/_doc/6.2.2?pretty", encodeBasicHeader("kibanaro", "kibanaro"))).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest(".kibana/_doc/6.2.2?pretty", encodeBasicHeader("kibanaro", "kibanaro"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest(".kibana-6/_doc/6.2.2?pretty", encodeBasicHeader("kibanaro", "kibanaro"))).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest(".kibana/_doc/6.2.2?pretty", encodeBasicHeader("kibanaro", "kibanaro"))).getStatusCode() + ); System.out.println(res.getBody()); @@ -360,8 +531,7 @@ public void testDashboardsAlias() throws Exception { @Test public void testDashboardsAlias65() throws Exception { - final Settings settings = Settings.builder() - .build(); + final Settings settings = Settings.builder().build(); setup(settings); try (Client tc = getClient()) { @@ -369,29 +539,39 @@ public void testDashboardsAlias65() throws Exception { Map indexSettings = new HashMap(); indexSettings.put("number_of_shards", 1); indexSettings.put("number_of_replicas", 0); - tc.admin().indices().create(new CreateIndexRequest(".kibana_1") - .alias(new Alias(".kibana")) - .settings(indexSettings)) + tc.admin() + .indices() + .create(new CreateIndexRequest(".kibana_1").alias(new Alias(".kibana")).settings(indexSettings)) .actionGet(); - tc.index(new IndexRequest(".kibana_1").id("6.2.2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(body, XContentType.JSON)).actionGet(); - tc.index(new IndexRequest(".kibana_-900636979_kibanaro").id("6.2.2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(body, XContentType.JSON)).actionGet(); + tc.index(new IndexRequest(".kibana_1").id("6.2.2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(body, XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest(".kibana_-900636979_kibanaro").id("6.2.2") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source(body, XContentType.JSON) + ).actionGet(); } final RestHelper rh = nonSslRestHelper(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest(".kibana/_doc/6.2.2?pretty", new BasicHeader("securitytenant", "__user__"), encodeBasicHeader("kibanaro", "kibanaro"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + ".kibana/_doc/6.2.2?pretty", + new BasicHeader("securitytenant", "__user__"), + encodeBasicHeader("kibanaro", "kibanaro") + )).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains(".kibana_-900636979_kibanaro")); } - @Test public void testTenantParametersSubstitution() throws Exception { - final Settings settings = Settings.builder() - .build(); + final Settings settings = Settings.builder().build(); setup(settings); final RestHelper rh = nonSslRestHelper(); @@ -419,9 +599,10 @@ public void testTenantParametersSubstitution() throws Exception { assertThat(res.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(res.findValueInJson("_source.tenant"), equalTo(tenantName)); - final String tenantNameAppended = "tenant_parameters_substitution_1"; - final String createTenantAppendedBody = "{\"buildNum\": 15460, \"defaultIndex\": \"plop\", \"tenant\": \"" + tenantNameAppended + "\"}"; + final String createTenantAppendedBody = "{\"buildNum\": 15460, \"defaultIndex\": \"plop\", \"tenant\": \"" + + tenantNameAppended + + "\"}"; final Header userTenantAppended = new BasicHeader("securitytenant", tenantNameAppended); res = rh.executeGetRequest(url, asNoAccessUser, userTenantAppended); @@ -440,8 +621,7 @@ public void testTenantParametersSubstitution() throws Exception { @Test public void testMultitenancyAnonymousUser() throws Exception { - final Settings settings = Settings.builder() - .build(); + final Settings settings = Settings.builder().build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_anonymous.yml"), settings); final RestHelper rh = nonSslRestHelper(); diff --git a/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java b/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java index 4839155232..b25a50d934 100644 --- a/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java +++ b/src/test/java/org/opensearch/security/multitenancy/test/TenancyMultitenancyEnabledTests.java @@ -31,12 +31,14 @@ public class TenancyMultitenancyEnabledTests extends SingleClusterTest { private static final Header ON_USER_TENANT = new BasicHeader("securitytenant", "__user__"); private static String createIndexPatternDoc(final String title) { - return "{"+ - "\"type\" : \"index-pattern\","+ - "\"updated_at\" : \"2018-09-29T08:56:59.066Z\","+ - "\"index-pattern\" : {"+ - "\"title\" : \"" + title + "\""+ - "}}"; + return "{" + + "\"type\" : \"index-pattern\"," + + "\"updated_at\" : \"2018-09-29T08:56:59.066Z\"," + + "\"index-pattern\" : {" + + "\"title\" : \"" + + title + + "\"" + + "}}"; } @Override @@ -46,37 +48,69 @@ protected String getResourceFolder() { @Test public void testMultitenancyDisabled_endToEndTest() throws Exception { - setup(Settings.EMPTY, - new DynamicSecurityConfig(), - Settings.builder().put("plugins.security.restapi.roles_enabled.0", "security_rest_api_access").build(), - true); - - final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", AS_REST_API_USER); + setup( + Settings.EMPTY, + new DynamicSecurityConfig(), + Settings.builder().put("plugins.security.restapi.roles_enabled.0", "security_rest_api_access").build(), + true + ); + + final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest( + "/_plugins/_security/api/tenancy/config", + AS_REST_API_USER + ); assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(getSettingResponse.findValueInJson("multitenancy_enabled"), equalTo("true")); HttpResponse getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", AS_USER); - assertThat(getDashboardsinfoResponse.findValueInJson("multitenancy_enabled"),equalTo("true")); + assertThat(getDashboardsinfoResponse.findValueInJson("multitenancy_enabled"), equalTo("true")); - final HttpResponse createDocInGlobalTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("globalIndex"), AS_USER); + final HttpResponse createDocInGlobalTenantResponse = nonSslRestHelper().executePostRequest( + ".kibana/_doc?refresh=true", + createIndexPatternDoc("globalIndex"), + AS_USER + ); assertThat(createDocInGlobalTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); - final HttpResponse createDocInUserTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("userIndex"), ON_USER_TENANT, AS_USER); + final HttpResponse createDocInUserTenantResponse = nonSslRestHelper().executePostRequest( + ".kibana/_doc?refresh=true", + createIndexPatternDoc("userIndex"), + ON_USER_TENANT, + AS_USER + ); assertThat(createDocInUserTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); - final HttpResponse searchInUserTenantWithMutlitenancyEnabled = nonSslRestHelper().executeGetRequest(".kibana/_search", ON_USER_TENANT, AS_USER); + final HttpResponse searchInUserTenantWithMutlitenancyEnabled = nonSslRestHelper().executeGetRequest( + ".kibana/_search", + ON_USER_TENANT, + AS_USER + ); assertThat(searchInUserTenantWithMutlitenancyEnabled.getStatusCode(), equalTo(HttpStatus.SC_OK)); - assertThat(searchInUserTenantWithMutlitenancyEnabled.findValueInJson("hits.hits[0]._source.index-pattern.title"), equalTo("userIndex")); - - final HttpResponse updateMutlitenancyToDisabled = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"multitenancy_enabled\": \"false\"}", AS_REST_API_USER); + assertThat( + searchInUserTenantWithMutlitenancyEnabled.findValueInJson("hits.hits[0]._source.index-pattern.title"), + equalTo("userIndex") + ); + + final HttpResponse updateMutlitenancyToDisabled = nonSslRestHelper().executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"multitenancy_enabled\": \"false\"}", + AS_REST_API_USER + ); assertThat(updateMutlitenancyToDisabled.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(updateMutlitenancyToDisabled.findValueInJson("multitenancy_enabled"), equalTo("false")); getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", AS_USER); - assertThat(getDashboardsinfoResponse.findValueInJson("multitenancy_enabled"),equalTo("false")); + assertThat(getDashboardsinfoResponse.findValueInJson("multitenancy_enabled"), equalTo("false")); - final HttpResponse searchInUserTenantWithMutlitenancyDisabled = nonSslRestHelper().executeGetRequest(".kibana/_search", ON_USER_TENANT, AS_USER); + final HttpResponse searchInUserTenantWithMutlitenancyDisabled = nonSslRestHelper().executeGetRequest( + ".kibana/_search", + ON_USER_TENANT, + AS_USER + ); assertThat(searchInUserTenantWithMutlitenancyDisabled.getStatusCode(), equalTo(HttpStatus.SC_OK)); - assertThat(searchInUserTenantWithMutlitenancyDisabled.findValueInJson("hits.hits[0]._source.index-pattern.title"), equalTo("globalIndex")); + assertThat( + searchInUserTenantWithMutlitenancyDisabled.findValueInJson("hits.hits[0]._source.index-pattern.title"), + equalTo("globalIndex") + ); } } diff --git a/src/test/java/org/opensearch/security/multitenancy/test/TenancyPrivateTenantEnabledTests.java b/src/test/java/org/opensearch/security/multitenancy/test/TenancyPrivateTenantEnabledTests.java index 599586239c..1af102802f 100644 --- a/src/test/java/org/opensearch/security/multitenancy/test/TenancyPrivateTenantEnabledTests.java +++ b/src/test/java/org/opensearch/security/multitenancy/test/TenancyPrivateTenantEnabledTests.java @@ -32,12 +32,14 @@ public class TenancyPrivateTenantEnabledTests extends SingleClusterTest { private static final Header ON_USER_TENANT = new BasicHeader("securitytenant", "__user__"); private static String createIndexPatternDoc(final String title) { - return "{"+ - "\"type\" : \"index-pattern\","+ - "\"updated_at\" : \"2018-09-29T08:56:59.066Z\","+ - "\"index-pattern\" : {"+ - "\"title\" : \"" + title + "\""+ - "}}"; + return "{" + + "\"type\" : \"index-pattern\"," + + "\"updated_at\" : \"2018-09-29T08:56:59.066Z\"," + + "\"index-pattern\" : {" + + "\"title\" : \"" + + title + + "\"" + + "}}"; } @Override @@ -47,37 +49,69 @@ protected String getResourceFolder() { @Test public void testPrivateTenantDisabled_Update_EndToEnd() throws Exception { - setup(Settings.EMPTY, - new DynamicSecurityConfig(), - Settings.builder().put("plugins.security.restapi.roles_enabled.0", "security_rest_api_access").build(), - true); - - final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/api/tenancy/config", AS_REST_API_USER); + setup( + Settings.EMPTY, + new DynamicSecurityConfig(), + Settings.builder().put("plugins.security.restapi.roles_enabled.0", "security_rest_api_access").build(), + true + ); + + final HttpResponse getSettingResponse = nonSslRestHelper().executeGetRequest( + "/_plugins/_security/api/tenancy/config", + AS_REST_API_USER + ); assertThat(getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(getSettingResponse.findValueInJson("private_tenant_enabled"), equalTo("true")); HttpResponse getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", AS_ADMIN_USER); assertThat(getDashboardsinfoResponse.findValueInJson("private_tenant_enabled"), equalTo("true")); - final HttpResponse createDocInGlobalTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("globalIndex"), AS_ADMIN_USER); + final HttpResponse createDocInGlobalTenantResponse = nonSslRestHelper().executePostRequest( + ".kibana/_doc?refresh=true", + createIndexPatternDoc("globalIndex"), + AS_ADMIN_USER + ); assertThat(createDocInGlobalTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); - final HttpResponse createDocInUserTenantResponse = nonSslRestHelper().executePostRequest(".kibana/_doc?refresh=true", createIndexPatternDoc("userIndex"), ON_USER_TENANT, AS_USER); + final HttpResponse createDocInUserTenantResponse = nonSslRestHelper().executePostRequest( + ".kibana/_doc?refresh=true", + createIndexPatternDoc("userIndex"), + ON_USER_TENANT, + AS_USER + ); assertThat(createDocInUserTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); - final HttpResponse searchInUserTenantWithPrivateTenantEnabled = nonSslRestHelper().executeGetRequest(".kibana/_search", ON_USER_TENANT, AS_USER); + final HttpResponse searchInUserTenantWithPrivateTenantEnabled = nonSslRestHelper().executeGetRequest( + ".kibana/_search", + ON_USER_TENANT, + AS_USER + ); assertThat(searchInUserTenantWithPrivateTenantEnabled.getStatusCode(), equalTo(HttpStatus.SC_OK)); - assertThat(searchInUserTenantWithPrivateTenantEnabled.findValueInJson("hits.hits[0]._source.index-pattern.title"), equalTo("userIndex")); - - final HttpResponse disablePrivateTenantResponse = nonSslRestHelper().executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\": \"false\"}", AS_REST_API_USER); + assertThat( + searchInUserTenantWithPrivateTenantEnabled.findValueInJson("hits.hits[0]._source.index-pattern.title"), + equalTo("userIndex") + ); + + final HttpResponse disablePrivateTenantResponse = nonSslRestHelper().executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"private_tenant_enabled\": \"false\"}", + AS_REST_API_USER + ); assertThat(disablePrivateTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(disablePrivateTenantResponse.findValueInJson("private_tenant_enabled"), equalTo("false")); getDashboardsinfoResponse = nonSslRestHelper().executeGetRequest("/_plugins/_security/dashboardsinfo", AS_ADMIN_USER); - assertThat(getDashboardsinfoResponse.findValueInJson("private_tenant_enabled"),equalTo("false")); + assertThat(getDashboardsinfoResponse.findValueInJson("private_tenant_enabled"), equalTo("false")); - final HttpResponse searchInUserTenantWithPrivateTenantDisabled = nonSslRestHelper().executeGetRequest(".kibana/_search", ON_USER_TENANT, AS_USER); + final HttpResponse searchInUserTenantWithPrivateTenantDisabled = nonSslRestHelper().executeGetRequest( + ".kibana/_search", + ON_USER_TENANT, + AS_USER + ); assertThat(searchInUserTenantWithPrivateTenantDisabled.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - assertThat(searchInUserTenantWithPrivateTenantDisabled.findValueInJson("error.reason"), containsString("no permissions for [indices:data/read/search] and User")); + assertThat( + searchInUserTenantWithPrivateTenantDisabled.findValueInJson("error.reason"), + containsString("no permissions for [indices:data/read/search] and User") + ); } From bfc8a6bbdc9669da98a42160bc70a44a7b35c295 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Mon, 5 Jun 2023 12:14:06 -0400 Subject: [PATCH 198/369] Update test/security/s* java files (#2836) * Update test/security/s* java files Signed-off-by: Stephen Crawford * Update test/security/s* java files Signed-off-by: Stephen Crawford --------- Signed-off-by: Stephen Crawford --- build.gradle | 2 + .../sanity/tests/SecurityRestTestCase.java | 46 +- .../sanity/tests/SingleClusterSanityIT.java | 1 - .../SecurityRolesPermissionsTest.java | 182 ++- .../impl/v7/IndexPatternTests.java | 50 +- .../ssl/CertificateValidatorTest.java | 54 +- .../opensearch/security/ssl/OpenSSLTest.java | 58 +- .../org/opensearch/security/ssl/SSLTest.java | 1108 +++++++++++------ .../security/ssl/SecureSSLSettingsTest.java | 10 +- .../SecuritySSLReloadCertsActionTests.java | 72 +- .../security/ssl/TestPrincipalExtractor.java | 11 +- .../transport/DualModeSSLHandlerTests.java | 12 +- .../security/ssl/util/CertFromFileTests.java | 67 +- .../ssl/util/CertFromKeystoreTests.java | 9 +- .../ssl/util/CertFromTruststoreTests.java | 6 +- .../ssl/util/SSLConnectionTestUtilTests.java | 90 +- .../security/ssl/util/TLSUtilTests.java | 4 +- .../GuardedSearchOperationWrapperTest.java | 18 +- .../security/support/SecurityUtilsTest.java | 10 +- .../system_indices/SystemIndicesTests.java | 235 ++-- 20 files changed, 1215 insertions(+), 830 deletions(-) diff --git a/build.gradle b/build.gradle index 943bdea29b..8d7f3d9edd 100644 --- a/build.gradle +++ b/build.gradle @@ -85,6 +85,7 @@ spotless { target '**/test/java/org/opensearch/security/f*/**/*.java' target '**/test/java/org/opensearch/security/h*/**/*.java' target '**/test/java/org/opensearch/security/m*/**/*.java' + target '**/test/java/org/opensearch/security/s*/**/*.java' removeUnusedImports() eclipse().configFile rootProject.file('formatter/formatterConfig.xml') @@ -125,6 +126,7 @@ spotless { targetExclude '**/test/java/org/opensearch/security/f*/**/*.java' targetExclude '**/test/java/org/opensearch/security/h*/**/*.java' targetExclude '**/test/java/org/opensearch/security/m*/**/*.java' + targetExclude '**/test/java/org/opensearch/security/s*/**/*.java' targetExclude 'src/integrationTest/**' trimTrailingWhitespace() diff --git a/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java b/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java index 51c4ddb984..b0d53deaa8 100644 --- a/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java +++ b/src/test/java/org/opensearch/security/sanity/tests/SecurityRestTestCase.java @@ -45,54 +45,52 @@ public class SecurityRestTestCase extends OpenSearchRestTestCase { private static final String CERT_FILE_DIRECTORY = "sanity-tests/"; + private boolean isHttps() { return System.getProperty("https").equals("true"); } + private boolean securityEnabled() { return System.getProperty("security.enabled").equals("true"); } @Override - protected Settings restAdminSettings(){ - return Settings - .builder() - .put("http.port", 9200) - .put(SECURITY_SSL_HTTP_ENABLED, isHttps()) - .put(SECURITY_SSL_HTTP_PEMCERT_FILEPATH, CERT_FILE_DIRECTORY + "opensearch-node.pem") - .put(SECURITY_SSL_HTTP_PEMKEY_FILEPATH, CERT_FILE_DIRECTORY + "opensearch-node-key.pem") - .put(SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, CERT_FILE_DIRECTORY + "root-ca.pem") - .put(SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, CERT_FILE_DIRECTORY + "test-kirk.jks") - .put(SECURITY_SSL_HTTP_KEYSTORE_PASSWORD.insecurePropertyName, "changeit") - .put(SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD.insecurePropertyName, "changeit") - .build(); + protected Settings restAdminSettings() { + return Settings.builder() + .put("http.port", 9200) + .put(SECURITY_SSL_HTTP_ENABLED, isHttps()) + .put(SECURITY_SSL_HTTP_PEMCERT_FILEPATH, CERT_FILE_DIRECTORY + "opensearch-node.pem") + .put(SECURITY_SSL_HTTP_PEMKEY_FILEPATH, CERT_FILE_DIRECTORY + "opensearch-node-key.pem") + .put(SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, CERT_FILE_DIRECTORY + "root-ca.pem") + .put(SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, CERT_FILE_DIRECTORY + "test-kirk.jks") + .put(SECURITY_SSL_HTTP_KEYSTORE_PASSWORD.insecurePropertyName, "changeit") + .put(SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD.insecurePropertyName, "changeit") + .build(); } @Override protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOException { - if(securityEnabled()){ + if (securityEnabled()) { String keystore = settings.get(SECURITY_SSL_HTTP_KEYSTORE_FILEPATH); - if(keystore != null){ + if (keystore != null) { // create adminDN (super-admin) client File file = new File(getClass().getClassLoader().getResource(CERT_FILE_DIRECTORY).getFile()); Path configPath = PathUtils.get(file.toURI()).getParent().toAbsolutePath(); - return new SecureRestClientBuilder(settings, configPath) - .setSocketTimeout(60000) - .setConnectionRequestTimeout(180000) - .build(); + return new SecureRestClientBuilder(settings, configPath).setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build(); } // create client with passed user String userName = System.getProperty("user"); String password = System.getProperty("password"); - return new SecureRestClientBuilder(hosts, isHttps(), userName, password) - .setSocketTimeout(60000) - .setConnectionRequestTimeout(180000) - .build(); - } - else { + return new SecureRestClientBuilder(hosts, isHttps(), userName, password).setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build(); + } else { RestClientBuilder builder = RestClient.builder(hosts); configureClient(builder, settings); builder.setStrictDeprecationMode(true); diff --git a/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java b/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java index 55d4a6ea0b..8987744d58 100644 --- a/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java +++ b/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java @@ -24,7 +24,6 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; - @SuppressWarnings("unchecked") public class SingleClusterSanityIT extends SecurityRestTestCase { diff --git a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java index 010b453b85..49a9be8a91 100644 --- a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java +++ b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java @@ -56,90 +56,62 @@ public class SecurityRolesPermissionsTest { - static final Map NO_REST_ADMIN_PERMISSIONS_ROLES = - ImmutableMap.builder() - .put( - "all_access", - role("*")) - .put( - "all_cluster_and_indices", - role("custer:*", "indices:*") - ).build(); - - static final Map REST_ADMIN_PERMISSIONS_FULL_ACCESS_ROLES = - ImmutableMap.builder() - .put( - "security_rest_api_full_access", - role(allRestApiPermissions())) - .put( - "security_rest_api_full_access_with_star", - role("restapi:admin/*")) - .build(); + static final Map NO_REST_ADMIN_PERMISSIONS_ROLES = ImmutableMap.builder() + .put("all_access", role("*")) + .put("all_cluster_and_indices", role("custer:*", "indices:*")) + .build(); + static final Map REST_ADMIN_PERMISSIONS_FULL_ACCESS_ROLES = ImmutableMap.builder() + .put("security_rest_api_full_access", role(allRestApiPermissions())) + .put("security_rest_api_full_access_with_star", role("restapi:admin/*")) + .build(); static String restAdminApiRoleName(final String endpoint) { return String.format("security_rest_api_%s_only", endpoint); } - static final Map REST_ADMIN_PERMISSIONS_ROLES = - ENDPOINTS_WITH_PERMISSIONS - .entrySet() - .stream() - .flatMap(e -> { - final String endpoint = e.getKey().name().toLowerCase(Locale.ROOT); - final PermissionBuilder pb = e.getValue(); - if (e.getKey() == Endpoint.SSL) { - return Stream.of( - new SimpleEntry<>( - restAdminApiRoleName(CERTS_INFO_ACTION), - role(pb.build(CERTS_INFO_ACTION)) - ), - new SimpleEntry<>( - restAdminApiRoleName(RELOAD_CERTS_ACTION), - role(pb.build(RELOAD_CERTS_ACTION)) - ) - ); - } else { - return Stream.of( - new SimpleEntry<>(restAdminApiRoleName(endpoint), role(pb.build())) - ); - } - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + static final Map REST_ADMIN_PERMISSIONS_ROLES = ENDPOINTS_WITH_PERMISSIONS.entrySet().stream().flatMap(e -> { + final String endpoint = e.getKey().name().toLowerCase(Locale.ROOT); + final PermissionBuilder pb = e.getValue(); + if (e.getKey() == Endpoint.SSL) { + return Stream.of( + new SimpleEntry<>(restAdminApiRoleName(CERTS_INFO_ACTION), role(pb.build(CERTS_INFO_ACTION))), + new SimpleEntry<>(restAdminApiRoleName(RELOAD_CERTS_ACTION), role(pb.build(RELOAD_CERTS_ACTION))) + ); + } else { + return Stream.of(new SimpleEntry<>(restAdminApiRoleName(endpoint), role(pb.build()))); + } + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); static ObjectNode role(final String... clusterPermissions) { final ArrayNode clusterPermissionsArrayNode = DefaultObjectMapper.objectMapper.createArrayNode(); Arrays.stream(clusterPermissions).forEach(clusterPermissionsArrayNode::add); - return DefaultObjectMapper.objectMapper - .createObjectNode() - .put("reserved", true) - .set("cluster_permissions", clusterPermissionsArrayNode); + return DefaultObjectMapper.objectMapper.createObjectNode() + .put("reserved", true) + .set("cluster_permissions", clusterPermissionsArrayNode); } static String[] allRestApiPermissions() { - return ENDPOINTS_WITH_PERMISSIONS - .entrySet() - .stream() - .flatMap(entry -> { - if (entry.getKey() == Endpoint.SSL) { - return Stream.of(entry.getValue().build(CERTS_INFO_ACTION), entry.getValue().build(RELOAD_CERTS_ACTION)); - } else { - return Stream.of(entry.getValue().build()); - } - }).toArray(String[]::new); + return ENDPOINTS_WITH_PERMISSIONS.entrySet().stream().flatMap(entry -> { + if (entry.getKey() == Endpoint.SSL) { + return Stream.of(entry.getValue().build(CERTS_INFO_ACTION), entry.getValue().build(RELOAD_CERTS_ACTION)); + } else { + return Stream.of(entry.getValue().build()); + } + }).toArray(String[]::new); } final ConfigModel configModel; public SecurityRolesPermissionsTest() throws IOException { - this.configModel = - new ConfigModelV7( - createRolesConfig(), - createRoleMappingsConfig(), - createActionGroupsConfig(), - createTenantsConfig(), - Mockito.mock(DynamicConfigModel.class), - Settings.EMPTY - ); + this.configModel = new ConfigModelV7( + createRolesConfig(), + createRoleMappingsConfig(), + createActionGroupsConfig(), + createTenantsConfig(), + Mockito.mock(DynamicConfigModel.class), + Settings.EMPTY + ); } @Test @@ -151,17 +123,17 @@ public void hasNoExplicitClusterPermissionPermissionForRestAdmin() { final PermissionBuilder permissionBuilder = entry.getValue(); if (endpoint == Endpoint.SSL) { Assert.assertFalse( - endpoint.name(), - securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(CERTS_INFO_ACTION)) + endpoint.name(), + securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(CERTS_INFO_ACTION)) ); Assert.assertFalse( - endpoint.name(), - securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(RELOAD_CERTS_ACTION)) + endpoint.name(), + securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(RELOAD_CERTS_ACTION)) ); } else { Assert.assertFalse( - endpoint.name(), - securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build()) + endpoint.name(), + securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build()) ); } } @@ -176,10 +148,19 @@ public void hasExplicitClusterPermissionPermissionForRestAdminWitFullAccess() { final Endpoint endpoint = entry.getKey(); final PermissionBuilder permissionBuilder = entry.getValue(); if (endpoint == Endpoint.SSL) { - Assert.assertTrue(endpoint.name() + "/" + CERTS_INFO_ACTION, securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(CERTS_INFO_ACTION))); - Assert.assertTrue(endpoint.name() + "/" + CERTS_INFO_ACTION, securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(RELOAD_CERTS_ACTION))); + Assert.assertTrue( + endpoint.name() + "/" + CERTS_INFO_ACTION, + securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(CERTS_INFO_ACTION)) + ); + Assert.assertTrue( + endpoint.name() + "/" + CERTS_INFO_ACTION, + securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(RELOAD_CERTS_ACTION)) + ); } else { - Assert.assertTrue(endpoint.name(), securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build())); + Assert.assertTrue( + endpoint.name(), + securityRolesForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build()) + ); } } } @@ -188,60 +169,53 @@ public void hasExplicitClusterPermissionPermissionForRestAdminWitFullAccess() { @Test public void hasExplicitClusterPermissionPermissionForRestAdmin() { // verify all endpoint except SSL - final Collection noSslEndpoints = - ENDPOINTS_WITH_PERMISSIONS.keySet().stream() - .filter(e -> e != Endpoint.SSL).collect(Collectors.toList()); + final Collection noSslEndpoints = ENDPOINTS_WITH_PERMISSIONS.keySet() + .stream() + .filter(e -> e != Endpoint.SSL) + .collect(Collectors.toList()); for (final Endpoint endpoint : noSslEndpoints) { final String permission = ENDPOINTS_WITH_PERMISSIONS.get(endpoint).build(); - final SecurityRoles allowOnePermissionRole = - configModel.getSecurityRoles().filter( - ImmutableSet.of(restAdminApiRoleName(endpoint.name().toLowerCase(Locale.ROOT)))); + final SecurityRoles allowOnePermissionRole = configModel.getSecurityRoles() + .filter(ImmutableSet.of(restAdminApiRoleName(endpoint.name().toLowerCase(Locale.ROOT)))); Assert.assertTrue(endpoint.name(), allowOnePermissionRole.hasExplicitClusterPermissionPermission(permission)); - assertHasNoPermissionsForRestApiAdminOnePermissionRole( - endpoint, - allowOnePermissionRole - ); + assertHasNoPermissionsForRestApiAdminOnePermissionRole(endpoint, allowOnePermissionRole); } // verify SSL endpoint with 2 actions for (final String sslAction : ImmutableSet.of(CERTS_INFO_ACTION, RELOAD_CERTS_ACTION)) { - final SecurityRoles sslAllowRole = - configModel.getSecurityRoles().filter(ImmutableSet.of(restAdminApiRoleName(sslAction))); + final SecurityRoles sslAllowRole = configModel.getSecurityRoles().filter(ImmutableSet.of(restAdminApiRoleName(sslAction))); final PermissionBuilder permissionBuilder = ENDPOINTS_WITH_PERMISSIONS.get(Endpoint.SSL); Assert.assertTrue( - Endpoint.SSL + "/" + sslAction, - sslAllowRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(sslAction)) + Endpoint.SSL + "/" + sslAction, + sslAllowRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(sslAction)) ); assertHasNoPermissionsForRestApiAdminOnePermissionRole(Endpoint.SSL, sslAllowRole); } } void assertHasNoPermissionsForRestApiAdminOnePermissionRole(final Endpoint allowEndpoint, final SecurityRoles allowOnlyRoleForRole) { - final Collection noPermissionEndpoints = - ENDPOINTS_WITH_PERMISSIONS.keySet().stream() - .filter(e -> e != allowEndpoint) - .collect(Collectors.toList()); + final Collection noPermissionEndpoints = ENDPOINTS_WITH_PERMISSIONS.keySet() + .stream() + .filter(e -> e != allowEndpoint) + .collect(Collectors.toList()); for (final Endpoint endpoint : noPermissionEndpoints) { final PermissionBuilder permissionBuilder = ENDPOINTS_WITH_PERMISSIONS.get(endpoint); if (endpoint == Endpoint.SSL) { Assert.assertFalse( - endpoint.name(), - allowOnlyRoleForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(CERTS_INFO_ACTION))); + endpoint.name(), + allowOnlyRoleForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(CERTS_INFO_ACTION)) + ); Assert.assertFalse( - endpoint.name(), - allowOnlyRoleForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(RELOAD_CERTS_ACTION))); + endpoint.name(), + allowOnlyRoleForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build(RELOAD_CERTS_ACTION)) + ); } else { - Assert.assertFalse( - endpoint.name(), - allowOnlyRoleForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build())); + Assert.assertFalse(endpoint.name(), allowOnlyRoleForRole.hasExplicitClusterPermissionPermission(permissionBuilder.build())); } } } static ObjectNode meta(final String type) { - return DefaultObjectMapper.objectMapper - .createObjectNode() - .put("type", type) - .put("config_version", 2); + return DefaultObjectMapper.objectMapper.createObjectNode().put("type", type).put("config_version", 2); } static SecurityDynamicConfiguration createRolesConfig() throws IOException { diff --git a/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java b/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java index 856f026d72..2b95a6e84c 100644 --- a/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java +++ b/src/test/java/org/opensearch/security/securityconf/impl/v7/IndexPatternTests.java @@ -108,7 +108,9 @@ public void testAttemptResolveIndexNamesOverload() { public void testExactNameWithNoMatches() { doReturn("index-17").when(ip).getUnresolvedIndexPattern(user); when(clusterService.state()).thenReturn(mock(ClusterState.class)); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-17"))).thenReturn(new String[]{}); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-17"))).thenReturn( + new String[] {} + ); final Set results = ip.concreteIndexNames(user, resolver, clusterService); @@ -124,7 +126,9 @@ public void testExactNameWithNoMatches() { public void testExactName() { doReturn("index-17").when(ip).getUnresolvedIndexPattern(user); when(clusterService.state()).thenReturn(mock(ClusterState.class)); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-17"))).thenReturn(new String[]{"resolved-index-17"}); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-17"))).thenReturn( + new String[] { "resolved-index-17" } + ); final Set results = ip.concreteIndexNames(user, resolver, clusterService); @@ -140,7 +144,9 @@ public void testExactName() { public void testMultipleConcreteIndices() { doReturn("index-1*").when(ip).getUnresolvedIndexPattern(user); doReturn(createClusterState()).when(clusterService).state(); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*"))).thenReturn(new String[]{"resolved-index-17", "resolved-index-18"}); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*"))).thenReturn( + new String[] { "resolved-index-17", "resolved-index-18" } + ); final Set results = ip.concreteIndexNames(user, resolver, clusterService); @@ -156,12 +162,18 @@ public void testMultipleConcreteIndices() { public void testMultipleConcreteIndicesWithOneAlias() { doReturn("index-1*").when(ip).getUnresolvedIndexPattern(user); - doReturn(createClusterState( - new IndexShorthand("index-100", Type.ALIAS), // Name and type match - new IndexShorthand("19", Type.ALIAS) // Type matches/wrong name - )).when(clusterService).state(); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-100"))).thenReturn(new String[]{"resolved-index-100"}); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*"))).thenReturn(new String[]{"resolved-index-17", "resolved-index-18"}); + doReturn( + createClusterState( + new IndexShorthand("index-100", Type.ALIAS), // Name and type match + new IndexShorthand("19", Type.ALIAS) // Type matches/wrong name + ) + ).when(clusterService).state(); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-100"))).thenReturn( + new String[] { "resolved-index-100" } + ); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*"))).thenReturn( + new String[] { "resolved-index-17", "resolved-index-18" } + ); final Set results = ip.concreteIndexNames(user, resolver, clusterService); @@ -177,13 +189,18 @@ public void testMultipleConcreteIndicesWithOneAlias() { @Test public void testMultipleConcreteAliasedAndUnresolved() { doReturn("index-1*").when(ip).getUnresolvedIndexPattern(user); - doReturn(createClusterState( - new IndexShorthand("index-100", Type.ALIAS), // Name and type match - new IndexShorthand("index-101", Type.ALIAS), // Name and type match - new IndexShorthand("19", Type.ALIAS) // Type matches/wrong name - )).when(clusterService).state(); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-100"), eq("index-101"))).thenReturn(new String[]{"resolved-index-100", "resolved-index-101"}); - when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*"))).thenReturn(new String[]{"resolved-index-17", "resolved-index-18"}); + doReturn( + createClusterState( + new IndexShorthand("index-100", Type.ALIAS), // Name and type match + new IndexShorthand("index-101", Type.ALIAS), // Name and type match + new IndexShorthand("19", Type.ALIAS) // Type matches/wrong name + ) + ).when(clusterService).state(); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-100"), eq("index-101"))) + .thenReturn(new String[] { "resolved-index-100", "resolved-index-101" }); + when(resolver.concreteIndexNames(any(), eq(IndicesOptions.lenientExpandOpen()), eq(true), eq("index-1*"))).thenReturn( + new String[] { "resolved-index-17", "resolved-index-18" } + ); final Set results = ip.attemptResolveIndexNames(user, resolver, clusterService); @@ -215,6 +232,7 @@ private ClusterState createClusterState(final IndexShorthand... indices) { private class IndexShorthand { public final String name; public final Type type; + public IndexShorthand(final String name, final Type type) { this.name = name; this.type = type; diff --git a/src/test/java/org/opensearch/security/ssl/CertificateValidatorTest.java b/src/test/java/org/opensearch/security/ssl/CertificateValidatorTest.java index b619c2707f..ce614a17ca 100644 --- a/src/test/java/org/opensearch/security/ssl/CertificateValidatorTest.java +++ b/src/test/java/org/opensearch/security/ssl/CertificateValidatorTest.java @@ -50,26 +50,26 @@ public void testStaticCRL() throws Exception { File staticCrl = FileHelper.getAbsoluteFilePathFromClassPath("ssl/crl/revoked.crl").toFile(); Collection crls = null; - try(FileInputStream crlin = new FileInputStream(staticCrl)) { + try (FileInputStream crlin = new FileInputStream(staticCrl)) { crls = CertificateFactory.getInstance("X.509").generateCRLs(crlin); } Assert.assertEquals(crls.size(), 1); - //trust chain incl intermediate certificates (root + intermediates) + // trust chain incl intermediate certificates (root + intermediates) Collection rootCas; final File trustedCas = FileHelper.getAbsoluteFilePathFromClassPath("ssl/chain-ca.pem").toFile(); - try(FileInputStream trin = new FileInputStream(trustedCas)) { - rootCas = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); + try (FileInputStream trin = new FileInputStream(trustedCas)) { + rootCas = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } Assert.assertEquals(rootCas.size(), 2); - //certificate chain to validate (client cert + intermediates but without root) + // certificate chain to validate (client cert + intermediates but without root) Collection certsToValidate; final File certs = FileHelper.getAbsoluteFilePathFromClassPath("ssl/crl/revoked.crt.pem").toFile(); - try(FileInputStream trin = new FileInputStream(certs)) { - certsToValidate = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); + try (FileInputStream trin = new FileInputStream(certs)) { + certsToValidate = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } Assert.assertEquals(certsToValidate.size(), 2); @@ -89,26 +89,26 @@ public void testStaticCRLOk() throws Exception { File staticCrl = FileHelper.getAbsoluteFilePathFromClassPath("ssl/crl/revoked.crl").toFile(); Collection crls = null; - try(FileInputStream crlin = new FileInputStream(staticCrl)) { + try (FileInputStream crlin = new FileInputStream(staticCrl)) { crls = CertificateFactory.getInstance("X.509").generateCRLs(crlin); } Assert.assertEquals(crls.size(), 1); - //trust chain incl intermediate certificates (root + intermediates) + // trust chain incl intermediate certificates (root + intermediates) Collection rootCas; final File trustedCas = FileHelper.getAbsoluteFilePathFromClassPath("ssl/chain-ca.pem").toFile(); - try(FileInputStream trin = new FileInputStream(trustedCas)) { - rootCas = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); + try (FileInputStream trin = new FileInputStream(trustedCas)) { + rootCas = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } Assert.assertEquals(rootCas.size(), 2); - //certificate chain to validate (client cert + intermediates but without root) + // certificate chain to validate (client cert + intermediates but without root) Collection certsToValidate; final File certs = FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem").toFile(); - try(FileInputStream trin = new FileInputStream(certs)) { - certsToValidate = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); + try (FileInputStream trin = new FileInputStream(certs)) { + certsToValidate = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } Assert.assertEquals(certsToValidate.size(), 3); @@ -125,20 +125,20 @@ public void testStaticCRLOk() throws Exception { @Test public void testNoValidationPossible() throws Exception { - //trust chain incl intermediate certificates (root + intermediates) + // trust chain incl intermediate certificates (root + intermediates) Collection rootCas; final File trustedCas = FileHelper.getAbsoluteFilePathFromClassPath("ssl/chain-ca.pem").toFile(); - try(FileInputStream trin = new FileInputStream(trustedCas)) { - rootCas = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); + try (FileInputStream trin = new FileInputStream(trustedCas)) { + rootCas = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } Assert.assertEquals(rootCas.size(), 2); - //certificate chain to validate (client cert + intermediates but without root) + // certificate chain to validate (client cert + intermediates but without root) Collection certsToValidate; final File certs = FileHelper.getAbsoluteFilePathFromClassPath("ssl/crl/revoked.crt.pem").toFile(); - try(FileInputStream trin = new FileInputStream(certs)) { - certsToValidate = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); + try (FileInputStream trin = new FileInputStream(certs)) { + certsToValidate = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } Assert.assertEquals(certsToValidate.size(), 2); @@ -157,21 +157,21 @@ public void testNoValidationPossible() throws Exception { @Test public void testCRLDP() throws Exception { - //trust chain incl intermediate certificates (root + intermediates) + // trust chain incl intermediate certificates (root + intermediates) Collection rootCas; final File trustedCas = FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem").toFile(); - try(FileInputStream trin = new FileInputStream(trustedCas)) { - rootCas = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); + try (FileInputStream trin = new FileInputStream(trustedCas)) { + rootCas = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } Assert.assertEquals(rootCas.size(), 1); - //certificate chain to validate (client cert + intermediates but without root) + // certificate chain to validate (client cert + intermediates but without root) Collection certsToValidate; final File certs = FileHelper.getAbsoluteFilePathFromClassPath("ssl/crl/revoked.crt.pem").toFile(); - //final File certs = getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem"); - try(FileInputStream trin = new FileInputStream(certs)) { - certsToValidate = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); + // final File certs = getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem"); + try (FileInputStream trin = new FileInputStream(certs)) { + certsToValidate = (Collection) CertificateFactory.getInstance("X.509").generateCertificates(trin); } Assert.assertEquals(certsToValidate.size(), 2); diff --git a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java index 4334d9a91c..f205f5fff9 100644 --- a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java @@ -104,9 +104,8 @@ public void testHttpsV3Fail() throws Exception { super.testHttpsV3Fail(); } - @Override - @Test(timeout=40000) + @Test(timeout = 40000) public void testNodeClientSSL() throws Exception { Assume.assumeTrue(OpenSearchSecuritySSLPlugin.OPENSSL_SUPPORTED && OpenSsl.isAvailable()); super.testNodeClientSSL(); @@ -175,37 +174,46 @@ public void testNodeClientSSLwithOpenSslTLSv13() throws Exception { Assume.assumeTrue(OpenSearchSecuritySSLPlugin.OPENSSL_SUPPORTED && OpenSsl.isAvailable() && OpenSsl.version() > 0x10101009L); - final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .put("plugins.security.ssl.transport.resolve_hostname", false) - .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.3") - .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_CHACHA20_POLY1305_SHA256") - .put("node.max_local_storage_nodes",4) - .build(); + final Settings settings = Settings.builder() + .put("plugins.security.ssl.transport.enabled", true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") + .put("plugins.security.ssl.transport.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .put("plugins.security.ssl.transport.enforce_hostname_verification", false) + .put("plugins.security.ssl.transport.resolve_hostname", false) + .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.3") + .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_CHACHA20_POLY1305_SHA256") + .put("node.max_local_storage_nodes", 4) + .build(); setupSslOnlyMode(settings); RestHelper rh = nonSslRestHelper(); final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) - .put("cluster.name", clusterInfo.clustername).put("path.home", "/tmp") - .put("node.name", "client_node_" + new Random().nextInt()) - .put("path.data", "./target/data/" + clusterInfo.clustername + "/ssl/data") - .put("path.logs", "./target/data/" + clusterInfo.clustername + "/ssl/logs") - .put("path.home", "./target") - .put("discovery.initial_state_timeout","8s") - .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost+":"+clusterInfo.nodePort) - .put(settings)// ----- - .build(); + .put("cluster.name", clusterInfo.clustername) + .put("path.home", "/tmp") + .put("node.name", "client_node_" + new Random().nextInt()) + .put("path.data", "./target/data/" + clusterInfo.clustername + "/ssl/data") + .put("path.logs", "./target/data/" + clusterInfo.clustername + "/ssl/logs") + .put("path.home", "./target") + .put("discovery.initial_state_timeout", "8s") + .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) + .put(settings)// ----- + .build(); try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { - ClusterHealthResponse res = node.client().admin().cluster().health(new ClusterHealthRequest().waitForNodes("4").timeout(TimeValue.timeValueSeconds(5))).actionGet(); + ClusterHealthResponse res = node.client() + .admin() + .cluster() + .health(new ClusterHealthRequest().waitForNodes("4").timeout(TimeValue.timeValueSeconds(5))) + .actionGet(); Assert.assertFalse(res.isTimedOut()); Assert.assertEquals(4, res.getNumberOfNodes()); Assert.assertEquals(4, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); diff --git a/src/test/java/org/opensearch/security/ssl/SSLTest.java b/src/test/java/org/opensearch/security/ssl/SSLTest.java index 65181d66b9..72ad6d5d16 100644 --- a/src/test/java/org/opensearch/security/ssl/SSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/SSLTest.java @@ -70,7 +70,7 @@ import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD; import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD; -@SuppressWarnings({"resource", "unchecked"}) +@SuppressWarnings({ "resource", "unchecked" }) public class SSLTest extends SingleClusterTest { @Rule @@ -82,19 +82,25 @@ public class SSLTest extends SingleClusterTest { public void testHttps() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "TLSv1.1", "TLSv1.2") - .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") - .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.1", "TLSv1.2") - .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "TLSv1.1", "TLSv1.2") + .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") + .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.1", "TLSv1.2") + .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .build(); setupSslOnlyMode(settings); @@ -105,14 +111,18 @@ public void testHttps() throws Exception { rh.keystore = "node-untspec5-keystore.p12"; System.out.println(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty&show_dn=true")); - Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty&show_dn=true").contains("EMAILADDRESS=unt@tst.com")); + Assert.assertTrue( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty&show_dn=true").contains("EMAILADDRESS=unt@tst.com") + ); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty&show_dn=true").contains("local_certificates_list")); - Assert.assertFalse(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty&show_dn=false").contains("local_certificates_list")); + Assert.assertFalse( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty&show_dn=false").contains("local_certificates_list") + ); Assert.assertFalse(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("local_certificates_list")); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); Assert.assertFalse(rh.executeSimpleRequest("_nodes/settings?pretty").contains("\"opendistro_security\"")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/settings?pretty").contains("keystore_filepath")); - //Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + // Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); } @@ -124,28 +134,35 @@ public void testCipherAndProtocols() throws Exception { System.out.println("allowOpenSSL: " + allowOpenSSL); Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - //WEAK and insecure cipher, do NOT use this, its here for unittesting only!!! - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "SSL_RSA_EXPORT_WITH_RC4_40_MD5") - //WEAK and insecure protocol, do NOT use this, its here for unittesting only!!! - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "SSLv3") - .put("client.type", "node") - .put("path.home", ".") - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + // WEAK and insecure cipher, do NOT use this, its here for unittesting only!!! + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "SSL_RSA_EXPORT_WITH_RC4_40_MD5") + // WEAK and insecure protocol, do NOT use this, its here for unittesting only!!! + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "SSLv3") + .put("client.type", "node") + .put("path.home", ".") + .build(); try { String[] enabledCiphers = new DefaultSecurityKeyStore(settings, Paths.get(".")).createHTTPSSLEngine().getEnabledCipherSuites(); String[] enabledProtocols = new DefaultSecurityKeyStore(settings, Paths.get(".")).createHTTPSSLEngine().getEnabledProtocols(); if (allowOpenSSL) { - Assert.assertEquals(2, enabledProtocols.length); //SSLv2Hello is always enabled when using openssl + Assert.assertEquals(2, enabledProtocols.length); // SSLv2Hello is always enabled when using openssl Assert.assertTrue("Check SSLv3", "SSLv3".equals(enabledProtocols[0]) || "SSLv3".equals(enabledProtocols[1])); Assert.assertEquals(1, enabledCiphers.length); Assert.assertEquals("TLS_RSA_EXPORT_WITH_RC4_40_MD5", enabledCiphers[0]); @@ -157,25 +174,32 @@ public void testCipherAndProtocols() throws Exception { } settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - //WEAK and insecure cipher, do NOT use this, its here for unittesting only!!! - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "SSL_RSA_EXPORT_WITH_RC4_40_MD5") - //WEAK and insecure protocol, do NOT use this, its here for unittesting only!!! - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "SSLv3") - .put("client.type", "node") - .put("path.home", ".") - .build(); - - enabledCiphers = new DefaultSecurityKeyStore(settings, Paths.get(".")).createServerTransportSSLEngine().getEnabledCipherSuites(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + // WEAK and insecure cipher, do NOT use this, its here for unittesting only!!! + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "SSL_RSA_EXPORT_WITH_RC4_40_MD5") + // WEAK and insecure protocol, do NOT use this, its here for unittesting only!!! + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "SSLv3") + .put("client.type", "node") + .put("path.home", ".") + .build(); + + enabledCiphers = new DefaultSecurityKeyStore(settings, Paths.get(".")).createServerTransportSSLEngine() + .getEnabledCipherSuites(); enabledProtocols = new DefaultSecurityKeyStore(settings, Paths.get(".")).createServerTransportSSLEngine().getEnabledProtocols(); if (allowOpenSSL) { - Assert.assertEquals(2, enabledProtocols.length); //SSLv2Hello is always enabled when using openssl + Assert.assertEquals(2, enabledProtocols.length); // SSLv2Hello is always enabled when using openssl Assert.assertTrue("Check SSLv3", "SSLv3".equals(enabledProtocols[0]) || "SSLv3".equals(enabledProtocols[1])); Assert.assertEquals(1, enabledCiphers.length); Assert.assertEquals("TLS_RSA_EXPORT_WITH_RC4_40_MD5", enabledCiphers[0]); @@ -185,11 +209,13 @@ public void testCipherAndProtocols() throws Exception { Assert.assertEquals(1, enabledCiphers.length); Assert.assertEquals("SSL_RSA_EXPORT_WITH_RC4_40_MD5", enabledCiphers[0]); } - enabledCiphers = new DefaultSecurityKeyStore(settings, Paths.get(".")).createClientTransportSSLEngine(null, -1).getEnabledCipherSuites(); - enabledProtocols = new DefaultSecurityKeyStore(settings, Paths.get(".")).createClientTransportSSLEngine(null, -1).getEnabledProtocols(); + enabledCiphers = new DefaultSecurityKeyStore(settings, Paths.get(".")).createClientTransportSSLEngine(null, -1) + .getEnabledCipherSuites(); + enabledProtocols = new DefaultSecurityKeyStore(settings, Paths.get(".")).createClientTransportSSLEngine(null, -1) + .getEnabledProtocols(); if (allowOpenSSL) { - Assert.assertEquals(2, enabledProtocols.length); //SSLv2Hello is always enabled when using openssl + Assert.assertEquals(2, enabledProtocols.length); // SSLv2Hello is always enabled when using openssl Assert.assertTrue("Check SSLv3", "SSLv3".equals(enabledProtocols[0]) || "SSLv3".equals(enabledProtocols[1])); Assert.assertEquals(1, enabledCiphers.length); Assert.assertEquals("TLS_RSA_EXPORT_WITH_RC4_40_MD5", enabledCiphers[0]); @@ -200,9 +226,18 @@ public void testCipherAndProtocols() throws Exception { Assert.assertEquals("SSL_RSA_EXPORT_WITH_RC4_40_MD5", enabledCiphers[0]); } } catch (OpenSearchSecurityException e) { - System.out.println("EXPECTED " + e.getClass().getSimpleName() + " for " + System.getProperty("java.specification.version") + ": " + e.toString()); + System.out.println( + "EXPECTED " + + e.getClass().getSimpleName() + + " for " + + System.getProperty("java.specification.version") + + ": " + + e.toString() + ); e.printStackTrace(); - Assert.assertTrue("Check if error contains 'no valid cipher suites' -> " + e.toString(), e.toString().contains("no valid cipher suites") + Assert.assertTrue( + "Check if error contains 'no valid cipher suites' -> " + e.toString(), + e.toString().contains("no valid cipher suites") || e.toString().contains("failed to set cipher suite") || e.toString().contains("Unable to configure permitted SSL ciphers") || e.toString().contains("OPENSSL_internal:NO_CIPHER_MATCH") @@ -215,13 +250,21 @@ public void testCipherAndProtocols() throws Exception { public void testHttpsOptionalAuth() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .build(); setupSslOnlyMode(settings); @@ -234,29 +277,44 @@ public void testHttpsOptionalAuth() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); Assert.assertFalse(rh.executeSimpleRequest("_nodes/settings?pretty").contains("\"opendistro_security\"")); - Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + Assert.assertTrue( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + ); } @Test public void testHttpsAndNodeSSL() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true).put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + + .build(); setupSslOnlyMode(settings); @@ -269,7 +327,9 @@ public void testHttpsAndNodeSSL() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); - Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + Assert.assertTrue( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + ); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"tx_size_in_bytes\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_count\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_size_in_bytes\" : 0")); @@ -281,22 +341,34 @@ public void testHttpsAndNodeSSL() throws Exception { public void testHttpsAndNodeSSLPKCS8Pem() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem") + ) + .build(); setupSslOnlyMode(settings); @@ -309,30 +381,47 @@ public void testHttpsAndNodeSSLPKCS8Pem() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); - //Assert.assertTrue(!executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("null")); - Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + // Assert.assertTrue(!executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("null")); + Assert.assertTrue( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + ); } @Test public void testHttpsAndNodeSSLPKCS1Pem() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-pkcs1.key.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-pkcs1.key.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-pkcs1.key.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-pkcs1.key.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem") + ) + .build(); setupSslOnlyMode(settings); @@ -344,7 +433,9 @@ public void testHttpsAndNodeSSLPKCS1Pem() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); - Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + Assert.assertTrue( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + ); } @Test @@ -354,23 +445,38 @@ public void testHttpsAndNodeSSLPemEnc() throws Exception { mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD.propertyName, "changeit"); final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .setSecureSettings(mockSecureSettings) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem") + ) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key")) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem") + ) + .setSecureSettings(mockSecureSettings) + .build(); setupSslOnlyMode(settings); @@ -383,32 +489,49 @@ public void testHttpsAndNodeSSLPemEnc() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); - //Assert.assertTrue(!executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("null")); - Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + // Assert.assertTrue(!executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("null")); + Assert.assertTrue( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + ); } @Test public void testSSLPemEncWithInsecureSettings() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key")) - // legacy insecure passwords - .put(SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD.insecurePropertyName, "changeit") - .put(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.insecurePropertyName, "changeit") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key") + ) + // legacy insecure passwords + .put(SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD.insecurePropertyName, "changeit") + .put(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.insecurePropertyName, "changeit") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.crt.pem") + ) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/pem/node-4.key")) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem") + ) + .build(); setupSslOnlyMode(settings); @@ -417,33 +540,46 @@ public void testSSLPemEncWithInsecureSettings() throws Exception { rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; - Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + Assert.assertTrue( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + ); } - @Test public void testHttpsAndNodeSSLFailedCipher() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "INVALID_CIPHER") - - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "INVALID_CIPHER") + + .build(); try { setupSslOnlyMode(settings); @@ -461,18 +597,25 @@ public void testHttpPlainFail() throws Exception { thrown.expect(NoHttpResponseException.class); final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "OPTIONAL") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "OPTIONAL") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .build(); setupSslOnlyMode(settings); - RestHelper rh = restHelper(); rh.enableHTTPClientSSL = false; rh.trustHTTPServerCertificate = true; @@ -480,21 +623,31 @@ public void testHttpPlainFail() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); - Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + Assert.assertTrue( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + ); } @Test public void testHttpsNoEnforce() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "NONE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "NONE") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .build(); setupSslOnlyMode(settings); @@ -505,21 +658,31 @@ public void testHttpsNoEnforce() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); - Assert.assertFalse(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + Assert.assertFalse( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + ); } @Test public void testHttpsEnforceFail() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .build(); setupSslOnlyMode(settings); @@ -532,7 +695,7 @@ public void testHttpsEnforceFail() throws Exception { rh.executeSimpleRequest(""); Assert.fail(); } catch (SocketException | SSLException e) { - //expected + // expected System.out.println("Expected SSLHandshakeException " + e.toString()); } catch (Exception e) { e.printStackTrace(); @@ -545,14 +708,22 @@ public void testHttpsV3Fail() throws Exception { thrown.expect(SSLHandshakeException.class); final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0").put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "NONE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "NONE") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .build(); setupSslOnlyMode(settings); @@ -570,34 +741,45 @@ public void testHttpsV3Fail() throws Exception { public void testNodeClientSSL() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + .build(); setupSslOnlyMode(settings); RestHelper rh = nonSslRestHelper(); final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) - .put("cluster.name", clusterInfo.clustername).put("path.home", ".") - .put("node.name", "client_node_" + new Random().nextInt()) - .put("path.data", "./target/data/" + clusterInfo.clustername + "/ssl/data") - .put("path.logs", "./target/data/" + clusterInfo.clustername + "/ssl/logs") - .put("path.home", "./target") - .put("discovery.initial_state_timeout", "8s") - .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) - .put(settings)// ----- - .build(); + .put("cluster.name", clusterInfo.clustername) + .put("path.home", ".") + .put("node.name", "client_node_" + new Random().nextInt()) + .put("path.data", "./target/data/" + clusterInfo.clustername + "/ssl/data") + .put("path.logs", "./target/data/" + clusterInfo.clustername + "/ssl/logs") + .put("path.home", "./target") + .put("discovery.initial_state_timeout", "8s") + .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) + .put(settings)// ----- + .build(); try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { - ClusterHealthResponse res = node.client().admin().cluster().health(new ClusterHealthRequest().waitForNodes("4").timeout(TimeValue.timeValueSeconds(15))).actionGet(); + ClusterHealthResponse res = node.client() + .admin() + .cluster() + .health(new ClusterHealthRequest().waitForNodes("4").timeout(TimeValue.timeValueSeconds(15))) + .actionGet(); Assert.assertFalse(res.isTimedOut()); Assert.assertEquals(4, res.getNumberOfNodes()); Assert.assertEquals(4, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); @@ -635,7 +817,7 @@ public void testUnmodifieableCipherProtocolConfig() throws Exception { SSLConfigConstants.getSecureSSLCiphers(Settings.EMPTY, false).set(0, "bogus"); Assert.fail(); } catch (UnsupportedOperationException e) { - //expected + // expected } } @@ -643,20 +825,33 @@ public void testUnmodifieableCipherProtocolConfig() throws Exception { public void testCustomPrincipalExtractor() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PRINCIPAL_EXTRACTOR_CLASS, "org.opensearch.security.ssl.TestPrincipalExtractor") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")).build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PRINCIPAL_EXTRACTOR_CLASS, "org.opensearch.security.ssl.TestPrincipalExtractor") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .build(); setupSslOnlyMode(settings); @@ -667,7 +862,11 @@ public void testCustomPrincipalExtractor() throws Exception { log.debug("OpenSearch started"); - final Settings tcSettings = Settings.builder().put("cluster.name", clusterInfo.clustername).put("path.home", ".").put(settings).build(); + final Settings tcSettings = Settings.builder() + .put("cluster.name", clusterInfo.clustername) + .put("path.home", ".") + .put(settings) + .build(); try (Client tc = getClient()) { @@ -676,7 +875,12 @@ public void testCustomPrincipalExtractor() throws Exception { Assert.assertEquals(3, tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); log.debug("Client connected"); TestPrincipalExtractor.reset(); - Assert.assertEquals("test", tc.index(new IndexRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"a\":5}", XContentType.JSON)).actionGet().getIndex()); + Assert.assertEquals( + "test", + tc.index(new IndexRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"a\":5}", XContentType.JSON)) + .actionGet() + .getIndex() + ); log.debug("Index created"); Assert.assertEquals(1L, tc.search(new SearchRequest("test")).actionGet().getHits().getTotalHits().value); log.debug("Search done"); @@ -688,8 +892,8 @@ public void testCustomPrincipalExtractor() throws Exception { rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty"); - //we need to test this in SG itself because in the SSL only plugin the info is not longer propagated - //Assert.assertTrue(TestPrincipalExtractor.getTransportCount() > 0); + // we need to test this in SG itself because in the SSL only plugin the info is not longer propagated + // Assert.assertTrue(TestPrincipalExtractor.getTransportCount() > 0); Assert.assertTrue(TestPrincipalExtractor.getHttpCount() > 0); } @@ -697,24 +901,36 @@ public void testCustomPrincipalExtractor() throws Exception { public void testCRLPem() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/chain-ca.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATE, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATION_DATE, CertificateValidatorTest.CRL_DATE.getTime()) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/chain-ca.pem") + ) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATE, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATION_DATE, CertificateValidatorTest.CRL_DATE.getTime()) + .build(); setupSslOnlyMode(settings); @@ -730,19 +946,25 @@ public void testCRLPem() throws Exception { public void testCRL() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATE, true) - .put(SSLConfigConstants.SSECURITY_SSL_HTTP_CRL_FILE, FileHelper.getAbsoluteFilePathFromClassPath("ssl/crl/revoked.crl")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATION_DATE, CertificateValidatorTest.CRL_DATE.getTime()) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, false) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATE, true) + .put(SSLConfigConstants.SSECURITY_SSL_HTTP_CRL_FILE, FileHelper.getAbsoluteFilePathFromClassPath("ssl/crl/revoked.crl")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CRL_VALIDATION_DATE, CertificateValidatorTest.CRL_DATE.getTime()) + .build(); setupSslOnlyMode(settings); @@ -758,40 +980,50 @@ public void testCRL() throws Exception { @Test public void testNodeClientSSLwithJavaTLSv13() throws Exception { - //Java TLS 1.3 is available since Java 11 + // Java TLS 1.3 is available since Java 11 Assume.assumeTrue(!allowOpenSSL && PlatformDependent.javaVersion() >= 11); final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.3") - .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_AES_128_GCM_SHA256") - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.3") + .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_AES_128_GCM_SHA256") + .build(); setupSslOnlyMode(settings); RestHelper rh = nonSslRestHelper(); final Settings tcSettings = Settings.builder() - .put("cluster.name", clusterInfo.clustername) - .put("path.data", "./target/data/" + clusterInfo.clustername + "/ssl/data") - .put("path.logs", "./target/data/" + clusterInfo.clustername + "/ssl/logs") - .put("path.home", "./target") - .put("node.name", "client_node_" + new Random().nextInt()) - .put("discovery.initial_state_timeout", "8s") - .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) - .put(settings)// ----- - .build(); + .put("cluster.name", clusterInfo.clustername) + .put("path.data", "./target/data/" + clusterInfo.clustername + "/ssl/data") + .put("path.logs", "./target/data/" + clusterInfo.clustername + "/ssl/logs") + .put("path.home", "./target") + .put("node.name", "client_node_" + new Random().nextInt()) + .put("discovery.initial_state_timeout", "8s") + .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) + .put(settings)// ----- + .build(); try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { - ClusterHealthResponse res = node.client().admin().cluster().health(new ClusterHealthRequest().waitForNodes("4").timeout(TimeValue.timeValueSeconds(5))).actionGet(); + ClusterHealthResponse res = node.client() + .admin() + .cluster() + .health(new ClusterHealthRequest().waitForNodes("4").timeout(TimeValue.timeValueSeconds(5))) + .actionGet(); Assert.assertFalse(res.isTimedOut()); Assert.assertEquals(4, res.getNumberOfNodes()); Assert.assertEquals(4, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); @@ -807,20 +1039,32 @@ public void testNodeClientSSLwithJavaTLSv13() throws Exception { public void testTLSv12() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "TLSv1.2") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "TLSv1.2") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .build(); setupSslOnlyMode(settings); @@ -831,7 +1075,6 @@ public void testTLSv12() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"tx_size_in_bytes\"")); } - @Test public void testHttpsAndNodeSSLKeyPass() throws Exception { final MockSecureSettings mockSecureSettings = new MockSecureSettings(); @@ -839,24 +1082,36 @@ public void testHttpsAndNodeSSLKeyPass() throws Exception { mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD.propertyName, "changeit"); final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .setSecureSettings(mockSecureSettings) - - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .setSecureSettings(mockSecureSettings) + + .build(); setupSslOnlyMode(settings); @@ -869,7 +1124,9 @@ public void testHttpsAndNodeSSLKeyPass() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); - Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + Assert.assertTrue( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + ); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"tx_size_in_bytes\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_count\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_size_in_bytes\" : 0")); @@ -885,30 +1142,43 @@ public void testHttpsAndNodeSSLKeyStoreExtendedUsageEnabled() throws Exception { mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.propertyName, "changeit"); final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_ALIAS, "node-0-client") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_KEYSTORE_ALIAS, "node-0-server") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_TRUSTSTORE_ALIAS, "root-ca") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_TRUSTSTORE_ALIAS, "root-ca") - - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/truststore.jks")) - - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true).put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .setSecureSettings(mockSecureSettings) - - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_KEYSTORE_ALIAS, "node-0-client") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_KEYSTORE_ALIAS, "node-0-server") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_TRUSTSTORE_ALIAS, "root-ca") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_TRUSTSTORE_ALIAS, "root-ca") + + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/truststore.jks") + ) + + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .setSecureSettings(mockSecureSettings) + + .build(); setupSslOnlyMode(settings); @@ -921,7 +1191,9 @@ public void testHttpsAndNodeSSLKeyStoreExtendedUsageEnabled() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); - Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + Assert.assertTrue( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + ); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"tx_size_in_bytes\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_count\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_size_in_bytes\" : 0")); @@ -936,25 +1208,36 @@ public void testHttpsAndNodeSSLKeyPassFail() throws Exception { mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_KEYSTORE_KEYPASSWORD.propertyName, "wrongpass"); final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks")) - .setSecureSettings(mockSecureSettings) - - - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/truststore.jks") + ) + .setSecureSettings(mockSecureSettings) + + .build(); setupSslOnlyMode(settings); @@ -971,26 +1254,47 @@ public void testHttpsAndNodeSSLKeyPassFail() throws Exception { public void testHttpsAndNodeSSLPemExtendedUsageEnabled() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-client.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-key-client.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/root-ca.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-server.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-key-server.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/root-ca.pem")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) - - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem")) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(ConfigConstants.SECURITY_SSL_ONLY, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED, true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMCERT_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-client.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMKEY_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-key-client.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_CLIENT_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/root-ca.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMCERT_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-server.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMKEY_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-key-server.pem") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_SERVER_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/root-ca.pem") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) + + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem")) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem")) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem") + ) + .build(); setupSslOnlyMode(settings); @@ -1003,6 +1307,8 @@ public void testHttpsAndNodeSSLPemExtendedUsageEnabled() throws Exception { Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("TLS")); Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").length() > 0); Assert.assertTrue(rh.executeSimpleRequest("_nodes/settings?pretty").contains(clusterInfo.clustername)); - Assert.assertTrue(rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE")); + Assert.assertTrue( + rh.executeSimpleRequest("_opendistro/_security/sslinfo?pretty").contains("CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + ); } } diff --git a/src/test/java/org/opensearch/security/ssl/SecureSSLSettingsTest.java b/src/test/java/org/opensearch/security/ssl/SecureSSLSettingsTest.java index d60c2cfcc5..beb170a31a 100644 --- a/src/test/java/org/opensearch/security/ssl/SecureSSLSettingsTest.java +++ b/src/test/java/org/opensearch/security/ssl/SecureSSLSettingsTest.java @@ -31,9 +31,7 @@ public void testGetSecureSetting() { @Test public void testGetInsecureSetting() { - final var settings = Settings.builder() - .put(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.insecurePropertyName, "test-password") - .build(); + final var settings = Settings.builder().put(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.insecurePropertyName, "test-password").build(); final var password = SECURITY_SSL_HTTP_PEMKEY_PASSWORD.getSetting(settings); Assert.assertEquals("test-password", password); } @@ -43,9 +41,9 @@ public void testShouldFavorSecureOverInsecureSetting() { final var mockSecureSettings = new MockSecureSettings(); mockSecureSettings.setString(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.propertyName, "secure-password"); final var settings = Settings.builder() - .setSecureSettings(mockSecureSettings) - .put(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.insecurePropertyName, "insecure-password") - .build(); + .setSecureSettings(mockSecureSettings) + .put(SECURITY_SSL_HTTP_PEMKEY_PASSWORD.insecurePropertyName, "insecure-password") + .build(); final var password = SECURITY_SSL_HTTP_PEMKEY_PASSWORD.getSetting(settings); Assert.assertEquals("secure-password", password); } diff --git a/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java b/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java index e1c8ec7282..ee1cd585f7 100644 --- a/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java +++ b/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java @@ -50,20 +50,31 @@ public class SecuritySSLReloadCertsActionTests extends SingleClusterTest { private final List> NODE_CERT_DETAILS = ImmutableList.of( ImmutableMap.of( - "issuer_dn", "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", - "subject_dn", "CN=node-1.example.com,OU=SSL,O=Test,L=Test,C=DE", - "san", "[[8, 1.2.3.4.5.5], [0, [2.5.4.3, node-1.example.com]], [2, node-1.example.com], [2, localhost], [7, 127.0.0.1]]", - "not_before", "2023-04-14T13:22:53Z", - "not_after", "2033-04-11T13:22:53Z" - )); + "issuer_dn", + "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", + "subject_dn", + "CN=node-1.example.com,OU=SSL,O=Test,L=Test,C=DE", + "san", + "[[8, 1.2.3.4.5.5], [0, [2.5.4.3, node-1.example.com]], [2, node-1.example.com], [2, localhost], [7, 127.0.0.1]]", + "not_before", + "2023-04-14T13:22:53Z", + "not_after", + "2033-04-11T13:22:53Z" + ) + ); private final List> NEW_NODE_CERT_DETAILS = ImmutableList.of( ImmutableMap.of( - "issuer_dn", "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", - "subject_dn", "CN=node-1.example.com,OU=SSL,O=Test,L=Test,C=DE", - "san", "[[8, 1.2.3.4.5.5], [0, [2.5.4.3, node-1.example.com]], [2, node-1.example.com], [2, localhost], [7, 127.0.0.1]]", - "not_before", "2023-04-14T13:23:00Z", - "not_after", "2033-04-11T13:23:00Z" + "issuer_dn", + "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", + "subject_dn", + "CN=node-1.example.com,OU=SSL,O=Test,L=Test,C=DE", + "san", + "[[8, 1.2.3.4.5.5], [0, [2.5.4.3, node-1.example.com]], [2, node-1.example.com], [2, localhost], [7, 127.0.0.1]]", + "not_before", + "2023-04-14T13:23:00Z", + "not_after", + "2033-04-11T13:23:00Z" ) ); @@ -71,7 +82,7 @@ public class SecuritySSLReloadCertsActionTests extends SingleClusterTest { private String pemKeyFilePath; private final String defaultCertFilePath = "ssl/reload/node.crt.pem"; private final String defaultKeyFilePath = "ssl/reload/node.key.pem"; - private final String newCertFilePath = "ssl/reload/node-new.crt.pem"; + private final String newCertFilePath = "ssl/reload/node-new.crt.pem"; private final String newKeyFilePath = "ssl/reload/node-new.key.pem"; @Before @@ -138,8 +149,11 @@ public void testSSLReloadFail_InvalidDNAndDate() throws Exception { RestHelper.HttpResponse reloadCertsResponse = rh.executePutRequest(RELOAD_TRANSPORT_CERTS_ENDPOINT, null); Assert.assertEquals(500, reloadCertsResponse.getStatusCode()); JSONObject expectedResponse = new JSONObject(); - expectedResponse.appendField("error", "OpenSearchSecurityException[Error while initializing transport SSL layer from PEM: java.lang.Exception: " + - "New Certs do not have valid Issuer DN, Subject DN or SAN.]; nested: Exception[New Certs do not have valid Issuer DN, Subject DN or SAN.];"); + expectedResponse.appendField( + "error", + "OpenSearchSecurityException[Error while initializing transport SSL layer from PEM: java.lang.Exception: " + + "New Certs do not have valid Issuer DN, Subject DN or SAN.]; nested: Exception[New Certs do not have valid Issuer DN, Subject DN or SAN.];" + ); Assert.assertEquals(expectedResponse.toString(), reloadCertsResponse.getBody()); } @@ -261,7 +275,13 @@ private void initClusterWithTestCerts() throws Exception { * @param httpPemKeyFilePath Absolute Path to transport pem key file * @param sslCertReload Sets the ssl cert reload flag */ - private void initTestCluster(final String transportPemCertFilePath, final String transportPemKeyFilePath, final String httpPemCertFilePath, final String httpPemKeyFilePath, final boolean sslCertReload) throws Exception { + private void initTestCluster( + final String transportPemCertFilePath, + final String transportPemKeyFilePath, + final String httpPemCertFilePath, + final String httpPemKeyFilePath, + final boolean sslCertReload + ) throws Exception { final Settings settings = Settings.builder() .putList(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, "CN=kirk,OU=client,O=client,L=Test,C=DE") .putList(ConfigConstants.SECURITY_NODES_DN, "CN=node-1.example.com,OU=SSL,O=Test,L=Test,C=DE") @@ -271,19 +291,29 @@ private void initTestCluster(final String transportPemCertFilePath, final String .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, false) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, transportPemCertFilePath) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, transportPemKeyFilePath) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/reload/root-ca.pem")) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/reload/root-ca.pem") + ) .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH, httpPemCertFilePath) // "ssl/reload/node.crt.pem" .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH, httpPemKeyFilePath) // "ssl/reload/node.key.pem" - .put(SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("ssl/reload/root-ca.pem")) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/reload/root-ca.pem") + ) .put(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, sslCertReload) .build(); final Settings initTransportClientSettings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ssl/reload/truststore.jks")) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/reload/truststore.jks") + ) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("ssl/reload/kirk-keystore.jks")) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("ssl/reload/kirk-keystore.jks") + ) .build(); setup(initTransportClientSettings, new DynamicSecurityConfig(), settings, true, clusterConfiguration); diff --git a/src/test/java/org/opensearch/security/ssl/TestPrincipalExtractor.java b/src/test/java/org/opensearch/security/ssl/TestPrincipalExtractor.java index 0dfaa557e1..886df37b91 100644 --- a/src/test/java/org/opensearch/security/ssl/TestPrincipalExtractor.java +++ b/src/test/java/org/opensearch/security/ssl/TestPrincipalExtractor.java @@ -26,16 +26,15 @@ public class TestPrincipalExtractor implements PrincipalExtractor { private static int transportCount = 0; private static int httpCount = 0; - public TestPrincipalExtractor() { - } + public TestPrincipalExtractor() {} @Override public String extractPrincipal(X509Certificate x509Certificate, Type type) { - if(type == Type.HTTP) { + if (type == Type.HTTP) { httpCount++; } - if(type == Type.TRANSPORT) { + if (type == Type.TRANSPORT) { transportCount++; } @@ -51,8 +50,8 @@ public static int getHttpCount() { } public static void reset() { - httpCount = 0; - transportCount = 0; + httpCount = 0; + transportCount = 0; } } diff --git a/src/test/java/org/opensearch/security/ssl/transport/DualModeSSLHandlerTests.java b/src/test/java/org/opensearch/security/ssl/transport/DualModeSSLHandlerTests.java index 9016a8a3db..79d53f4d7b 100644 --- a/src/test/java/org/opensearch/security/ssl/transport/DualModeSSLHandlerTests.java +++ b/src/test/java/org/opensearch/security/ssl/transport/DualModeSSLHandlerTests.java @@ -76,10 +76,8 @@ public void testValidTLSMessage() throws Exception { handler.decode(ctx, buffer, null); // ensure ssl handler is added Mockito.verify(ctx, Mockito.times(1)).pipeline(); - Mockito.verify(pipeline, Mockito.times(1)) - .addAfter("port_unification_handler", "ssl_server", sslHandler); - Mockito.verify(pipeline, - Mockito.times(1)).remove(handler); + Mockito.verify(pipeline, Mockito.times(1)).addAfter("port_unification_handler", "ssl_server", sslHandler); + Mockito.verify(pipeline, Mockito.times(1)).remove(handler); } @Test @@ -95,10 +93,8 @@ public void testNonTLSMessage() throws Exception { handler.decode(ctx, buffer, null); // ensure ssl handler is added Mockito.verify(ctx, Mockito.times(1)).pipeline(); - Mockito.verify(pipeline, Mockito.times(0)) - .addAfter("port_unification_handler", "ssl_server", sslHandler); - Mockito.verify(pipeline, - Mockito.times(1)).remove(handler); + Mockito.verify(pipeline, Mockito.times(0)).addAfter("port_unification_handler", "ssl_server", sslHandler); + Mockito.verify(pipeline, Mockito.times(1)).remove(handler); } @Test diff --git a/src/test/java/org/opensearch/security/ssl/util/CertFromFileTests.java b/src/test/java/org/opensearch/security/ssl/util/CertFromFileTests.java index 383c60147c..097d65472c 100644 --- a/src/test/java/org/opensearch/security/ssl/util/CertFromFileTests.java +++ b/src/test/java/org/opensearch/security/ssl/util/CertFromFileTests.java @@ -22,60 +22,65 @@ public class CertFromFileTests { @Test public void testLoadSameCertForClientServerUsage() throws Exception { - CertFileProps certProps = new CertFileProps( - FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem").toString(), - FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem").toString(), - FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem").toString(), - null); - - CertFromFile cert = new CertFromFile(certProps); - - Assert.assertEquals(1, cert.getCerts().length); - Assert.assertNotNull(cert.getClientPemCert()); - Assert.assertNotNull(cert.getClientPemKey()); - Assert.assertNotNull(cert.getClientTrustedCas()); + CertFileProps certProps = new CertFileProps( + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem").toString(), + FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem").toString(), + FileHelper.getAbsoluteFilePathFromClassPath("ssl/root-ca.pem").toString(), + null + ); + + CertFromFile cert = new CertFromFile(certProps); + + Assert.assertEquals(1, cert.getCerts().length); + Assert.assertNotNull(cert.getClientPemCert()); + Assert.assertNotNull(cert.getClientPemKey()); + Assert.assertNotNull(cert.getClientTrustedCas()); } - @Test - public void testLoadCertWithoutCA() throws Exception { + @Test + public void testLoadCertWithoutCA() throws Exception { CertFileProps certProps = new CertFileProps( FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.crt.pem").toString(), FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem").toString(), null, - null); + null + ); CertFromFile cert = new CertFromFile(certProps); Assert.assertNull(cert.getClientTrustedCas()); } - @Test(expected= FileNotFoundException.class) + @Test(expected = FileNotFoundException.class) public void testLoadCertWithMissingFiles() throws Exception { CertFileProps certProps = new CertFileProps( "missing.pem", FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0.key.pem").toString(), null, - null); + null + ); CertFromFile cert = new CertFromFile(certProps); } @Test public void testLoadDifferentCertsForClientServerUsage() throws Exception { - CertFileProps clientCertProps = new CertFileProps( - FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-client.pem").toString(), - FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-key-client.pem").toString(), - FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/root-ca.pem").toString(), - null); - CertFileProps servertCertProps = new CertFileProps( - FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-server.pem").toString(), - FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-key-server.pem").toString(), - FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/root-ca.pem").toString(), - null); - - CertFromFile cert = new CertFromFile(clientCertProps, servertCertProps); - - Assert.assertEquals(2, cert.getCerts().length); + CertFileProps clientCertProps = new CertFileProps( + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-client.pem").toString(), + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-key-client.pem").toString(), + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/root-ca.pem").toString(), + null + ); + CertFileProps servertCertProps = new CertFileProps( + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-server.pem").toString(), + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-key-server.pem").toString(), + FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/root-ca.pem").toString(), + null + ); + + CertFromFile cert = new CertFromFile(clientCertProps, servertCertProps); + + Assert.assertEquals(2, cert.getCerts().length); } } diff --git a/src/test/java/org/opensearch/security/ssl/util/CertFromKeystoreTests.java b/src/test/java/org/opensearch/security/ssl/util/CertFromKeystoreTests.java index 0a2cac18b5..fb97fdcfd1 100644 --- a/src/test/java/org/opensearch/security/ssl/util/CertFromKeystoreTests.java +++ b/src/test/java/org/opensearch/security/ssl/util/CertFromKeystoreTests.java @@ -25,7 +25,8 @@ public class CertFromKeystoreTests { @Test - public void testLoadSameCertForClientServerUsage() throws UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { + public void testLoadSameCertForClientServerUsage() throws UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, + KeyStoreException, IOException { KeystoreProps props = new KeystoreProps( FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks").toString(), "JKS", @@ -43,7 +44,8 @@ public void testLoadSameCertForClientServerUsage() throws UnrecoverableKeyExcept } @Test - public void testLoadSameCertWithoutAlias() throws UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { + public void testLoadSameCertWithoutAlias() throws UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, + KeyStoreException, IOException { KeystoreProps props = new KeystoreProps( FileHelper.getAbsoluteFilePathFromClassPath("ssl/node-0-keystore.jks").toString(), "JKS", @@ -58,7 +60,8 @@ public void testLoadSameCertWithoutAlias() throws UnrecoverableKeyException, Cer } @Test - public void testLoadDifferentCertsForClientServerUsage() throws UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { + public void testLoadDifferentCertsForClientServerUsage() throws UnrecoverableKeyException, CertificateException, + NoSuchAlgorithmException, KeyStoreException, IOException { KeystoreProps props = new KeystoreProps( FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/node-0-keystore.jks").toString(), "JKS", diff --git a/src/test/java/org/opensearch/security/ssl/util/CertFromTruststoreTests.java b/src/test/java/org/opensearch/security/ssl/util/CertFromTruststoreTests.java index ed0f0ac252..e4ab860759 100644 --- a/src/test/java/org/opensearch/security/ssl/util/CertFromTruststoreTests.java +++ b/src/test/java/org/opensearch/security/ssl/util/CertFromTruststoreTests.java @@ -24,7 +24,8 @@ public class CertFromTruststoreTests { @Test - public void testLoadSameCertForClientServerUsage() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { + public void testLoadSameCertForClientServerUsage() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, + IOException { KeystoreProps props = new KeystoreProps( FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/truststore.jks").toString(), "JKS", @@ -50,7 +51,8 @@ public void testLoadSameCertWithoutAlias() throws CertificateException, NoSuchAl Assert.assertEquals(1, cert.getClientTrustedCerts().length); } - public void testLoadDifferentCertsForClientServerUsage() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { + public void testLoadDifferentCertsForClientServerUsage() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, + IOException { KeystoreProps props = new KeystoreProps( FileHelper.getAbsoluteFilePathFromClassPath("ssl/extended_key_usage/truststore.jks").toString(), "JKS", diff --git a/src/test/java/org/opensearch/security/ssl/util/SSLConnectionTestUtilTests.java b/src/test/java/org/opensearch/security/ssl/util/SSLConnectionTestUtilTests.java index 1400b0d401..98ee95a90b 100644 --- a/src/test/java/org/opensearch/security/ssl/util/SSLConnectionTestUtilTests.java +++ b/src/test/java/org/opensearch/security/ssl/util/SSLConnectionTestUtilTests.java @@ -43,16 +43,22 @@ public void setup() { public void testConnectionSSLAvailable() throws Exception { Mockito.doNothing().when(outputStreamWriter).write(Mockito.anyString()); Mockito.when(inputStreamReader.read()) - .thenReturn((int)'D') - .thenReturn((int)'U') - .thenReturn((int)'A') - .thenReturn((int)'L') - .thenReturn((int)'S') - .thenReturn((int)'M') + .thenReturn((int) 'D') + .thenReturn((int) 'U') + .thenReturn((int) 'A') + .thenReturn((int) 'L') + .thenReturn((int) 'S') + .thenReturn((int) 'M') .thenReturn(-1); Mockito.doNothing().when(socket).close(); - SSLConnectionTestUtil connectionTestUtil = new SSLConnectionTestUtil("127.0.0.1", 443, socket, outputStreamWriter, inputStreamReader); + SSLConnectionTestUtil connectionTestUtil = new SSLConnectionTestUtil( + "127.0.0.1", + 443, + socket, + outputStreamWriter, + inputStreamReader + ); SSLConnectionTestResult result = connectionTestUtil.testConnection(); verifyClientHelloSend(); @@ -66,7 +72,13 @@ public void testConnectionSSLNotAvailable() throws Exception { setupMocksForOpenSearchPingSuccess(); Mockito.doNothing().when(socket).close(); - SSLConnectionTestUtil connectionTestUtil = new SSLConnectionTestUtil("127.0.0.1", 443, socket, outputStreamWriter, inputStreamReader); + SSLConnectionTestUtil connectionTestUtil = new SSLConnectionTestUtil( + "127.0.0.1", + 443, + socket, + outputStreamWriter, + inputStreamReader + ); SSLConnectionTestResult result = connectionTestUtil.testConnection(); verifyClientHelloSend(); @@ -77,13 +89,17 @@ public void testConnectionSSLNotAvailable() throws Exception { @Test public void testConnectionSSLNotAvailableIOException() throws Exception { - Mockito.doThrow(new IOException("Error while writing bytes to output stream")) - .when(outputStreamWriter) - .write(Mockito.anyString()); + Mockito.doThrow(new IOException("Error while writing bytes to output stream")).when(outputStreamWriter).write(Mockito.anyString()); setupMocksForOpenSearchPingSuccess(); Mockito.doNothing().when(socket).close(); - SSLConnectionTestUtil connectionTestUtil = new SSLConnectionTestUtil("127.0.0.1", 443, socket, outputStreamWriter, inputStreamReader); + SSLConnectionTestUtil connectionTestUtil = new SSLConnectionTestUtil( + "127.0.0.1", + 443, + socket, + outputStreamWriter, + inputStreamReader + ); SSLConnectionTestResult result = connectionTestUtil.testConnection(); verifyClientHelloSend(); @@ -99,11 +115,16 @@ public void testConnectionOpenSearchPingFailed() throws Exception { Mockito.when(socket.getOutputStream()).thenReturn(outputStream); Mockito.when(socket.getInputStream()).thenReturn(inputStream); Mockito.doNothing().when(outputStream).write(Mockito.any(byte[].class)); - Mockito.when(inputStream.read()) - .thenReturn(-1); + Mockito.when(inputStream.read()).thenReturn(-1); Mockito.doNothing().when(socket).close(); - SSLConnectionTestUtil connectionTestUtil = new SSLConnectionTestUtil("127.0.0.1", 443, socket, outputStreamWriter, inputStreamReader); + SSLConnectionTestUtil connectionTestUtil = new SSLConnectionTestUtil( + "127.0.0.1", + 443, + socket, + outputStreamWriter, + inputStreamReader + ); SSLConnectionTestResult result = connectionTestUtil.testConnection(); verifyClientHelloSend(); @@ -119,15 +140,21 @@ public void testConnectionOpenSearchPingFailedInvalidReply() throws Exception { Mockito.when(socket.getInputStream()).thenReturn(inputStream); Mockito.doNothing().when(outputStream).write(Mockito.any(byte[].class)); Mockito.when(inputStream.read()) - .thenReturn((int)'E') - .thenReturn((int)'E') + .thenReturn((int) 'E') + .thenReturn((int) 'E') .thenReturn(0xFF) .thenReturn(0xFF) .thenReturn(0xFF) .thenReturn(0xFF); Mockito.doNothing().when(socket).close(); - SSLConnectionTestUtil connectionTestUtil = new SSLConnectionTestUtil("127.0.0.1", 443, socket, outputStreamWriter, inputStreamReader); + SSLConnectionTestUtil connectionTestUtil = new SSLConnectionTestUtil( + "127.0.0.1", + 443, + socket, + outputStreamWriter, + inputStreamReader + ); SSLConnectionTestResult result = connectionTestUtil.testConnection(); verifyClientHelloSend(); @@ -144,7 +171,13 @@ public void testConnectionOpenSearchPingFailedIOException() throws Exception { Mockito.doThrow(new IOException("Error while writing bytes to output stream")).when(outputStream).write(Mockito.any(byte[].class)); Mockito.doNothing().when(socket).close(); - SSLConnectionTestUtil connectionTestUtil = new SSLConnectionTestUtil("127.0.0.1", 443, socket, outputStreamWriter, inputStreamReader); + SSLConnectionTestUtil connectionTestUtil = new SSLConnectionTestUtil( + "127.0.0.1", + 443, + socket, + outputStreamWriter, + inputStreamReader + ); SSLConnectionTestResult result = connectionTestUtil.testConnection(); verifyClientHelloSend(); @@ -156,9 +189,7 @@ public void testConnectionOpenSearchPingFailedIOException() throws Exception { private void verifyClientHelloSend() throws IOException { ArgumentCaptor clientHelloMsgArgCaptor = ArgumentCaptor.forClass(String.class); - Mockito.verify(outputStreamWriter, - Mockito.times(1)) - .write(clientHelloMsgArgCaptor.capture()); + Mockito.verify(outputStreamWriter, Mockito.times(1)).write(clientHelloMsgArgCaptor.capture()); String msgWritten = clientHelloMsgArgCaptor.getValue(); String expectedMsg = "DUALCM"; Assert.assertEquals("Unexpected Dual SSL Client Hello message written to socket", expectedMsg, msgWritten); @@ -166,20 +197,17 @@ private void verifyClientHelloSend() throws IOException { private void verifyOpenSearchPingSend() throws IOException { ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(byte[].class); - Mockito.verify(outputStream, - Mockito.times(1)) - .write(argumentCaptor.capture()); + Mockito.verify(outputStream, Mockito.times(1)).write(argumentCaptor.capture()); byte[] bytesWritten = argumentCaptor.getValue(); - byte[] expectedBytes = new byte[]{'E','S',(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF}; - for(int i = 0; i < bytesWritten.length; i++) { + byte[] expectedBytes = new byte[] { 'E', 'S', (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; + for (int i = 0; i < bytesWritten.length; i++) { Assert.assertEquals("Unexpected OpenSearch Ping bytes written to socket", expectedBytes[i], bytesWritten[i]); } } private void setupMocksForClientHelloFailure() throws IOException { Mockito.doNothing().when(outputStreamWriter).write(Mockito.anyString()); - Mockito.when(inputStreamReader.read()) - .thenReturn(-1); + Mockito.when(inputStreamReader.read()).thenReturn(-1); } private void setupMocksForOpenSearchPingSuccess() throws IOException { @@ -187,8 +215,8 @@ private void setupMocksForOpenSearchPingSuccess() throws IOException { Mockito.when(socket.getInputStream()).thenReturn(inputStream); Mockito.doNothing().when(outputStream).write(Mockito.any(byte[].class)); Mockito.when(inputStream.read()) - .thenReturn((int)'E') - .thenReturn((int)'S') + .thenReturn((int) 'E') + .thenReturn((int) 'S') .thenReturn(0xFF) .thenReturn(0xFF) .thenReturn(0xFF) diff --git a/src/test/java/org/opensearch/security/ssl/util/TLSUtilTests.java b/src/test/java/org/opensearch/security/ssl/util/TLSUtilTests.java index 83ef5668ac..c77f05b39f 100644 --- a/src/test/java/org/opensearch/security/ssl/util/TLSUtilTests.java +++ b/src/test/java/org/opensearch/security/ssl/util/TLSUtilTests.java @@ -49,7 +49,7 @@ public void testSSLUtilWrongTLSVersion() { for (int byteToSend = 20; byteToSend <= 24; byteToSend++) { ByteBuf buffer = ALLOCATOR.buffer(5); buffer.writeByte(byteToSend); - //setting invalid TLS version 100 + // setting invalid TLS version 100 buffer.writeByte(100); buffer.writeByte(TLS_MINOR_VERSION); buffer.writeByte(100); @@ -66,7 +66,7 @@ public void testSSLUtilInvalidContentLength() { buffer.writeByte(byteToSend); buffer.writeByte(TLS_MAJOR_VERSION); buffer.writeByte(TLS_MINOR_VERSION); - //setting content length as 0 + // setting content length as 0 buffer.writeShort(0); Assert.assertFalse(TLSUtil.isTLS(buffer)); } diff --git a/src/test/java/org/opensearch/security/support/GuardedSearchOperationWrapperTest.java b/src/test/java/org/opensearch/security/support/GuardedSearchOperationWrapperTest.java index 982d1108ad..67090e9b3d 100644 --- a/src/test/java/org/opensearch/security/support/GuardedSearchOperationWrapperTest.java +++ b/src/test/java/org/opensearch/security/support/GuardedSearchOperationWrapperTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; - public class GuardedSearchOperationWrapperTest { @Test @@ -115,26 +114,21 @@ public void onQueryPhase(SearchContext context, long tookInNanos) { private static class DefaultingGuardedSearchOperationWrapper implements GuardedSearchOperationWrapper { @Override - public void onNewReaderContext(ReaderContext readerContext) { - } + public void onNewReaderContext(ReaderContext readerContext) {} @Override - public void onNewScrollContext(ReaderContext readerContext) { - } + public void onNewScrollContext(ReaderContext readerContext) {} @Override - public void onPreQueryPhase(SearchContext context) { - } + public void onPreQueryPhase(SearchContext context) {} @Override - public void onQueryPhase(SearchContext searchContext, long tookInNanos) { - } + public void onQueryPhase(SearchContext searchContext, long tookInNanos) {} @Override - public void validateReaderContext(ReaderContext readerContext, TransportRequest transportRequest) { - } + public void validateReaderContext(ReaderContext readerContext, TransportRequest transportRequest) {} - void exerciseAllMethods(){ + void exerciseAllMethods() { final SearchOperationListener sol = this.toListener(); sol.onNewReaderContext(mock(ReaderContext.class)); sol.onNewScrollContext(mock(ReaderContext.class)); diff --git a/src/test/java/org/opensearch/security/support/SecurityUtilsTest.java b/src/test/java/org/opensearch/security/support/SecurityUtilsTest.java index ed6a471421..f0645d4958 100644 --- a/src/test/java/org/opensearch/security/support/SecurityUtilsTest.java +++ b/src/test/java/org/opensearch/security/support/SecurityUtilsTest.java @@ -60,14 +60,14 @@ private void checkKeysWithPredicate(Collection keys, String predicateNam final String prefixWithKeyName = "${" + predicateName + "." + envKeyName; final String baseKeyName = prefixWithKeyName + "}"; - assertThat("Testing " + envKeyName + ", " + baseKeyName, - predicate.test(baseKeyName), - equalTo(true)); + assertThat("Testing " + envKeyName + ", " + baseKeyName, predicate.test(baseKeyName), equalTo(true)); final String baseKeyNameWithDefault = prefixWithKeyName + ":-tTt}"; - assertThat("Testing " + envKeyName + " with defaultValue, " + baseKeyNameWithDefault, + assertThat( + "Testing " + envKeyName + " with defaultValue, " + baseKeyNameWithDefault, predicate.test(baseKeyNameWithDefault), - equalTo(true)); + equalTo(true) + ); }); } } diff --git a/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java b/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java index 273214b0fe..e298ae45c7 100644 --- a/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java +++ b/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java @@ -53,46 +53,51 @@ public class SystemIndicesTests extends SingleClusterTest { private static final String matchAllQuery = "{\n\"query\": {\"match_all\": {}}}"; private static final String allAccessUser = "admin_all_access"; private static final Header allAccessUserHeader = encodeBasicHeader(allAccessUser, allAccessUser); - private static final String generalErrorMessage = String.format("no permissions for [] and User [name=%s, backend_roles=[], requestedTenant=null]", allAccessUser); + private static final String generalErrorMessage = String.format( + "no permissions for [] and User [name=%s, backend_roles=[], requestedTenant=null]", + allAccessUser + ); private void setupSystemIndicesDisabledWithSsl() throws Exception { Settings systemIndexSettings = Settings.builder() - .put(ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, false) - .putList(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, listOfIndexesToTest) - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .put("path.repo", repositoryPath.getRoot().getAbsolutePath()) - .build(); - setup(Settings.EMPTY, - new DynamicSecurityConfig() - .setConfig("config_system_indices.yml") - .setSecurityRoles("roles_system_indices.yml") - .setSecurityInternalUsers("internal_users_system_indices.yml") - .setSecurityRolesMapping("roles_mapping_system_indices.yml"), - systemIndexSettings, - true); + .put(ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, false) + .putList(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, listOfIndexesToTest) + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .put("path.repo", repositoryPath.getRoot().getAbsolutePath()) + .build(); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setConfig("config_system_indices.yml") + .setSecurityRoles("roles_system_indices.yml") + .setSecurityInternalUsers("internal_users_system_indices.yml") + .setSecurityRolesMapping("roles_mapping_system_indices.yml"), + systemIndexSettings, + true + ); } private void setupSystemIndicesEnabledWithSsl() throws Exception { Settings systemIndexSettings = Settings.builder() - .put(ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, true) - .putList(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, listOfIndexesToTest) - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .put("path.repo", repositoryPath.getRoot().getAbsolutePath()) - .build(); - setup(Settings.EMPTY, - new DynamicSecurityConfig() - .setConfig("config_system_indices.yml") - .setSecurityRoles("roles_system_indices.yml") - .setSecurityInternalUsers("internal_users_system_indices.yml") - .setSecurityRolesMapping("roles_mapping_system_indices.yml"), - systemIndexSettings, - true); + .put(ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, true) + .putList(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, listOfIndexesToTest) + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .put("path.repo", repositoryPath.getRoot().getAbsolutePath()) + .build(); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setConfig("config_system_indices.yml") + .setSecurityRoles("roles_system_indices.yml") + .setSecurityInternalUsers("internal_users_system_indices.yml") + .setSecurityRolesMapping("roles_mapping_system_indices.yml"), + systemIndexSettings, + true + ); } /** @@ -104,7 +109,11 @@ private void createTestIndicesAndDocs() { try (Client tc = getClient()) { for (String index : listOfIndexesToTest) { tc.admin().indices().create(new CreateIndexRequest(index)).actionGet(); - tc.index(new IndexRequest(index).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).id("document1").source("{ \"foo\": \"bar\" }", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest(index).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .id("document1") + .source("{ \"foo\": \"bar\" }", XContentType.JSON) + ).actionGet(); } } } @@ -112,8 +121,19 @@ private void createTestIndicesAndDocs() { private void createSnapshots() { try (Client tc = getClient()) { for (String index : listOfIndexesToTest) { - tc.admin().cluster().putRepository(new PutRepositoryRequest(index).type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/" + index))).actionGet(); - tc.admin().cluster().createSnapshot(new CreateSnapshotRequest(index, index + "_1").indices(index).includeGlobalState(true).waitForCompletion(true)).actionGet(); + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest(index).type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/" + index)) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot( + new CreateSnapshotRequest(index, index + "_1").indices(index).includeGlobalState(true).waitForCompletion(true) + ) + .actionGet(); } } } @@ -140,7 +160,8 @@ private RestHelper sslRestHelper() { private void validateSearchResponse(RestHelper.HttpResponse response, int expectecdHits) throws IOException { assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); SearchResponse searchResponse = SearchResponse.fromXContent(xcp); assertEquals(RestStatus.OK, searchResponse.status()); assertEquals(expectecdHits, searchResponse.getHits().getHits().length); @@ -154,12 +175,12 @@ public void testSearchAsSuperAdmin() throws Exception { createTestIndicesAndDocs(); RestHelper restHelper = keyStoreRestHelper(); - //search system indices + // search system indices for (String index : listOfIndexesToTest) { validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery), 1); } - //search all indices + // search all indices RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery); assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); } @@ -170,12 +191,12 @@ public void testSearchAsAdmin() throws Exception { createTestIndicesAndDocs(); RestHelper restHelper = sslRestHelper(); - //search system indices + // search system indices for (String index : listOfIndexesToTest) { validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery, allAccessUserHeader), 1); } - //search all indices + // search all indices RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery, allAccessUserHeader); assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); } @@ -186,17 +207,16 @@ public void testSearchWithSystemIndicesAsSuperAdmin() throws Exception { createTestIndicesAndDocs(); RestHelper restHelper = keyStoreRestHelper(); - //search system indices + // search system indices for (String index : listOfIndexesToTest) { validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery), 1); } - //search all indices + // search all indices RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery); assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); } - @Test public void testSearchWithSystemIndicesAsAdmin() throws Exception { setupSystemIndicesEnabledWithSsl(); @@ -207,10 +227,11 @@ public void testSearchWithSystemIndicesAsAdmin() throws Exception { validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery, allAccessUserHeader), 0); } - //search all indices + // search all indices RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery, allAccessUserHeader); assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); SearchResponse searchResponse = SearchResponse.fromXContent(xcp); assertEquals(RestStatus.OK, searchResponse.status()); assertEquals(0, searchResponse.getHits().getHits().length); @@ -227,7 +248,7 @@ public void testDelete() throws Exception { RestHelper keyStoreRestHelper = keyStoreRestHelper(); RestHelper sslRestHelper = sslRestHelper(); - //as super-admin + // as super-admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse responseDoc = keyStoreRestHelper.executeDeleteRequest(index + "/_doc/document1"); assertEquals(RestStatus.OK.getStatus(), responseDoc.getStatusCode()); @@ -237,7 +258,7 @@ public void testDelete() throws Exception { } createTestIndicesAndDocs(); - //as admin + // as admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse responseDoc = sslRestHelper.executeDeleteRequest(index + "/_doc/document1", allAccessUserHeader); assertEquals(RestStatus.OK.getStatus(), responseDoc.getStatusCode()); @@ -254,7 +275,7 @@ public void testDeleteWithSystemIndices() throws Exception { RestHelper keyStoreRestHelper = keyStoreRestHelper(); RestHelper sslRestHelper = sslRestHelper(); - //as super-admin + // as super-admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse responseDoc = keyStoreRestHelper.executeDeleteRequest(index + "/_doc/document1"); assertEquals(RestStatus.OK.getStatus(), responseDoc.getStatusCode()); @@ -264,7 +285,7 @@ public void testDeleteWithSystemIndices() throws Exception { } createTestIndicesAndDocs(); - //as admin + // as admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse responseDoc = sslRestHelper.executeDeleteRequest(index + "/_doc/document1", allAccessUserHeader); assertEquals(RestStatus.FORBIDDEN.getStatus(), responseDoc.getStatusCode()); @@ -285,18 +306,18 @@ public void testCloseOpen() throws Exception { RestHelper keyStoreRestHelper = keyStoreRestHelper(); RestHelper sslRestHelper = sslRestHelper(); - //as super-admin + // as super-admin for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseClose = keyStoreRestHelper.executePostRequest(index + "/_close",""); + RestHelper.HttpResponse responseClose = keyStoreRestHelper.executePostRequest(index + "/_close", ""); assertEquals(RestStatus.OK.getStatus(), responseClose.getStatusCode()); RestHelper.HttpResponse responseOpen = keyStoreRestHelper.executePostRequest(index + "/_open", ""); assertEquals(RestStatus.OK.getStatus(), responseOpen.getStatusCode()); } - //as admin + // as admin for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseClose = sslRestHelper.executePostRequest(index + "/_close","", allAccessUserHeader); + RestHelper.HttpResponse responseClose = sslRestHelper.executePostRequest(index + "/_close", "", allAccessUserHeader); assertEquals(RestStatus.OK.getStatus(), responseClose.getStatusCode()); RestHelper.HttpResponse responseOpen = sslRestHelper.executePostRequest(index + "/_open", "", allAccessUserHeader); @@ -311,18 +332,18 @@ public void testCloseOpenWithSystemIndices() throws Exception { RestHelper keyStoreRestHelper = keyStoreRestHelper(); RestHelper sslRestHelper = sslRestHelper(); - //as super-admin + // as super-admin for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseClose = keyStoreRestHelper.executePostRequest(index + "/_close",""); + RestHelper.HttpResponse responseClose = keyStoreRestHelper.executePostRequest(index + "/_close", ""); assertEquals(RestStatus.OK.getStatus(), responseClose.getStatusCode()); RestHelper.HttpResponse responseOpen = keyStoreRestHelper.executePostRequest(index + "/_open", ""); assertEquals(RestStatus.OK.getStatus(), responseOpen.getStatusCode()); } - //as admin + // as admin for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseClose = sslRestHelper.executePostRequest(index + "/_close","", allAccessUserHeader); + RestHelper.HttpResponse responseClose = sslRestHelper.executePostRequest(index + "/_close", "", allAccessUserHeader); assertEquals(RestStatus.FORBIDDEN.getStatus(), responseClose.getStatusCode()); RestHelper.HttpResponse responseOpen = sslRestHelper.executePostRequest(index + "/_open", "", allAccessUserHeader); @@ -341,18 +362,14 @@ public void testUpdateIndexSettings() throws Exception { RestHelper keyStoreRestHelper = keyStoreRestHelper(); RestHelper sslRestHelper = sslRestHelper(); - String indexSettings = "{\n" + - " \"index\" : {\n" + - " \"refresh_interval\" : null\n" + - " }\n" + - "}"; + String indexSettings = "{\n" + " \"index\" : {\n" + " \"refresh_interval\" : null\n" + " }\n" + "}"; - //as super-admin + // as super-admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = keyStoreRestHelper.executePutRequest(index + "/_settings", indexSettings); assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); } - //as admin + // as admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = sslRestHelper.executePutRequest(index + "/_settings", indexSettings, allAccessUserHeader); assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); @@ -366,23 +383,20 @@ public void testUpdateIndexSettingsWithSystemIndices() throws Exception { RestHelper keyStoreRestHelper = keyStoreRestHelper(); RestHelper sslRestHelper = sslRestHelper(); - String indexSettings = "{\n" + - " \"index\" : {\n" + - " \"refresh_interval\" : null\n" + - " }\n" + - "}"; + String indexSettings = "{\n" + " \"index\" : {\n" + " \"refresh_interval\" : null\n" + " }\n" + "}"; - //as super-admin + // as super-admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = keyStoreRestHelper.executePutRequest(index + "/_settings", indexSettings); assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); } - //as admin + // as admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = sslRestHelper.executePutRequest(index + "/_settings", indexSettings, allAccessUserHeader); assertEquals(RestStatus.FORBIDDEN.getStatus(), response.getStatusCode()); } } + /*************************************************************************************************************************** * Index mappings. indices:admin/mapping/put ************************************************************************************************************************** */ @@ -394,18 +408,15 @@ public void testUpdateMappings() throws Exception { RestHelper keyStoreRestHelper = keyStoreRestHelper(); RestHelper sslRestHelper = sslRestHelper(); - String newMappings = "{\"properties\": {" + - "\"user_name\": {" + - "\"type\": \"text\"" + - "}}}"; + String newMappings = "{\"properties\": {" + "\"user_name\": {" + "\"type\": \"text\"" + "}}}"; - //as super-admin + // as super-admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = keyStoreRestHelper.executePutRequest(index + "/_mapping", newMappings); assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); } - //as admin + // as admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = sslRestHelper.executePutRequest(index + "/_mapping", newMappings, allAccessUserHeader); assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); @@ -419,18 +430,15 @@ public void testUpdateMappingsWithSystemIndices() throws Exception { RestHelper keyStoreRestHelper = keyStoreRestHelper(); RestHelper sslRestHelper = sslRestHelper(); - String newMappings = "{\"properties\": {" + - "\"user_name\": {" + - "\"type\": \"text\"" + - "}}}"; + String newMappings = "{\"properties\": {" + "\"user_name\": {" + "\"type\": \"text\"" + "}}}"; - //as super-admin + // as super-admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = keyStoreRestHelper.executePutRequest(index + "/_mapping", newMappings); assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); } - //as admin + // as admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = sslRestHelper.executePutRequest(index + "/_mapping", newMappings, allAccessUserHeader); assertEquals(RestStatus.FORBIDDEN.getStatus(), response.getStatusCode()); @@ -448,16 +456,16 @@ public void testCreate() throws Exception { RestHelper keyStoreRestHelper = keyStoreRestHelper(); RestHelper sslRestHelper = sslRestHelper(); - String indexSettings = "{\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : 3, \n" + - " \"number_of_replicas\" : 2 \n" + - " }\n" + - " }\n" + - "}"; + String indexSettings = "{\n" + + " \"settings\" : {\n" + + " \"index\" : {\n" + + " \"number_of_shards\" : 3, \n" + + " \"number_of_replicas\" : 2 \n" + + " }\n" + + " }\n" + + "}"; - //as super-admin + // as super-admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse responseIndex = keyStoreRestHelper.executePutRequest(index, indexSettings); assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); @@ -470,7 +478,7 @@ public void testCreate() throws Exception { keyStoreRestHelper.executeDeleteRequest(index); } - //as admin + // as admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse responseIndex = sslRestHelper.executePutRequest(index, indexSettings, allAccessUserHeader); assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); @@ -486,16 +494,16 @@ public void testCreateWithSystemIndices() throws Exception { RestHelper keyStoreRestHelper = keyStoreRestHelper(); RestHelper sslRestHelper = sslRestHelper(); - String indexSettings = "{\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : 3, \n" + - " \"number_of_replicas\" : 2 \n" + - " }\n" + - " }\n" + - "}"; + String indexSettings = "{\n" + + " \"settings\" : {\n" + + " \"index\" : {\n" + + " \"number_of_shards\" : 3, \n" + + " \"number_of_replicas\" : 2 \n" + + " }\n" + + " }\n" + + "}"; - //as super-admin + // as super-admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse responseIndex = keyStoreRestHelper.executePutRequest(index, indexSettings); assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); @@ -508,7 +516,7 @@ public void testCreateWithSystemIndices() throws Exception { keyStoreRestHelper.executeDeleteRequest(index); } - //as admin + // as admin for (String index : listOfIndexesToTest) { RestHelper.HttpResponse responseIndex = sslRestHelper.executePutRequest(index, indexSettings, allAccessUserHeader); assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); @@ -536,9 +544,26 @@ public void testSnapshotWithSystemIndices() throws Exception { RestHelper sslRestHelper = sslRestHelper(); // as admin for (String index : listOfIndexesToTest) { - assertEquals(HttpStatus.SC_OK, sslRestHelper.executeGetRequest("_snapshot/" + index + "/" + index + "_1", allAccessUserHeader).getStatusCode()); - assertEquals(HttpStatus.SC_OK, sslRestHelper.executePostRequest("_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", allAccessUserHeader).getStatusCode()); - assertEquals(HttpStatus.SC_FORBIDDEN, sslRestHelper.executePostRequest("_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", "", allAccessUserHeader).getStatusCode()); + assertEquals( + HttpStatus.SC_OK, + sslRestHelper.executeGetRequest("_snapshot/" + index + "/" + index + "_1", allAccessUserHeader).getStatusCode() + ); + assertEquals( + HttpStatus.SC_OK, + sslRestHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + allAccessUserHeader + ).getStatusCode() + ); + assertEquals( + HttpStatus.SC_FORBIDDEN, + sslRestHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "", + allAccessUserHeader + ).getStatusCode() + ); } } } From ceb5ad29e835592fe84878506c5ed8b31b9d3677 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Mon, 5 Jun 2023 14:22:34 -0400 Subject: [PATCH 199/369] Format all *.java files tests under test/security (#2837) * Update test/security/s* java files Signed-off-by: Stephen Crawford * Update test/security/s* java files Signed-off-by: Stephen Crawford * Update all tests under security Signed-off-by: Stephen Crawford --------- Signed-off-by: Stephen Crawford --- build.gradle | 18 +- .../AdvancedSecurityMigrationTests.java | 145 +- .../opensearch/security/AggregationTests.java | 106 +- ...waysFalseInterClusterRequestEvaluator.java | 19 +- .../org/opensearch/security/ConfigTests.java | 34 +- .../security/DataStreamIntegrationTests.java | 36 +- .../EncryptionInTransitMigrationTests.java | 44 +- .../org/opensearch/security/HealthTests.java | 15 +- .../security/HttpIntegrationTests.java | 1269 +++++++++++------ .../security/IndexIntegrationTests.java | 634 +++++--- ...exTemplateClusterPermissionsCheckTest.java | 76 +- .../InitializationIntegrationTests.java | 176 ++- .../opensearch/security/IntegrationTests.java | 876 ++++++++---- .../security/PitIntegrationTests.java | 134 +- .../security/PrivilegesEvaluationTest.java | 28 +- .../opensearch/security/ResolveAPITests.java | 134 +- .../security/RolesInjectorIntegTest.java | 99 +- .../security/RolesValidationIntegTest.java | 77 +- .../SecurityAdminIEndpointsTests.java | 99 +- .../SecurityAdminInvalidConfigsTests.java | 278 ++-- .../security/SecurityAdminTests.java | 285 ++-- .../security/SecurityRolesTests.java | 157 +- .../security/SlowIntegrationTests.java | 172 ++- .../security/SnapshotRestoreTests.java | 971 ++++++++++--- .../security/SystemIntegratorsTests.java | 194 ++- .../org/opensearch/security/TaskTests.java | 11 +- .../org/opensearch/security/TracingTests.java | 621 +++++--- .../TransportUserInjectorIntegTest.java | 133 +- .../org/opensearch/security/UtilTests.java | 115 +- .../auditlog/AbstractAuditlogiUnitTest.java | 20 +- .../security/auditlog/AuditTestUtils.java | 3 +- .../compliance/ComplianceAuditlogTest.java | 289 ++-- .../compliance/ComplianceConfigTest.java | 54 +- .../RestApiComplianceAuditlogTest.java | 151 +- .../config/AuditConfigFilterTest.java | 121 +- .../config/AuditConfigSerializeTest.java | 313 ++-- .../auditlog/config/ThreadPoolConfigTest.java | 6 +- .../auditlog/helper/ErroneousHttpHandler.java | 8 +- .../security/auditlog/helper/FailingSink.java | 8 +- .../security/auditlog/helper/LoggingSink.java | 5 +- .../helper/MockAuditMessageFactory.java | 26 +- .../auditlog/helper/MockRestRequest.java | 4 +- .../auditlog/helper/MyOwnAuditLog.java | 23 +- .../security/auditlog/helper/RetrySink.java | 4 +- .../security/auditlog/helper/SlowSink.java | 11 +- .../auditlog/helper/TestHttpHandler.java | 30 +- .../auditlog/impl/AuditCategoryTest.java | 46 +- .../auditlog/impl/AuditMessageTest.java | 31 +- .../security/auditlog/impl/AuditlogTest.java | 86 +- .../security/auditlog/impl/DelegateTest.java | 44 +- .../auditlog/impl/DisabledCategoriesTest.java | 315 ++-- .../auditlog/impl/IgnoreAuditUsersTest.java | 193 ++- .../security/auditlog/impl/TracingTests.java | 411 +++--- .../integration/BasicAuditlogTest.java | 566 +++++--- .../auditlog/integration/SSLAuditlogTest.java | 133 +- .../integration/TestAuditlogImpl.java | 29 +- .../auditlog/routing/FallbackTest.java | 185 +-- .../security/auditlog/routing/PerfTest.java | 42 +- .../security/auditlog/routing/RouterTest.java | 130 +- .../routing/RoutingConfigurationTest.java | 286 ++-- .../security/auditlog/sink/KafkaSinkTest.java | 7 +- .../auditlog/sink/MockWebhookAuditLog.java | 37 +- .../auditlog/sink/SinkProviderTLSTest.java | 209 +-- .../auditlog/sink/SinkProviderTest.java | 157 +- .../auditlog/sink/WebhookAuditLogTest.java | 1036 +++++++------- .../auth/InternalAuthBackendTests.java | 32 +- .../security/auth/RolesInjectorTest.java | 9 +- .../security/auth/UserInjectorTest.java | 4 +- .../limiting/HeapBasedRateTrackerTest.java | 1 - .../security/cache/CachingTest.java | 22 +- .../cache/DummyAuthenticationBackend.java | 8 +- .../security/cache/DummyAuthorizer.java | 6 +- .../cache/DummyHTTPAuthenticator.java | 5 +- ...ossClusterMinimalRoundtripSearchTests.java | 4 +- .../ccstest/CrossClusterSearchTests.java | 1065 ++++++++++---- .../security/ccstest/RemoteReindexTests.java | 72 +- .../security/configuration/SaltTest.java | 18 +- .../dlic/dlsfls/AbstractDlsFlsTest.java | 66 +- .../dlic/dlsfls/CCReplicationTest.java | 134 +- .../CustomFieldMaskedComplexMappingTest.java | 152 +- .../dlic/dlsfls/CustomFieldMaskedTest.java | 247 ++-- .../security/dlic/dlsfls/DateMathTest.java | 81 +- .../dlic/dlsfls/DfmOverwritesAllTest.java | 102 +- .../security/dlic/dlsfls/DlsDateMathTest.java | 49 +- ...ossClusterMinimalRoundtripSearchTests.java | 4 +- .../dlsfls/DlsFlsCrossClusterSearchTest.java | 338 +++-- .../security/dlic/dlsfls/DlsNestedTest.java | 103 +- .../dlic/dlsfls/DlsPropsReplaceTest.java | 63 +- .../security/dlic/dlsfls/DlsScrollTest.java | 54 +- .../dlic/dlsfls/DlsTermLookupQueryTest.java | 1236 +++++++++------- .../security/dlic/dlsfls/DlsTest.java | 369 +++-- .../security/dlic/dlsfls/FieldMaskedTest.java | 203 ++- .../security/dlic/dlsfls/Fls983Test.java | 16 +- .../security/dlic/dlsfls/FlsDlsTestAB.java | 80 +- .../dlic/dlsfls/FlsDlsTestForbiddenField.java | 148 +- .../security/dlic/dlsfls/FlsDlsTestMulti.java | 320 +++-- .../dlic/dlsfls/FlsExistsFieldsTest.java | 123 +- .../security/dlic/dlsfls/FlsFieldsTest.java | 32 +- .../security/dlic/dlsfls/FlsFieldsWcTest.java | 32 +- .../dlic/dlsfls/FlsIndexingTests.java | 73 +- .../security/dlic/dlsfls/FlsKeywordTests.java | 24 +- .../security/dlic/dlsfls/FlsPerfTest.java | 106 +- .../security/dlic/dlsfls/FlsTest.java | 122 +- .../dlic/dlsfls/IndexPatternTest.java | 74 +- .../security/dlic/dlsfls/MFlsTest.java | 80 +- .../rest/api/AbstractRestApiUnitTest.java | 480 ++++--- .../dlic/rest/api/AccountApiTest.java | 27 +- .../dlic/rest/api/ActionGroupsApiTest.java | 195 ++- .../dlic/rest/api/AllowlistApiTest.java | 115 +- .../dlic/rest/api/AuditApiActionTest.java | 308 ++-- .../rest/api/DashboardsInfoActionTest.java | 7 +- .../dlic/rest/api/FlushCacheApiTest.java | 4 +- .../rest/api/GetConfigurationApiTest.java | 7 +- .../dlic/rest/api/IndexMissingTest.java | 14 +- .../rest/api/MultiTenancyConfigApiTest.java | 107 +- .../dlic/rest/api/NodesDnApiTest.java | 107 +- .../api/RestApiPrivilegesEvaluatorTest.java | 14 +- .../dlic/rest/api/RoleBasedAccessTest.java | 56 +- .../security/dlic/rest/api/RolesApiTest.java | 470 +++--- .../dlic/rest/api/RolesMappingApiTest.java | 274 ++-- .../dlic/rest/api/SecurityApiAccessTest.java | 19 +- .../dlic/rest/api/SecurityConfigApiTest.java | 50 +- .../rest/api/SecurityHealthActionTest.java | 7 +- .../dlic/rest/api/SecurityInfoActionTest.java | 7 +- .../dlic/rest/api/SslCertsApiTest.java | 58 +- .../dlic/rest/api/TenantInfoActionTest.java | 26 +- .../security/dlic/rest/api/UserApiTest.java | 499 ++++--- .../dlic/rest/api/WhitelistApiTest.java | 129 +- .../api/legacy/LegacyAccountApiTests.java | 8 +- .../legacy/LegacyActionGroupsApiTests.java | 8 +- .../api/legacy/LegacyAuditApiActionTests.java | 8 +- .../LegacyDashboardsInfoActionTests.java | 2 +- .../api/legacy/LegacyFlushCacheApiTests.java | 8 +- .../LegacyGetConfigurationApiTests.java | 8 +- .../api/legacy/LegacyIndexMissingTests.java | 8 +- .../api/legacy/LegacyNodesDnApiTests.java | 8 +- .../legacy/LegacyRoleBasedAccessTests.java | 8 +- .../rest/api/legacy/LegacyRolesApiTests.java | 8 +- .../legacy/LegacyRolesMappingApiTests.java | 8 +- .../legacy/LegacySecurityApiAccessTests.java | 8 +- .../legacy/LegacySecurityConfigApiTests.java | 8 +- .../LegacySecurityHealthActionTests.java | 8 +- .../legacy/LegacySecurityInfoActionTests.java | 8 +- .../legacy/LegacyTenantInfoActionTests.java | 8 +- .../rest/api/legacy/LegacyUserApiTests.java | 8 +- .../api/legacy/LegacyWhitelistApiTests.java | 8 +- .../validation/PasswordValidatorTest.java | 164 +-- .../security/filter/SecurityFilterTest.java | 47 +- .../filter/SecurityRestFilterTest.java | 273 ++-- .../HTTPExtendedProxyAuthenticatorTest.java | 36 +- .../security/httpclient/HttpClientTest.java | 86 +- .../privileges/PrivilegesEvaluatorTest.java | 26 +- .../SecurityIndexAccessEvaluatorTest.java | 17 +- .../ProtectedIndicesTests.java | 327 +++-- .../test/AbstractSecurityUnitTest.java | 159 ++- .../security/test/DynamicSecurityConfig.java | 89 +- .../security/test/SingleClusterTest.java | 81 +- .../helper/cluster/ClusterConfiguration.java | 145 +- .../test/helper/cluster/ClusterHelper.java | 216 ++- .../test/helper/cluster/ClusterInfo.java | 14 +- .../security/test/helper/file/FileHelper.java | 102 +- .../test/helper/network/SocketUtils.java | 51 +- .../security/test/helper/rest/RestHelper.java | 919 ++++++------ .../helper/rules/SecurityTestWatcher.java | 44 +- .../test/plugin/UserInjectorPlugin.java | 51 +- .../security/util/FakeRestRequest.java | 11 +- .../SettingsBasedSSLConfiguratorV4Test.java | 336 +++-- 167 files changed, 14803 insertions(+), 9161 deletions(-) diff --git a/build.gradle b/build.gradle index 8d7f3d9edd..863a4ae234 100644 --- a/build.gradle +++ b/build.gradle @@ -78,14 +78,7 @@ spotless { // non-standard places target '**/com/amazon/dlic/**/*.java' target '**/com/amazon/security/**/*.java' - target '**/test/java/org/opensearch/security/a*/**/*.java' - target '**/test/java/org/opensearch/security/b*/**/*.java' - target '**/test/java/org/opensearch/security/c*/**/*.java' - target '**/test/java/org/opensearch/security/d*/**/*.java' - target '**/test/java/org/opensearch/security/f*/**/*.java' - target '**/test/java/org/opensearch/security/h*/**/*.java' - target '**/test/java/org/opensearch/security/m*/**/*.java' - target '**/test/java/org/opensearch/security/s*/**/*.java' + target '**/test/java/org/opensearch/security/**/*.java' removeUnusedImports() eclipse().configFile rootProject.file('formatter/formatterConfig.xml') @@ -119,14 +112,7 @@ spotless { target '**/*.java' targetExclude '**/com/amazon/dlic/**/*.java' targetExclude '**/com/amazon/security/**/*.java' - targetExclude '**/test/java/org/opensearch/security/a*/**/*.java' - targetExclude '**/test/java/org/opensearch/security/b*/**/*.java' - targetExclude '**/test/java/org/opensearch/security/c*/**/*.java' - targetExclude '**/test/java/org/opensearch/security/d*/**/*.java' - targetExclude '**/test/java/org/opensearch/security/f*/**/*.java' - targetExclude '**/test/java/org/opensearch/security/h*/**/*.java' - targetExclude '**/test/java/org/opensearch/security/m*/**/*.java' - targetExclude '**/test/java/org/opensearch/security/s*/**/*.java' + targetExclude '**/test/java/org/opensearch/security/**/*.java' targetExclude 'src/integrationTest/**' trimTrailingWhitespace() diff --git a/src/test/java/org/opensearch/security/AdvancedSecurityMigrationTests.java b/src/test/java/org/opensearch/security/AdvancedSecurityMigrationTests.java index 3b069a2339..e8ac049385 100644 --- a/src/test/java/org/opensearch/security/AdvancedSecurityMigrationTests.java +++ b/src/test/java/org/opensearch/security/AdvancedSecurityMigrationTests.java @@ -50,8 +50,11 @@ public void testPluginEnabledDataNodeWithSSlOnlyClusterManagerNode_ReqOnSSLNode( final Settings advSecSettings = getAdvSecSettings().build(); final Settings sslOnlySettings = getSSLOnlyModeSettings().build(); - setupGenericNodes(Arrays.asList(sslOnlySettings, advSecSettings, advSecSettings, sslOnlySettings), - Arrays.asList(true, false, false, true), ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA); + setupGenericNodes( + Arrays.asList(sslOnlySettings, advSecSettings, advSecSettings, sslOnlySettings), + Arrays.asList(true, false, false, true), + ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA + ); Thread.sleep(10000); commonTestsForAdvancedSecurityMigration(nonSslRestHelper(), null); @@ -67,8 +70,11 @@ public void testPluginEnabledDataNodeWithSSlOnlyClusterManagerNode_ReqOnAdvSecNo final Settings advSecSettings = getAdvSecSettings().build(); final Settings sslOnlySettings = getSSLOnlyModeSettings().build(); - setupGenericNodes(Arrays.asList(advSecSettings, sslOnlySettings, advSecSettings, sslOnlySettings), - Arrays.asList(false, true, false, true), ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA); + setupGenericNodes( + Arrays.asList(advSecSettings, sslOnlySettings, advSecSettings, sslOnlySettings), + Arrays.asList(false, true, false, true), + ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA + ); Thread.sleep(10000); commonTestsForAdvancedSecurityMigration(nonSslRestHelper(), encodeBasicHeader("admin", "admin")); @@ -84,11 +90,14 @@ public void testPluginEnabledClusterManagerNodeWithSSlOnlyDataNode_ReqOnSSLNode( final Settings advSecSettings = getAdvSecSettings().build(); final Settings sslOnlySettings = getSSLOnlyModeSettings().build(); - setupGenericNodes(Arrays.asList(sslOnlySettings, sslOnlySettings, advSecSettings, advSecSettings), - Arrays.asList(true, true, false, false), ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA); + setupGenericNodes( + Arrays.asList(sslOnlySettings, sslOnlySettings, advSecSettings, advSecSettings), + Arrays.asList(true, true, false, false), + ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA + ); Thread.sleep(10000); - commonTestsForAdvancedSecurityMigration(nonSslRestHelper(),null); + commonTestsForAdvancedSecurityMigration(nonSslRestHelper(), null); } /** @@ -101,8 +110,11 @@ public void testPluginEnabledClusterManagerNodeWithSSlOnlyDataNode_ReqOnAdvSecNo final Settings advSecSettings = getAdvSecSettings().build(); final Settings sslOnlySettings = getSSLOnlyModeSettings().build(); - setupGenericNodes(Arrays.asList(advSecSettings, sslOnlySettings, sslOnlySettings, advSecSettings), - Arrays.asList(false, true, true, false), ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA); + setupGenericNodes( + Arrays.asList(advSecSettings, sslOnlySettings, sslOnlySettings, advSecSettings), + Arrays.asList(false, true, true, false), + ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA + ); Thread.sleep(10000); commonTestsForAdvancedSecurityMigration(nonSslRestHelper(), encodeBasicHeader("admin", "admin")); @@ -118,11 +130,14 @@ public void testPluginEnabledDataNodeWithDisabledClusterManagerNode_ReqOnDisable final Settings advSecSettings = getAdvSecSettingsDualMode().build(); final Settings disabledSettings = getDisabledSettings().build(); - setupGenericNodes(Arrays.asList(disabledSettings, advSecSettings, advSecSettings, disabledSettings), - Arrays.asList(false, false, false, false), ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA); + setupGenericNodes( + Arrays.asList(disabledSettings, advSecSettings, advSecSettings, disabledSettings), + Arrays.asList(false, false, false, false), + ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA + ); Thread.sleep(10000); - commonTestsForAdvancedSecurityMigration(nonSslRestHelper(),null); + commonTestsForAdvancedSecurityMigration(nonSslRestHelper(), null); } /** @@ -135,8 +150,11 @@ public void testPluginEnabledDataNodeWithDisabledClusterManagerNode_ReqOnAdvSecN final Settings advSecSettings = getAdvSecSettingsDualMode().build(); final Settings disabledSettings = getDisabledSettings().build(); - setupGenericNodes(Arrays.asList(advSecSettings, disabledSettings, advSecSettings, disabledSettings), - Arrays.asList(false, false, false, false), ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA); + setupGenericNodes( + Arrays.asList(advSecSettings, disabledSettings, advSecSettings, disabledSettings), + Arrays.asList(false, false, false, false), + ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA + ); Thread.sleep(10000); commonTestsForAdvancedSecurityMigration(nonSslRestHelper(), encodeBasicHeader("admin", "admin")); @@ -152,8 +170,11 @@ public void testPluginEnabledClusterManagerNodeWithDisabledDataNode_ReqOnDisable final Settings advSecSettings = getAdvSecSettingsDualMode().build(); final Settings disabledSettings = getDisabledSettings().build(); - setupGenericNodes(Arrays.asList(disabledSettings, disabledSettings, advSecSettings, advSecSettings), - Arrays.asList(false, false, false, false), ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA); + setupGenericNodes( + Arrays.asList(disabledSettings, disabledSettings, advSecSettings, advSecSettings), + Arrays.asList(false, false, false, false), + ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA + ); Thread.sleep(10000); commonTestsForAdvancedSecurityMigration(nonSslRestHelper(), null); @@ -169,8 +190,11 @@ public void testPluginEnabledClusterManagerNodeWithDisabledDataNode_ReqOnAdvSecN final Settings advSecSettings = getAdvSecSettingsDualMode().build(); final Settings disabledSettings = getDisabledSettings().build(); - setupGenericNodes(Arrays.asList(advSecSettings, disabledSettings, advSecSettings, advSecSettings), - Arrays.asList(false, false, false, false), ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA); + setupGenericNodes( + Arrays.asList(advSecSettings, disabledSettings, advSecSettings, advSecSettings), + Arrays.asList(false, false, false, false), + ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA + ); Thread.sleep(10000); commonTestsForAdvancedSecurityMigration(nonSslRestHelper(), encodeBasicHeader("admin", "admin")); @@ -178,13 +202,17 @@ public void testPluginEnabledClusterManagerNodeWithDisabledDataNode_ReqOnAdvSecN @Test public void testWithPassiveAuthDisabled() throws Exception { - final Settings advSecSettings = getAdvSecSettings() - .put(ConfigConstants.SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY, false) - .build(); + final Settings advSecSettings = getAdvSecSettings().put( + ConfigConstants.SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY, + false + ).build(); final Settings sslOnlySettings = getSSLOnlyModeSettings().build(); - setupGenericNodes(Arrays.asList(sslOnlySettings, sslOnlySettings, advSecSettings, advSecSettings), - Arrays.asList(true, true, false, false), ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA); + setupGenericNodes( + Arrays.asList(sslOnlySettings, sslOnlySettings, advSecSettings, advSecSettings), + Arrays.asList(true, true, false, false), + ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA + ); Thread.sleep(10000); RestHelper.HttpResponse res; @@ -196,15 +224,19 @@ public void testWithPassiveAuthDisabled() throws Exception { @Test public void testWithPassiveAuthDisabledDynamic() throws Exception { - final Settings advSecSettings = getAdvSecSettingsDualMode() - .put(ConfigConstants.SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY, false) - .build(); + final Settings advSecSettings = getAdvSecSettingsDualMode().put( + ConfigConstants.SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY, + false + ).build(); final Settings disabledSettings = getDisabledSettings().build(); - setupGenericNodes(Arrays.asList(disabledSettings, disabledSettings, advSecSettings, advSecSettings), - Arrays.asList(false, false, false, false), ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA); + setupGenericNodes( + Arrays.asList(disabledSettings, disabledSettings, advSecSettings, advSecSettings), + Arrays.asList(false, false, false, false), + ClusterConfiguration.ONE_CLUSTER_MANAGER_THREE_DATA + ); - Thread.sleep(5*1000); + Thread.sleep(5 * 1000); RestHelper.HttpResponse res; RestHelper rh = nonSslRestHelper(); @@ -214,7 +246,7 @@ public void testWithPassiveAuthDisabledDynamic() throws Exception { } private void commonTestsForAdvancedSecurityMigration(final RestHelper rh, final Header basicHeaders) throws Exception { - Thread.sleep(5*1000); + Thread.sleep(5 * 1000); RestHelper.HttpResponse res; res = rh.executePutRequest("testindex", getIndexSettingsForAdvSec(), basicHeaders); @@ -250,50 +282,47 @@ private void commonTestsForAnIndex(final RestHelper rh, final String index, fina private Settings.Builder getAdvSecSettings() { return Settings.builder() - .put(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, true) - .put(ConfigConstants.SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY, true) - .put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true) - .put("node.attr.custom_node", true); + .put(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, true) + .put(ConfigConstants.SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY, true) + .put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true) + .put("node.attr.custom_node", true); } private Settings.Builder getAdvSecSettingsDualMode() { - return getAdvSecSettings() - .put(ConfigConstants.SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED, true); + return getAdvSecSettings().put(ConfigConstants.SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED, true); } private Settings.Builder getSSLOnlyModeSettings() { - return Settings.builder() - .put(ConfigConstants.SECURITY_SSL_ONLY, true); + return Settings.builder().put(ConfigConstants.SECURITY_SSL_ONLY, true); } private Settings.Builder getDisabledSettings() { - return Settings.builder() - .put(ConfigConstants.SECURITY_DISABLED, true); + return Settings.builder().put(ConfigConstants.SECURITY_DISABLED, true); } // Create index with shards only in adv sec nodes private String getIndexSettingsForAdvSec() { - return "{\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : 2, \n" + - " \"number_of_replicas\" : 1, \n" + - " \"routing.allocation.include.custom_node\" : true \n" + - " }\n" + - " }\n" + - "}"; + return "{\n" + + " \"settings\" : {\n" + + " \"index\" : {\n" + + " \"number_of_shards\" : 2, \n" + + " \"number_of_replicas\" : 1, \n" + + " \"routing.allocation.include.custom_node\" : true \n" + + " }\n" + + " }\n" + + "}"; } // Create index with shards only in non adv sec nodes private String getIndexSettingForSSLOnlyNode() { - return "{\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : 2, \n" + - " \"number_of_replicas\" : 1, \n" + - " \"routing.allocation.exclude.custom_node\" : true \n" + - " }\n" + - " }\n" + - "}"; + return "{\n" + + " \"settings\" : {\n" + + " \"index\" : {\n" + + " \"number_of_shards\" : 2, \n" + + " \"number_of_replicas\" : 1, \n" + + " \"routing.allocation.exclude.custom_node\" : true \n" + + " }\n" + + " }\n" + + "}"; } } diff --git a/src/test/java/org/opensearch/security/AggregationTests.java b/src/test/java/org/opensearch/security/AggregationTests.java index a8a0f94078..728965f82d 100644 --- a/src/test/java/org/opensearch/security/AggregationTests.java +++ b/src/test/java/org/opensearch/security/AggregationTests.java @@ -46,37 +46,74 @@ public class AggregationTests extends SingleClusterTest { @Test public void testBasicAggregations() throws Exception { - final Settings settings = Settings.builder() - .build(); + final Settings settings = Settings.builder().build(); setup(settings); final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("xyz").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("starfleet","starfleet_academy","starfleet_library").alias("sf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire","vulcangov").alias("nonsf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("xyz").alias("alias1"))).actionGet(); + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.index(new IndexRequest("xyz").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add().indices("starfleet", "starfleet_academy", "starfleet_library").alias("sf") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire", "vulcangov").alias("nonsf")) + ) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("xyz").alias("alias1"))) + .actionGet(); } HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_search?pretty", "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":40}}}}",encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest( + "_search?pretty", + "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":40}}}}", + encodeBasicHeader("nagilum", "nagilum") + )).getStatusCode() + ); System.out.println(res.getBody()); assertNotContains(res, "*xception*"); assertNotContains(res, "*erial*"); @@ -89,7 +126,14 @@ public void testBasicAggregations() throws Exception { assertContains(res, "*role01_role02*"); assertContains(res, "*\"failed\" : 0*"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("*/_search?pretty", "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":40}}}}",encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest( + "*/_search?pretty", + "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":40}}}}", + encodeBasicHeader("nagilum", "nagilum") + )).getStatusCode() + ); System.out.println(res.getBody()); assertNotContains(res, "*xception*"); assertNotContains(res, "*erial*"); @@ -102,7 +146,14 @@ public void testBasicAggregations() throws Exception { assertContains(res, "*role01_role02*"); assertContains(res, "*\"failed\" : 0*"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_search?pretty", "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":40}}}}",encodeBasicHeader("worf", "worf"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest( + "_search?pretty", + "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":40}}}}", + encodeBasicHeader("worf", "worf") + )).getStatusCode() + ); System.out.println(res.getBody()); assertNotContains(res, "*xception*"); assertNotContains(res, "*erial*"); @@ -115,7 +166,14 @@ public void testBasicAggregations() throws Exception { assertContains(res, "*xyz*"); assertContains(res, "*\"failed\" : 0*"); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePostRequest("_search?pretty", "{\"size\":0,\"aggs\":{\"myindices\":{\"terms\":{\"field\":\"_index\",\"size\":40}}}}",encodeBasicHeader("worf", "worf"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executePostRequest( + "_search?pretty", + "{\"size\":0,\"aggs\":{\"myindices\":{\"terms\":{\"field\":\"_index\",\"size\":40}}}}", + encodeBasicHeader("worf", "worf") + )).getStatusCode() + ); } diff --git a/src/test/java/org/opensearch/security/AlwaysFalseInterClusterRequestEvaluator.java b/src/test/java/org/opensearch/security/AlwaysFalseInterClusterRequestEvaluator.java index a0a1b8bb7b..b741dd2c70 100644 --- a/src/test/java/org/opensearch/security/AlwaysFalseInterClusterRequestEvaluator.java +++ b/src/test/java/org/opensearch/security/AlwaysFalseInterClusterRequestEvaluator.java @@ -32,7 +32,6 @@ import org.opensearch.security.transport.InterClusterRequestEvaluator; import org.opensearch.transport.TransportRequest; - public class AlwaysFalseInterClusterRequestEvaluator implements InterClusterRequestEvaluator { public AlwaysFalseInterClusterRequestEvaluator(Settings settings) { @@ -40,11 +39,19 @@ public AlwaysFalseInterClusterRequestEvaluator(Settings settings) { } @Override - public boolean isInterClusterRequest(TransportRequest request, X509Certificate[] localCerts, X509Certificate[] peerCerts, - String principal) { - - if(localCerts == null || peerCerts == null || principal == null - || localCerts.length == 0 || peerCerts.length == 0 || principal.length() == 0) { + public boolean isInterClusterRequest( + TransportRequest request, + X509Certificate[] localCerts, + X509Certificate[] peerCerts, + String principal + ) { + + if (localCerts == null + || peerCerts == null + || principal == null + || localCerts.length == 0 + || peerCerts.length == 0 + || principal.length() == 0) { return true; } diff --git a/src/test/java/org/opensearch/security/ConfigTests.java b/src/test/java/org/opensearch/security/ConfigTests.java index 3c083ac153..519faa612d 100644 --- a/src/test/java/org/opensearch/security/ConfigTests.java +++ b/src/test/java/org/opensearch/security/ConfigTests.java @@ -57,20 +57,29 @@ public void testEmptyConfig() throws Exception { @Test public void testMigrate() throws Exception { - Tuple, SecurityDynamicConfiguration> rolesResult = Migration.migrateRoles((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/roles.yml", CType.ROLES), - (SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/roles_mapping.yml", CType.ROLESMAPPING)); + Tuple, SecurityDynamicConfiguration> rolesResult = Migration.migrateRoles( + (SecurityDynamicConfiguration) load("./legacy/securityconfig_v6/roles.yml", CType.ROLES), + (SecurityDynamicConfiguration) load("./legacy/securityconfig_v6/roles_mapping.yml", CType.ROLESMAPPING) + ); System.out.println(Strings.toString(XContentType.JSON, rolesResult.v2(), true, false)); System.out.println(Strings.toString(XContentType.JSON, rolesResult.v1(), true, false)); - - SecurityDynamicConfiguration actionGroupsResult = Migration.migrateActionGroups((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/action_groups.yml", CType.ACTIONGROUPS)); + SecurityDynamicConfiguration actionGroupsResult = Migration.migrateActionGroups( + (SecurityDynamicConfiguration) load("./legacy/securityconfig_v6/action_groups.yml", CType.ACTIONGROUPS) + ); System.out.println(Strings.toString(XContentType.JSON, actionGroupsResult, true, false)); - SecurityDynamicConfiguration configResult =Migration.migrateConfig((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/config.yml", CType.CONFIG)); + SecurityDynamicConfiguration configResult = Migration.migrateConfig( + (SecurityDynamicConfiguration) load("./legacy/securityconfig_v6/config.yml", CType.CONFIG) + ); System.out.println(Strings.toString(XContentType.JSON, configResult, true, false)); - SecurityDynamicConfiguration internalUsersResult = Migration.migrateInternalUsers((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/internal_users.yml", CType.INTERNALUSERS)); + SecurityDynamicConfiguration internalUsersResult = Migration.migrateInternalUsers( + (SecurityDynamicConfiguration) load("./legacy/securityconfig_v6/internal_users.yml", CType.INTERNALUSERS) + ); System.out.println(Strings.toString(XContentType.JSON, internalUsersResult, true, false)); - SecurityDynamicConfiguration rolemappingsResult = Migration.migrateRoleMappings((SecurityDynamicConfiguration)load("./legacy/securityconfig_v6/roles_mapping.yml", CType.ROLESMAPPING)); + SecurityDynamicConfiguration rolemappingsResult = Migration.migrateRoleMappings( + (SecurityDynamicConfiguration) load("./legacy/securityconfig_v6/roles_mapping.yml", CType.ROLESMAPPING) + ); System.out.println(Strings.toString(XContentType.JSON, rolemappingsResult, true, false)); } @@ -101,17 +110,16 @@ private void check(String file, CType cType) throws Exception { JsonNode jsonNode = YAML.readTree(FileUtils.readFileToString(new File(adjustedFilePath), "UTF-8")); int configVersion = 1; System.out.println("%%%%%%%% THIS IS A LINE OF INTEREST %%%%%%%"); - if(jsonNode.get("_meta") != null) { + if (jsonNode.get("_meta") != null) { Assert.assertEquals(jsonNode.get("_meta").get("type").asText(), cType.toLCString()); configVersion = jsonNode.get("_meta").get("config_version").asInt(); } - - System.out.println("%%%%%%%% THIS IS A LINE OF INTEREST: CONFIG VERSION: "+ configVersion + "%%%%%%%"); + System.out.println("%%%%%%%% THIS IS A LINE OF INTEREST: CONFIG VERSION: " + configVersion + "%%%%%%%"); SecurityDynamicConfiguration dc = load(file, cType); Assert.assertNotNull(dc); - //Assert.assertTrue(dc.getCEntries().size() > 0); + // Assert.assertTrue(dc.getCEntries().size() > 0); String jsonSerialize = DefaultObjectMapper.objectMapper.writeValueAsString(dc); SecurityDynamicConfiguration conf = SecurityDynamicConfiguration.fromJson(jsonSerialize, cType, configVersion, 0, 0); SecurityDynamicConfiguration.fromJson(Strings.toString(XContentType.JSON, conf), cType, configVersion, 0, 0); @@ -124,11 +132,11 @@ private SecurityDynamicConfiguration load(String file, CType cType) throws Ex int configVersion = 1; System.out.println("%%%%%%%% THIS IS A LINE OF INTEREST LOAD: CONFIG VERSION: %%%%%%%"); - if(jsonNode.get("_meta") != null) { + if (jsonNode.get("_meta") != null) { Assert.assertEquals(jsonNode.get("_meta").get("type").asText(), cType.toLCString()); configVersion = jsonNode.get("_meta").get("config_version").asInt(); } - System.out.println("%%%%%%%% THIS IS A LINE OF INTEREST: CONFIG VERSION: "+ configVersion + "%%%%%%%"); + System.out.println("%%%%%%%% THIS IS A LINE OF INTEREST: CONFIG VERSION: " + configVersion + "%%%%%%%"); return SecurityDynamicConfiguration.fromNode(jsonNode, cType, configVersion, 0, 0); } } diff --git a/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java b/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java index 78c9ab7818..773244c7ea 100644 --- a/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java +++ b/src/test/java/org/opensearch/security/DataStreamIntegrationTests.java @@ -19,25 +19,29 @@ import org.opensearch.security.test.helper.rest.RestHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; - public class DataStreamIntegrationTests extends SingleClusterTest { - final String bulkDocsBody = - "{ \"create\" : {} }" + System.lineSeparator() + - "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"vlb44hny\", \"name\": \"Sam\"}, \"message\" : \"Login attempt failed\" }" + System.lineSeparator() + - "{ \"create\" : {} }" + System.lineSeparator() + - "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"8a4f500d\", \"name\": \"Dam\"}, \"message\" : \"Login successful\" }" + System.lineSeparator() + - "{ \"create\" : {} }" + System.lineSeparator() + - "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"l7gk7f82\", \"name\": \"Pam\"}, \"message\" : \"Login attempt failed\" }" + System.lineSeparator(); + final String bulkDocsBody = "{ \"create\" : {} }" + + System.lineSeparator() + + "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"vlb44hny\", \"name\": \"Sam\"}, \"message\" : \"Login attempt failed\" }" + + System.lineSeparator() + + "{ \"create\" : {} }" + + System.lineSeparator() + + "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"8a4f500d\", \"name\": \"Dam\"}, \"message\" : \"Login successful\" }" + + System.lineSeparator() + + "{ \"create\" : {} }" + + System.lineSeparator() + + "{ \"@timestamp\" : \"2099-03-08T11:04:05.000Z\", \"user\" : { \"id\" : \"l7gk7f82\", \"name\": \"Pam\"}, \"message\" : \"Login attempt failed\" }" + + System.lineSeparator(); final String searchQuery1 = "{ \"seq_no_primary_term\" : true, \"query\": { \"match\": { \"user.id\": \"8a4f500d\"}}}"; final String searchQuery2 = "{ \"seq_no_primary_term\" : true, \"query\": { \"match\": { \"user.id\": \"l7gk7f82\"}}}"; public String getIndexTemplateBody() { - return "{\"index_patterns\": [ \"my-data-stream*\" ], \"data_stream\": { }, \"priority\": 200, \"template\": {\"settings\": { } } }"; + return "{\"index_patterns\": [ \"my-data-stream*\" ], \"data_stream\": { }, \"priority\": 200, \"template\": {\"settings\": { } } }"; } - public void createSampleDataStreams(RestHelper rh) throws Exception{ + public void createSampleDataStreams(RestHelper rh) throws Exception { // Valid index-template is required to create data-streams rh.executePutRequest("/_index_template/my-data-stream-template", getIndexTemplateBody(), encodeBasicHeader("ds1", "nagilum")); @@ -55,10 +59,18 @@ public void testCreateDataStream() throws Exception { RestHelper rh = nonSslRestHelper(); HttpResponse response; - response = rh.executePutRequest("/_index_template/my-data-stream-template", getIndexTemplateBody(), encodeBasicHeader("ds0", "nagilum")); + response = rh.executePutRequest( + "/_index_template/my-data-stream-template", + getIndexTemplateBody(), + encodeBasicHeader("ds0", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - response = rh.executePutRequest("/_index_template/my-data-stream-template", getIndexTemplateBody(), encodeBasicHeader("ds1", "nagilum")); + response = rh.executePutRequest( + "/_index_template/my-data-stream-template", + getIndexTemplateBody(), + encodeBasicHeader("ds1", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executePutRequest("/_data_stream/my-data-stream11", getIndexTemplateBody(), encodeBasicHeader("ds0", "nagilum")); diff --git a/src/test/java/org/opensearch/security/EncryptionInTransitMigrationTests.java b/src/test/java/org/opensearch/security/EncryptionInTransitMigrationTests.java index 5535d8a924..462cd591e6 100644 --- a/src/test/java/org/opensearch/security/EncryptionInTransitMigrationTests.java +++ b/src/test/java/org/opensearch/security/EncryptionInTransitMigrationTests.java @@ -44,7 +44,7 @@ private void testSslOnlyMode(boolean dualModeEnabled) throws Exception { HttpResponse res = rh.executeGetRequest("_opendistro/_security/sslinfo"); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - res = rh.executePutRequest("/xyz/_doc/1","{\"a\":5}"); + res = rh.executePutRequest("/xyz/_doc/1", "{\"a\":5}"); Assert.assertEquals(HttpStatus.SC_CREATED, res.getStatusCode()); res = rh.executeGetRequest("/_mappings"); @@ -58,21 +58,29 @@ private void testSslOnlyMode(boolean dualModeEnabled) throws Exception { Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("\"plugins.security_config.ssl_dual_mode_enabled\":\"true\"")); - String disableDualModeClusterSetting = "{ \"persistent\": { \"" + ConfigConstants.SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED + "\": false } }"; + String disableDualModeClusterSetting = "{ \"persistent\": { \"" + + ConfigConstants.SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED + + "\": false } }"; res = rh.executePutRequest("_cluster/settings", disableDualModeClusterSetting); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertEquals("{\"acknowledged\":true,\"persistent\":{\"plugins\":{\"security_config\":{\"ssl_dual_mode_enabled\":\"false\"}}},\"transient\":{}}", res.getBody()); - + Assert.assertEquals( + "{\"acknowledged\":true,\"persistent\":{\"plugins\":{\"security_config\":{\"ssl_dual_mode_enabled\":\"false\"}}},\"transient\":{}}", + res.getBody() + ); res = rh.executeGetRequest("_cluster/settings?flat_settings&include_defaults"); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("\"plugins.security_config.ssl_dual_mode_enabled\":\"false\"")); - String enableDualModeClusterSetting = "{ \"persistent\": { \"" + ConfigConstants.SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED + "\": true } }"; + String enableDualModeClusterSetting = "{ \"persistent\": { \"" + + ConfigConstants.SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED + + "\": true } }"; res = rh.executePutRequest("_cluster/settings", enableDualModeClusterSetting); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertEquals("{\"acknowledged\":true,\"persistent\":{\"plugins\":{\"security_config\":{\"ssl_dual_mode_enabled\":\"true\"}}},\"transient\":{}}", res.getBody()); - + Assert.assertEquals( + "{\"acknowledged\":true,\"persistent\":{\"plugins\":{\"security_config\":{\"ssl_dual_mode_enabled\":\"true\"}}},\"transient\":{}}", + res.getBody() + ); res = rh.executeGetRequest("_cluster/settings?flat_settings&include_defaults"); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); @@ -80,8 +88,10 @@ private void testSslOnlyMode(boolean dualModeEnabled) throws Exception { res = rh.executePutRequest("_cluster/settings", disableDualModeClusterSetting); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertEquals("{\"acknowledged\":true,\"persistent\":{\"plugins\":{\"security_config\":{\"ssl_dual_mode_enabled\":\"false\"}}},\"transient\":{}}", res.getBody()); - + Assert.assertEquals( + "{\"acknowledged\":true,\"persistent\":{\"plugins\":{\"security_config\":{\"ssl_dual_mode_enabled\":\"false\"}}},\"transient\":{}}", + res.getBody() + ); res = rh.executeGetRequest("_cluster/settings?flat_settings&include_defaults"); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); @@ -118,23 +128,19 @@ public void testSslOnlyModeDualModeWithNonSSLDataNode() throws Exception { @Test public void testDualModeSettingFallback() throws Exception { final Settings legacySettings = Settings.builder() - .put(ConfigConstants.LEGACY_OPENDISTRO_SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED, true) - .build(); + .put(ConfigConstants.LEGACY_OPENDISTRO_SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED, true) + .build(); Assert.assertEquals(SecuritySettings.SSL_DUAL_MODE_SETTING.get(legacySettings), true); final Settings legacySettings2 = Settings.builder() - .put(ConfigConstants.LEGACY_OPENDISTRO_SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED, false) - .build(); + .put(ConfigConstants.LEGACY_OPENDISTRO_SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED, false) + .build(); Assert.assertEquals(SecuritySettings.SSL_DUAL_MODE_SETTING.get(legacySettings2), false); - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED, true) - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED, true).build(); Assert.assertEquals(SecuritySettings.SSL_DUAL_MODE_SETTING.get(settings), true); - final Settings settings2 = Settings.builder() - .put(ConfigConstants.SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED, false) - .build(); + final Settings settings2 = Settings.builder().put(ConfigConstants.SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED, false).build(); Assert.assertEquals(SecuritySettings.SSL_DUAL_MODE_SETTING.get(settings2), false); } } diff --git a/src/test/java/org/opensearch/security/HealthTests.java b/src/test/java/org/opensearch/security/HealthTests.java index 0785fd620f..a31e22dff4 100644 --- a/src/test/java/org/opensearch/security/HealthTests.java +++ b/src/test/java/org/opensearch/security/HealthTests.java @@ -44,7 +44,10 @@ public void testHealth() throws Exception { RestHelper rh = nonSslRestHelper(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty&mode=lenient")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("_opendistro/_security/health?pretty&mode=lenient")).getStatusCode() + ); System.out.println(res.getBody()); assertContains(res, "*UP*"); assertNotContains(res, "*DOWN*"); @@ -63,13 +66,19 @@ public void testHealthUnitialized() throws Exception { RestHelper rh = nonSslRestHelper(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty&mode=lenient")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("_opendistro/_security/health?pretty&mode=lenient")).getStatusCode() + ); System.out.println(res.getBody()); assertContains(res, "*UP*"); assertNotContains(res, "*DOWN*"); assertNotContains(res, "*strict*"); - Assert.assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_SERVICE_UNAVAILABLE, + (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode() + ); System.out.println(res.getBody()); assertContains(res, "*DOWN*"); assertContains(res, "*strict*"); diff --git a/src/test/java/org/opensearch/security/HttpIntegrationTests.java b/src/test/java/org/opensearch/security/HttpIntegrationTests.java index bf185e0972..a4011b05f6 100644 --- a/src/test/java/org/opensearch/security/HttpIntegrationTests.java +++ b/src/test/java/org/opensearch/security/HttpIntegrationTests.java @@ -64,205 +64,373 @@ public class HttpIntegrationTests extends SingleClusterTest { @Test public void testHTTPBasic() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".worf", "knuddel","nonexists") - .build(); + .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".worf", "knuddel", "nonexists") + .build(); setup(settings); final RestHelper rh = nonSslRestHelper(); - try (Client tc = getClient()) { - tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("v2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("v3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("starfleet","starfleet_academy","starfleet_library").alias("sf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire","vulcangov").alias("nonsf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))).actionGet(); - - } - - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("_search").getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeDeleteRequest("nonexistentindex*", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest(".nonexistentindex*", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest(".opendistro_security/_doc/2", "{}",encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, rh.executeGetRequest(".opendistro_security/_doc/0", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, rh.executeGetRequest("xxxxyyyy/_doc/0", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("abc", "abc:abc")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("userwithnopassword", "")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("userwithblankpassword", "")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrongpasswd")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "Basic "+"wrongheader")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "Basic ")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "Basic")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("picard", "picard")).getStatusCode()); - - for(int i=0; i< 10; i++) { - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrongpasswd")).getStatusCode()); - } - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("/theindex","{}",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_CREATED, rh.executePutRequest("/theindex/_doc/1?refresh=true","{\"a\":0}",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); - //Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("/theindex/_analyze?text=this+is+a+test",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); - //Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("_analyze?text=this+is+a+test",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeDeleteRequest("/theindex",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeDeleteRequest("/klingonempire",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("starfleet/_search", encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("_search", encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeDeleteRequest(".opendistro_security/", encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("/.opendistro_security/_close", null,encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("/.opendistro_security/_upgrade", null,encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest("/.opendistro_security/_mapping","{}",encodeBasicHeader("worf", "worf")).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest(".opendistro_security/", encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest(".opendistro_security/_doc/2", "{}",encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest(".opendistro_security/_doc/0",encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeDeleteRequest(".opendistro_security/_doc/0",encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest(".opendistro_security/_doc/0","{}",encodeBasicHeader("worf", "worf")).getStatusCode()); - - HttpResponse resc = rh.executeGetRequest("_cat/indices/public?v",encodeBasicHeader("bug108", "nagilum")); - Assert.assertTrue(resc.getBody().contains("green")); - Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("role01_role02/_search?pretty",encodeBasicHeader("user_role01_role02_role03", "user_role01_role02_role03")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("role01_role02/_search?pretty",encodeBasicHeader("user_role01", "user_role01")).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("spock/_search?pretty",encodeBasicHeader("spock", "spock")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("spock/_search?pretty",encodeBasicHeader("kirk", "kirk")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("kirk/_search?pretty",encodeBasicHeader("kirk", "kirk")).getStatusCode()); - - //all - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest(".opendistro_security/_mget","{\"ids\" : [\"0\"]}",encodeBasicHeader("worf", "worf")).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode()); - - try (Client tc = getClient()) { - tc.index(new IndexRequest(".opendistro_security").id("roles").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("roles", FileHelper.readYamlContent("roles_deny.yml"))).actionGet(); - ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"roles"})).actionGet(); - Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); - } - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode()); - - try (Client tc = getClient()) { - tc.index(new IndexRequest(".opendistro_security").id("roles").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("roles", FileHelper.readYamlContent("roles.yml"))).actionGet(); - ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"roles"})).actionGet(); - Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); - } - - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode()); - HttpResponse res = rh.executeGetRequest("_search?pretty", encodeBasicHeader("nagilum", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody().contains("\"value\" : 11")); - Assert.assertTrue(!res.getBody().contains(".opendistro_security")); - - res = rh.executeGetRequest("_nodes/stats?pretty", encodeBasicHeader("nagilum", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody().contains("total_in_bytes")); - Assert.assertTrue(res.getBody().contains("max_file_descriptors")); - Assert.assertTrue(res.getBody().contains("buffer_pools")); - Assert.assertFalse(res.getBody().contains("\"nodes\" : { }")); - - res = rh.executePostRequest("*/_upgrade", "", encodeBasicHeader("nagilum", "nagilum")); - System.out.println(res.getBody()); - System.out.println(res.getStatusReason()); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - - String bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator(); - - res = rh.executePostRequest("_bulk", bulkBody, encodeBasicHeader("writer", "writer")); - System.out.println(res.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody().contains("\"errors\":false")); - Assert.assertTrue(res.getBody().contains("\"status\":201")); - - res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("security_tenant", "unittesttenant"), encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody().contains("tenant")); - Assert.assertTrue(res.getBody().contains("unittesttenant")); - Assert.assertTrue(res.getBody().contains("\"kltentrw\":true")); - Assert.assertTrue(res.getBody().contains("\"user_name\":\"worf\"")); - - res = rh.executeGetRequest("_opendistro/_security/authinfo", encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody().contains("tenant")); - Assert.assertTrue(res.getBody().contains("\"user_requested_tenant\":null")); - Assert.assertTrue(res.getBody().contains("\"kltentrw\":true")); - Assert.assertTrue(res.getBody().contains("\"user_name\":\"worf\"")); - Assert.assertTrue(res.getBody().contains("\"custom_attribute_names\":[]")); - Assert.assertFalse(res.getBody().contains("attributes=")); - - res = rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("custattr", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody().contains("tenants")); - Assert.assertTrue(res.getBody().contains("\"user_requested_tenant\" : null")); - Assert.assertTrue(res.getBody().contains("\"user_name\" : \"custattr\"")); - Assert.assertTrue(res.getBody().contains("\"custom_attribute_names\" : [")); - Assert.assertTrue(res.getBody().contains("attr.internal.c3")); - Assert.assertTrue(res.getBody().contains("attr.internal.c1")); - - res = rh.executeGetRequest("v2/_search", encodeBasicHeader("custattr", "nagilum")); - Assert.assertEquals(res.getBody(), HttpStatus.SC_OK, res.getStatusCode()); - - res = rh.executeGetRequest("v3/_search", encodeBasicHeader("custattr", "nagilum")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - - final String reindex = "{"+ - "\"source\": {"+ - "\"index\": \"starfleet\""+ - "},"+ - "\"dest\": {"+ - "\"index\": \"copysf\""+ - "}"+ - "}"; - - res = rh.executePostRequest("_reindex?pretty", reindex, encodeBasicHeader("nagilum", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody().contains("\"total\" : 1")); - Assert.assertTrue(res.getBody().contains("\"batches\" : 1")); - Assert.assertTrue(res.getBody().contains("\"failures\" : [ ]")); - - //rest impersonation - res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as","knuddel"), encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody().contains("name=knuddel")); - Assert.assertTrue(res.getBody().contains("attr.internal.test1")); - Assert.assertFalse(res.getBody().contains("worf")); - - res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as","nonexists"), encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - - res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as","notallowed"), encodeBasicHeader("worf", "worf")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); + try (Client tc = getClient()) { + tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("v2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("v3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add().indices("starfleet", "starfleet_academy", "starfleet_library").alias("sf") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire", "vulcangov").alias("nonsf")) + ) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))) + .actionGet(); + + } + + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("_search").getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("worf", "worf")).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeDeleteRequest("nonexistentindex*", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest(".nonexistentindex*", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest(".opendistro_security/_doc/2", "{}", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_NOT_FOUND, + rh.executeGetRequest(".opendistro_security/_doc/0", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_NOT_FOUND, + rh.executeGetRequest("xxxxyyyy/_doc/0", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("abc", "abc:abc")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest("", encodeBasicHeader("userwithnopassword", "")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest("", encodeBasicHeader("userwithblankpassword", "")).getStatusCode() + ); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrongpasswd")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest("", new BasicHeader("Authorization", "Basic " + "wrongheader")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest("", new BasicHeader("Authorization", "Basic ")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest("", new BasicHeader("Authorization", "Basic")).getStatusCode() + ); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "")).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("picard", "picard")).getStatusCode()); + + for (int i = 0; i < 10; i++) { + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest("", encodeBasicHeader("worf", "wrongpasswd")).getStatusCode() + ); } + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePutRequest("/theindex", "{}", encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_CREATED, + rh.executePutRequest("/theindex/_doc/1?refresh=true", "{\"a\":0}", encodeBasicHeader("theindexadmin", "theindexadmin")) + .getStatusCode() + ); + // Assert.assertEquals(HttpStatus.SC_OK, + // rh.executeGetRequest("/theindex/_analyze?text=this+is+a+test",encodeBasicHeader("theindexadmin", + // "theindexadmin")).getStatusCode()); + // Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + // rh.executeGetRequest("_analyze?text=this+is+a+test",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeDeleteRequest("/theindex", encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeDeleteRequest("/klingonempire", encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode() + ); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("starfleet/_search", encodeBasicHeader("worf", "worf")).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("_search", encodeBasicHeader("worf", "worf")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeDeleteRequest(".opendistro_security/", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest("/.opendistro_security/_close", null, encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest("/.opendistro_security/_upgrade", null, encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("/.opendistro_security/_mapping", "{}", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest(".opendistro_security/", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest(".opendistro_security/_doc/2", "{}", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest(".opendistro_security/_doc/0", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeDeleteRequest(".opendistro_security/_doc/0", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest(".opendistro_security/_doc/0", "{}", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + + HttpResponse resc = rh.executeGetRequest("_cat/indices/public?v", encodeBasicHeader("bug108", "nagilum")); + Assert.assertTrue(resc.getBody().contains("green")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest( + "role01_role02/_search?pretty", + encodeBasicHeader("user_role01_role02_role03", "user_role01_role02_role03") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest("role01_role02/_search?pretty", encodeBasicHeader("user_role01", "user_role01")).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("spock/_search?pretty", encodeBasicHeader("spock", "spock")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest("spock/_search?pretty", encodeBasicHeader("kirk", "kirk")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("kirk/_search?pretty", encodeBasicHeader("kirk", "kirk")).getStatusCode() + ); + + // all + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest(".opendistro_security/_mget", "{\"ids\" : [\"0\"]}", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + + try (Client tc = getClient()) { + tc.index( + new IndexRequest(".opendistro_security").id("roles") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("roles", FileHelper.readYamlContent("roles_deny.yml")) + ).actionGet(); + ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[] { "roles" })) + .actionGet(); + Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); + } + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + + try (Client tc = getClient()) { + tc.index( + new IndexRequest(".opendistro_security").id("roles") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("roles", FileHelper.readYamlContent("roles.yml")) + ).actionGet(); + ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[] { "roles" })) + .actionGet(); + Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); + } + + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + HttpResponse res = rh.executeGetRequest("_search?pretty", encodeBasicHeader("nagilum", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertTrue(res.getBody().contains("\"value\" : 11")); + Assert.assertTrue(!res.getBody().contains(".opendistro_security")); + + res = rh.executeGetRequest("_nodes/stats?pretty", encodeBasicHeader("nagilum", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertTrue(res.getBody().contains("total_in_bytes")); + Assert.assertTrue(res.getBody().contains("max_file_descriptors")); + Assert.assertTrue(res.getBody().contains("buffer_pools")); + Assert.assertFalse(res.getBody().contains("\"nodes\" : { }")); + + res = rh.executePostRequest("*/_upgrade", "", encodeBasicHeader("nagilum", "nagilum")); + System.out.println(res.getBody()); + System.out.println(res.getStatusReason()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + + String bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field1\" : \"value1\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator(); + + res = rh.executePostRequest("_bulk", bulkBody, encodeBasicHeader("writer", "writer")); + System.out.println(res.getBody()); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertTrue(res.getBody().contains("\"errors\":false")); + Assert.assertTrue(res.getBody().contains("\"status\":201")); + + res = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader("security_tenant", "unittesttenant"), + encodeBasicHeader("worf", "worf") + ); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertTrue(res.getBody().contains("tenant")); + Assert.assertTrue(res.getBody().contains("unittesttenant")); + Assert.assertTrue(res.getBody().contains("\"kltentrw\":true")); + Assert.assertTrue(res.getBody().contains("\"user_name\":\"worf\"")); + + res = rh.executeGetRequest("_opendistro/_security/authinfo", encodeBasicHeader("worf", "worf")); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertTrue(res.getBody().contains("tenant")); + Assert.assertTrue(res.getBody().contains("\"user_requested_tenant\":null")); + Assert.assertTrue(res.getBody().contains("\"kltentrw\":true")); + Assert.assertTrue(res.getBody().contains("\"user_name\":\"worf\"")); + Assert.assertTrue(res.getBody().contains("\"custom_attribute_names\":[]")); + Assert.assertFalse(res.getBody().contains("attributes=")); + + res = rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("custattr", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertTrue(res.getBody().contains("tenants")); + Assert.assertTrue(res.getBody().contains("\"user_requested_tenant\" : null")); + Assert.assertTrue(res.getBody().contains("\"user_name\" : \"custattr\"")); + Assert.assertTrue(res.getBody().contains("\"custom_attribute_names\" : [")); + Assert.assertTrue(res.getBody().contains("attr.internal.c3")); + Assert.assertTrue(res.getBody().contains("attr.internal.c1")); + + res = rh.executeGetRequest("v2/_search", encodeBasicHeader("custattr", "nagilum")); + Assert.assertEquals(res.getBody(), HttpStatus.SC_OK, res.getStatusCode()); + + res = rh.executeGetRequest("v3/_search", encodeBasicHeader("custattr", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); + + final String reindex = "{" + + "\"source\": {" + + "\"index\": \"starfleet\"" + + "}," + + "\"dest\": {" + + "\"index\": \"copysf\"" + + "}" + + "}"; + + res = rh.executePostRequest("_reindex?pretty", reindex, encodeBasicHeader("nagilum", "nagilum")); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertTrue(res.getBody().contains("\"total\" : 1")); + Assert.assertTrue(res.getBody().contains("\"batches\" : 1")); + Assert.assertTrue(res.getBody().contains("\"failures\" : [ ]")); + + // rest impersonation + res = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader("opendistro_security_impersonate_as", "knuddel"), + encodeBasicHeader("worf", "worf") + ); + Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); + Assert.assertTrue(res.getBody().contains("name=knuddel")); + Assert.assertTrue(res.getBody().contains("attr.internal.test1")); + Assert.assertFalse(res.getBody().contains("worf")); + + res = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader("opendistro_security_impersonate_as", "nonexists"), + encodeBasicHeader("worf", "worf") + ); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); + + res = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader("opendistro_security_impersonate_as", "notallowed"), + encodeBasicHeader("worf", "worf") + ); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); + } + @Test public void testHTTPSCompressionEnabled() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .put("http.compression",true) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .put("http.compression", true) + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings, true); - final RestHelper rh = restHelper(); //ssl resthelper + final RestHelper rh = restHelper(); // ssl resthelper HttpResponse res = rh.executeGetRequest("_opendistro/_security/sslinfo", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); @@ -278,12 +446,12 @@ public void testHTTPSCompressionEnabled() throws Exception { @Test public void testHTTPSCompression() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings, true); - final RestHelper rh = restHelper(); //ssl resthelper + final RestHelper rh = restHelper(); // ssl resthelper HttpResponse res = rh.executeGetRequest("_opendistro/_security/sslinfo", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); @@ -299,64 +467,78 @@ public void testHTTPSCompression() throws Exception { @Test public void testHTTPAnon() throws Exception { - setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_anon.yml"), Settings.EMPTY, true); + setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_anon.yml"), Settings.EMPTY, true); - RestHelper rh = nonSslRestHelper(); + RestHelper rh = nonSslRestHelper(); + + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("").getStatusCode()); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrong")).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + + HttpResponse resc = rh.executeGetRequest("_opendistro/_security/authinfo"); + Assert.assertTrue(resc.getBody().contains("opendistro_security_anonymous")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + resc = rh.executeGetRequest("_opendistro/_security/authinfo?pretty=true"); + System.out.println(resc.getBody()); + Assert.assertTrue(resc.getBody().contains("\"remote_address\" : \"")); // check pretty print + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + resc = rh.executeGetRequest("_opendistro/_security/authinfo", encodeBasicHeader("nagilum", "nagilum")); + System.out.println(resc.getBody()); + Assert.assertTrue(resc.getBody().contains("nagilum")); + Assert.assertFalse(resc.getBody().contains("opendistro_security_anonymous")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + try (Client tc = getClient()) { + tc.index( + new IndexRequest(".opendistro_security").id("config") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("config", FileHelper.readYamlContent("config.yml")) + ).actionGet(); + tc.index( + new IndexRequest(".opendistro_security").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("internalusers") + .source("internalusers", FileHelper.readYamlContent("internal_users.yml")) + ).actionGet(); + ConfigUpdateResponse cur = tc.execute( + ConfigUpdateAction.INSTANCE, + new ConfigUpdateRequest(new String[] { "config", "roles", "rolesmapping", "internalusers", "actiongroups" }) + ).actionGet(); + Assert.assertFalse(cur.hasFailures()); + Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); + } - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("").getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrong")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - - HttpResponse resc = rh.executeGetRequest("_opendistro/_security/authinfo"); - Assert.assertTrue(resc.getBody().contains("opendistro_security_anonymous")); - Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - - resc = rh.executeGetRequest("_opendistro/_security/authinfo?pretty=true"); - System.out.println(resc.getBody()); - Assert.assertTrue(resc.getBody().contains("\"remote_address\" : \"")); //check pretty print - Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - - resc = rh.executeGetRequest("_opendistro/_security/authinfo", encodeBasicHeader("nagilum", "nagilum")); - System.out.println(resc.getBody()); - Assert.assertTrue(resc.getBody().contains("nagilum")); - Assert.assertFalse(resc.getBody().contains("opendistro_security_anonymous")); - Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - - try (Client tc = getClient()) { - tc.index(new IndexRequest(".opendistro_security").id("config").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("config", FileHelper.readYamlContent("config.yml"))).actionGet(); - tc.index(new IndexRequest(".opendistro_security").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("internalusers").source("internalusers", FileHelper.readYamlContent("internal_users.yml"))).actionGet(); - ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config","roles","rolesmapping","internalusers","actiongroups"})).actionGet(); - Assert.assertFalse(cur.hasFailures()); - Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); - } - - - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("_opendistro/_security/authinfo").getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrong")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("_opendistro/_security/authinfo").getStatusCode()); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrong")).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); } @Test public void testHTTPClientCert() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.clientauth_mode","REQUIRE") - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "TLSv1.1","TLSv1.2") - .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256") - .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.1","TLSv1.2") - .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256") - .build(); + .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_PROTOCOLS, "TLSv1.1", "TLSv1.2") + .putList(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256") + .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, "TLSv1.1", "TLSv1.2") + .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256") + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_clientcert.yml"), settings, true); try (Client tc = getClient()) { - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); - ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config","roles","rolesmapping","internalusers","actiongroups"})).actionGet(); + ConfigUpdateResponse cur = tc.execute( + ConfigUpdateAction.INSTANCE, + new ConfigUpdateRequest(new String[] { "config", "roles", "rolesmapping", "internalusers", "actiongroups" }) + ).actionGet(); Assert.assertFalse(cur.hasFailures()); Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); } @@ -383,10 +565,10 @@ public void testHTTPPlaintextErrMsg() throws Exception { try { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .put("plugins.security.ssl.http.enabled", true) - .build(); + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .put("plugins.security.ssl.http.enabled", true) + .build(); setup(settings); RestHelper rh = nonSslRestHelper(); rh.executeGetRequest("", encodeBasicHeader("worf", "worf")); @@ -395,12 +577,11 @@ public void testHTTPPlaintextErrMsg() throws Exception { String log = FileUtils.readFileToString(new File("unittest.log"), StandardCharsets.UTF_8); Assert.assertTrue(log, log.contains("speaks http plaintext instead of ssl, will close the channel")); } catch (Exception e) { - Assert.fail("NoHttpResponseException expected but was "+e.getClass()+"#"+e.getMessage()); + Assert.fail("NoHttpResponseException expected but was " + e.getClass() + "#" + e.getMessage()); } } - @Test public void testHTTPProxyDefault() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_proxy.yml"), Settings.EMPTY, true); @@ -408,13 +589,62 @@ public void testHTTPProxyDefault() throws Exception { Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("x-proxy-user", "scotty"), encodeBasicHeader("nagilum-wrong", "nagilum-wrong")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("x-proxy-user-wrong", "scotty"), encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, rh.executeGetRequest("", new BasicHeader("x-forwarded-for", "a"),new BasicHeader("x-proxy-user", "scotty"), encodeBasicHeader("nagilum-wrong", "nagilum-wrong")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, rh.executeGetRequest("", new BasicHeader("x-forwarded-for", "a,b,c"),new BasicHeader("x-proxy-user", "scotty")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("x-proxy-user", "scotty")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("X-Proxy-User", "scotty")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("x-proxy-user", "scotty"),new BasicHeader("x-proxy-roles", "starfleet,engineer")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest( + "", + new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"), + new BasicHeader("x-proxy-user", "scotty"), + encodeBasicHeader("nagilum-wrong", "nagilum-wrong") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest( + "", + new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"), + new BasicHeader("x-proxy-user-wrong", "scotty"), + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_INTERNAL_SERVER_ERROR, + rh.executeGetRequest( + "", + new BasicHeader("x-forwarded-for", "a"), + new BasicHeader("x-proxy-user", "scotty"), + encodeBasicHeader("nagilum-wrong", "nagilum-wrong") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_INTERNAL_SERVER_ERROR, + rh.executeGetRequest("", new BasicHeader("x-forwarded-for", "a,b,c"), new BasicHeader("x-proxy-user", "scotty")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest( + "", + new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"), + new BasicHeader("x-proxy-user", "scotty") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest( + "", + new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"), + new BasicHeader("X-Proxy-User", "scotty") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest( + "", + new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"), + new BasicHeader("x-proxy-user", "scotty"), + new BasicHeader("x-proxy-roles", "starfleet,engineer") + ).getStatusCode() + ); } @@ -423,119 +653,260 @@ public void testHTTPProxyRolesSeparator() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_proxy_custom.yml"), Settings.EMPTY, true); RestHelper rh = nonSslRestHelper(); // separator is configured as ";" so separating roles with "," leads to one (wrong) backend role - HttpResponse res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("user", "scotty"),new BasicHeader("roles", "starfleet,engineer")); - Assert.assertTrue("Expected one backend role since separator is incorrect", res.getBody().contains("\"backend_roles\":[\"starfleet,engineer\"]")); + HttpResponse res = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"), + new BasicHeader("user", "scotty"), + new BasicHeader("roles", "starfleet,engineer") + ); + Assert.assertTrue( + "Expected one backend role since separator is incorrect", + res.getBody().contains("\"backend_roles\":[\"starfleet,engineer\"]") + ); // correct separator, now we should see two backend roles - res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"),new BasicHeader("user", "scotty"),new BasicHeader("roles", "starfleet;engineer")); - Assert.assertTrue("Expected two backend roles string since separator is correct: " + res.getBody(), res.getBody().contains("\"backend_roles\":[\"starfleet\",\"engineer\"]")); + res = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader("x-forwarded-for", "localhost,192.168.0.1,10.0.0.2"), + new BasicHeader("user", "scotty"), + new BasicHeader("roles", "starfleet;engineer") + ); + Assert.assertTrue( + "Expected two backend roles string since separator is correct: " + res.getBody(), + res.getBody().contains("\"backend_roles\":[\"starfleet\",\"engineer\"]") + ); } @Test - public void testHTTPBasic2() throws Exception { - - setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY); + public void testHTTPBasic2() throws Exception { - try (Client tc = getClient()) { + setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY); - tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + try (Client tc = getClient()) { - tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("starfleet","starfleet_academy","starfleet_library").alias("sf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire","vulcangov").alias("nonsf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))).actionGet(); - } + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.index( + new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.index( + new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.index( + new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add().indices("starfleet", "starfleet_academy", "starfleet_library").alias("sf") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire", "vulcangov").alias("nonsf")) + ) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))) + .actionGet(); + } - RestHelper rh = nonSslRestHelper(); + RestHelper rh = nonSslRestHelper(); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeDeleteRequest("nonexistentindex*", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest(".nonexistentindex*", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest(".opendistro_security/_doc/2", "{}",encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, rh.executeGetRequest(".opendistro_security/_doc/0", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, rh.executeGetRequest("xxxxyyyy/_doc/0", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("abc", "abc:abc")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("userwithnopassword", "")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("userwithblankpassword", "")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrongpasswd")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "Basic "+"wrongheader")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "Basic ")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "Basic")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("picard", "picard")).getStatusCode()); - - for(int i=0; i< 10; i++) { - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrongpasswd")).getStatusCode()); - } - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("/theindex","{}",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_CREATED, rh.executePutRequest("/theindex/_doc/1?refresh=true","{\"a\":0}",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); - //Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("/theindex/_analyze?text=this+is+a+test",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); - //Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("_analyze?text=this+is+a+test",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeDeleteRequest("/theindex",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeDeleteRequest("/klingonempire",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("starfleet/_search", encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("_search", encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeDeleteRequest(".opendistro_security/", encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("/.opendistro_security/_close", null,encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("/.opendistro_security/_upgrade", null,encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest("/.opendistro_security/_mapping","{}",encodeBasicHeader("worf", "worf")).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest(".opendistro_security/", encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest(".opendistro_security/_doc/2", "{}",encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest(".opendistro_security/_doc/0",encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeDeleteRequest(".opendistro_security/_doc/0",encodeBasicHeader("worf", "worf")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest(".opendistro_security/_doc/0","{}",encodeBasicHeader("worf", "worf")).getStatusCode()); - - HttpResponse resc = rh.executeGetRequest("_cat/indices/public",encodeBasicHeader("bug108", "nagilum")); - System.out.println(resc.getBody()); - //Assert.assertTrue(resc.getBody().contains("green")); - Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("role01_role02/_search?pretty",encodeBasicHeader("user_role01_role02_role03", "user_role01_role02_role03")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("role01_role02/_search?pretty",encodeBasicHeader("user_role01", "user_role01")).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("spock/_search?pretty",encodeBasicHeader("spock", "spock")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("spock/_search?pretty",encodeBasicHeader("kirk", "kirk")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("kirk/_search?pretty",encodeBasicHeader("kirk", "kirk")).getStatusCode()); - - System.out.println("ok"); - //all + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("").getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("worf", "worf")).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeDeleteRequest("nonexistentindex*", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest(".nonexistentindex*", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest(".opendistro_security/_doc/2", "{}", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_NOT_FOUND, + rh.executeGetRequest(".opendistro_security/_doc/0", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_NOT_FOUND, + rh.executeGetRequest("xxxxyyyy/_doc/0", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("abc", "abc:abc")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest("", encodeBasicHeader("userwithnopassword", "")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest("", encodeBasicHeader("userwithblankpassword", "")).getStatusCode() + ); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("worf", "wrongpasswd")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest("", new BasicHeader("Authorization", "Basic " + "wrongheader")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest("", new BasicHeader("Authorization", "Basic ")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest("", new BasicHeader("Authorization", "Basic")).getStatusCode() + ); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", new BasicHeader("Authorization", "")).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("picard", "picard")).getStatusCode()); + + for (int i = 0; i < 10; i++) { + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executeGetRequest("", encodeBasicHeader("worf", "wrongpasswd")).getStatusCode() + ); + } + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePutRequest("/theindex", "{}", encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_CREATED, + rh.executePutRequest("/theindex/_doc/1?refresh=true", "{\"a\":0}", encodeBasicHeader("theindexadmin", "theindexadmin")) + .getStatusCode() + ); + // Assert.assertEquals(HttpStatus.SC_OK, + // rh.executeGetRequest("/theindex/_analyze?text=this+is+a+test",encodeBasicHeader("theindexadmin", + // "theindexadmin")).getStatusCode()); + // Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + // rh.executeGetRequest("_analyze?text=this+is+a+test",encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeDeleteRequest("/theindex", encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeDeleteRequest("/klingonempire", encodeBasicHeader("theindexadmin", "theindexadmin")).getStatusCode() + ); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("starfleet/_search", encodeBasicHeader("worf", "worf")).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("_search", encodeBasicHeader("worf", "worf")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeDeleteRequest(".opendistro_security/", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest("/.opendistro_security/_close", null, encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest("/.opendistro_security/_upgrade", null, encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("/.opendistro_security/_mapping", "{}", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest(".opendistro_security/", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest(".opendistro_security/_doc/2", "{}", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest(".opendistro_security/_doc/0", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeDeleteRequest(".opendistro_security/_doc/0", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest(".opendistro_security/_doc/0", "{}", encodeBasicHeader("worf", "worf")).getStatusCode() + ); + + HttpResponse resc = rh.executeGetRequest("_cat/indices/public", encodeBasicHeader("bug108", "nagilum")); + System.out.println(resc.getBody()); + // Assert.assertTrue(resc.getBody().contains("green")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest( + "role01_role02/_search?pretty", + encodeBasicHeader("user_role01_role02_role03", "user_role01_role02_role03") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest("role01_role02/_search?pretty", encodeBasicHeader("user_role01", "user_role01")).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("spock/_search?pretty", encodeBasicHeader("spock", "spock")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest("spock/_search?pretty", encodeBasicHeader("kirk", "kirk")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("kirk/_search?pretty", encodeBasicHeader("kirk", "kirk")).getStatusCode() + ); + + System.out.println("ok"); + // all - } + } @Test public void testBulk() throws Exception { - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH") - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH").build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityRoles("roles_bulk.yml"), settings); final RestHelper rh = nonSslRestHelper(); - String bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator(); + String bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field1\" : \"value1\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator(); HttpResponse res = rh.executePostRequest("_bulk", bulkBody, encodeBasicHeader("bulk", "nagilum")); System.out.println(res.getBody()); @@ -546,17 +917,18 @@ public void testBulk() throws Exception { @Test public void testBulkWithOneIndexFailure() throws Exception { - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH") - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH").build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityRoles("roles_bulk.yml"), settings); final RestHelper rh = nonSslRestHelper(); - String bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"a\" : \"b\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"myindex\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"a\" : \"b\" }"+System.lineSeparator(); + String bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"a\" : \"b\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"myindex\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"a\" : \"b\" }" + + System.lineSeparator(); HttpResponse res = rh.executePostRequest("_bulk?refresh=true", bulkBody, encodeBasicHeader("bulk_test_user", "nagilum")); System.out.println(res.getBody()); @@ -569,30 +941,40 @@ public void testBulkWithOneIndexFailure() throws Exception { @Test public void test557() throws Exception { - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH") - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH").build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings); try (Client tc = getClient()) { tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); - tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); - tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); } final RestHelper rh = nonSslRestHelper(); - HttpResponse res = rh.executePostRequest("/*/_search", "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":10}}}}", encodeBasicHeader("nagilum", "nagilum")); + HttpResponse res = rh.executePostRequest( + "/*/_search", + "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":10}}}}", + encodeBasicHeader("nagilum", "nagilum") + ); System.out.println(res.getBody()); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("starfleet_academy")); - res = rh.executePostRequest("/*/_search", "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":10}}}}", encodeBasicHeader("557", "nagilum")); + res = rh.executePostRequest( + "/*/_search", + "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":10}}}}", + encodeBasicHeader("557", "nagilum") + ); System.out.println(res.getBody()); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("starfleet_academy")); @@ -600,25 +982,52 @@ public void test557() throws Exception { @Test public void testITT1635() throws Exception { - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH") - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH").build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_dnfof.yml").setSecurityRoles("roles_itt1635.yml"), settings); try (Client tc = getClient()) { - tc.index(new IndexRequest("esb-prod-1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("esb-prod-2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("esb-prod-3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("esb-prod-4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":4}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("esb-prod-5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":5}", XContentType.JSON)).actionGet(); - - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-1","esb-prod-2","esb-prod-3","esb-prod-4","esb-prod-5").alias("esb-prod-all"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-1").alias("esb-alias-1"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-2").alias("esb-alias-2"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-3").alias("esb-alias-3"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-4").alias("esb-alias-4"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-5").alias("esb-alias-5"))).actionGet(); + tc.index(new IndexRequest("esb-prod-1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("esb-prod-2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("esb-prod-3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("esb-prod-4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":4}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("esb-prod-5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":5}", XContentType.JSON)) + .actionGet(); + + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add() + .indices("esb-prod-1", "esb-prod-2", "esb-prod-3", "esb-prod-4", "esb-prod-5") + .alias("esb-prod-all") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-1").alias("esb-alias-1"))) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-2").alias("esb-alias-2"))) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-3").alias("esb-alias-3"))) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-4").alias("esb-alias-4"))) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-5").alias("esb-alias-5"))) + .actionGet(); } @@ -637,39 +1046,72 @@ public void testITT1635() throws Exception { @Test public void testTenantInfo() throws Exception { - final Settings settings = Settings.builder() - .build(); + final Settings settings = Settings.builder().build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings); /* [admin_1, praxisrw, abcdef_2_2, kltentro, praxisro, kltentrw] - admin_1==.kibana_-1139640511_admin1 - praxisrw==.kibana_-1386441176_praxisrw - abcdef_2_2==.kibana_-634608247_abcdef22 - kltentro==.kibana_-2014056171_kltentro - praxisro==.kibana_-1386441184_praxisro - kltentrw==.kibana_-2014056163_kltentrw + admin_1==.kibana_-1139640511_admin1 + praxisrw==.kibana_-1386441176_praxisrw + abcdef_2_2==.kibana_-634608247_abcdef22 + kltentro==.kibana_-2014056171_kltentro + praxisro==.kibana_-1386441184_praxisro + kltentrw==.kibana_-2014056163_kltentrw */ try (Client tc = getClient()) { - tc.index(new IndexRequest(".kibana-6").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest(".kibana_-1139640511_admin1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest(".kibana_-1386441176_praxisrw").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest(".kibana_-634608247_abcdef22").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest(".kibana_-12345_123456").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest(".kibana2_-12345_123456").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest(".kibana_9876_xxx_ccc").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest(".kibana_fff_eee").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); - - - tc.index(new IndexRequest("esb-prod-5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":5}", XContentType.JSON)).actionGet(); - - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices(".kibana-6").alias(".kibana"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-5").alias(".kibana_-2014056163_kltentrw"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-5").alias("esb-alias-5"))).actionGet(); + tc.index(new IndexRequest(".kibana-6").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest(".kibana_-1139640511_admin1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":3}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest(".kibana_-1386441176_praxisrw").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":3}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest(".kibana_-634608247_abcdef22").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":3}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest(".kibana_-12345_123456").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":3}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest(".kibana2_-12345_123456").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":3}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest(".kibana_9876_xxx_ccc").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":3}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest(".kibana_fff_eee").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON) + ).actionGet(); + + tc.index(new IndexRequest("esb-prod-5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":5}", XContentType.JSON)) + .actionGet(); + + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices(".kibana-6").alias(".kibana"))) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add().indices("esb-prod-5").alias(".kibana_-2014056163_kltentrw") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("esb-prod-5").alias("esb-alias-5"))) + .actionGet(); } @@ -695,13 +1137,17 @@ public void testTenantInfo() throws Exception { @Test public void testRestImpersonation() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".worf", "someotherusernotininternalusersfile") - .build(); + .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".worf", "someotherusernotininternalusersfile") + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_rest_impersonation.yml"), settings); final RestHelper rh = nonSslRestHelper(); - //rest impersonation - HttpResponse res = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as","someotherusernotininternalusersfile"), encodeBasicHeader("worf", "worf")); + // rest impersonation + HttpResponse res = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader("opendistro_security_impersonate_as", "someotherusernotininternalusersfile"), + encodeBasicHeader("worf", "worf") + ); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("name=someotherusernotininternalusersfile")); Assert.assertFalse(res.getBody().contains("worf")); @@ -709,16 +1155,14 @@ public void testRestImpersonation() throws Exception { @Test public void testSslOnlyMode() throws Exception { - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_SSL_ONLY, true) - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_SSL_ONLY, true).build(); setupSslOnlyMode(settings); final RestHelper rh = nonSslRestHelper(); HttpResponse res = rh.executeGetRequest("_opendistro/_security/sslinfo"); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - res = rh.executePutRequest("/xyz/_doc/1","{\"a\":5}"); + res = rh.executePutRequest("/xyz/_doc/1", "{\"a\":5}"); Assert.assertEquals(HttpStatus.SC_CREATED, res.getStatusCode()); res = rh.executeGetRequest("/_mappings"); @@ -736,10 +1180,13 @@ public void testAll() throws Exception { try (Client tc = getClient()) { tc.index(new IndexRequest("abcdef").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) - .actionGet(); + .actionGet(); } - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("_all/_search", encodeBasicHeader("worf", "worf")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest("_all/_search", encodeBasicHeader("worf", "worf")).getStatusCode() + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("*/_search", encodeBasicHeader("worf", "worf")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("_search", encodeBasicHeader("worf", "worf")).getStatusCode()); } diff --git a/src/test/java/org/opensearch/security/IndexIntegrationTests.java b/src/test/java/org/opensearch/security/IndexIntegrationTests.java index 7dcef21483..6a8703842e 100644 --- a/src/test/java/org/opensearch/security/IndexIntegrationTests.java +++ b/src/test/java/org/opensearch/security/IndexIntegrationTests.java @@ -60,23 +60,36 @@ public class IndexIntegrationTests extends SingleClusterTest { @Test public void testComposite() throws Exception { - setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("composite_config.yml").setSecurityRoles("roles_composite.yml"), Settings.EMPTY, true); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setConfig("composite_config.yml").setSecurityRoles("roles_composite.yml"), + Settings.EMPTY, + true + ); final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { - tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); } - String msearchBody = - "{\"index\":\"starfleet\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"klingonempire\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"public\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); - + String msearchBody = "{\"index\":\"starfleet\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"klingonempire\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"public\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); HttpResponse resc = rh.executePostRequest("_msearch", msearchBody, encodeBasicHeader("worf", "worf")); Assert.assertEquals(200, resc.getStatusCode()); @@ -93,33 +106,55 @@ public void testBulkShards() throws Exception { final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { - //create indices and mapping upfront - tc.index(new IndexRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("lorem").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON)).actionGet(); + // create indices and mapping upfront + tc.index(new IndexRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("lorem").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON)) + .actionGet(); } - String bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"3\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"4\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"5\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"lorem\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"lorem\", \"_id\" : \"2\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"lorem\", \"_id\" : \"3\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"lorem\", \"_id\" : \"4\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"lorem\", \"_id\" : \"5\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"delete\" : { \"_index\" : \"lorem\", \"_id\" : \"5\" } }"+System.lineSeparator(); + String bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value1\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"3\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"4\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"5\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"lorem\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"lorem\", \"_id\" : \"2\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"lorem\", \"_id\" : \"3\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"lorem\", \"_id\" : \"4\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"lorem\", \"_id\" : \"5\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"delete\" : { \"_index\" : \"lorem\", \"_id\" : \"5\" } }" + + System.lineSeparator(); System.out.println("############ _bulk"); HttpResponse res = rh.executePostRequest("_bulk?refresh=true&pretty=true", bulkBody, encodeBasicHeader("worf", "worf")); @@ -132,7 +167,6 @@ public void testBulkShards() throws Exception { System.out.println("############ check shards"); System.out.println(rh.executeGetRequest("_cat/shards?v", encodeBasicHeader("nagilum", "nagilum"))); - } @Test @@ -142,21 +176,39 @@ public void testCreateIndex() throws Exception { RestHelper rh = nonSslRestHelper(); HttpResponse res; - Assert.assertEquals("Unable to create index 'nag'", HttpStatus.SC_OK, rh.executePutRequest("nag1", null, encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals("Unable to create index 'starfleet_library'", HttpStatus.SC_OK, rh.executePutRequest("starfleet_library", null, encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + "Unable to create index 'nag'", + HttpStatus.SC_OK, + rh.executePutRequest("nag1", null, encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + "Unable to create index 'starfleet_library'", + HttpStatus.SC_OK, + rh.executePutRequest("starfleet_library", null, encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); clusterHelper.waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(10), clusterInfo.numNodes); - Assert.assertEquals("Unable to close index 'starfleet_library'", HttpStatus.SC_OK, rh.executePostRequest("starfleet_library/_close", null, encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - - Assert.assertEquals("Unable to open index 'starfleet_library'", HttpStatus.SC_OK, (res = rh.executePostRequest("starfleet_library/_open", null, encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + "Unable to close index 'starfleet_library'", + HttpStatus.SC_OK, + rh.executePostRequest("starfleet_library/_close", null, encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + + Assert.assertEquals( + "Unable to open index 'starfleet_library'", + HttpStatus.SC_OK, + (res = rh.executePostRequest("starfleet_library/_open", null, encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); Assert.assertTrue("open index 'starfleet_library' not acknowledged", res.getBody().contains("acknowledged")); Assert.assertFalse("open index 'starfleet_library' not acknowledged", res.getBody().contains("false")); clusterHelper.waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(10), clusterInfo.numNodes); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePutRequest("public", null, encodeBasicHeader("spock", "spock")).getStatusCode()); - + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("public", null, encodeBasicHeader("spock", "spock")).getStatusCode() + ); } @@ -167,29 +219,51 @@ public void testFilteredAlias() throws Exception { try (Client tc = getClient()) { - tc.index(new IndexRequest("theindex").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("otherindex").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().alias("alias1").filter(QueryBuilders.termQuery("_type", "type1")).index("theindex"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().alias("alias2").filter(QueryBuilders.termQuery("_type", "type2")).index("theindex"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().alias("alias3").filter(QueryBuilders.termQuery("_type", "type2")).index("otherindex"))).actionGet(); + tc.index(new IndexRequest("theindex").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("otherindex").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add().alias("alias1").filter(QueryBuilders.termQuery("_type", "type1")).index("theindex") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add().alias("alias2").filter(QueryBuilders.termQuery("_type", "type2")).index("theindex") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add().alias("alias3").filter(QueryBuilders.termQuery("_type", "type2")).index("otherindex") + ) + ) + .actionGet(); } - RestHelper rh = nonSslRestHelper(); - //opendistro_security_user1 -> worf - //opendistro_security_user2 -> picard + // opendistro_security_user1 -> worf + // opendistro_security_user2 -> picard HttpResponse resc = rh.executeGetRequest("alias*/_search", encodeBasicHeader("worf", "worf")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - resc = rh.executeGetRequest("theindex/_search", encodeBasicHeader("nagilum", "nagilum")); + resc = rh.executeGetRequest("theindex/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - resc = rh.executeGetRequest("alias3/_search", encodeBasicHeader("nagilum", "nagilum")); + resc = rh.executeGetRequest("alias3/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - resc = rh.executeGetRequest("_cat/indices", encodeBasicHeader("nagilum", "nagilum")); + resc = rh.executeGetRequest("_cat/indices", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); } @@ -200,16 +274,25 @@ public void testIndexTypeEvaluation() throws Exception { setup(); try (Client tc = getClient()) { - tc.index(new IndexRequest("foo1").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("foo2").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("foo").id("3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("fooba").id("4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":4}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("foo1").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("foo2").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON) + ).actionGet(); + tc.index(new IndexRequest("foo").id("3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("fooba").id("4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":4}", XContentType.JSON) + ).actionGet(); try { - tc.index(new IndexRequest("x#a").id("4a").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":4}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("x#a").id("4a").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":4}", XContentType.JSON) + ).actionGet(); Assert.fail("Indexname can contain #"); } catch (InvalidIndexNameException e) { - //expected + // expected } } @@ -227,8 +310,8 @@ public void testIndexTypeEvaluation() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("\"content\" : 3")); - //resc = rh.executeGetRequest("/fooba/z/_search?pretty", encodeBasicHeader("baz", "worf")); - //Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + // resc = rh.executeGetRequest("/fooba/z/_search?pretty", encodeBasicHeader("baz", "worf")); + // Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); resc = rh.executeGetRequest("/foo1/_doc/1?pretty", encodeBasicHeader("baz", "worf")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); @@ -245,11 +328,11 @@ public void testIndexTypeEvaluation() throws Exception { Assert.assertTrue(resc.getBody().contains("\"content\" : 3")); Assert.assertTrue(resc.getBody().contains("\"found\" : true")); - //resc = rh.executeGetRequest("/fooba/z/4?pretty", encodeBasicHeader("baz", "worf")); - //Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + // resc = rh.executeGetRequest("/fooba/z/4?pretty", encodeBasicHeader("baz", "worf")); + // Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - //resc = rh.executeGetRequest("/foo*/_search?pretty", encodeBasicHeader("baz", "worf")); - //Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + // resc = rh.executeGetRequest("/foo*/_search?pretty", encodeBasicHeader("baz", "worf")); + // Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); resc = rh.executeGetRequest("/foo*,-fooba/_search?pretty", encodeBasicHeader("baz", "worf")); Assert.assertEquals(200, resc.getStatusCode()); @@ -263,73 +346,174 @@ public void testIndices() throws Exception { setup(); try (Client tc = getClient()) { - tc.index(new IndexRequest("nopermindex").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("logstash-1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash-2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash-3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash-4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("nopermindex").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.index(new IndexRequest("logstash-1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("logstash-2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("logstash-3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("logstash-4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd", SecurityUtils.EN_Locale); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); String date = sdf.format(new Date()); - tc.index(new IndexRequest("logstash-"+date).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("logstash-" + date).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); } RestHelper rh = nonSslRestHelper(); HttpResponse res = null; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-1/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - //nonexistent index with permissions - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, (res = rh.executeGetRequest("/logstash-nonex/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - //existent index without permissions - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/nopermindex/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - //nonexistent index without permissions - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/does-not-exist-and-no-perm/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - //nonexistent and existent index with permissions - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, (res = rh.executeGetRequest("/logstash-nonex,logstash-1/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - //existent index with permissions - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-1/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - //nonexistent index with failed login - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, (res = rh.executeGetRequest("/logstash-nonex/_search", encodeBasicHeader("nouser", "nosuer"))).getStatusCode()); - - //nonexistent index with no login + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash-1/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))) + .getStatusCode() + ); + + // nonexistent index with permissions + Assert.assertEquals( + HttpStatus.SC_NOT_FOUND, + (res = rh.executeGetRequest("/logstash-nonex/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))) + .getStatusCode() + ); + + // existent index without permissions + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest("/nopermindex/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))) + .getStatusCode() + ); + + // nonexistent index without permissions + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest( + "/does-not-exist-and-no-perm/_search", + encodeBasicHeader("opendistro_security_logstash", "nagilum") + )).getStatusCode() + ); + + // nonexistent and existent index with permissions + Assert.assertEquals( + HttpStatus.SC_NOT_FOUND, + (res = rh.executeGetRequest("/logstash-nonex,logstash-1/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))) + .getStatusCode() + ); + + // existent index with permissions + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash-1/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))) + .getStatusCode() + ); + + // nonexistent index with failed login + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + (res = rh.executeGetRequest("/logstash-nonex/_search", encodeBasicHeader("nouser", "nosuer"))).getStatusCode() + ); + + // nonexistent index with no login Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, (res = rh.executeGetRequest("/logstash-nonex/_search")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/_all/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/*/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/nopermindex,logstash-1,nonexist/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/logstash-1,nonexist/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/nonexist/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/%3Clogstash-%7Bnow%2Fd%7D%3E/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/%3Cnonex-%7Bnow%2Fd%7D%3E/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/%3Clogstash-%7Bnow%2Fd%7D%3E,logstash-*/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/%3Clogstash-%7Bnow%2Fd%7D%3E,logstash-1/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_CREATED, (res = rh.executePutRequest("/logstash-b/_doc/1", "{}",encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePutRequest("/%3Clogstash-cnew-%7Bnow%2Fd%7D%3E", "{}",encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_CREATED, (res = rh.executePutRequest("/%3Clogstash-new-%7Bnow%2Fd%7D%3E/_doc/1", "{}",encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/_cat/indices?v" ,encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest("/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest("/_all/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest("/*/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest( + "/nopermindex,logstash-1,nonexist/_search", + encodeBasicHeader("opendistro_security_logstash", "nagilum") + )).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest("/logstash-1,nonexist/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))) + .getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest("/nonexist/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + "/%3Clogstash-%7Bnow%2Fd%7D%3E/_search", + encodeBasicHeader("opendistro_security_logstash", "nagilum") + )).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest("/%3Cnonex-%7Bnow%2Fd%7D%3E/_search", encodeBasicHeader("opendistro_security_logstash", "nagilum"))) + .getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + "/%3Clogstash-%7Bnow%2Fd%7D%3E,logstash-*/_search", + encodeBasicHeader("opendistro_security_logstash", "nagilum") + )).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + "/%3Clogstash-%7Bnow%2Fd%7D%3E,logstash-1/_search", + encodeBasicHeader("opendistro_security_logstash", "nagilum") + )).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_CREATED, + (res = rh.executePutRequest("/logstash-b/_doc/1", "{}", encodeBasicHeader("opendistro_security_logstash", "nagilum"))) + .getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePutRequest( + "/%3Clogstash-cnew-%7Bnow%2Fd%7D%3E", + "{}", + encodeBasicHeader("opendistro_security_logstash", "nagilum") + )).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_CREATED, + (res = rh.executePutRequest( + "/%3Clogstash-new-%7Bnow%2Fd%7D%3E/_doc/1", + "{}", + encodeBasicHeader("opendistro_security_logstash", "nagilum") + )).getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/_cat/indices?v", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("logstash-b")); @@ -341,82 +525,122 @@ public void testIndices() throws Exception { @Test public void testAliases() throws Exception { - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH") - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH").build(); setup(settings); try (Client tc = getClient()) { - tc.index(new IndexRequest("nopermindex").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("logstash-1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash-2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash-3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash-4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash-5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash-del").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash-del-ok").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("nopermindex").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.index(new IndexRequest("logstash-1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("logstash-2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("logstash-3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("logstash-4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("logstash-5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("logstash-del").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("logstash-del-ok").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); String date = new SimpleDateFormat("YYYY.MM.dd").format(new Date()); - tc.index(new IndexRequest("logstash-"+date).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("nopermindex").alias("nopermalias"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices(".opendistro_security").alias("mysgi"))).actionGet(); + tc.index( + new IndexRequest("logstash-" + date).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("nopermindex").alias("nopermalias"))) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices(".opendistro_security").alias("mysgi"))) + .actionGet(); } RestHelper rh = nonSslRestHelper(); HttpResponse res = null; - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePostRequest("/mysgi/_doc", "{}",encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/mysgi/_search?pretty", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executePostRequest("/mysgi/_doc", "{}", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/mysgi/_search?pretty", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); assertContains(res, "*\"hits\" : {*\"value\" : 0,*\"hits\" : [ ]*"); System.out.println("#### add alias to allowed index"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePutRequest("/logstash-1/_alias/alog1", "",encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePutRequest("/logstash-1/_alias/alog1", "", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode() + ); System.out.println("#### add alias to not existing (no perm)"); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePutRequest("/nonexitent/_alias/alnp", "",encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executePutRequest("/nonexitent/_alias/alnp", "", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode() + ); System.out.println("#### add alias to not existing (with perm)"); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, (res = rh.executePutRequest("/logstash-nonex/_alias/alnp", "",encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_NOT_FOUND, + (res = rh.executePutRequest("/logstash-nonex/_alias/alnp", "", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode() + ); System.out.println("#### add alias to not allowed index"); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePutRequest("/nopermindex/_alias/alnp", "",encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); - - String aliasRemoveIndex = "{"+ - "\"actions\" : ["+ - "{ \"add\": { \"index\": \"logstash-del-ok\", \"alias\": \"logstash-del\" } },"+ - "{ \"remove_index\": { \"index\": \"logstash-del\" } } "+ - "]"+ - "}"; + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executePutRequest("/nopermindex/_alias/alnp", "", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode() + ); + + String aliasRemoveIndex = "{" + + "\"actions\" : [" + + "{ \"add\": { \"index\": \"logstash-del-ok\", \"alias\": \"logstash-del\" } }," + + "{ \"remove_index\": { \"index\": \"logstash-del\" } } " + + "]" + + "}"; System.out.println("#### remove_index"); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePostRequest("/_aliases", aliasRemoveIndex,encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); - + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executePostRequest("/_aliases", aliasRemoveIndex, encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode() + ); System.out.println("#### get alias for permitted index"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-1/_alias/alog1", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); - + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash-1/_alias/alog1", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode() + ); System.out.println("#### get alias for all indices"); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/_alias/alog1", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); - + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest("/_alias/alog1", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode() + ); System.out.println("#### get alias no perm"); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("/_alias/nopermalias", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); - - String alias = - "{"+ - "\"aliases\": {"+ - "\"alias1\": {}"+ - "}"+ - "}"; + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest("/_alias/nopermalias", encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode() + ); + String alias = "{" + "\"aliases\": {" + "\"alias1\": {}" + "}" + "}"; System.out.println("#### create alias along with index"); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executePutRequest("/beats-withalias", alias,encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executePutRequest("/beats-withalias", alias, encodeBasicHeader("aliasmngt", "nagilum"))).getStatusCode() + ); } @Test @@ -425,7 +649,10 @@ public void testIndexResolveInvalidIndexName() throws Exception { final RestHelper rh = nonSslRestHelper(); // invalid_index_name_exception should be thrown and responded when invalid index name is mentioned in requests. - HttpResponse res = rh.executeGetRequest(URLEncoder.encode("_##pdt_data/_search", "UTF-8"), encodeBasicHeader("ccsresolv", "nagilum")); + HttpResponse res = rh.executeGetRequest( + URLEncoder.encode("_##pdt_data/_search", "UTF-8"), + encodeBasicHeader("ccsresolv", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, res.getStatusCode()); Assert.assertTrue(res.getBody().contains("invalid_index_name_exception")); } @@ -437,16 +664,17 @@ public void testCCSIndexResolve() throws Exception { final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { - tc.index(new IndexRequest(".abc-6").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest(".abc-6").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); } - //ccsresolv has perm for ?abc* + // ccsresolv has perm for ?abc* HttpResponse res = rh.executeGetRequest("ggg:.abc-6,.abc-6/_search", encodeBasicHeader("ccsresolv", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); res = rh.executeGetRequest("/*:.abc-6,.abc-6/_search", encodeBasicHeader("ccsresolv", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - //TODO: Change for 25.0 to be forbidden (possible bug in ES regarding ccs wildcard) + // TODO: Change for 25.0 to be forbidden (possible bug in ES regarding ccs wildcard) } @Test @@ -457,38 +685,41 @@ public void testCCSIndexResolve2() throws Exception { final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { - tc.index(new IndexRequest(".abc").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("xyz").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("noperm").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest(".abc").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("xyz").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("noperm").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":3}", XContentType.JSON)) + .actionGet(); } HttpResponse res = rh.executeGetRequest("/*:.abc,.abc/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":1")); + Assert.assertTrue(res.getBody(), res.getBody().contains("\"content\":1")); res = rh.executeGetRequest("/ba*bcuzh/_search", encodeBasicHeader("nagilum", "nagilum")); - Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":12")); + Assert.assertTrue(res.getBody(), res.getBody().contains("\"content\":12")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); res = rh.executeGetRequest("/*:.abc/_search", encodeBasicHeader("nagilum", "nagilum")); - Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":1")); + Assert.assertTrue(res.getBody(), res.getBody().contains("\"content\":1")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); res = rh.executeGetRequest("/*:xyz,xyz/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":2")); + Assert.assertTrue(res.getBody(), res.getBody().contains("\"content\":2")); - //res = rh.executeGetRequest("/*noexist/_search", encodeBasicHeader("nagilum", "nagilum")); - //Assert.assertEquals(HttpStatus.SC_NOT_FOUND, res.getStatusCode()); + // res = rh.executeGetRequest("/*noexist/_search", encodeBasicHeader("nagilum", "nagilum")); + // Assert.assertEquals(HttpStatus.SC_NOT_FOUND, res.getStatusCode()); res = rh.executeGetRequest("/*:.abc/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":1")); + Assert.assertTrue(res.getBody(), res.getBody().contains("\"content\":1")); res = rh.executeGetRequest("/*:xyz/_search", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":2")); + Assert.assertTrue(res.getBody(), res.getBody().contains("\"content\":2")); res = rh.executeGetRequest("/.abc/_search", encodeBasicHeader("ccsresolv", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); @@ -515,18 +746,26 @@ public void testCCSIndexResolve2() throws Exception { @Test public void testIndexResolveIgnoreUnavailable() throws Exception { - setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_respect_indices_options.yml").setSecurityRoles("roles_bs.yml"), Settings.EMPTY, true); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setConfig("config_respect_indices_options.yml").setSecurityRoles("roles_bs.yml"), + Settings.EMPTY, + true + ); final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { - //create indices and mapping upfront - tc.index(new IndexRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("lorem").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON)).actionGet(); + // create indices and mapping upfront + tc.index(new IndexRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("lorem").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON)) + .actionGet(); } - String msearchBody = - "{\"index\": [\"tes*\",\"-security\",\"-missing\"], \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"match_all\":{}}}"+System.lineSeparator(); + String msearchBody = "{\"index\": [\"tes*\",\"-security\",\"-missing\"], \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"match_all\":{}}}" + + System.lineSeparator(); HttpResponse resc = rh.executePostRequest("_msearch", msearchBody, encodeBasicHeader("worf", "worf")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); @@ -540,9 +779,14 @@ public void testIndexResolveIndicesAlias() throws Exception { final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { - //create indices and mapping upfront - tc.index(new IndexRequest("foo-index").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON)).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("foo-index").alias("foo-alias"))).actionGet(); + // create indices and mapping upfront + tc.index( + new IndexRequest("foo-index").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON) + ).actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("foo-index").alias("foo-alias"))) + .actionGet(); tc.admin().indices().delete(new DeleteIndexRequest("foo-index")).actionGet(); } @@ -567,8 +811,10 @@ public void testIndexResolveMinus() throws Exception { final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { - //create indices and mapping upfront - tc.index(new IndexRequest("foo-abc").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON)).actionGet(); + // create indices and mapping upfront + tc.index( + new IndexRequest("foo-abc").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"field2\":\"init\"}", XContentType.JSON) + ).actionGet(); } HttpResponse resc = rh.executeGetRequest("/**/_search", encodeBasicHeader("foo_all", "nagilum")); diff --git a/src/test/java/org/opensearch/security/IndexTemplateClusterPermissionsCheckTest.java b/src/test/java/org/opensearch/security/IndexTemplateClusterPermissionsCheckTest.java index b2c483abd7..03d26e2062 100644 --- a/src/test/java/org/opensearch/security/IndexTemplateClusterPermissionsCheckTest.java +++ b/src/test/java/org/opensearch/security/IndexTemplateClusterPermissionsCheckTest.java @@ -20,45 +20,55 @@ import org.opensearch.security.test.helper.rest.RestHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class IndexTemplateClusterPermissionsCheckTest extends SingleClusterTest{ - private RestHelper rh; +public class IndexTemplateClusterPermissionsCheckTest extends SingleClusterTest { + private RestHelper rh; - final static String indexTemplateBody = "{ \"index_patterns\": [\"sem1234*\"], \"template\": { \"settings\": { \"number_of_shards\": 2, \"number_of_replicas\": 1 }, \"mappings\": { \"properties\": { \"timestamp\": { \"type\": \"date\", \"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis\" }, \"value\": { \"type\": \"double\" } } } } }"; + final static String indexTemplateBody = + "{ \"index_patterns\": [\"sem1234*\"], \"template\": { \"settings\": { \"number_of_shards\": 2, \"number_of_replicas\": 1 }, \"mappings\": { \"properties\": { \"timestamp\": { \"type\": \"date\", \"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis\" }, \"value\": { \"type\": \"double\" } } } } }"; - private String getFailureResponseReason(String user) { - return "no permissions for [indices:admin/index_template/put] and User [name=" + user + ", backend_roles=[], requestedTenant=null]"; - } + private String getFailureResponseReason(String user) { + return "no permissions for [indices:admin/index_template/put] and User [name=" + user + ", backend_roles=[], requestedTenant=null]"; + } - @Before - public void setupRestHelper() throws Exception{ - setup(); - rh = nonSslRestHelper(); - } - @Test - public void testPutIndexTemplateByNonPrivilegedUser() throws Exception { - String expectedFailureResponse = getFailureResponseReason("ds4"); + @Before + public void setupRestHelper() throws Exception { + setup(); + rh = nonSslRestHelper(); + } - // should fail, as user `ds3` doesn't have correct permissions - HttpResponse response = rh.executePutRequest("/_index_template/sem1234", indexTemplateBody, encodeBasicHeader("ds4", "nagilum")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - Assert.assertEquals(expectedFailureResponse, response.findValueInJson("error.root_cause[0].reason")); - } + @Test + public void testPutIndexTemplateByNonPrivilegedUser() throws Exception { + String expectedFailureResponse = getFailureResponseReason("ds4"); - @Test - public void testPutIndexTemplateByPrivilegedUser() throws Exception { - // should pass, as user `sem-user` has correct permissions - HttpResponse response = rh.executePutRequest("/_index_template/sem1234", indexTemplateBody, encodeBasicHeader("sem-user", "nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - } + // should fail, as user `ds3` doesn't have correct permissions + HttpResponse response = rh.executePutRequest("/_index_template/sem1234", indexTemplateBody, encodeBasicHeader("ds4", "nagilum")); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + Assert.assertEquals(expectedFailureResponse, response.findValueInJson("error.root_cause[0].reason")); + } - @Test - public void testPutIndexTemplateAsIndexLevelPermission() throws Exception { - String expectedFailureResponse = getFailureResponseReason("sem-user2"); + @Test + public void testPutIndexTemplateByPrivilegedUser() throws Exception { + // should pass, as user `sem-user` has correct permissions + HttpResponse response = rh.executePutRequest( + "/_index_template/sem1234", + indexTemplateBody, + encodeBasicHeader("sem-user", "nagilum") + ); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + } - // should fail, as user `sem-user2` is assigned `put-template` permission as index-level, not cluster-level - HttpResponse response = rh.executePutRequest("/_index_template/sem1234", indexTemplateBody, encodeBasicHeader("sem-user2", "nagilum")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - Assert.assertEquals(expectedFailureResponse, response.findValueInJson("error.root_cause[0].reason")); - } + @Test + public void testPutIndexTemplateAsIndexLevelPermission() throws Exception { + String expectedFailureResponse = getFailureResponseReason("sem-user2"); + // should fail, as user `sem-user2` is assigned `put-template` permission as index-level, not cluster-level + HttpResponse response = rh.executePutRequest( + "/_index_template/sem1234", + indexTemplateBody, + encodeBasicHeader("sem-user2", "nagilum") + ); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + Assert.assertEquals(expectedFailureResponse, response.findValueInJson("error.root_cause[0].reason")); } + +} diff --git a/src/test/java/org/opensearch/security/InitializationIntegrationTests.java b/src/test/java/org/opensearch/security/InitializationIntegrationTests.java index 87241ee110..0fec953472 100644 --- a/src/test/java/org/opensearch/security/InitializationIntegrationTests.java +++ b/src/test/java/org/opensearch/security/InitializationIntegrationTests.java @@ -67,23 +67,31 @@ public class InitializationIntegrationTests extends SingleClusterTest { public void testEnsureInitViaRestDoesWork() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); setup(Settings.EMPTY, null, settings, false); - final RestHelper rh = restHelper(); //ssl resthelper + final RestHelper rh = restHelper(); // ssl resthelper rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; - Assert.assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, rh.executePutRequest(".opendistro_security/_doc/0", "{}", encodeBasicHeader("___", "")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, rh.executePutRequest(".opendistro_security/_doc/config", "{}", encodeBasicHeader("___", "")).getStatusCode()); - + Assert.assertEquals( + HttpStatus.SC_SERVICE_UNAVAILABLE, + rh.executePutRequest(".opendistro_security/_doc/0", "{}", encodeBasicHeader("___", "")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_SERVICE_UNAVAILABLE, + rh.executePutRequest(".opendistro_security/_doc/config", "{}", encodeBasicHeader("___", "")).getStatusCode() + ); rh.keystore = "kirk-keystore.jks"; - Assert.assertEquals(HttpStatus.SC_CREATED, rh.executePutRequest(".opendistro_security/_doc/config", "{}", encodeBasicHeader("___", "")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_CREATED, + rh.executePutRequest(".opendistro_security/_doc/config", "{}", encodeBasicHeader("___", "")).getStatusCode() + ); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"tx_size_in_bytes\" : 0")); Assert.assertFalse(rh.executeSimpleRequest("_nodes/stats?pretty").contains("\"rx_count\" : 0")); @@ -96,28 +104,38 @@ public void testEnsureInitViaRestDoesWork() throws Exception { public void testInitWithInjectedUser() throws Exception { final Settings settings = Settings.builder() - .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) - .put("plugins.security.unsupported.inject_user.enabled", true) - .build(); + .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) + .put("plugins.security.unsupported.inject_user.enabled", true) + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_disable_all.yml"), settings, true); RestHelper rh = nonSslRestHelper(); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executePutRequest(".opendistro_security/_doc/0", "{}", encodeBasicHeader("___", "")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executePutRequest(".opendistro_security/_doc/config", "{}", encodeBasicHeader("___", "")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executePutRequest(".opendistro_security/_doc/0", "{}", encodeBasicHeader("___", "")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_UNAUTHORIZED, + rh.executePutRequest(".opendistro_security/_doc/config", "{}", encodeBasicHeader("___", "")).getStatusCode() + ); } @Test public void testWhoAmI() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); - setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityInternalUsers("internal_empty.yml") - .setSecurityRoles("roles_deny.yml"), settings, true); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setSecurityInternalUsers("internal_empty.yml").setSecurityRoles("roles_deny.yml"), + settings, + true + ); try (RestHighLevelClient restHighLevelClient = getRestClient(clusterInfo, "spock-keystore.jks", "truststore.jks")) { Response whoAmIRes = restHighLevelClient.getLowLevelClient().performRequest(new Request("GET", "/_plugins/_security/whoami")); @@ -135,14 +153,25 @@ public void testWhoAmI() throws Exception { @Test public void testWhoAmIForceHttp1() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); - setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityInternalUsers("internal_empty.yml") - .setSecurityRoles("roles_deny.yml"), settings, true); - - try (RestHighLevelClient restHighLevelClient = getRestClient(clusterInfo, "spock-keystore.jks", "truststore.jks", HttpVersionPolicy.FORCE_HTTP_1)) { + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setSecurityInternalUsers("internal_empty.yml").setSecurityRoles("roles_deny.yml"), + settings, + true + ); + + try ( + RestHighLevelClient restHighLevelClient = getRestClient( + clusterInfo, + "spock-keystore.jks", + "truststore.jks", + HttpVersionPolicy.FORCE_HTTP_1 + ) + ) { Response whoAmIRes = restHighLevelClient.getLowLevelClient().performRequest(new Request("GET", "/_plugins/_security/whoami")); Assert.assertEquals(whoAmIRes.getStatusLine().getStatusCode(), 200); // The HTTP/1.1 is forced and should be used instead @@ -164,7 +193,17 @@ public void testConfigHotReload() throws Exception { for (Iterator iterator = clusterInfo.httpAdresses.iterator(); iterator.hasNext();) { TransportAddress TransportAddress = (TransportAddress) iterator.next(); - HttpResponse res = rh.executeRequest(new HttpGet("http://"+TransportAddress.getAddress()+":"+TransportAddress.getPort() + "/" + "_opendistro/_security/authinfo?pretty=true"), spock); + HttpResponse res = rh.executeRequest( + new HttpGet( + "http://" + + TransportAddress.getAddress() + + ":" + + TransportAddress.getPort() + + "/" + + "_opendistro/_security/authinfo?pretty=true" + ), + spock + ); Assert.assertTrue(res.getBody().contains("spock")); Assert.assertFalse(res.getBody().contains("additionalrole")); Assert.assertTrue(res.getBody().contains("vulcan")); @@ -172,15 +211,32 @@ public void testConfigHotReload() throws Exception { try (Client tc = getClient()) { Assert.assertEquals(clusterInfo.numNodes, tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); - tc.index(new IndexRequest(".opendistro_security").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("internalusers").source("internalusers", FileHelper.readYamlContent("internal_users_spock_add_roles.yml"))).actionGet(); - ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config","roles","rolesmapping","internalusers","actiongroups"})).actionGet(); + tc.index( + new IndexRequest(".opendistro_security").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("internalusers") + .source("internalusers", FileHelper.readYamlContent("internal_users_spock_add_roles.yml")) + ).actionGet(); + ConfigUpdateResponse cur = tc.execute( + ConfigUpdateAction.INSTANCE, + new ConfigUpdateRequest(new String[] { "config", "roles", "rolesmapping", "internalusers", "actiongroups" }) + ).actionGet(); Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); } for (Iterator iterator = clusterInfo.httpAdresses.iterator(); iterator.hasNext();) { TransportAddress TransportAddress = (TransportAddress) iterator.next(); - log.debug("http://"+TransportAddress.getAddress()+":"+TransportAddress.getPort()); - HttpResponse res = rh.executeRequest(new HttpGet("http://"+TransportAddress.getAddress()+":"+TransportAddress.getPort() + "/" + "_opendistro/_security/authinfo?pretty=true"), spock); + log.debug("http://" + TransportAddress.getAddress() + ":" + TransportAddress.getPort()); + HttpResponse res = rh.executeRequest( + new HttpGet( + "http://" + + TransportAddress.getAddress() + + ":" + + TransportAddress.getPort() + + "/" + + "_opendistro/_security/authinfo?pretty=true" + ), + spock + ); Assert.assertTrue(res.getBody().contains("spock")); Assert.assertTrue(res.getBody().contains("additionalrole1")); Assert.assertTrue(res.getBody().contains("additionalrole2")); @@ -189,14 +245,28 @@ public void testConfigHotReload() throws Exception { try (Client tc = getClient()) { Assert.assertEquals(clusterInfo.numNodes, tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); - tc.index(new IndexRequest(".opendistro_security").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("config").source("config", FileHelper.readYamlContent("config_anon.yml"))).actionGet(); - ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config"})).actionGet(); + tc.index( + new IndexRequest(".opendistro_security").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("config") + .source("config", FileHelper.readYamlContent("config_anon.yml")) + ).actionGet(); + ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[] { "config" })) + .actionGet(); Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); } for (Iterator iterator = clusterInfo.httpAdresses.iterator(); iterator.hasNext();) { TransportAddress TransportAddress = (TransportAddress) iterator.next(); - HttpResponse res = rh.executeRequest(new HttpGet("http://"+TransportAddress.getAddress()+":"+TransportAddress.getPort() + "/" + "_opendistro/_security/authinfo?pretty=true")); + HttpResponse res = rh.executeRequest( + new HttpGet( + "http://" + + TransportAddress.getAddress() + + ":" + + TransportAddress.getPort() + + "/" + + "_opendistro/_security/authinfo?pretty=true" + ) + ); log.debug(res.getBody()); Assert.assertTrue(res.getBody().contains("role_host1")); Assert.assertTrue(res.getBody().contains("opendistro_security_anonymous")); @@ -208,9 +278,7 @@ public void testConfigHotReload() throws Exception { @Test public void testDefaultConfig() throws Exception { - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, true) - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, true).build(); setup(Settings.EMPTY, null, settings, false); RestHelper rh = nonSslRestHelper(); Thread.sleep(10000); @@ -223,14 +291,17 @@ public void testDefaultConfig() throws Exception { @Test public void testInvalidDefaultConfig() throws Exception { try { - final String defaultInitDirectory = ClusterHelper.updateDefaultDirectory(new File(TEST_RESOURCE_RELATIVE_PATH + "invalid_config").getAbsolutePath()); - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, true) - .build(); + final String defaultInitDirectory = ClusterHelper.updateDefaultDirectory( + new File(TEST_RESOURCE_RELATIVE_PATH + "invalid_config").getAbsolutePath() + ); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, true).build(); setup(Settings.EMPTY, null, settings, false); RestHelper rh = nonSslRestHelper(); Thread.sleep(10000); - Assert.assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, rh.executeGetRequest("", encodeBasicHeader("admin", "admin")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_SERVICE_UNAVAILABLE, + rh.executeGetRequest("", encodeBasicHeader("admin", "admin")).getStatusCode() + ); ClusterHelper.updateDefaultDirectory(defaultInitDirectory); restart(Settings.EMPTY, null, settings, false); @@ -258,7 +329,18 @@ public void testDisabled() throws Exception { @Test public void testDiscoveryWithoutInitialization() throws Exception { setup(Settings.EMPTY, null, Settings.EMPTY, false); - Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); - Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); + Assert.assertEquals( + clusterInfo.numNodes, + clusterHelper.nodeClient() + .admin() + .cluster() + .health(new ClusterHealthRequest().waitForGreenStatus()) + .actionGet() + .getNumberOfNodes() + ); + Assert.assertEquals( + ClusterHealthStatus.GREEN, + clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus() + ); } } diff --git a/src/test/java/org/opensearch/security/IntegrationTests.java b/src/test/java/org/opensearch/security/IntegrationTests.java index 8f2b0f7282..399d226bd9 100644 --- a/src/test/java/org/opensearch/security/IntegrationTests.java +++ b/src/test/java/org/opensearch/security/IntegrationTests.java @@ -63,41 +63,47 @@ public class IntegrationTests extends SingleClusterTest { @Test public void testSearchScroll() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".worf", "knuddel","nonexists") + final Settings settings = Settings.builder() + .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".worf", "knuddel", "nonexists") .build(); - setup(settings); - final RestHelper rh = nonSslRestHelper(); + setup(settings); + final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { - for(int i=0; i<3; i++) - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + for (int i = 0; i < 3; i++) + tc.index( + new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); } - System.out.println("########search"); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executeGetRequest("vulcangov/_search?scroll=1m&pretty=true", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("vulcangov/_search?scroll=1m&pretty=true", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); System.out.println(res.getBody()); int start = res.getBody().indexOf("_scroll_id") + 15; - String scrollid = res.getBody().substring(start, res.getBody().indexOf("\"", start+1)); + String scrollid = res.getBody().substring(start, res.getBody().indexOf("\"", start + 1)); System.out.println(scrollid); System.out.println("########search scroll"); - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executePostRequest("/_search/scroll?pretty=true", "{\"scroll_id\" : \""+scrollid+"\"}", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); - + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest( + "/_search/scroll?pretty=true", + "{\"scroll_id\" : \"" + scrollid + "\"}", + encodeBasicHeader("nagilum", "nagilum") + )).getStatusCode() + ); System.out.println("########search done"); - } @Test public void testDnParsingCertAuth() throws Exception { - Settings settings = Settings.builder() - .put("username_attribute", "cn") - .put("roles_attribute", "l") - .build(); + Settings settings = Settings.builder().put("username_attribute", "cn").put("roles_attribute", "l").build(); HTTPClientCertAuthenticator auth = new HTTPClientCertAuthenticator(settings, null); Assert.assertEquals("abc", auth.extractCredentials(null, newThreadContext("cn=abc,cn=xxx,l=ert,st=zui,c=qwe")).getUsername()); Assert.assertEquals("abc", auth.extractCredentials(null, newThreadContext("cn=abc,l=ert,st=zui,c=qwe")).getUsername()); @@ -107,13 +113,23 @@ public void testDnParsingCertAuth() throws Exception { Assert.assertEquals("abc", auth.extractCredentials(null, newThreadContext("l=ert,st=zui,c=qwe,cn=abc")).getUsername()); Assert.assertEquals("abc", auth.extractCredentials(null, newThreadContext("L=ert,st=zui,c=qwe,CN=abc")).getUsername()); Assert.assertEquals("L=ert,st=zui,c=qwe", auth.extractCredentials(null, newThreadContext("L=ert,st=zui,c=qwe")).getUsername()); - Assert.assertArrayEquals(new String[] {"ert"}, auth.extractCredentials(null, newThreadContext("cn=abc,l=ert,st=zui,c=qwe")).getBackendRoles().toArray(new String[0])); - Assert.assertArrayEquals(new String[] {"bleh", "ert"}, new TreeSet<>(auth.extractCredentials(null, newThreadContext("cn=abc,l=ert,L=bleh,st=zui,c=qwe")).getBackendRoles()).toArray(new String[0])); - - settings = Settings.builder() - .build(); + Assert.assertArrayEquals( + new String[] { "ert" }, + auth.extractCredentials(null, newThreadContext("cn=abc,l=ert,st=zui,c=qwe")).getBackendRoles().toArray(new String[0]) + ); + Assert.assertArrayEquals( + new String[] { "bleh", "ert" }, + new TreeSet<>(auth.extractCredentials(null, newThreadContext("cn=abc,l=ert,L=bleh,st=zui,c=qwe")).getBackendRoles()).toArray( + new String[0] + ) + ); + + settings = Settings.builder().build(); auth = new HTTPClientCertAuthenticator(settings, null); - Assert.assertEquals("cn=abc,l=ert,st=zui,c=qwe", auth.extractCredentials(null, newThreadContext("cn=abc,l=ert,st=zui,c=qwe")).getUsername()); + Assert.assertEquals( + "cn=abc,l=ert,st=zui,c=qwe", + auth.extractCredentials(null, newThreadContext("cn=abc,l=ert,st=zui,c=qwe")).getUsername() + ); } private ThreadContext newThreadContext(String sslPrincipal) { @@ -126,19 +142,30 @@ private ThreadContext newThreadContext(String sslPrincipal) { public void testDNSpecials() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("node-untspec5-keystore.p12")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "1") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "PKCS12") - .putList(ConfigConstants.SECURITY_NODES_DN, "EMAILADDRESS=unt@tst.com,CN=node-untspec5.example.com,OU=SSL,O=Te\\, st,L=Test,C=DE") - .putList(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, "EMAILADDRESS=unt@xxx.com,CN=node-untspec6.example.com,OU=SSL,O=Te\\, st,L=Test,C=DE") - .put(ConfigConstants.SECURITY_CERT_OID,"1.2.3.4.5.6") - .build(); - + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("node-untspec5-keystore.p12") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "1") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "PKCS12") + .putList( + ConfigConstants.SECURITY_NODES_DN, + "EMAILADDRESS=unt@tst.com,CN=node-untspec5.example.com,OU=SSL,O=Te\\, st,L=Test,C=DE" + ) + .putList( + ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, + "EMAILADDRESS=unt@xxx.com,CN=node-untspec6.example.com,OU=SSL,O=Te\\, st,L=Test,C=DE" + ) + .put(ConfigConstants.SECURITY_CERT_OID, "1.2.3.4.5.6") + .build(); Settings tcSettings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("node-untspec6-keystore.p12")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "PKCS12") - .build(); + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("node-untspec6-keystore.p12") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "PKCS12") + .build(); setup(tcSettings, new DynamicSecurityConfig(), settings, true); RestHelper rh = nonSslRestHelper(); @@ -152,19 +179,27 @@ public void testDNSpecials() throws Exception { public void testDNSpecials1() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("node-untspec5-keystore.p12")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "1") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "PKCS12") - .putList("plugins.security.nodes_dn", "EMAILADDRESS=unt@tst.com,CN=node-untspec5.example.com,OU=SSL,O=Te\\, st,L=Test,C=DE") - .putList("plugins.security.authcz.admin_dn", "EMAILADDREss=unt@xxx.com, cn=node-untspec6.example.com, OU=SSL,O=Te\\, st,L=Test, c=DE") - .put("plugins.security.cert.oid","1.2.3.4.5.6") - .build(); - + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("node-untspec5-keystore.p12") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "1") + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "PKCS12") + .putList("plugins.security.nodes_dn", "EMAILADDRESS=unt@tst.com,CN=node-untspec5.example.com,OU=SSL,O=Te\\, st,L=Test,C=DE") + .putList( + "plugins.security.authcz.admin_dn", + "EMAILADDREss=unt@xxx.com, cn=node-untspec6.example.com, OU=SSL,O=Te\\, st,L=Test, c=DE" + ) + .put("plugins.security.cert.oid", "1.2.3.4.5.6") + .build(); Settings tcSettings = Settings.builder() - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-untspec6-keystore.p12")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "PKCS12") - .build(); + .put( + "plugins.security.ssl.transport.keystore_filepath", + FileHelper.getAbsoluteFilePathFromClassPath("node-untspec6-keystore.p12") + ) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "PKCS12") + .build(); setup(tcSettings, new DynamicSecurityConfig(), settings, true); RestHelper rh = nonSslRestHelper(); @@ -185,31 +220,34 @@ public void testMultiget() throws Exception { setup(); try (Client tc = getClient()) { - tc.index(new IndexRequest("mindex1").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("mindex2").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("mindex1").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("mindex2").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON) + ).actionGet(); } - //opendistro_security_multiget -> picard - - - String mgetBody = "{"+ - "\"docs\" : ["+ - "{"+ - "\"_index\" : \"mindex1\","+ - "\"_id\" : \"1\""+ - " },"+ - " {"+ - "\"_index\" : \"mindex2\","+ - " \"_id\" : \"2\""+ - "}"+ - "]"+ - "}"; + // opendistro_security_multiget -> picard + + String mgetBody = "{" + + "\"docs\" : [" + + "{" + + "\"_index\" : \"mindex1\"," + + "\"_id\" : \"1\"" + + " }," + + " {" + + "\"_index\" : \"mindex2\"," + + " \"_id\" : \"2\"" + + "}" + + "]" + + "}"; - RestHelper rh = nonSslRestHelper(); - HttpResponse resc = rh.executePostRequest("_mget?refresh=true", mgetBody, encodeBasicHeader("picard", "picard")); - System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - Assert.assertFalse(resc.getBody().contains("type2")); + RestHelper rh = nonSslRestHelper(); + HttpResponse resc = rh.executePostRequest("_mget?refresh=true", mgetBody, encodeBasicHeader("picard", "picard")); + System.out.println(resc.getBody()); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + Assert.assertFalse(resc.getBody().contains("type2")); } @@ -217,29 +255,46 @@ public void testMultiget() throws Exception { public void testRestImpersonation() throws Exception { final Settings settings = Settings.builder() - .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".spock", "knuddel","userwhonotexists").build(); + .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".spock", "knuddel", "userwhonotexists") + .build(); setup(settings); RestHelper rh = nonSslRestHelper(); - //knuddel: - // hash: _rest_impersonation_only_ + // knuddel: + // hash: _rest_impersonation_only_ HttpResponse resp; - resp = rh.executeGetRequest("/_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as", "knuddel"), encodeBasicHeader("worf", "worf")); + resp = rh.executeGetRequest( + "/_opendistro/_security/authinfo", + new BasicHeader("opendistro_security_impersonate_as", "knuddel"), + encodeBasicHeader("worf", "worf") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resp.getStatusCode()); - resp = rh.executeGetRequest("/_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as", "knuddel"), encodeBasicHeader("spock", "spock")); + resp = rh.executeGetRequest( + "/_opendistro/_security/authinfo", + new BasicHeader("opendistro_security_impersonate_as", "knuddel"), + encodeBasicHeader("spock", "spock") + ); Assert.assertEquals(HttpStatus.SC_OK, resp.getStatusCode()); Assert.assertTrue(resp.getBody().contains("name=knuddel")); Assert.assertFalse(resp.getBody().contains("spock")); - resp = rh.executeGetRequest("/_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as", "userwhonotexists"), encodeBasicHeader("spock", "spock")); + resp = rh.executeGetRequest( + "/_opendistro/_security/authinfo", + new BasicHeader("opendistro_security_impersonate_as", "userwhonotexists"), + encodeBasicHeader("spock", "spock") + ); System.out.println(resp.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resp.getStatusCode()); - resp = rh.executeGetRequest("/_opendistro/_security/authinfo", new BasicHeader("opendistro_security_impersonate_as", "invalid"), encodeBasicHeader("spock", "spock")); + resp = rh.executeGetRequest( + "/_opendistro/_security/authinfo", + new BasicHeader("opendistro_security_impersonate_as", "invalid"), + encodeBasicHeader("spock", "spock") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resp.getStatusCode()); } @@ -249,14 +304,21 @@ public void testSingle() throws Exception { setup(); try (Client tc = getClient()) { - tc.index(new IndexRequest("shakespeare").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("shakespeare").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON) + ).actionGet(); - ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config","roles","rolesmapping","internalusers","actiongroups"})).actionGet(); + ConfigUpdateResponse cur = tc.execute( + ConfigUpdateAction.INSTANCE, + new ConfigUpdateRequest(new String[] { "config", "roles", "rolesmapping", "internalusers", "actiongroups" }) + ).actionGet(); Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); } RestHelper rh = nonSslRestHelper(); - //opendistro_security_shakespeare -> picard + // opendistro_security_shakespeare -> picard HttpResponse resc = rh.executeGetRequest("shakespeare/_search", encodeBasicHeader("picard", "picard")); System.out.println(resc.getBody()); @@ -276,7 +338,10 @@ public void testSpecialUsernames() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("bug.99", "nagilum")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest("", encodeBasicHeader("a", "b")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("\"'+-,;_?*@<>!$%&/()=#", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("", encodeBasicHeader("\"'+-,;_?*@<>!$%&/()=#", "nagilum")).getStatusCode() + ); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("", encodeBasicHeader("§ÄÖÜäöüß", "nagilum")).getStatusCode()); } @@ -286,7 +351,11 @@ public void testXff() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_xff.yml"), Settings.EMPTY, true); RestHelper rh = nonSslRestHelper(); - HttpResponse resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader("x-forwarded-for", "10.0.0.7"), encodeBasicHeader("worf", "worf")); + HttpResponse resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader("x-forwarded-for", "10.0.0.7"), + encodeBasicHeader("worf", "worf") + ); Assert.assertEquals(200, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("10.0.0.7")); } @@ -297,19 +366,41 @@ public void testRegexExcludes() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY); try (Client tc = getClient()) { - tc.index(new IndexRequest("indexa").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"indexa\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("indexb").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"indexb\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("isallowed").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"isallowed\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("special").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"special\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("alsonotallowed").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"alsonotallowed\":1}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("indexa").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"indexa\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("indexb").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"indexb\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("isallowed").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"isallowed\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("special").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"special\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("alsonotallowed").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"alsonotallowed\":1}", XContentType.JSON) + ).actionGet(); } RestHelper rh = nonSslRestHelper(); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("index*/_search",encodeBasicHeader("rexclude", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("indexa/_search",encodeBasicHeader("rexclude", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("isallowed/_search",encodeBasicHeader("rexclude", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("special/_search",encodeBasicHeader("rexclude", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("alsonotallowed/_search",encodeBasicHeader("rexclude", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("index*/_search", encodeBasicHeader("rexclude", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("indexa/_search", encodeBasicHeader("rexclude", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("isallowed/_search", encodeBasicHeader("rexclude", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest("special/_search", encodeBasicHeader("rexclude", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executeGetRequest("alsonotallowed/_search", encodeBasicHeader("rexclude", "nagilum")).getStatusCode() + ); } @Test @@ -319,8 +410,10 @@ public void testMultiRoleSpan() throws Exception { final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { - tc.index(new IndexRequest("mindex_1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("mindex_2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("mindex_1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("mindex_2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)) + .actionGet(); } HttpResponse res = rh.executeGetRequest("/mindex_1,mindex_2/_search", encodeBasicHeader("mindex12", "nagilum")); @@ -330,9 +423,14 @@ public void testMultiRoleSpan() throws Exception { Assert.assertFalse(res.getBody().contains("\"content\":2")); try (Client tc = getClient()) { - tc.index(new IndexRequest(".opendistro_security").id("config").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("config", FileHelper.readYamlContent("config_multirolespan.yml"))).actionGet(); + tc.index( + new IndexRequest(".opendistro_security").id("config") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("config", FileHelper.readYamlContent("config_multirolespan.yml")) + ).actionGet(); - ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config"})).actionGet(); + ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[] { "config" })) + .actionGet(); Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); } @@ -351,10 +449,14 @@ public void testMultiRoleSpan2() throws Exception { final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { - tc.index(new IndexRequest("mindex_1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("mindex_2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("mindex_3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("mindex_4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("mindex_1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("mindex_2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("mindex_3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("mindex_4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":2}", XContentType.JSON)) + .actionGet(); } HttpResponse res = rh.executeGetRequest("/mindex_1,mindex_2/_search", encodeBasicHeader("mindex12", "nagilum")); @@ -374,10 +476,14 @@ public void testSecurityUnderscore() throws Exception { setup(); final RestHelper rh = nonSslRestHelper(); - HttpResponse res = rh.executePostRequest("abc_xyz_2018_05_24/_doc/1", "{\"content\":1}", encodeBasicHeader("underscore", "nagilum")); + HttpResponse res = rh.executePostRequest( + "abc_xyz_2018_05_24/_doc/1", + "{\"content\":1}", + encodeBasicHeader("underscore", "nagilum") + ); res = rh.executeGetRequest("abc_xyz_2018_05_24/_doc/1", encodeBasicHeader("underscore", "nagilum")); - Assert.assertTrue(res.getBody(),res.getBody().contains("\"content\":1")); + Assert.assertTrue(res.getBody(), res.getBody().contains("\"content\":1")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); res = rh.executeGetRequest("abc_xyz_2018_05_24/_refresh", encodeBasicHeader("underscore", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); @@ -391,45 +497,52 @@ public void testDeleteByQueryDnfof() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_dnfof.yml"), Settings.EMPTY); try (Client tc = getClient()) { - for(int i=0; i<3; i++) { - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + for (int i = 0; i < 3; i++) { + tc.index( + new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); } } RestHelper rh = nonSslRestHelper(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executePostRequest("/vulcango*/_delete_by_query?refresh=true&wait_for_completion=true&pretty=true", "{\"query\" : {\"match_all\" : {}}}", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest( + "/vulcango*/_delete_by_query?refresh=true&wait_for_completion=true&pretty=true", + "{\"query\" : {\"match_all\" : {}}}", + encodeBasicHeader("nagilum", "nagilum") + )).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"deleted\" : 3")); } @Test public void testUpdate() throws Exception { - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH") - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH").build(); setup(settings); final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { - tc.index(new IndexRequest("indexc").id("0") - .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("indexc").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); } - HttpResponse res = rh.executePostRequest("indexc/_update/0?pretty=true&refresh=true", "{\"doc\" : {\"content\":2}}", - encodeBasicHeader("user_c", "user_c")); + HttpResponse res = rh.executePostRequest( + "indexc/_update/0?pretty=true&refresh=true", + "{\"doc\" : {\"content\":2}}", + encodeBasicHeader("user_c", "user_c") + ); System.out.println(res.getBody()); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); } - @Test public void testDnfof() throws Exception { - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH") - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH").build(); setup(Settings.EMPTY, new DynamicSecurityConfig().setConfig("config_dnfof.yml"), settings); final RestHelper rh = nonSslRestHelper(); @@ -437,49 +550,95 @@ public void testDnfof() throws Exception { try (Client tc = getClient()) { tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - tc.index(new IndexRequest("indexa").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":\"indexa\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("indexb").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":\"indexb\"}", XContentType.JSON)).actionGet(); - - - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("starfleet","starfleet_academy","starfleet_library").alias("sf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire","vulcangov").alias("nonsf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))).actionGet(); + tc.index( + new IndexRequest("indexa").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":\"indexa\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("indexb").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":\"indexb\"}", XContentType.JSON) + ).actionGet(); + + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add().indices("starfleet", "starfleet_academy", "starfleet_library").alias("sf") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire", "vulcangov").alias("nonsf")) + ) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))) + .actionGet(); } HttpResponse resc; - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("indexa,indexb/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("indexa,indexb/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); Assert.assertTrue(resc.getBody(), resc.getBody().contains("indexa")); Assert.assertFalse(resc.getBody(), resc.getBody().contains("indexb")); Assert.assertFalse(resc.getBody(), resc.getBody().contains("exception")); Assert.assertFalse(resc.getBody(), resc.getBody().contains("permission")); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("indexa,indexb/_search?pretty", encodeBasicHeader("user_b", "user_b"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("indexa,indexb/_search?pretty", encodeBasicHeader("user_b", "user_b"))).getStatusCode() + ); System.out.println(resc.getBody()); Assert.assertFalse(resc.getBody(), resc.getBody().contains("indexa")); Assert.assertTrue(resc.getBody(), resc.getBody().contains("indexb")); Assert.assertFalse(resc.getBody(), resc.getBody().contains("exception")); Assert.assertFalse(resc.getBody(), resc.getBody().contains("permission")); - String msearchBody = - "{\"index\":\"indexa\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"indexb\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"index*\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); + String msearchBody = "{\"index\":\"indexa\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"indexb\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"index*\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); System.out.println("#### msearch"); resc = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("user_a", "user_a")); Assert.assertEquals(200, resc.getStatusCode()); @@ -501,106 +660,145 @@ public void testDnfof() throws Exception { Assert.assertEquals(3, resc.getBody().split("\"status\" : 200").length); Assert.assertEquals(2, resc.getBody().split("\"status\" : 403").length); - msearchBody = - "{\"index\":\"indexc\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"indexd\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); + msearchBody = "{\"index\":\"indexc\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"indexd\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); resc = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("user_b", "user_b")); Assert.assertEquals(403, resc.getStatusCode()); - String mgetBody = "{"+ - "\"docs\" : ["+ - "{"+ - "\"_index\" : \"indexa\","+ - "\"_id\" : \"0\""+ - " },"+ - " {"+ - "\"_index\" : \"indexb\","+ - " \"_id\" : \"0\""+ - "}"+ - "]"+ - "}"; + String mgetBody = "{" + + "\"docs\" : [" + + "{" + + "\"_index\" : \"indexa\"," + + "\"_id\" : \"0\"" + + " }," + + " {" + + "\"_index\" : \"indexb\"," + + " \"_id\" : \"0\"" + + "}" + + "]" + + "}"; System.out.println("#### mget"); - resc = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("user_b", "user_b")); + resc = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("user_b", "user_b")); Assert.assertEquals(200, resc.getStatusCode()); Assert.assertFalse(resc.getBody(), resc.getBody().contains("\"content\" : \"indexa\"")); Assert.assertTrue(resc.getBody(), resc.getBody().contains("\"content\" : \"indexb\"")); Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); Assert.assertTrue(resc.getBody(), resc.getBody().contains("permission")); - mgetBody = "{"+ - "\"docs\" : ["+ - "{"+ - "\"_index\" : \"indexx\","+ - "\"_id\" : \"0\""+ - " },"+ - " {"+ - "\"_index\" : \"indexy\","+ - " \"_id\" : \"0\""+ - "}"+ - "]"+ - "}"; - - resc = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("user_b", "user_b")); + mgetBody = "{" + + "\"docs\" : [" + + "{" + + "\"_index\" : \"indexx\"," + + "\"_id\" : \"0\"" + + " }," + + " {" + + "\"_index\" : \"indexy\"," + + " \"_id\" : \"0\"" + + "}" + + "]" + + "}"; + + resc = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("user_b", "user_b")); Assert.assertEquals(403, resc.getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); Assert.assertTrue(resc.getBody(), resc.getBody().contains("indexa")); Assert.assertFalse(resc.getBody(), resc.getBody().contains("indexb")); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("index*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("index*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); Assert.assertTrue(resc.getBody(), resc.getBody().contains("indexa")); Assert.assertFalse(resc.getBody(), resc.getBody().contains("indexb")); Assert.assertFalse(resc.getBody(), resc.getBody().contains("exception")); Assert.assertFalse(resc.getBody(), resc.getBody().contains("permission")); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("indexa/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("indexa/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("indexb/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("indexb/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("_all/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("_all/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("notexists/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("notexists/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, (resc=rh.executeGetRequest("permitnotexistentindex/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_NOT_FOUND, + (resc = rh.executeGetRequest("permitnotexistentindex/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("permitnotexistentindex*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("permitnotexistentindex*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, (resc=rh.executeGetRequest("indexanbh,indexabb*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_NOT_FOUND, + (resc = rh.executeGetRequest("indexanbh,indexabb*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode() + ); System.out.println(resc.getBody()); System.out.println("#### _all/_mapping/field/*"); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("_all/_mapping/field/*", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("_all/_mapping/field/*", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); System.out.println(resc.getBody()); } - @Test public void testNoDnfof() throws Exception { - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH") - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, "BOTH").build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings); final RestHelper rh = nonSslRestHelper(); @@ -608,39 +806,83 @@ public void testNoDnfof() throws Exception { try (Client tc = getClient()) { tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - tc.index(new IndexRequest("indexa").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":\"indexa\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("indexb").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":\"indexb\"}", XContentType.JSON)).actionGet(); - - - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("starfleet","starfleet_academy","starfleet_library").alias("sf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire","vulcangov").alias("nonsf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))).actionGet(); + tc.index( + new IndexRequest("indexa").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":\"indexa\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("indexb").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":\"indexb\"}", XContentType.JSON) + ).actionGet(); + + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add().indices("starfleet", "starfleet_academy", "starfleet_library").alias("sf") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire", "vulcangov").alias("nonsf")) + ) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))) + .actionGet(); } HttpResponse resc; - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("indexa,indexb/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("indexa,indexb/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("indexa,indexb/_search?pretty", encodeBasicHeader("user_b", "user_b"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("indexa,indexb/_search?pretty", encodeBasicHeader("user_b", "user_b"))).getStatusCode() + ); System.out.println(resc.getBody()); - String msearchBody = - "{\"index\":\"indexa\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"indexb\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); + String msearchBody = "{\"index\":\"indexa\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"indexb\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); System.out.println("#### msearch a"); resc = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("user_a", "user_a")); Assert.assertEquals(200, resc.getStatusCode()); @@ -659,11 +901,14 @@ public void testNoDnfof() throws Exception { Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); Assert.assertTrue(resc.getBody(), resc.getBody().contains("permission")); - msearchBody = - "{\"index\":\"indexc\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"indexd\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); + msearchBody = "{\"index\":\"indexc\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"indexd\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); System.out.println("#### msearch b2"); resc = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("user_b", "user_b")); @@ -676,82 +921,120 @@ public void testNoDnfof() throws Exception { int count = resc.getBody().split("\"status\" : 403").length; Assert.assertEquals(3, count); - String mgetBody = "{"+ - "\"docs\" : ["+ - "{"+ - "\"_index\" : \"indexa\","+ - "\"_id\" : \"0\""+ - " },"+ - " {"+ - "\"_index\" : \"indexb\","+ - " \"_id\" : \"0\""+ - "}"+ - "]"+ - "}"; - - resc = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("user_b", "user_b")); + String mgetBody = "{" + + "\"docs\" : [" + + "{" + + "\"_index\" : \"indexa\"," + + "\"_id\" : \"0\"" + + " }," + + " {" + + "\"_index\" : \"indexb\"," + + " \"_id\" : \"0\"" + + "}" + + "]" + + "}"; + + resc = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("user_b", "user_b")); Assert.assertEquals(200, resc.getStatusCode()); Assert.assertFalse(resc.getBody(), resc.getBody().contains("\"content\" : \"indexa\"")); Assert.assertTrue(resc.getBody(), resc.getBody().contains("indexb")); Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); Assert.assertTrue(resc.getBody(), resc.getBody().contains("permission")); - mgetBody = "{"+ - "\"docs\" : ["+ - "{"+ - "\"_index\" : \"indexx\","+ - "\"_id\" : \"0\""+ - " },"+ - " {"+ - "\"_index\" : \"indexy\","+ - " \"_id\" : \"0\""+ - "}"+ - "]"+ - "}"; - - resc = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("user_b", "user_b")); + mgetBody = "{" + + "\"docs\" : [" + + "{" + + "\"_index\" : \"indexx\"," + + "\"_id\" : \"0\"" + + " }," + + " {" + + "\"_index\" : \"indexy\"," + + " \"_id\" : \"0\"" + + "}" + + "]" + + "}"; + + resc = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("user_b", "user_b")); Assert.assertEquals(200, resc.getStatusCode()); Assert.assertTrue(resc.getBody(), resc.getBody().contains("exception")); count = resc.getBody().split("root_cause").length; Assert.assertEquals(3, count); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("index*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("index*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("indexa/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("indexa/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("indexb/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("indexb/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("_all/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("_all/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("notexists/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("notexists/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, (resc=rh.executeGetRequest("indexanbh,indexabb*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_NOT_FOUND, + (resc = rh.executeGetRequest("indexanbh,indexabb*/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (resc=rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (resc = rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("user_a", "user_a"))).getStatusCode() + ); System.out.println(resc.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("starfleet/_search?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode() + ); System.out.println(resc.getBody()); System.out.println("#### _all/_mapping/field/*"); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("_all/_mapping/field/*", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("_all/_mapping/field/*", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); System.out.println(resc.getBody()); System.out.println("#### _mapping/field/*"); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("_mapping/field/*", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("_mapping/field/*", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); System.out.println(resc.getBody()); System.out.println("#### */_mapping/field/*"); - Assert.assertEquals(HttpStatus.SC_OK, (resc=rh.executeGetRequest("*/_mapping/field/*", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (resc = rh.executeGetRequest("*/_mapping/field/*", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); System.out.println(resc.getBody()); } @@ -760,43 +1043,58 @@ public void testSecurityIndexSecurity() throws Exception { setup(); final RestHelper rh = nonSslRestHelper(); - HttpResponse res = rh.executePutRequest(".opendistro_security/_mapping?pretty", "{\"properties\": {\"name\":{\"type\":\"text\"}}}", - encodeBasicHeader("nagilum", "nagilum")); + HttpResponse res = rh.executePutRequest( + ".opendistro_security/_mapping?pretty", + "{\"properties\": {\"name\":{\"type\":\"text\"}}}", + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - res = rh.executePutRequest("*dis*rit*/_mapping?pretty", "{\"properties\": {\"name\":{\"type\":\"text\"}}}", - encodeBasicHeader("nagilum", "nagilum")); + res = rh.executePutRequest( + "*dis*rit*/_mapping?pretty", + "{\"properties\": {\"name\":{\"type\":\"text\"}}}", + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - res = rh.executePutRequest("*/_mapping?pretty", "{\"properties\": {\"name\":{\"type\":\"text\"}}}", - encodeBasicHeader("nagilum", "nagilum")); + res = rh.executePutRequest( + "*/_mapping?pretty", + "{\"properties\": {\"name\":{\"type\":\"text\"}}}", + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - res = rh.executePutRequest("_all/_mapping?pretty", "{\"properties\": {\"name\":{\"type\":\"text\"}}}", - encodeBasicHeader("nagilum", "nagilum")); + res = rh.executePutRequest( + "_all/_mapping?pretty", + "{\"properties\": {\"name\":{\"type\":\"text\"}}}", + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - res = rh.executePostRequest(".opendistro_security/_close", "", - encodeBasicHeader("nagilum", "nagilum")); + res = rh.executePostRequest(".opendistro_security/_close", "", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - res = rh.executeDeleteRequest(".opendistro_security", - encodeBasicHeader("nagilum", "nagilum")); - res = rh.executeDeleteRequest("_all", - encodeBasicHeader("nagilum", "nagilum")); + res = rh.executeDeleteRequest(".opendistro_security", encodeBasicHeader("nagilum", "nagilum")); + res = rh.executeDeleteRequest("_all", encodeBasicHeader("nagilum", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - res = rh.executePutRequest(".opendistro_security/_settings", "{\"index\" : {\"number_of_replicas\" : 2}}", - encodeBasicHeader("nagilum", "nagilum")); + res = rh.executePutRequest( + ".opendistro_security/_settings", + "{\"index\" : {\"number_of_replicas\" : 2}}", + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - res = rh.executePutRequest(".opendistro_secur*/_settings", "{\"index\" : {\"number_of_replicas\" : 2}}", - encodeBasicHeader("nagilum", "nagilum")); + res = rh.executePutRequest( + ".opendistro_secur*/_settings", + "{\"index\" : {\"number_of_replicas\" : 2}}", + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); -// res = rh.executePostRequest(".opendistro_security/_freeze", "", -// encodeBasicHeader("nagilum", "nagilum")); -// Assert.assertTrue(res.getStatusCode() >= 400); + // res = rh.executePostRequest(".opendistro_security/_freeze", "", + // encodeBasicHeader("nagilum", "nagilum")); + // Assert.assertTrue(res.getStatusCode() >= 400); String bulkBody = "{ \"index\" : { \"_index\" : \".opendistro_security\", \"_id\" : \"1\" } }\n" - + "{ \"field1\" : \"value1\" }\n" - + "{ \"index\" : { \"_index\" : \".opendistro_security\", \"_id\" : \"2\" } }\n" - + "{ \"field2\" : \"value2\" }\n" - + "{ \"index\" : { \"_index\" : \"myindex\", \"_id\" : \"2\" } }\n" - + "{ \"field2\" : \"value2\" }\n" - + "{ \"delete\" : { \"_index\" : \".opendistro_security\", \"_id\" : \"config\" } }\n"; + + "{ \"field1\" : \"value1\" }\n" + + "{ \"index\" : { \"_index\" : \".opendistro_security\", \"_id\" : \"2\" } }\n" + + "{ \"field2\" : \"value2\" }\n" + + "{ \"index\" : { \"_index\" : \"myindex\", \"_id\" : \"2\" } }\n" + + "{ \"field2\" : \"value2\" }\n" + + "{ \"delete\" : { \"_index\" : \".opendistro_security\", \"_id\" : \"config\" } }\n"; res = rh.executePostRequest("_bulk?refresh=true&pretty", bulkBody, encodeBasicHeader("nagilum", "nagilum")); JsonNode jsonNode = readTree(res.getBody()); System.out.println(res.getBody()); @@ -813,6 +1111,6 @@ public void testMonitorHealth() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY); RestHelper rh = nonSslRestHelper(); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_cat/health",encodeBasicHeader("picard", "picard")).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_cat/health", encodeBasicHeader("picard", "picard")).getStatusCode()); } } diff --git a/src/test/java/org/opensearch/security/PitIntegrationTests.java b/src/test/java/org/opensearch/security/PitIntegrationTests.java index 11c624eba6..035cc2ce3e 100644 --- a/src/test/java/org/opensearch/security/PitIntegrationTests.java +++ b/src/test/java/org/opensearch/security/PitIntegrationTests.java @@ -37,86 +37,75 @@ public void testPitExplicitAPIAccess() throws Exception { RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { // create alias - tc.admin().indices().create(new CreateIndexRequest("pit_1") - .alias(new Alias("alias"))) - .actionGet(); + tc.admin().indices().create(new CreateIndexRequest("pit_1").alias(new Alias("alias"))).actionGet(); // create index - tc.index(new IndexRequest("pit_2").id("2").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE). - source("{\"content\":2}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("pit_2").id("2") + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":2}", XContentType.JSON) + ).actionGet(); } RestHelper.HttpResponse resc; // Create point in time in index should be successful since the user has permission for index - resc = rh.executePostRequest("/alias/_search/point_in_time?keep_alive=100m", "", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executePostRequest("/alias/_search/point_in_time?keep_alive=100m", "", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - String pitId1 = resc.findValueInJson("pit_id"); + String pitId1 = resc.findValueInJson("pit_id"); // Create point in time in index for which the user does not have permission - resc = rh.executePostRequest("/pit_2/_search/point_in_time?keep_alive=100m", "", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executePostRequest("/pit_2/_search/point_in_time?keep_alive=100m", "", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); // Create point in time in index for which the user has permission for - resc = rh.executePostRequest("/pit_2/_search/point_in_time?keep_alive=100m", "", - encodeBasicHeader("pit-2", "nagilum")); + resc = rh.executePostRequest("/pit_2/_search/point_in_time?keep_alive=100m", "", encodeBasicHeader("pit-2", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - String pitId2 = resc.findValueInJson("pit_id"); - resc = rh.executePostRequest("/pit*/_search/point_in_time?keep_alive=100m", "", - encodeBasicHeader("all-pit", "nagilum")); + String pitId2 = resc.findValueInJson("pit_id"); + resc = rh.executePostRequest("/pit*/_search/point_in_time?keep_alive=100m", "", encodeBasicHeader("all-pit", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); // PIT segments should work since there is atleast one PIT for which user has access for - resc = rh.executeGetRequest("/_cat/pit_segments", - "{\"pit_id\":\"" + pitId1 +"\"}", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executeGetRequest("/_cat/pit_segments", "{\"pit_id\":\"" + pitId1 + "\"}", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); // PIT segments should work since there is atleast one PIT for which user has access for - resc = rh.executeGetRequest("/_cat/pit_segments", - "{\"pit_id\":\"" + pitId1 +"\"}", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executeGetRequest("/_cat/pit_segments", "{\"pit_id\":\"" + pitId1 + "\"}", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); // Should throw error since user does not have access for pitId2 - resc = rh.executeGetRequest("/_cat/pit_segments", - "{\"pit_id\":\"" + pitId2 +"\"}", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executeGetRequest("/_cat/pit_segments", "{\"pit_id\":\"" + pitId2 + "\"}", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); // Should throw error since user does not have access for pitId2 - resc = rh.executeGetRequest("/_cat/pit_segments", - "{\"pit_id\":[\"" + pitId1 +"\",\"" + pitId2 + "\"]}", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executeGetRequest( + "/_cat/pit_segments", + "{\"pit_id\":[\"" + pitId1 + "\",\"" + pitId2 + "\"]}", + encodeBasicHeader("pit-1", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); // Delete explicit PITs should work for PIT for which user has access for - resc = rh.executeDeleteRequest("/_search/point_in_time", - "{\"pit_id\":\"" + pitId1 +"\"}", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executeDeleteRequest("/_search/point_in_time", "{\"pit_id\":\"" + pitId1 + "\"}", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertEquals(pitId1, resc.findValueInJson("pits[0].pit_id")); Assert.assertEquals("true", resc.findValueInJson("pits[0].successful")); // Should throw error since user does not have access for pitId2 - resc = rh.executeDeleteRequest("/_search/point_in_time", - "{\"pit_id\":\"" + pitId2 +"\"}", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executeDeleteRequest("/_search/point_in_time", "{\"pit_id\":\"" + pitId2 + "\"}", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); // Should throw error since user does not have access for pitId2 - resc = rh.executeDeleteRequest("/_search/point_in_time", - "{\"pit_id\":[\"" + pitId1 +"\",\"" + pitId2 + "\"]}", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executeDeleteRequest( + "/_search/point_in_time", + "{\"pit_id\":[\"" + pitId1 + "\",\"" + pitId2 + "\"]}", + encodeBasicHeader("pit-1", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); // Delete explicit PITs should work for PIT for which user has access for - resc = rh.executeDeleteRequest("/_search/point_in_time", - "{\"pit_id\":\"" + pitId2 +"\"}", - encodeBasicHeader("pit-2", "nagilum")); + resc = rh.executeDeleteRequest("/_search/point_in_time", "{\"pit_id\":\"" + pitId2 + "\"}", encodeBasicHeader("pit-2", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertEquals(pitId2, resc.findValueInJson("pits[0].pit_id")); Assert.assertEquals("true", resc.findValueInJson("pits[0].successful")); @@ -130,40 +119,41 @@ public void testPitAllAPIAccess() throws Exception { // Create two indices try (Client tc = getClient()) { - tc.index(new IndexRequest("pit_1").id("1").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE). - source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("pit_2").id("2").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE). - source("{\"content\":2}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("pit_1").id("1") + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("pit_2").id("2") + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":2}", XContentType.JSON) + ).actionGet(); } RestHelper.HttpResponse resc; // Create point in time in index should be successful since the user has permission for index - resc = rh.executePostRequest("/pit_1/_search/point_in_time?keep_alive=100m", "", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executePostRequest("/pit_1/_search/point_in_time?keep_alive=100m", "", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); String pitId1 = resc.findValueInJson("pit_id"); // Create point in time in index for which the user does not have permission - resc = rh.executePostRequest("/pit_2/_search/point_in_time?keep_alive=100m", "", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executePostRequest("/pit_2/_search/point_in_time?keep_alive=100m", "", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); // Create point in time in index for which the user has permission for - resc = rh.executePostRequest("/pit_2/_search/point_in_time?keep_alive=100m", "", - encodeBasicHeader("pit-2", "nagilum")); + resc = rh.executePostRequest("/pit_2/_search/point_in_time?keep_alive=100m", "", encodeBasicHeader("pit-2", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); String pitId2 = resc.findValueInJson("pit_id"); // Throw security error if user does not have all index permission - resc = rh.executeGetRequest("/_search/point_in_time/_all", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executeGetRequest("/_search/point_in_time/_all", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); // List all PITs should work for user with all index access - resc = rh.executeGetRequest("/_search/point_in_time/_all", - encodeBasicHeader("all-pit", "nagilum")); + resc = rh.executeGetRequest("/_search/point_in_time/_all", encodeBasicHeader("all-pit", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); List pitList = new ArrayList<>(); pitList.add(pitId1); @@ -172,24 +162,19 @@ public void testPitAllAPIAccess() throws Exception { pitList.contains(resc.findValueInJson("pits[1].pit_id")); // Throw security error if user does not have all index permission - resc = rh.executeGetRequest("/_cat/pit_segments/_all", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executeGetRequest("/_cat/pit_segments/_all", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); // PIT segments should work for user with all index access - resc = rh.executeGetRequest("/_cat/pit_segments/_all", - encodeBasicHeader("all-pit", "nagilum")); + resc = rh.executeGetRequest("/_cat/pit_segments/_all", encodeBasicHeader("all-pit", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - // Throw security error if user does not have all index permission - resc = rh.executeDeleteRequest("/_search/point_in_time/_all", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executeDeleteRequest("/_search/point_in_time/_all", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); // Delete all PITs should work for user with all index access - resc = rh.executeDeleteRequest("/_search/point_in_time/_all", - encodeBasicHeader("all-pit", "nagilum")); + resc = rh.executeDeleteRequest("/_search/point_in_time/_all", encodeBasicHeader("all-pit", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); pitList.contains(resc.findValueInJson("pits[0].pit_id")); pitList.contains(resc.findValueInJson("pits[1].pit_id")); @@ -202,8 +187,8 @@ public void testPitAllAPIAccess() throws Exception { public void testDataStreamWithPits() throws Exception { setup(); RestHelper rh = nonSslRestHelper(); - String indexTemplate = "{\"index_patterns\": [ \"my-data-stream*\" ], \"data_stream\": { }, \"priority\": 200, " + - "\"template\": {\"settings\": { } } }"; + String indexTemplate = "{\"index_patterns\": [ \"my-data-stream*\" ], \"data_stream\": { }, \"priority\": 200, " + + "\"template\": {\"settings\": { } } }"; rh.executePutRequest("/_index_template/my-data-stream-template", indexTemplate, encodeBasicHeader("ds1", "nagilum")); @@ -212,32 +197,25 @@ public void testDataStreamWithPits() throws Exception { RestHelper.HttpResponse resc; // create pit should work since user has permission on data stream - resc = rh.executePostRequest("/my-data-stream11/_search/point_in_time?keep_alive=100m", "", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executePostRequest("/my-data-stream11/_search/point_in_time?keep_alive=100m", "", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - String pitId1 = resc.findValueInJson("pit_id"); + String pitId1 = resc.findValueInJson("pit_id"); // PIT segments works since the user has access for backing indices - resc = rh.executeGetRequest("/_cat/pit_segments", - "{\"pit_id\":\"" + pitId1 +"\"}", - encodeBasicHeader("pit-1", "nagilum")); + resc = rh.executeGetRequest("/_cat/pit_segments", "{\"pit_id\":\"" + pitId1 + "\"}", encodeBasicHeader("pit-1", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); // create pit should work since user has permission on data stream - resc = rh.executePostRequest("/my-data-stream21/_search/point_in_time?keep_alive=100m", "", - encodeBasicHeader("pit-2", "nagilum")); + resc = rh.executePostRequest("/my-data-stream21/_search/point_in_time?keep_alive=100m", "", encodeBasicHeader("pit-2", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - String pitId2 = resc.findValueInJson("pit_id"); + String pitId2 = resc.findValueInJson("pit_id"); // since pit-3 doesn't have permission to backing data stream indices, throw security error - resc = rh.executeGetRequest("/_cat/pit_segments", - "{\"pit_id\":\"" + pitId2 +"\"}", - encodeBasicHeader("pit-3", "nagilum")); + resc = rh.executeGetRequest("/_cat/pit_segments", "{\"pit_id\":\"" + pitId2 + "\"}", encodeBasicHeader("pit-3", "nagilum")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); // Delete all PITs should work for user with all index access - resc = rh.executeDeleteRequest("/_search/point_in_time/_all", - encodeBasicHeader("all-pit", "nagilum")); + resc = rh.executeDeleteRequest("/_search/point_in_time/_all", encodeBasicHeader("all-pit", "nagilum")); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); } } diff --git a/src/test/java/org/opensearch/security/PrivilegesEvaluationTest.java b/src/test/java/org/opensearch/security/PrivilegesEvaluationTest.java index 1f9668c641..b7af395daa 100644 --- a/src/test/java/org/opensearch/security/PrivilegesEvaluationTest.java +++ b/src/test/java/org/opensearch/security/PrivilegesEvaluationTest.java @@ -31,21 +31,27 @@ public void resolveTestHidden() throws Exception { try (Client client = getClient()) { - client.index(new IndexRequest("hidden_test_not_hidden").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(XContentType.JSON, "index", - "hidden_test_not_hidden", "b", "y", "date", "1985/01/01")).actionGet(); - - client.admin().indices().create(new CreateIndexRequest(".hidden_test_actually_hidden").settings(ImmutableMap.of("index.hidden", true))) - .actionGet(); - client.index(new IndexRequest(".hidden_test_actually_hidden").id("test").source("a", "b").setRefreshPolicy(RefreshPolicy.IMMEDIATE)) - .actionGet(); + client.index( + new IndexRequest("hidden_test_not_hidden").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source(XContentType.JSON, "index", "hidden_test_not_hidden", "b", "y", "date", "1985/01/01") + ).actionGet(); + + client.admin() + .indices() + .create(new CreateIndexRequest(".hidden_test_actually_hidden").settings(ImmutableMap.of("index.hidden", true))) + .actionGet(); + client.index( + new IndexRequest(".hidden_test_actually_hidden").id("test").source("a", "b").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + ).actionGet(); } RestHelper rh = nonSslRestHelper(); - RestHelper.HttpResponse httpResponse = rh.executeGetRequest("/*hidden_test*/_search?expand_wildcards=all&pretty=true", - encodeBasicHeader("hidden_test", "nagilum")); + RestHelper.HttpResponse httpResponse = rh.executeGetRequest( + "/*hidden_test*/_search?expand_wildcards=all&pretty=true", + encodeBasicHeader("hidden_test", "nagilum") + ); Assert.assertEquals(httpResponse.getBody(), 403, httpResponse.getStatusCode()); - httpResponse = rh.executeGetRequest("/hidden_test_not_hidden?pretty=true", - encodeBasicHeader("hidden_test", "nagilum")); + httpResponse = rh.executeGetRequest("/hidden_test_not_hidden?pretty=true", encodeBasicHeader("hidden_test", "nagilum")); Assert.assertEquals(httpResponse.getBody(), 200, httpResponse.getStatusCode()); } } diff --git a/src/test/java/org/opensearch/security/ResolveAPITests.java b/src/test/java/org/opensearch/security/ResolveAPITests.java index a27c338dd1..088702acd9 100644 --- a/src/test/java/org/opensearch/security/ResolveAPITests.java +++ b/src/test/java/org/opensearch/security/ResolveAPITests.java @@ -32,7 +32,6 @@ import org.opensearch.security.test.SingleClusterTest; import org.opensearch.security.test.helper.rest.RestHelper; - public class ResolveAPITests extends SingleClusterTest { protected final Logger log = LogManager.getLogger(this.getClass()); @@ -48,7 +47,10 @@ public void testResolveDnfofFalse() throws Exception { final RestHelper rh = nonSslRestHelper(); RestHelper.HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_resolve/index/*?pretty", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("_resolve/index/*?pretty", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); log.debug(res.getBody()); assertNotContains(res, "*xception*"); assertNotContains(res, "*erial*"); @@ -59,7 +61,10 @@ public void testResolveDnfofFalse() throws Exception { assertContains(res, "*xyz*"); assertContains(res, "*role01_role02*"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_resolve/index/starfleet*?pretty", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("_resolve/index/starfleet*?pretty", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); log.debug(res.getBody()); assertNotContains(res, "*xception*"); assertNotContains(res, "*erial*"); @@ -72,10 +77,16 @@ public void testResolveDnfofFalse() throws Exception { assertContains(res, "*starfleet_academy*"); assertContains(res, "*starfleet_library*"); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("_resolve/index/*?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest("_resolve/index/*?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode() + ); log.debug(res.getBody()); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_resolve/index/starfleet*?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("_resolve/index/starfleet*?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode() + ); log.debug(res.getBody()); assertContains(res, "*starfleet*"); assertContains(res, "*starfleet_academy*"); @@ -92,7 +103,10 @@ public void testResolveDnfofTrue() throws Exception { final RestHelper rh = nonSslRestHelper(); RestHelper.HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_resolve/index/*?pretty", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("_resolve/index/*?pretty", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); log.debug(res.getBody()); assertNotContains(res, "*xception*"); assertNotContains(res, "*erial*"); @@ -103,7 +117,10 @@ public void testResolveDnfofTrue() throws Exception { assertContains(res, "*xyz*"); assertContains(res, "*role01_role02*"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_resolve/index/starfleet*?pretty", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("_resolve/index/starfleet*?pretty", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); log.debug(res.getBody()); assertNotContains(res, "*xception*"); assertNotContains(res, "*erial*"); @@ -116,7 +133,10 @@ public void testResolveDnfofTrue() throws Exception { assertContains(res, "*starfleet_academy*"); assertContains(res, "*starfleet_library*"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_resolve/index/*?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("_resolve/index/*?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode() + ); log.debug(res.getBody()); assertNotContains(res, "*xception*"); assertNotContains(res, "*erial*"); @@ -127,7 +147,10 @@ public void testResolveDnfofTrue() throws Exception { assertContains(res, "*public*"); assertContains(res, "*xyz*"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_resolve/index/starfleet*?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("_resolve/index/starfleet*?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode() + ); log.debug(res.getBody()); assertNotContains(res, "*xception*"); assertNotContains(res, "*erial*"); @@ -140,30 +163,87 @@ public void testResolveDnfofTrue() throws Exception { assertContains(res, "*starfleet_academy*"); assertContains(res, "*starfleet_library*"); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res = rh.executeGetRequest("_resolve/index/vulcangov*?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executeGetRequest("_resolve/index/vulcangov*?pretty", encodeBasicHeader("worf", "worf"))).getStatusCode() + ); log.debug(res.getBody()); } private void setupIndices() { try (Client tc = getClient()) { tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_library").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("klingonempire").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("public").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("spock").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("kirk").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("role01_role02").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("xyz").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(IndicesAliasesRequest.AliasActions.add().indices("starfleet","starfleet_academy","starfleet_library").alias("sf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(IndicesAliasesRequest.AliasActions.add().indices("klingonempire","vulcangov").alias("nonsf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(IndicesAliasesRequest.AliasActions.add().indices("public").alias("unrestricted"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(IndicesAliasesRequest.AliasActions.add().indices("xyz").alias("alias1"))).actionGet(); + tc.index( + new IndexRequest("vulcangov").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("starfleet").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("starfleet_academy").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("starfleet_library").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("klingonempire").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("public").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.index( + new IndexRequest("spock").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("kirk").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("role01_role02").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.index( + new IndexRequest("xyz").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + IndicesAliasesRequest.AliasActions.add().indices("starfleet", "starfleet_academy", "starfleet_library").alias("sf") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + IndicesAliasesRequest.AliasActions.add().indices("klingonempire", "vulcangov").alias("nonsf") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + IndicesAliasesRequest.AliasActions.add().indices("public").alias("unrestricted") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction(IndicesAliasesRequest.AliasActions.add().indices("xyz").alias("alias1")) + ) + .actionGet(); } } } diff --git a/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java b/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java index af71d590bf..0ab9736378 100644 --- a/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesInjectorIntegTest.java @@ -63,14 +63,21 @@ public RolesInjectorPlugin(final Settings settings, final Path configPath) { } @Override - public Collection createComponents(Client client, ClusterService clusterService, ThreadPool threadPool, - ResourceWatcherService resourceWatcherService, ScriptService scriptService, - NamedXContentRegistry xContentRegistry, Environment environment, - NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry, - IndexNameExpressionResolver indexNameExpressionResolver, - Supplier repositoriesServiceSupplier) { - if(injectedRoles != null) - threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, injectedRoles); + public Collection createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { + if (injectedRoles != null) threadPool.getThreadContext() + .putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, injectedRoles); return new ArrayList<>(); } } @@ -79,26 +86,42 @@ public Collection createComponents(Client client, ClusterService cluster public void testRolesInject() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityRoles("roles.yml"), Settings.EMPTY); - Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health( - new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); - Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster(). - health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); + Assert.assertEquals( + clusterInfo.numNodes, + clusterHelper.nodeClient() + .admin() + .cluster() + .health(new ClusterHealthRequest().waitForGreenStatus()) + .actionGet() + .getNumberOfNodes() + ); + Assert.assertEquals( + ClusterHealthStatus.GREEN, + clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus() + ); final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) - .put(minimumSecuritySettings(Settings.EMPTY).get(0)) - .put("cluster.name", clusterInfo.clustername) - .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") - .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") - .put("path.home", "./target") - .put("node.name", "testclient") - .put("discovery.initial_state_timeout", "8s") - .put("plugins.security.allow_default_init_securityindex", "true") - .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) - .build(); - - //1. Without roles injection. - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, - OpenSearchSecurityPlugin.class, RolesInjectorPlugin.class).start()) { + .put(minimumSecuritySettings(Settings.EMPTY).get(0)) + .put("cluster.name", clusterInfo.clustername) + .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") + .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") + .put("path.home", "./target") + .put("node.name", "testclient") + .put("discovery.initial_state_timeout", "8s") + .put("plugins.security.allow_default_init_securityindex", "true") + .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) + .build(); + + // 1. Without roles injection. + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + RolesInjectorPlugin.class + ).start() + ) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-1")).actionGet(); @@ -107,10 +130,18 @@ public void testRolesInject() throws Exception { Assert.assertTrue(ier.isExists()); } - //2. With invalid roles, must throw security exception. + // 2. With invalid roles, must throw security exception. RolesInjectorPlugin.injectedRoles = "invalid_user|invalid_role"; Exception exception = null; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, RolesInjectorPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + RolesInjectorPlugin.class + ).start() + ) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-2")).actionGet(); @@ -122,9 +153,17 @@ public void testRolesInject() throws Exception { Assert.assertNotNull(exception); Assert.assertTrue(exception.getMessage().contains("indices:admin/create")); - //3. With valid roles - which has permission to create index. + // 3. With valid roles - which has permission to create index. RolesInjectorPlugin.injectedRoles = "valid_user|opendistro_security_all_access"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, RolesInjectorPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + RolesInjectorPlugin.class + ).start() + ) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-3")).actionGet(); diff --git a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java index 9a8278804a..89e0cd2e45 100644 --- a/src/test/java/org/opensearch/security/RolesValidationIntegTest.java +++ b/src/test/java/org/opensearch/security/RolesValidationIntegTest.java @@ -57,14 +57,22 @@ public RolesValidationPlugin(final Settings settings, final Path configPath) { } @Override - public Collection createComponents(Client client, ClusterService clusterService, ThreadPool threadPool, - ResourceWatcherService resourceWatcherService, ScriptService scriptService, - NamedXContentRegistry xContentRegistry, Environment environment, - NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry, - IndexNameExpressionResolver indexNameExpressionResolver, - Supplier repositoriesServiceSupplier) { - if(rolesValidation != null) { - threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, "test|opendistro_security_all_access"); + public Collection createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { + if (rolesValidation != null) { + threadPool.getThreadContext() + .putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, "test|opendistro_security_all_access"); threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION, rolesValidation); } return new ArrayList<>(); @@ -76,20 +84,27 @@ public void testRolesValidation() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityRoles("roles.yml"), Settings.EMPTY); final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) - .put(minimumSecuritySettings(Settings.EMPTY).get(0)) - .put("cluster.name", clusterInfo.clustername) - .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") - .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") - .put("path.home", "./target") - .put("node.name", "testclient") - .put("discovery.initial_state_timeout", "8s") - .put("plugins.security.allow_default_init_securityindex", "true") - .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) - .build(); + .put(minimumSecuritySettings(Settings.EMPTY).get(0)) + .put("cluster.name", clusterInfo.clustername) + .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") + .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") + .put("path.home", "./target") + .put("node.name", "testclient") + .put("discovery.initial_state_timeout", "8s") + .put("plugins.security.allow_default_init_securityindex", "true") + .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) + .build(); // 1. Without roles validation - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, - OpenSearchSecurityPlugin.class, RolesValidationPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + RolesValidationPlugin.class + ).start() + ) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-1")).actionGet(); Assert.assertTrue(cir.isAcknowledged()); @@ -100,8 +115,15 @@ public void testRolesValidation() throws Exception { OpenSearchSecurityException exception = null; // 2. with roles invalid to the user RolesValidationPlugin.rolesValidation = "invalid_role"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, - OpenSearchSecurityPlugin.class, RolesValidationPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + RolesValidationPlugin.class + ).start() + ) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-2")).actionGet(); } catch (OpenSearchSecurityException ex) { @@ -112,8 +134,15 @@ public void testRolesValidation() throws Exception { // 3. with roles valid to the user RolesValidationPlugin.rolesValidation = "opendistro_security_all_access"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, - OpenSearchSecurityPlugin.class, RolesValidationPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + RolesValidationPlugin.class + ).start() + ) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-3")).actionGet(); Assert.assertTrue(cir.isAcknowledged()); diff --git a/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java b/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java index bc5d174739..b8da89e2dc 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java @@ -28,35 +28,45 @@ public void testNoSSL() throws Exception { setup(settings); final RestHelper rh = nonSslRestHelper(); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, - rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) - .getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executePutRequest("_plugins/_security/configupdate", "").getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, - rh.executePutRequest("_plugins/_security/configupdate?config_types=xxx", "", encodeBasicHeader("nagilum", "nagilum")) - .getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=xxx", "", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("_plugins/_security/whoami").getStatusCode()); } @Test public void testEndpoints() throws Exception { - final Settings settings = Settings.builder().put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .putList("plugins.security.nodes_dn", "CN=node-*.example.com,OU=SSL,O=Test,L=Test,C=DE").build(); + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .putList("plugins.security.nodes_dn", "CN=node-*.example.com,OU=SSL,O=Test,L=Test,C=DE") + .build(); setup(settings); final RestHelper rh = restHelper(); rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = false; - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, - rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) - .getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executePutRequest("_plugins/_security/configupdate", "").getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, - rh.executePutRequest("_plugins/_security/configupdate?config_types=xxx", "", encodeBasicHeader("nagilum", "nagilum")) - .getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=xxx", "", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); RestHelper.HttpResponse res; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_plugins/_security/whoami")).getStatusCode()); @@ -71,13 +81,17 @@ public void testEndpoints() throws Exception { assertContains(res, "*\"is_admin\":false*"); assertContains(res, "*\"is_node_certificate_request\":true*"); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, - rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) - .getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executePutRequest("_plugins/_security/configupdate", "").getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, - rh.executePutRequest("_plugins/_security/configupdate?config_types=xxx", "", encodeBasicHeader("nagilum", "nagilum")) - .getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=xxx", "", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); rh.keystore = "spock-keystore.jks"; @@ -87,13 +101,17 @@ public void testEndpoints() throws Exception { assertContains(res, "*\"is_admin\":false*"); assertContains(res, "*\"is_node_certificate_request\":false*"); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, - rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) - .getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executePutRequest("_plugins/_security/configupdate", "").getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, - rh.executePutRequest("_plugins/_security/configupdate?config_types=xxx", "", encodeBasicHeader("nagilum", "nagilum")) - .getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=xxx", "", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); rh.keystore = "kirk-keystore.jks"; @@ -103,14 +121,25 @@ public void testEndpoints() throws Exception { assertContains(res, "*\"is_admin\":true*"); assertContains(res, "*\"is_node_certificate_request\":false*"); - Assert.assertEquals(HttpStatus.SC_OK, - rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) - .getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, rh.executePutRequest("_plugins/_security/configupdate", "").getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "").getStatusCode()); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePutRequest("_plugins/_security/configupdate?config_types=unknown_xxx", "", - encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "").getStatusCode() + ); + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePutRequest( + "_plugins/_security/configupdate?config_types=unknown_xxx", + "", + encodeBasicHeader("nagilum", "nagilum") + )).getStatusCode() + ); assertContains(res, "*\"successful\":0*failed_node_exception*"); } diff --git a/src/test/java/org/opensearch/security/SecurityAdminInvalidConfigsTests.java b/src/test/java/org/opensearch/security/SecurityAdminInvalidConfigsTests.java index 18f5c06529..6cb89dc18f 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminInvalidConfigsTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminInvalidConfigsTests.java @@ -42,133 +42,153 @@ public class SecurityAdminInvalidConfigsTests extends SingleClusterTest { - @Test - public void testSecurityAdminDuplicateKey() throws Exception { - final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); - setup(settings); - - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; - - List argsAsList = new ArrayList<>(); - argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); - argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); - argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.httpPort)); - argsAsList.add("-cn"); - argsAsList.add(clusterInfo.clustername); - argsAsList.add("-cd"); - argsAsList.add(new File("./src/test/resources/invalid_dupkey").getAbsolutePath()); - argsAsList.add("-nhnv"); - - - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); - Assert.assertNotEquals(0, returnCode); - - RestHelper rh = restHelper(); - - Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - } - - @Test - public void testSecurityAdminDuplicateKeyReload() throws Exception { - testSecurityAdminDuplicateKey(); - - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; - - List argsAsList = new ArrayList<>(); - argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); - argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); - argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.httpPort)); - argsAsList.add("-cn"); - argsAsList.add(clusterInfo.clustername); - argsAsList.add("-rl"); - argsAsList.add("-nhnv"); - - - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); - Assert.assertEquals(0, returnCode); - - RestHelper rh = restHelper(); - - Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - } - - @Test - public void testSecurityAdminDuplicateKeySingleFile() throws Exception { - final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); - setup(settings); - - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; - - List argsAsList = new ArrayList<>(); - argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); - argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); - argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.httpPort)); - argsAsList.add("-cn"); - argsAsList.add(clusterInfo.clustername); - argsAsList.add("-f"); - argsAsList.add(new File("./src/test/resources/invalid_dupkey/roles_mapping.yml").getAbsolutePath()); - argsAsList.add("-t"); - argsAsList.add("rolesmapping"); - argsAsList.add("-nhnv"); - - - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); - Assert.assertNotEquals(0, returnCode); - - RestHelper rh = restHelper(); - - Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - } - - @Test - public void testSecurityAdminDuplicateKeyReloadSingleFile() throws Exception { - testSecurityAdminDuplicateKeySingleFile(); - - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; - - List argsAsList = new ArrayList<>(); - argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); - argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); - argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.httpPort)); - argsAsList.add("-cn"); - argsAsList.add(clusterInfo.clustername); - argsAsList.add("-rl"); - argsAsList.add("-nhnv"); - - - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); - Assert.assertEquals(0, returnCode); - - RestHelper rh = restHelper(); - - Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - } + @Test + public void testSecurityAdminDuplicateKey() throws Exception { + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(settings); + + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; + + List argsAsList = new ArrayList<>(); + argsAsList.add("-ts"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); + argsAsList.add("-ks"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add("-p"); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); + argsAsList.add("-cn"); + argsAsList.add(clusterInfo.clustername); + argsAsList.add("-cd"); + argsAsList.add(new File("./src/test/resources/invalid_dupkey").getAbsolutePath()); + argsAsList.add("-nhnv"); + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + Assert.assertNotEquals(0, returnCode); + + RestHelper rh = restHelper(); + + Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + } + + @Test + public void testSecurityAdminDuplicateKeyReload() throws Exception { + testSecurityAdminDuplicateKey(); + + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; + + List argsAsList = new ArrayList<>(); + argsAsList.add("-ts"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); + argsAsList.add("-ks"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add("-p"); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); + argsAsList.add("-cn"); + argsAsList.add(clusterInfo.clustername); + argsAsList.add("-rl"); + argsAsList.add("-nhnv"); + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + Assert.assertEquals(0, returnCode); + + RestHelper rh = restHelper(); + + Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + } + + @Test + public void testSecurityAdminDuplicateKeySingleFile() throws Exception { + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(settings); + + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; + + List argsAsList = new ArrayList<>(); + argsAsList.add("-ts"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); + argsAsList.add("-ks"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add("-p"); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); + argsAsList.add("-cn"); + argsAsList.add(clusterInfo.clustername); + argsAsList.add("-f"); + argsAsList.add(new File("./src/test/resources/invalid_dupkey/roles_mapping.yml").getAbsolutePath()); + argsAsList.add("-t"); + argsAsList.add("rolesmapping"); + argsAsList.add("-nhnv"); + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + Assert.assertNotEquals(0, returnCode); + + RestHelper rh = restHelper(); + + Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + } + + @Test + public void testSecurityAdminDuplicateKeyReloadSingleFile() throws Exception { + testSecurityAdminDuplicateKeySingleFile(); + + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; + + List argsAsList = new ArrayList<>(); + argsAsList.add("-ts"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); + argsAsList.add("-ks"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add("-p"); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); + argsAsList.add("-cn"); + argsAsList.add(clusterInfo.clustername); + argsAsList.add("-rl"); + argsAsList.add("-nhnv"); + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + Assert.assertEquals(0, returnCode); + + RestHelper rh = restHelper(); + + Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + } } diff --git a/src/test/java/org/opensearch/security/SecurityAdminTests.java b/src/test/java/org/opensearch/security/SecurityAdminTests.java index 553ab0a5f2..681d04fc3e 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminTests.java @@ -47,19 +47,19 @@ public class SecurityAdminTests extends SingleClusterTest { @Test public void testSecurityAdmin() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); setup(Settings.EMPTY, null, settings, false); - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); @@ -67,8 +67,7 @@ public void testSecurityAdmin() throws Exception { addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-nhnv"); - - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); RestHelper rh = restHelper(); @@ -79,79 +78,89 @@ public void testSecurityAdmin() throws Exception { @Test public void testSecurityAdminHostnameVerificationEnforced() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/root-ca.pem")) - .put("plugins.security.ssl.http.pemcert_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.crt.pem")) - .put("plugins.security.ssl.http.pemkey_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.key.pem")) - .putList("plugins.security.authcz.admin_dn", List.of("CN=kirk,OU=client,O=client,L=test,C=de")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put( + "plugins.security.ssl.http.pemtrustedcas_filepath", + FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/root-ca.pem") + ) + .put("plugins.security.ssl.http.pemcert_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.crt.pem")) + .put("plugins.security.ssl.http.pemkey_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.key.pem")) + .putList("plugins.security.authcz.admin_dn", List.of("CN=kirk,OU=client,O=client,L=test,C=de")) + .build(); setup(Settings.EMPTY, null, settings, false); - final String prefix = getResourceFolder()==null?"securityadmin/":getResourceFolder()+"/securityadmin/"; + final String prefix = getResourceFolder() == null ? "securityadmin/" : getResourceFolder() + "/securityadmin/"; List argsAsList = new ArrayList<>(); argsAsList.add("-cacert"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"root-ca.pem").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "root-ca.pem").toFile().getAbsolutePath()); argsAsList.add("-cert"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk.crt.pem").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk.crt.pem").toFile().getAbsolutePath()); argsAsList.add("-key"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk.key.pem").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk.key.pem").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-icl"); addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); - final IOException expectedException = assertThrows(IOException.class, () -> SecurityAdmin.execute(argsAsList.toArray(new String[0]))); - final String expectedMessagePattern = "Certificate for <.+> doesn't match any of the subject alternative names: \\[node-.\\.example\\.com\\]"; + final IOException expectedException = assertThrows( + IOException.class, + () -> SecurityAdmin.execute(argsAsList.toArray(new String[0])) + ); + final String expectedMessagePattern = + "Certificate for <.+> doesn't match any of the subject alternative names: \\[node-.\\.example\\.com\\]"; assertThat(expectedException.getMessage(), matchesPattern(expectedMessagePattern)); } @Test public void testSecurityAdminHostnameVerificationNotEnforced() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/root-ca.pem")) - .put("plugins.security.ssl.http.pemcert_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.crt.pem")) - .put("plugins.security.ssl.http.pemkey_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.key.pem")) - .putList("plugins.security.authcz.admin_dn", List.of("CN=kirk,OU=client,O=client,L=test,C=de")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put( + "plugins.security.ssl.http.pemtrustedcas_filepath", + FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/root-ca.pem") + ) + .put("plugins.security.ssl.http.pemcert_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.crt.pem")) + .put("plugins.security.ssl.http.pemkey_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.key.pem")) + .putList("plugins.security.authcz.admin_dn", List.of("CN=kirk,OU=client,O=client,L=test,C=de")) + .build(); setup(Settings.EMPTY, null, settings, false); - final String prefix = getResourceFolder()==null?"securityadmin/":getResourceFolder()+"/securityadmin/"; + final String prefix = getResourceFolder() == null ? "securityadmin/" : getResourceFolder() + "/securityadmin/"; List argsAsList = new ArrayList<>(); argsAsList.add("-cacert"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"root-ca.pem").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "root-ca.pem").toFile().getAbsolutePath()); argsAsList.add("-cert"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk.crt.pem").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk.crt.pem").toFile().getAbsolutePath()); argsAsList.add("-key"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk.key.pem").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk.key.pem").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-icl"); addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-nhnv"); - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); } @Test public void testSecurityAdminInvalidCert() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); setup(Settings.EMPTY, null, settings, false); - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); @@ -159,7 +168,7 @@ public void testSecurityAdminInvalidCert() throws Exception { addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-nhnv"); - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); RestHelper rh = restHelper(); @@ -168,9 +177,9 @@ public void testSecurityAdminInvalidCert() throws Exception { argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"spock-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "spock-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); @@ -179,17 +188,16 @@ public void testSecurityAdminInvalidCert() throws Exception { argsAsList.add("--diagnose"); argsAsList.add("-nhnv"); - - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(-1, returnCode); Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_plugins/_security/health?pretty")).getStatusCode()); argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"node-0-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "node-0-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); @@ -197,7 +205,7 @@ public void testSecurityAdminInvalidCert() throws Exception { addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-nhnv"); - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(-1, returnCode); Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_plugins/_security/health?pretty")).getStatusCode()); @@ -206,19 +214,19 @@ public void testSecurityAdminInvalidCert() throws Exception { @Test public void testSecurityAdminV6Update() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); setup(Settings.EMPTY, null, settings, false); - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); @@ -226,35 +234,34 @@ public void testSecurityAdminV6Update() throws Exception { addDirectoryPath(argsAsList, new File("./legacy/securityconfig_v6").getAbsolutePath()); argsAsList.add("-nhnv"); - - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); RestHelper rh = restHelper(); Assert.assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, rh.executeGetRequest("_opendistro/_security/health?pretty").getStatusCode()); - //System.out.println(res.getBody()); - //assertContains(res, "*UP*"); - //assertContains(res, "*strict*"); - //assertNotContains(res, "*DOWN*"); + // System.out.println(res.getBody()); + // assertContains(res, "*UP*"); + // assertContains(res, "*strict*"); + // assertNotContains(res, "*DOWN*"); } @Test public void testSecurityAdminRegularUpdate() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); setup(Settings.EMPTY, null, settings, true); - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); @@ -262,8 +269,7 @@ public void testSecurityAdminRegularUpdate() throws Exception { addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-nhnv"); - - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); RestHelper rh = restHelper(); @@ -279,19 +285,19 @@ public void testSecurityAdminRegularUpdate() throws Exception { @Test public void testSecurityAdminSingularV7Updates() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings, true); - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); @@ -302,15 +308,14 @@ public void testSecurityAdminSingularV7Updates() throws Exception { argsAsList.add("config"); argsAsList.add("-nhnv"); - - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); @@ -321,15 +326,14 @@ public void testSecurityAdminSingularV7Updates() throws Exception { argsAsList.add("rolesmapping"); argsAsList.add("-nhnv"); - - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); @@ -340,8 +344,7 @@ public void testSecurityAdminSingularV7Updates() throws Exception { argsAsList.add("tenants"); argsAsList.add("-nhnv"); - - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); RestHelper rh = restHelper(); @@ -357,19 +360,19 @@ public void testSecurityAdminSingularV7Updates() throws Exception { @Test public void testSecurityAdminSingularV6Updates() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings, true); - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); @@ -380,10 +383,9 @@ public void testSecurityAdminSingularV6Updates() throws Exception { argsAsList.add("config"); argsAsList.add("-nhnv"); - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); - RestHelper rh = restHelper(); HttpResponse res; @@ -397,31 +399,30 @@ public void testSecurityAdminSingularV6Updates() throws Exception { @Test public void testSecurityAdminInvalidYml() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings, true); - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-f"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"roles_invalidxcontent.yml").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "roles_invalidxcontent.yml").toFile().getAbsolutePath()); argsAsList.add("-t"); argsAsList.add("roles"); argsAsList.add("-nhnv"); - - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); RestHelper rh = restHelper(); @@ -437,29 +438,34 @@ public void testSecurityAdminInvalidYml() throws Exception { @Test public void testSecurityAdminReloadInvalidConfig() throws Exception { final Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_HTTP_CLIENTAUTH_MODE, "REQUIRE") + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings, true); - final RestHelper rh = restHelper(); //ssl resthelper + final RestHelper rh = restHelper(); // ssl resthelper rh.enableHTTPClientSSL = true; rh.trustHTTPServerCertificate = true; rh.sendAdminCertificate = true; rh.keystore = "kirk-keystore.jks"; - System.out.println(rh.executePutRequest(".opendistro_security/_doc/roles", FileHelper.loadFile("roles_invalidxcontent.yml")).getBody());; - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest(".opendistro_security/_doc/roles", "{\"roles\":\"dummy\"}").getStatusCode()); - + System.out.println( + rh.executePutRequest(".opendistro_security/_doc/roles", FileHelper.loadFile("roles_invalidxcontent.yml")).getBody() + ); + ; + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePutRequest(".opendistro_security/_doc/roles", "{\"roles\":\"dummy\"}").getStatusCode() + ); - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); @@ -467,8 +473,7 @@ public void testSecurityAdminReloadInvalidConfig() throws Exception { argsAsList.add("-rl"); argsAsList.add("-nhnv"); - - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); HttpResponse res; @@ -485,7 +490,7 @@ public void testSecurityAdminValidateConfig() throws Exception { addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-vc"); - int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); argsAsList = new ArrayList<>(); @@ -493,7 +498,7 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList.add(new File(PROJECT_ROOT_RELATIVE_PATH + "src/test/resources/roles.yml").getAbsolutePath()); argsAsList.add("-vc"); - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); argsAsList = new ArrayList<>(); @@ -501,15 +506,17 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList.add(new File(PROJECT_ROOT_RELATIVE_PATH + "src/main/resources/static_config/static_roles.yml").getAbsolutePath()); argsAsList.add("-vc"); - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); argsAsList = new ArrayList<>(); argsAsList.add("-f"); - argsAsList.add(new File(PROJECT_ROOT_RELATIVE_PATH + "src/main/resources/static_config/static_action_groups.yml").getAbsolutePath()); + argsAsList.add( + new File(PROJECT_ROOT_RELATIVE_PATH + "src/main/resources/static_config/static_action_groups.yml").getAbsolutePath() + ); argsAsList.add("-vc"); - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); argsAsList = new ArrayList<>(); @@ -517,7 +524,7 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList.add(new File(PROJECT_ROOT_RELATIVE_PATH + "src/main/resources/static_config/static_tenants.yml").getAbsolutePath()); argsAsList.add("-vc"); - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); argsAsList = new ArrayList<>(); @@ -527,7 +534,7 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList.add("-t"); argsAsList.add("config"); - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); argsAsList = new ArrayList<>(); @@ -535,14 +542,14 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList.add(TEST_RESOURCE_ABSOLUTE_PATH); argsAsList.add("-vc"); - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); argsAsList = new ArrayList<>(); addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH + "legacy/securityconfig_v6"); argsAsList.add("-vc"); - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); argsAsList = new ArrayList<>(); @@ -550,7 +557,7 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList.add("-vc"); argsAsList.add("6"); - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); argsAsList = new ArrayList<>(); @@ -558,26 +565,26 @@ public void testSecurityAdminValidateConfig() throws Exception { argsAsList.add("-vc"); argsAsList.add("8"); - returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); } @Test public void testIsLegacySecurityIndexOnV7Index() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); setup(Settings.EMPTY, null, settings, false); - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; List argsAsList = new ArrayList<>(); argsAsList.add("-ts"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks").toFile().getAbsolutePath()); argsAsList.add("-ks"); - argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix + "kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); @@ -600,7 +607,9 @@ public void testIsLegacySecurityIndexOnV7Index() throws Exception { System.out.flush(); System.setOut(old); String standardOut = baos.toString(); - String legacyIndexOutput = "Legacy index '"+ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX+"' (ES 6) detected (or forced). You should migrate the configuration!"; + String legacyIndexOutput = "Legacy index '" + + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + + "' (ES 6) detected (or forced). You should migrate the configuration!"; Assert.assertFalse(standardOut.contains(legacyIndexOutput)); } diff --git a/src/test/java/org/opensearch/security/SecurityRolesTests.java b/src/test/java/org/opensearch/security/SecurityRolesTests.java index ee8e1ea150..24a6bafbb8 100644 --- a/src/test/java/org/opensearch/security/SecurityRolesTests.java +++ b/src/test/java/org/opensearch/security/SecurityRolesTests.java @@ -40,76 +40,89 @@ public class SecurityRolesTests extends SingleClusterTest { - @Test - public void testSecurityRolesAnon() throws Exception { - - setup(Settings.EMPTY, new DynamicSecurityConfig() - .setSecurityInternalUsers("internal_users_sr.yml") - .setConfig("config_anon.yml"), Settings.EMPTY, true); - - RestHelper rh = nonSslRestHelper(); - - HttpResponse resc = rh.executeGetRequest("_opendistro/_security/authinfo?pretty"); - Assert.assertTrue(resc.getBody().contains("anonymous")); - Assert.assertFalse(resc.getBody().contains("xyz_sr")); - Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - - resc = rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("sr_user", "nagilum")); - Assert.assertTrue(resc.getBody().contains("sr_user")); - Assert.assertTrue(resc.getBody().contains("xyz_sr")); - Assert.assertFalse(resc.getBody().contains("opendistro_security_kibana_server")); - Assert.assertTrue(resc.getBody().contains("backend_roles=[abc_ber]")); - Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - } - - @Test - public void testSecurityRoles() throws Exception { - - setup(Settings.EMPTY, new DynamicSecurityConfig() - .setSecurityRolesMapping("roles_mapping.yml") - .setSecurityInternalUsers("internal_users_sr.yml"), Settings.EMPTY, true); - - RestHelper rh = nonSslRestHelper(); - rh.sendAdminCertificate = false; - - HttpResponse resc = rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("sr_user", "nagilum")); - Assert.assertTrue(resc.getBody().contains("sr_user")); - Assert.assertTrue(resc.getBody().contains("xyz_sr")); - - // Opendistro_security_roles cannot contain roles that don't exist. - Assert.assertFalse(resc.getBody().contains("xyz_sr_non_existent")); - - // Opendistro_security_roles can contain reserved roles. - Assert.assertTrue(resc.getBody().contains("xyz_sr_reserved")); - - // Opendistro_security_roles cannot contain roles that are hidden in rolesmapping.yml. - Assert.assertFalse(resc.getBody().contains("xyz_sr_hidden")); - - Assert.assertTrue(resc.getBody().contains("backend_roles=[abc_ber]")); - Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - } - - @Test - public void testSecurityRolesImpersonation() throws Exception { - - Settings settings = Settings.builder() - .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".sr_user", "sr_impuser") - .build(); - - setup(Settings.EMPTY, new DynamicSecurityConfig() - .setSecurityInternalUsers("internal_users_sr.yml"), settings, true); - - RestHelper rh = nonSslRestHelper(); - - HttpResponse resc = rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("sr_user", "nagilum"), new BasicHeader("opendistro_security_impersonate_as", "sr_impuser")); - Assert.assertFalse(resc.getBody().contains("sr_user")); - Assert.assertTrue(resc.getBody().contains("sr_impuser")); - Assert.assertFalse(resc.getBody().contains("xyz_sr")); - Assert.assertTrue(resc.getBody().contains("xyz_impsr")); - Assert.assertTrue(resc.getBody().contains("backend_roles=[ert_ber]")); - Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - - resc = rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("sr_user", "nagilum"), new BasicHeader("opendistro_security_impersonate_as", "sr_impuser")); - Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - } + @Test + public void testSecurityRolesAnon() throws Exception { + + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setSecurityInternalUsers("internal_users_sr.yml").setConfig("config_anon.yml"), + Settings.EMPTY, + true + ); + + RestHelper rh = nonSslRestHelper(); + + HttpResponse resc = rh.executeGetRequest("_opendistro/_security/authinfo?pretty"); + Assert.assertTrue(resc.getBody().contains("anonymous")); + Assert.assertFalse(resc.getBody().contains("xyz_sr")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + resc = rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("sr_user", "nagilum")); + Assert.assertTrue(resc.getBody().contains("sr_user")); + Assert.assertTrue(resc.getBody().contains("xyz_sr")); + Assert.assertFalse(resc.getBody().contains("opendistro_security_kibana_server")); + Assert.assertTrue(resc.getBody().contains("backend_roles=[abc_ber]")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + } + + @Test + public void testSecurityRoles() throws Exception { + + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setSecurityRolesMapping("roles_mapping.yml").setSecurityInternalUsers("internal_users_sr.yml"), + Settings.EMPTY, + true + ); + + RestHelper rh = nonSslRestHelper(); + rh.sendAdminCertificate = false; + + HttpResponse resc = rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("sr_user", "nagilum")); + Assert.assertTrue(resc.getBody().contains("sr_user")); + Assert.assertTrue(resc.getBody().contains("xyz_sr")); + + // Opendistro_security_roles cannot contain roles that don't exist. + Assert.assertFalse(resc.getBody().contains("xyz_sr_non_existent")); + + // Opendistro_security_roles can contain reserved roles. + Assert.assertTrue(resc.getBody().contains("xyz_sr_reserved")); + + // Opendistro_security_roles cannot contain roles that are hidden in rolesmapping.yml. + Assert.assertFalse(resc.getBody().contains("xyz_sr_hidden")); + + Assert.assertTrue(resc.getBody().contains("backend_roles=[abc_ber]")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + } + + @Test + public void testSecurityRolesImpersonation() throws Exception { + + Settings settings = Settings.builder() + .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".sr_user", "sr_impuser") + .build(); + + setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityInternalUsers("internal_users_sr.yml"), settings, true); + + RestHelper rh = nonSslRestHelper(); + + HttpResponse resc = rh.executeGetRequest( + "_opendistro/_security/authinfo?pretty", + encodeBasicHeader("sr_user", "nagilum"), + new BasicHeader("opendistro_security_impersonate_as", "sr_impuser") + ); + Assert.assertFalse(resc.getBody().contains("sr_user")); + Assert.assertTrue(resc.getBody().contains("sr_impuser")); + Assert.assertFalse(resc.getBody().contains("xyz_sr")); + Assert.assertTrue(resc.getBody().contains("xyz_impsr")); + Assert.assertTrue(resc.getBody().contains("backend_roles=[ert_ber]")); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + + resc = rh.executeGetRequest( + "*/_search?pretty", + encodeBasicHeader("sr_user", "nagilum"), + new BasicHeader("opendistro_security_impersonate_as", "sr_impuser") + ); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + } } diff --git a/src/test/java/org/opensearch/security/SlowIntegrationTests.java b/src/test/java/org/opensearch/security/SlowIntegrationTests.java index b2efada0d8..0e7585a08d 100644 --- a/src/test/java/org/opensearch/security/SlowIntegrationTests.java +++ b/src/test/java/org/opensearch/security/SlowIntegrationTests.java @@ -55,38 +55,72 @@ public class SlowIntegrationTests extends SingleClusterTest { public void testCustomInterclusterRequestEvaluator() throws Exception { final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_INTERCLUSTER_REQUEST_EVALUATOR_CLASS, "org.opensearch.security.AlwaysFalseInterClusterRequestEvaluator") - .put("discovery.initial_state_timeout","8s") - .build(); - setup(Settings.EMPTY, null, settings, false, ClusterConfiguration.DEFAULT ,5,1); - Assert.assertEquals(1, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); - Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); + .put( + ConfigConstants.SECURITY_INTERCLUSTER_REQUEST_EVALUATOR_CLASS, + "org.opensearch.security.AlwaysFalseInterClusterRequestEvaluator" + ) + .put("discovery.initial_state_timeout", "8s") + .build(); + setup(Settings.EMPTY, null, settings, false, ClusterConfiguration.DEFAULT, 5, 1); + Assert.assertEquals( + 1, + clusterHelper.nodeClient() + .admin() + .cluster() + .health(new ClusterHealthRequest().waitForGreenStatus()) + .actionGet() + .getNumberOfNodes() + ); + Assert.assertEquals( + ClusterHealthStatus.GREEN, + clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus() + ); } @SuppressWarnings("resource") @Test public void testNodeClientAllowedWithServerCertificate() throws Exception { setup(); - Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); - Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - + Assert.assertEquals( + clusterInfo.numNodes, + clusterHelper.nodeClient() + .admin() + .cluster() + .health(new ClusterHealthRequest().waitForGreenStatus()) + .actionGet() + .getNumberOfNodes() + ); + Assert.assertEquals( + ClusterHealthStatus.GREEN, + clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus() + ); final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) - .put(minimumSecuritySettings(Settings.EMPTY).get(0)) - .put("cluster.name", clusterInfo.clustername) - .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") - .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") - .put("path.home", "./target") - .put("node.name", "transportclient") - .put("discovery.initial_state_timeout","8s") - .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost+":"+clusterInfo.nodePort) - .build(); + .put(minimumSecuritySettings(Settings.EMPTY).get(0)) + .put("cluster.name", clusterInfo.clustername) + .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") + .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") + .put("path.home", "./target") + .put("node.name", "transportclient") + .put("discovery.initial_state_timeout", "8s") + .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) + .build(); log.debug("Start node client"); try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class).start()) { - Assert.assertFalse(node.client().admin().cluster().health(new ClusterHealthRequest().waitForNodes(String.valueOf(clusterInfo.numNodes+1))).actionGet().isTimedOut()); - Assert.assertEquals(clusterInfo.numNodes+1, node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); + Assert.assertFalse( + node.client() + .admin() + .cluster() + .health(new ClusterHealthRequest().waitForNodes(String.valueOf(clusterInfo.numNodes + 1))) + .actionGet() + .isTimedOut() + ); + Assert.assertEquals( + clusterInfo.numNodes + 1, + node.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size() + ); } } @@ -94,22 +128,32 @@ public void testNodeClientAllowedWithServerCertificate() throws Exception { @Test public void testNodeClientDisallowedWithNonServerCertificate() throws Exception { setup(); - Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); - Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - + Assert.assertEquals( + clusterInfo.numNodes, + clusterHelper.nodeClient() + .admin() + .cluster() + .health(new ClusterHealthRequest().waitForGreenStatus()) + .actionGet() + .getNumberOfNodes() + ); + Assert.assertEquals( + ClusterHealthStatus.GREEN, + clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus() + ); final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) - .put(minimumSecuritySettings(Settings.EMPTY).get(0)) - .put("cluster.name", clusterInfo.clustername) - .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") - .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") - .put("path.home", "./target") - .put("node.name", "transportclient") - .put("discovery.initial_state_timeout","8s") - .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost+":"+clusterInfo.nodePort) - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("kirk-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS,"kirk") - .build(); + .put(minimumSecuritySettings(Settings.EMPTY).get(0)) + .put("cluster.name", clusterInfo.clustername) + .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") + .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") + .put("path.home", "./target") + .put("node.name", "transportclient") + .put("discovery.initial_state_timeout", "8s") + .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) + .put("plugins.security.ssl.transport.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("kirk-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "kirk") + .build(); log.debug("Start node client"); @@ -126,21 +170,32 @@ public void testNodeClientDisallowedWithNonServerCertificate() throws Exception @Test public void testNodeClientDisallowedWithNonServerCertificate2() throws Exception { setup(); - Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); - Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); + Assert.assertEquals( + clusterInfo.numNodes, + clusterHelper.nodeClient() + .admin() + .cluster() + .health(new ClusterHealthRequest().waitForGreenStatus()) + .actionGet() + .getNumberOfNodes() + ); + Assert.assertEquals( + ClusterHealthStatus.GREEN, + clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus() + ); final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) - .put(minimumSecuritySettings(Settings.EMPTY).get(0)) - .put("cluster.name", clusterInfo.clustername) - .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") - .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") - .put("path.home", "./target") - .put("node.name", "transportclient") - .put("discovery.initial_state_timeout","8s") - .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost+":"+clusterInfo.nodePort) - .put("plugins.security.ssl.transport.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("spock-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS,"spock") - .build(); + .put(minimumSecuritySettings(Settings.EMPTY).get(0)) + .put("cluster.name", clusterInfo.clustername) + .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") + .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") + .put("path.home", "./target") + .put("node.name", "transportclient") + .put("discovery.initial_state_timeout", "8s") + .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) + .put("plugins.security.ssl.transport.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("spock-keystore.jks")) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "spock") + .build(); log.debug("Start node client"); @@ -155,19 +210,26 @@ public void testNodeClientDisallowedWithNonServerCertificate2() throws Exception @Test public void testDelayInSecurityIndexInitialization() throws Exception { final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, true) - .put("cluster.routing.allocation.exclude._ip", "127.0.0.1") - .build(); + .put(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, true) + .put("cluster.routing.allocation.exclude._ip", "127.0.0.1") + .build(); try { setup(Settings.EMPTY, null, settings, false); Assert.fail("Expected IOException here due to red cluster state"); } catch (IOException e) { // Index request has a default timeout of 1 minute, adding buffer between nodes initialization and cluster health check - Thread.sleep(1000*80); - // Ideally, we would want to remove this cluster setting, but default settings cannot be removed. So overriding with a reserved IP address - clusterHelper.nodeClient().admin().cluster().updateSettings( - new ClusterUpdateSettingsRequest().transientSettings(Settings.builder().put("cluster.routing.allocation.exclude._ip", "192.0.2.0").build())); - this.clusterInfo = clusterHelper.waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(10),3); + Thread.sleep(1000 * 80); + // Ideally, we would want to remove this cluster setting, but default settings cannot be removed. So overriding with a reserved + // IP address + clusterHelper.nodeClient() + .admin() + .cluster() + .updateSettings( + new ClusterUpdateSettingsRequest().transientSettings( + Settings.builder().put("cluster.routing.allocation.exclude._ip", "192.0.2.0").build() + ) + ); + this.clusterInfo = clusterHelper.waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(10), 3); } RestHelper rh = nonSslRestHelper(); Thread.sleep(10000); diff --git a/src/test/java/org/opensearch/security/SnapshotRestoreTests.java b/src/test/java/org/opensearch/security/SnapshotRestoreTests.java index 8e869e250d..1c884a8e5d 100644 --- a/src/test/java/org/opensearch/security/SnapshotRestoreTests.java +++ b/src/test/java/org/opensearch/security/SnapshotRestoreTests.java @@ -54,7 +54,6 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; - public class SnapshotRestoreTests extends SingleClusterTest { private ClusterConfiguration currentClusterConfig = ClusterConfiguration.DEFAULT; @@ -62,233 +61,711 @@ public class SnapshotRestoreTests extends SingleClusterTest { public void testSnapshotEnableSecurityIndexRestore() throws Exception { final Settings settings = Settings.builder() - .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) - .put("plugins.security.check_snapshot_restore_write_privileges", false) - .put("plugins.security.unsupported.restore.securityindex.enabled", true) - .build(); + .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) + .put("plugins.security.check_snapshot_restore_write_privileges", false) + .put("plugins.security.unsupported.restore.securityindex.enabled", true) + .build(); setup(settings, currentClusterConfig); try (Client tc = getClient()) { - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.admin().cluster().putRepository(new PutRepositoryRequest("vulcangov").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/vulcangov"))).actionGet(); - tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("vulcangov", "vulcangov_1").indices("vulcangov").includeGlobalState(true).waitForCompletion(true)).actionGet(); - - tc.admin().cluster().putRepository(new PutRepositoryRequest(".opendistro_security").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/.opendistro_security"))).actionGet(); - tc.admin().cluster().createSnapshot(new CreateSnapshotRequest(".opendistro_security", "opendistro_security_1").indices(".opendistro_security").includeGlobalState(false).waitForCompletion(true)).actionGet(); - - tc.admin().cluster().putRepository(new PutRepositoryRequest("all").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/all"))).actionGet(); - tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("all", "all_1").indices("*").includeGlobalState(false).waitForCompletion(true)).actionGet(); + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest("vulcangov").type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/vulcangov")) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot( + new CreateSnapshotRequest("vulcangov", "vulcangov_1").indices("vulcangov") + .includeGlobalState(true) + .waitForCompletion(true) + ) + .actionGet(); + + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest(".opendistro_security").type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/.opendistro_security")) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot( + new CreateSnapshotRequest(".opendistro_security", "opendistro_security_1").indices(".opendistro_security") + .includeGlobalState(false) + .waitForCompletion(true) + ) + .actionGet(); + + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest("all").type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/all")) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot(new CreateSnapshotRequest("all", "all_1").indices("*").includeGlobalState(false).waitForCompletion(true)) + .actionGet(); } RestHelper rh = nonSslRestHelper(); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/vulcangov", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/vulcangov/vulcangov_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"include_global_state\": true, \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/vulcangov", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/vulcangov/vulcangov_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"include_global_state\": true, \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // worf not allowed to restore vulcangov index - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","", encodeBasicHeader("worf", "worf")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "", + encodeBasicHeader("worf", "worf") + ).getStatusCode() + ); // Try to restore vulcangov index as .opendistro_security index, not possible since Security index is open - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_INTERNAL_SERVER_ERROR, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore .opendistro_security index. - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/.opendistro_security", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/.opendistro_security/opendistro_security_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/.opendistro_security", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/.opendistro_security/opendistro_security_1", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); // 500 because Security index is open - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_INTERNAL_SERVER_ERROR, + rh.executePostRequest( + "_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true", + "", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore .opendistro_security index as .opendistro_security_copy index - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true", + "{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore all indices. - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/all", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/all/all_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/all", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/all/all_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); // 500 because Security index is open - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_INTERNAL_SERVER_ERROR, + rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true", "", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); // Try to restore vulcangov index as .opendistro_security index -> 500 because Security index is open - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - // Try to restore .opendistro_security index as .opendistro_security_copy index. Delete opendistro_security_copy first, was created in test above - Assert.assertEquals(HttpStatus.SC_OK, rh.executeDeleteRequest("opendistro_security_copy", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_INTERNAL_SERVER_ERROR, + rh.executePostRequest( + "_snapshot/all/all_1/_restore?wait_for_completion=true", + "{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); + // Try to restore .opendistro_security index as .opendistro_security_copy index. Delete opendistro_security_copy first, was created + // in test above + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeDeleteRequest("opendistro_security_copy", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/all/all_1/_restore?wait_for_completion=true", + "{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore an unknown snapshot - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, rh.executePostRequest("_snapshot/all/unknown-snapshot/_restore?wait_for_completion=true", "", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_INTERNAL_SERVER_ERROR, + rh.executePostRequest( + "_snapshot/all/unknown-snapshot/_restore?wait_for_completion=true", + "", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // close and restore Security index - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest(".opendistro_security/_close", "", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest(".opendistro_security/_open", "", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest(".opendistro_security/_close", "", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true", + "", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest(".opendistro_security/_open", "", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); } @Test public void testSnapshot() throws Exception { final Settings settings = Settings.builder() - .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) - .put("plugins.security.check_snapshot_restore_write_privileges", false) - .build(); + .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) + .put("plugins.security.check_snapshot_restore_write_privileges", false) + .build(); setup(settings, currentClusterConfig); try (Client tc = getClient()) { - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.admin().cluster().putRepository(new PutRepositoryRequest("vulcangov").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/vulcangov"))).actionGet(); - tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("vulcangov", "vulcangov_1").indices("vulcangov").includeGlobalState(true).waitForCompletion(true)).actionGet(); - - tc.admin().cluster().putRepository(new PutRepositoryRequest(".opendistro_security").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/.opendistro_security"))).actionGet(); - tc.admin().cluster().createSnapshot(new CreateSnapshotRequest(".opendistro_security", "opendistro_security_1").indices(".opendistro_security").includeGlobalState(false).waitForCompletion(true)).actionGet(); - - tc.admin().cluster().putRepository(new PutRepositoryRequest("all").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/all"))).actionGet(); - tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("all", "all_1").indices("*").includeGlobalState(false).waitForCompletion(true)).actionGet(); + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest("vulcangov").type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/vulcangov")) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot( + new CreateSnapshotRequest("vulcangov", "vulcangov_1").indices("vulcangov") + .includeGlobalState(true) + .waitForCompletion(true) + ) + .actionGet(); + + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest(".opendistro_security").type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/.opendistro_security")) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot( + new CreateSnapshotRequest(".opendistro_security", "opendistro_security_1").indices(".opendistro_security") + .includeGlobalState(false) + .waitForCompletion(true) + ) + .actionGet(); + + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest("all").type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/all")) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot(new CreateSnapshotRequest("all", "all_1").indices("*").includeGlobalState(false).waitForCompletion(true)) + .actionGet(); } RestHelper rh = nonSslRestHelper(); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/vulcangov", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/vulcangov/vulcangov_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"include_global_state\": true, \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","", encodeBasicHeader("worf", "worf")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/vulcangov", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/vulcangov/vulcangov_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"include_global_state\": true, \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "", + encodeBasicHeader("worf", "worf") + ).getStatusCode() + ); // Try to restore vulcangov index as .opendistro_security index - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore .opendistro_security index. - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/.opendistro_security", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/.opendistro_security/opendistro_security_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/.opendistro_security", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/.opendistro_security/opendistro_security_1", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true", + "", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore .opendistro_security index as .opendistro_security_copy index - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true", + "{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore all indices. - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/all", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/all/all_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/all", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/all/all_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true", "", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); // Try to restore .opendistro_security index as .opendistro_security_copy index - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/all/all_1/_restore?wait_for_completion=true", + "{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore .opendistro_security index as .opendistro_security_copy index - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/all/all_1/_restore?wait_for_completion=true", + "{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore an unknown snapshot - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/unknown-snapshot/_restore?wait_for_completion=true", "", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - // Assert.assertEquals(HttpStatus.SC_FORBIDDEN, executePostRequest("_snapshot/all/unknown-snapshot/_restore?wait_for_completion=true","{ \"indices\": \"the-unknown-index\" }", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/all/unknown-snapshot/_restore?wait_for_completion=true", + "", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); + // Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + // executePostRequest("_snapshot/all/unknown-snapshot/_restore?wait_for_completion=true","{ \"indices\": \"the-unknown-index\" }", + // encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); } @Test public void testSnapshotCheckWritePrivileges() throws Exception { - final Settings settings = Settings.builder() - .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) - .build(); + final Settings settings = Settings.builder().putList("path.repo", repositoryPath.getRoot().getAbsolutePath()).build(); setup(settings, currentClusterConfig); try (Client tc = getClient()) { - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.admin().cluster().putRepository(new PutRepositoryRequest("vulcangov").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/vulcangov"))).actionGet(); - tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("vulcangov", "vulcangov_1").indices("vulcangov").includeGlobalState(true).waitForCompletion(true)).actionGet(); - - tc.admin().cluster().putRepository(new PutRepositoryRequest(".opendistro_security").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/.opendistro_security"))).actionGet(); - tc.admin().cluster().createSnapshot(new CreateSnapshotRequest(".opendistro_security", "opendistro_security_1").indices(".opendistro_security").includeGlobalState(false).waitForCompletion(true)).actionGet(); - - tc.admin().cluster().putRepository(new PutRepositoryRequest("all").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/all"))).actionGet(); - tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("all", "all_1").indices("*").includeGlobalState(false).waitForCompletion(true)).actionGet(); - - ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{"config","roles","rolesmapping","internalusers","actiongroups"})).actionGet(); + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest("vulcangov").type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/vulcangov")) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot( + new CreateSnapshotRequest("vulcangov", "vulcangov_1").indices("vulcangov") + .includeGlobalState(true) + .waitForCompletion(true) + ) + .actionGet(); + + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest(".opendistro_security").type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/.opendistro_security")) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot( + new CreateSnapshotRequest(".opendistro_security", "opendistro_security_1").indices(".opendistro_security") + .includeGlobalState(false) + .waitForCompletion(true) + ) + .actionGet(); + + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest("all").type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/all")) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot(new CreateSnapshotRequest("all", "all_1").indices("*").includeGlobalState(false).waitForCompletion(true)) + .actionGet(); + + ConfigUpdateResponse cur = tc.execute( + ConfigUpdateAction.INSTANCE, + new ConfigUpdateRequest(new String[] { "config", "roles", "rolesmapping", "internalusers", "actiongroups" }) + ).actionGet(); Assert.assertFalse(cur.hasFailures()); Assert.assertEquals(currentClusterConfig.getNodes(), cur.getNodes().size()); } RestHelper rh = nonSslRestHelper(); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/vulcangov", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/vulcangov/vulcangov_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"include_global_state\": true, \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","", encodeBasicHeader("worf", "worf")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/vulcangov", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/vulcangov/vulcangov_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"include_global_state\": true, \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "", + encodeBasicHeader("worf", "worf") + ).getStatusCode() + ); // Try to restore vulcangov index as .opendistro_security index - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore .opendistro_security index. - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/.opendistro_security", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/.opendistro_security/opendistro_security_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/.opendistro_security", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/.opendistro_security/opendistro_security_1", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true", + "", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore .opendistro_security index as .opendistro_security_copy index - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true","{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/.opendistro_security/opendistro_security_1/_restore?wait_for_completion=true", + "{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore all indices. - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/all", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/all/all_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/all", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/all/all_1", encodeBasicHeader("nagilum", "nagilum")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true", "", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode() + ); // Try to restore .opendistro_security index as .opendistro_security_copy index - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/all/all_1/_restore?wait_for_completion=true", + "{ \"indices\": \"vulcangov\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \".opendistro_security\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore .opendistro_security index as .opendistro_security_copy index - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/all/all_1/_restore?wait_for_completion=true", + "{ \"indices\": \".opendistro_security\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"opendistro_security_copy\" }", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Try to restore an unknown snapshot - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/all/unknown-snapshot/_restore?wait_for_completion=true", "", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/all/unknown-snapshot/_restore?wait_for_completion=true", + "", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); // Tests snapshot with write permissions (OK) - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_restore_1\" }", encodeBasicHeader("restoreuser", "restoreuser")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_restore_2a\" }", encodeBasicHeader("restoreuser", "restoreuser")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_restore_1\" }", + encodeBasicHeader("restoreuser", "restoreuser") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_restore_2a\" }", + encodeBasicHeader("restoreuser", "restoreuser") + ).getStatusCode() + ); // Test snapshot with write permissions (OK) - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_no_restore_1\" }", encodeBasicHeader("restoreuser", "restoreuser")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_no_restore_2\" }", encodeBasicHeader("restoreuser", "restoreuser")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_no_restore_3\" }", encodeBasicHeader("restoreuser", "restoreuser")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_no_restore_4\" }", encodeBasicHeader("restoreuser", "restoreuser")).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_no_restore_1\" }", + encodeBasicHeader("restoreuser", "restoreuser") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_no_restore_2\" }", + encodeBasicHeader("restoreuser", "restoreuser") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_no_restore_3\" }", + encodeBasicHeader("restoreuser", "restoreuser") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/vulcangov/vulcangov_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"$1_no_restore_4\" }", + encodeBasicHeader("restoreuser", "restoreuser") + ).getStatusCode() + ); } @Test public void testSnapshotRestore() throws Exception { - final Settings settings = Settings.builder() - .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) - .build(); + final Settings settings = Settings.builder().putList("path.repo", repositoryPath.getRoot().getAbsolutePath()).build(); - setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityActionGroups("action_groups_packaged.yml"), settings, true, currentClusterConfig); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setSecurityActionGroups("action_groups_packaged.yml"), + settings, + true, + currentClusterConfig + ); try (Client tc = getClient()) { - tc.index(new IndexRequest("testsnap1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("testsnap2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("testsnap3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("testsnap4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("testsnap5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("testsnap6").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.admin().cluster().putRepository(new PutRepositoryRequest("bckrepo").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/bckrepo"))).actionGet(); + tc.index(new IndexRequest("testsnap1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("testsnap2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("testsnap3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("testsnap4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("testsnap5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("testsnap6").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest("bckrepo").type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/bckrepo")) + ) + .actionGet(); } RestHelper rh = nonSslRestHelper(); - String putSnapshot = - "{"+ - "\"indices\": \"testsnap1\","+ - "\"ignore_unavailable\": false,"+ - "\"include_global_state\": false"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"/_restore?wait_for_completion=true&pretty","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - - putSnapshot = - "{"+ - "\"indices\": \".opendistro_security\","+ - "\"ignore_unavailable\": false,"+ - "\"include_global_state\": false"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"/_restore?wait_for_completion=true&pretty","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - - - putSnapshot = - "{"+ - "\"indices\": \"testsnap2\","+ - "\"ignore_unavailable\": false,"+ - "\"include_global_state\": true"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"/_restore?wait_for_completion=true&pretty","{ \"include_global_state\": true, \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); + String putSnapshot = "{" + + "\"indices\": \"testsnap1\"," + + "\"ignore_unavailable\": false," + + "\"include_global_state\": false" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePutRequest( + "_snapshot/bckrepo/" + putSnapshot.hashCode() + "?wait_for_completion=true&pretty", + putSnapshot, + encodeBasicHeader("snapresuser", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/bckrepo/" + putSnapshot.hashCode() + "/_restore?wait_for_completion=true&pretty", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", + encodeBasicHeader("snapresuser", "nagilum") + ).getStatusCode() + ); + + putSnapshot = "{" + + "\"indices\": \".opendistro_security\"," + + "\"ignore_unavailable\": false," + + "\"include_global_state\": false" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePutRequest( + "_snapshot/bckrepo/" + putSnapshot.hashCode() + "?wait_for_completion=true&pretty", + putSnapshot, + encodeBasicHeader("snapresuser", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/bckrepo/" + putSnapshot.hashCode() + "/_restore?wait_for_completion=true&pretty", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", + encodeBasicHeader("snapresuser", "nagilum") + ).getStatusCode() + ); + + putSnapshot = "{" + "\"indices\": \"testsnap2\"," + "\"ignore_unavailable\": false," + "\"include_global_state\": true" + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePutRequest( + "_snapshot/bckrepo/" + putSnapshot.hashCode() + "?wait_for_completion=true&pretty", + putSnapshot, + encodeBasicHeader("snapresuser", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/bckrepo/" + putSnapshot.hashCode() + "/_restore?wait_for_completion=true&pretty", + "{ \"include_global_state\": true, \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", + encodeBasicHeader("snapresuser", "nagilum") + ).getStatusCode() + ); } @Test @@ -297,33 +774,74 @@ public void testSnapshotRestoreSpecialIndicesPatterns() throws Exception { final List listOfIndexesToTest = Arrays.asList("foo", "bar", "baz"); - final Settings settings = Settings.builder() - .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) - .build(); + final Settings settings = Settings.builder().putList("path.repo", repositoryPath.getRoot().getAbsolutePath()).build(); - setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityActionGroups("action_groups_packaged.yml"), settings, true, currentClusterConfig); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setSecurityActionGroups("action_groups_packaged.yml"), + settings, + true, + currentClusterConfig + ); try (Client tc = getClient()) { for (String index : listOfIndexesToTest) { tc.admin().indices().create(new CreateIndexRequest(index)).actionGet(); - tc.index(new IndexRequest(index).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).id("document1").source("{ \"foo\": \"bar\" }", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest(index).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .id("document1") + .source("{ \"foo\": \"bar\" }", XContentType.JSON) + ).actionGet(); } } - try (Client tc = getClient()) { - tc.admin().cluster().putRepository(new PutRepositoryRequest("all").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/all"))).actionGet(); - tc.admin().cluster().createSnapshot(new CreateSnapshotRequest("all", "all_1").indices(listOfIndexesToTest).includeGlobalState(false).waitForCompletion(true)).actionGet(); - } + try (Client tc = getClient()) { + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest("all").type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/all")) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot( + new CreateSnapshotRequest("all", "all_1").indices(listOfIndexesToTest).includeGlobalState(false).waitForCompletion(true) + ) + .actionGet(); + } RestHelper rh = nonSslRestHelper(); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{\"indices\": \"b*,-bar\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"wild_first_restored_index_$1\"}", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{\"indices\": \"-bar,b*\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"neg_first_restored_index_$1\"}", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); - String wild_first_body = rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{\"indices\": \"b*,-bar\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"wild_first_restored_index_$1\"}", encodeBasicHeader("nagilum", "nagilum")).getBody(); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/all/all_1/_restore?wait_for_completion=true", + "{\"indices\": \"b*,-bar\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"wild_first_restored_index_$1\"}", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/all/all_1/_restore?wait_for_completion=true", + "{\"indices\": \"-bar,b*\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"neg_first_restored_index_$1\"}", + encodeBasicHeader("nagilum", "nagilum") + ).getStatusCode() + ); + String wild_first_body = rh.executePostRequest( + "_snapshot/all/all_1/_restore?wait_for_completion=true", + "{\"indices\": \"b*,-bar\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"wild_first_restored_index_$1\"}", + encodeBasicHeader("nagilum", "nagilum") + ).getBody(); assertThat(wild_first_body, not(containsString("wild_first_restored_index_foo"))); assertThat(wild_first_body, not(containsString("wild_first_restored_index_bar"))); assertThat(wild_first_body, containsString("wild_first_restored_index_baz")); - String neg_first_body = rh.executePostRequest("_snapshot/all/all_1/_restore?wait_for_completion=true","{\"indices\": \"-bar,b*\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"negate_first_restored_index_$1\"}", encodeBasicHeader("nagilum", "nagilum")).getBody(); + String neg_first_body = rh.executePostRequest( + "_snapshot/all/all_1/_restore?wait_for_completion=true", + "{\"indices\": \"-bar,b*\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"negate_first_restored_index_$1\"}", + encodeBasicHeader("nagilum", "nagilum") + ).getBody(); assertThat(neg_first_body, not(containsString("negate_first_restored_index_foo"))); assertThat(neg_first_body, not(containsString("negate_first_restored_index_bar"))); assertThat(neg_first_body, containsString("negate_first_restored_index_baz")); @@ -333,52 +851,105 @@ public void testSnapshotRestoreSpecialIndicesPatterns() throws Exception { public void testNoSnapshotRestore() throws Exception { final Settings settings = Settings.builder() - .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) - .put("plugins.security.enable_snapshot_restore_privilege", false) - .build(); - - setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityActionGroups("action_groups_packaged.yml"), settings, true, currentClusterConfig); + .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) + .put("plugins.security.enable_snapshot_restore_privilege", false) + .build(); + + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setSecurityActionGroups("action_groups_packaged.yml"), + settings, + true, + currentClusterConfig + ); try (Client tc = getClient()) { - tc.index(new IndexRequest("testsnap1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("testsnap2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("testsnap3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("testsnap4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("testsnap5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("testsnap6").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.admin().cluster().putRepository(new PutRepositoryRequest("bckrepo").type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/bckrepo"))).actionGet(); + tc.index(new IndexRequest("testsnap1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("testsnap2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("testsnap3").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("testsnap4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("testsnap5").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("testsnap6").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest("bckrepo").type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/bckrepo")) + ) + .actionGet(); } RestHelper rh = nonSslRestHelper(); - String putSnapshot = - "{"+ - "\"indices\": \"testsnap1\","+ - "\"ignore_unavailable\": false,"+ - "\"include_global_state\": false"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"/_restore?wait_for_completion=true&pretty","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - - putSnapshot = - "{"+ - "\"indices\": \".opendistro_security\","+ - "\"ignore_unavailable\": false,"+ - "\"include_global_state\": false"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"/_restore?wait_for_completion=true&pretty","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - - putSnapshot = - "{"+ - "\"indices\": \"testsnap2\","+ - "\"ignore_unavailable\": false,"+ - "\"include_global_state\": true"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"?wait_for_completion=true&pretty", putSnapshot, encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executePostRequest("_snapshot/bckrepo/"+putSnapshot.hashCode()+"/_restore?wait_for_completion=true&pretty","{ \"include_global_state\": true, \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", encodeBasicHeader("snapresuser", "nagilum")).getStatusCode()); + String putSnapshot = "{" + + "\"indices\": \"testsnap1\"," + + "\"ignore_unavailable\": false," + + "\"include_global_state\": false" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePutRequest( + "_snapshot/bckrepo/" + putSnapshot.hashCode() + "?wait_for_completion=true&pretty", + putSnapshot, + encodeBasicHeader("snapresuser", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/bckrepo/" + putSnapshot.hashCode() + "/_restore?wait_for_completion=true&pretty", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", + encodeBasicHeader("snapresuser", "nagilum") + ).getStatusCode() + ); + + putSnapshot = "{" + + "\"indices\": \".opendistro_security\"," + + "\"ignore_unavailable\": false," + + "\"include_global_state\": false" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePutRequest( + "_snapshot/bckrepo/" + putSnapshot.hashCode() + "?wait_for_completion=true&pretty", + putSnapshot, + encodeBasicHeader("snapresuser", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/bckrepo/" + putSnapshot.hashCode() + "/_restore?wait_for_completion=true&pretty", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", + encodeBasicHeader("snapresuser", "nagilum") + ).getStatusCode() + ); + + putSnapshot = "{" + "\"indices\": \"testsnap2\"," + "\"ignore_unavailable\": false," + "\"include_global_state\": true" + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executePutRequest( + "_snapshot/bckrepo/" + putSnapshot.hashCode() + "?wait_for_completion=true&pretty", + putSnapshot, + encodeBasicHeader("snapresuser", "nagilum") + ).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + rh.executePostRequest( + "_snapshot/bckrepo/" + putSnapshot.hashCode() + "/_restore?wait_for_completion=true&pretty", + "{ \"include_global_state\": true, \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_$1\" }", + encodeBasicHeader("snapresuser", "nagilum") + ).getStatusCode() + ); } } diff --git a/src/test/java/org/opensearch/security/SystemIntegratorsTests.java b/src/test/java/org/opensearch/security/SystemIntegratorsTests.java index 6ccc11104a..8d287dd8a3 100644 --- a/src/test/java/org/opensearch/security/SystemIntegratorsTests.java +++ b/src/test/java/org/opensearch/security/SystemIntegratorsTests.java @@ -45,9 +45,9 @@ public class SystemIntegratorsTests extends SingleClusterTest { public void testInjectedUserMalformed() throws Exception { final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) - .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") - .build(); + .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) + .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") + .build(); setup(settings, ClusterConfiguration.USERINJECTOR); @@ -56,31 +56,58 @@ public void testInjectedUserMalformed() throws Exception { HttpResponse resc; - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, null)); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, null) + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "|||")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "|||") + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "||127.0.0:80|")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "||127.0.0:80|") + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "username||ip|")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "username||ip|") + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "username||ip:port|")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "username||ip:port|") + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "username||ip:80|")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "username||ip:80|") + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "username||127.0.x:80|")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "username||127.0.x:80|") + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "username||127.0.0:80|key1,value1,key2")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "username||127.0.0:80|key1,value1,key2") + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "||127.0.0:80|key1,value1,key2,value2")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "||127.0.0:80|key1,value1,key2,value2") + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); } @@ -89,9 +116,9 @@ public void testInjectedUserMalformed() throws Exception { public void testInjectedUser() throws Exception { final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) - .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") - .build(); + .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) + .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") + .build(); setup(settings, ClusterConfiguration.USERINJECTOR); @@ -100,21 +127,30 @@ public void testInjectedUser() throws Exception { HttpResponse resc; - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "admin||127.0.0:80|")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "admin||127.0.0:80|") + ); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("User [name=admin, backend_roles=[], requestedTenant=null]")); Assert.assertTrue(resc.getBody().contains("\"remote_address\":\"127.0.0.0:80\"")); Assert.assertTrue(resc.getBody().contains("\"backend_roles\":[]")); Assert.assertTrue(resc.getBody().contains("\"custom_attribute_names\":[]")); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "admin|role1|127.0.0:80|key1,value1")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "admin|role1|127.0.0:80|key1,value1") + ); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("User [name=admin, backend_roles=[role1], requestedTenant=null]")); Assert.assertTrue(resc.getBody().contains("\"remote_address\":\"127.0.0.0:80\"")); Assert.assertTrue(resc.getBody().contains("\"backend_roles\":[\"role1\"]")); Assert.assertTrue(resc.getBody().contains("\"custom_attribute_names\":[\"key1\"]")); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "admin|role1,role2||key1,value1")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "admin|role1,role2||key1,value1") + ); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("User [name=admin, backend_roles=[role1, role2], requestedTenant=null]")); // remote IP is assigned by XFFResolver @@ -122,7 +158,10 @@ public void testInjectedUser() throws Exception { Assert.assertTrue(resc.getBody().contains("\"backend_roles\":[\"role1\",\"role2\"]")); Assert.assertTrue(resc.getBody().contains("\"custom_attribute_names\":[\"key1\"]")); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "admin|role1,role2|8.8.8.8:8|key1,value1,key2,value2")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "admin|role1,role2|8.8.8.8:8|key1,value1,key2,value2") + ); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("User [name=admin, backend_roles=[role1, role2], requestedTenant=null]")); // remote IP is assigned by XFFResolver @@ -130,7 +169,10 @@ public void testInjectedUser() throws Exception { Assert.assertTrue(resc.getBody().contains("\"backend_roles\":[\"role1\",\"role2\"]")); Assert.assertTrue(resc.getBody().contains("\"custom_attribute_names\":[\"key1\",\"key2\"]")); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "nagilum|role1,role2|8.8.8.8:8|key1,value1,key2,value2")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "nagilum|role1,role2|8.8.8.8:8|key1,value1,key2,value2") + ); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("User [name=nagilum, backend_roles=[role1, role2], requestedTenant=null]")); // remote IP is assigned by XFFResolver @@ -140,7 +182,10 @@ public void testInjectedUser() throws Exception { Assert.assertTrue(resc.getBody().contains("\"roles\":[\"opendistro_security_all_access\"")); Assert.assertTrue(resc.getBody().contains("\"custom_attribute_names\":[\"key1\",\"key2\"]")); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "myuser|role1,vulcanadmin|8.8.8.8:8|key1,value1,key2,value2")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "myuser|role1,vulcanadmin|8.8.8.8:8|key1,value1,key2,value2") + ); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("User [name=myuser, backend_roles=[role1, vulcanadmin], requestedTenant=null]")); // remote IP is assigned by XFFResolver @@ -151,7 +196,13 @@ public void testInjectedUser() throws Exception { Assert.assertTrue(resc.getBody().contains("\"custom_attribute_names\":[\"key1\",\"key2\"]")); // add requested tenant - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "myuser|role1,vulcanadmin|8.8.8.8:8|key1,value1,key2,value2|")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader( + ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, + "myuser|role1,vulcanadmin|8.8.8.8:8|key1,value1,key2,value2|" + ) + ); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("User [name=myuser, backend_roles=[role1, vulcanadmin], requestedTenant=null]")); // remote IP is assigned by XFFResolver @@ -161,7 +212,13 @@ public void testInjectedUser() throws Exception { Assert.assertTrue(resc.getBody(), resc.getBody().contains("\"roles\":[\"public\",\"role_vulcans_admin\"]")); Assert.assertTrue(resc.getBody().contains("\"custom_attribute_names\":[\"key1\",\"key2\"]")); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "myuser|role1,vulcanadmin|8.8.8.8:8|key1,value1,key2,value2|mytenant")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader( + ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, + "myuser|role1,vulcanadmin|8.8.8.8:8|key1,value1,key2,value2|mytenant" + ) + ); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); Assert.assertTrue(resc.getBody().contains("User [name=myuser, backend_roles=[role1, vulcanadmin], requestedTenant=mytenant]")); // remote IP is assigned by XFFResolver @@ -171,24 +228,29 @@ public void testInjectedUser() throws Exception { Assert.assertTrue(resc.getBody().contains("\"roles\":[\"public\",\"role_vulcans_admin\"]")); Assert.assertTrue(resc.getBody().contains("\"custom_attribute_names\":[\"key1\",\"key2\"]")); - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "myuser|role1,vulcanadmin|8.8.8.8:8||mytenant with whitespace")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader( + ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, + "myuser|role1,vulcanadmin|8.8.8.8:8||mytenant with whitespace" + ) + ); Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - Assert.assertTrue(resc.getBody().contains("User [name=myuser, backend_roles=[role1, vulcanadmin], requestedTenant=mytenant with whitespace]")); + Assert.assertTrue( + resc.getBody().contains("User [name=myuser, backend_roles=[role1, vulcanadmin], requestedTenant=mytenant with whitespace]") + ); // remote IP is assigned by XFFResolver Assert.assertTrue(resc.getBody().contains("\"remote_address\":\"8.8.8.8:8\"")); Assert.assertTrue(resc.getBody().contains("\"backend_roles\":[\"role1\",\"vulcanadmin\"]")); // mapped by backend role "twitter" Assert.assertTrue(resc.getBody().contains("\"roles\":[\"public\",\"role_vulcans_admin\"]")); - } @Test public void testInjectedUserDisabled() throws Exception { - final Settings settings = Settings.builder() - .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") - .build(); + final Settings settings = Settings.builder().put("http.type", "org.opensearch.security.http.UserInjectingServerTransport").build(); setup(settings, ClusterConfiguration.USERINJECTOR); @@ -197,46 +259,61 @@ public void testInjectedUserDisabled() throws Exception { HttpResponse resc; - resc = rh.executeGetRequest("_opendistro/_security/authinfo", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "admin|role1|127.0.0:80|key1,value1")); + resc = rh.executeGetRequest( + "_opendistro/_security/authinfo", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "admin|role1|127.0.0:80|key1,value1") + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, resc.getStatusCode()); } - @Test - public void testInjectedAdminUser() throws Exception { - - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) - .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_ADMIN_USER_ENABLED, true) - .putList(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, Lists.newArrayList("CN=kirk,OU=client,O=client,L=Test,C=DE","injectedadmin")) - .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") - .build(); + @Test + public void testInjectedAdminUser() throws Exception { - setup(settings, ClusterConfiguration.USERINJECTOR); + final Settings settings = Settings.builder() + .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) + .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_ADMIN_USER_ENABLED, true) + .putList( + ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, + Lists.newArrayList("CN=kirk,OU=client,O=client,L=Test,C=DE", "injectedadmin") + ) + .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") + .build(); - final RestHelper rh = nonSslRestHelper(); - HttpResponse resc; + setup(settings, ClusterConfiguration.USERINJECTOR); - // injected user is admin, access to Security index must be allowed - resc = rh.executeGetRequest(".opendistro_security/_search?pretty", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "injectedadmin|role1|127.0.0:80|key1,value1")); - Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); - Assert.assertTrue(resc.getBody().contains("\"_id\" : \"config\"")); - Assert.assertTrue(resc.getBody().contains("\"_id\" : \"roles\"")); - Assert.assertTrue(resc.getBody().contains("\"_id\" : \"internalusers\"")); - Assert.assertTrue(resc.getBody().contains("\"total\" : 5")); + final RestHelper rh = nonSslRestHelper(); + HttpResponse resc; - resc = rh.executeGetRequest(".opendistro_security/_search?pretty", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "wrongadmin|role1|127.0.0:80|key1,value1")); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); + // injected user is admin, access to Security index must be allowed + resc = rh.executeGetRequest( + ".opendistro_security/_search?pretty", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "injectedadmin|role1|127.0.0:80|key1,value1") + ); + Assert.assertEquals(HttpStatus.SC_OK, resc.getStatusCode()); + Assert.assertTrue(resc.getBody().contains("\"_id\" : \"config\"")); + Assert.assertTrue(resc.getBody().contains("\"_id\" : \"roles\"")); + Assert.assertTrue(resc.getBody().contains("\"_id\" : \"internalusers\"")); + Assert.assertTrue(resc.getBody().contains("\"total\" : 5")); + + resc = rh.executeGetRequest( + ".opendistro_security/_search?pretty", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "wrongadmin|role1|127.0.0:80|key1,value1") + ); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); - } + } @Test public void testInjectedAdminUserAdminInjectionDisabled() throws Exception { final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) - .putList(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, Lists.newArrayList("CN=kirk,OU=client,O=client,L=Test,C=DE","injectedadmin")) - .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") - .build(); + .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) + .putList( + ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, + Lists.newArrayList("CN=kirk,OU=client,O=client,L=Test,C=DE", "injectedadmin") + ) + .put("http.type", "org.opensearch.security.http.UserInjectingServerTransport") + .build(); setup(settings, ClusterConfiguration.USERINJECTOR); @@ -244,7 +321,10 @@ public void testInjectedAdminUserAdminInjectionDisabled() throws Exception { HttpResponse resc; // injected user is admin, access to Security index must be allowed - resc = rh.executeGetRequest(".opendistro_security/_search?pretty", new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "injectedadmin|role1|127.0.0:80|key1,value1")); + resc = rh.executeGetRequest( + ".opendistro_security/_search?pretty", + new BasicHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "injectedadmin|role1|127.0.0:80|key1,value1") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, resc.getStatusCode()); Assert.assertFalse(resc.getBody().contains("\"_id\" : \"config\"")); Assert.assertFalse(resc.getBody().contains("\"_id\" : \"roles\"")); diff --git a/src/test/java/org/opensearch/security/TaskTests.java b/src/test/java/org/opensearch/security/TaskTests.java index 3a86b7e2bf..784f5c7418 100644 --- a/src/test/java/org/opensearch/security/TaskTests.java +++ b/src/test/java/org/opensearch/security/TaskTests.java @@ -37,9 +37,14 @@ public void testXOpaqueIdHeader() throws Exception { RestHelper rh = nonSslRestHelper(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_tasks?group_by=parents&pretty" - , encodeBasicHeader("nagilum", "nagilum") - , new BasicHeader(Task.X_OPAQUE_ID, "myOpaqueId12"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + "_tasks?group_by=parents&pretty", + encodeBasicHeader("nagilum", "nagilum"), + new BasicHeader(Task.X_OPAQUE_ID, "myOpaqueId12") + )).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().split("X-Opaque-Id").length > 2); Assert.assertTrue(!res.getBody().contains("failures")); diff --git a/src/test/java/org/opensearch/security/TracingTests.java b/src/test/java/org/opensearch/security/TracingTests.java index 10372cf73b..2fefd33155 100644 --- a/src/test/java/org/opensearch/security/TracingTests.java +++ b/src/test/java/org/opensearch/security/TracingTests.java @@ -56,10 +56,22 @@ public void testAdvancedMapping() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY, true, ClusterConfiguration.DEFAULT); try (Client tc = getClient()) { - tc.admin().indices().create(new CreateIndexRequest("myindex1").mapping(FileHelper.loadFile("mapping1.json"), XContentType.JSON)).actionGet(); - tc.admin().indices().create(new CreateIndexRequest("myindex2").mapping(FileHelper.loadFile("mapping2.json"), XContentType.JSON)).actionGet(); - tc.admin().indices().create(new CreateIndexRequest("myindex3").mapping(FileHelper.loadFile("mapping3.json"), XContentType.JSON)).actionGet(); - tc.admin().indices().create(new CreateIndexRequest("myindex4").mapping(FileHelper.loadFile("mapping4.json"), XContentType.JSON)).actionGet(); + tc.admin() + .indices() + .create(new CreateIndexRequest("myindex1").mapping(FileHelper.loadFile("mapping1.json"), XContentType.JSON)) + .actionGet(); + tc.admin() + .indices() + .create(new CreateIndexRequest("myindex2").mapping(FileHelper.loadFile("mapping2.json"), XContentType.JSON)) + .actionGet(); + tc.admin() + .indices() + .create(new CreateIndexRequest("myindex3").mapping(FileHelper.loadFile("mapping3.json"), XContentType.JSON)) + .actionGet(); + tc.admin() + .indices() + .create(new CreateIndexRequest("myindex4").mapping(FileHelper.loadFile("mapping4.json"), XContentType.JSON)) + .actionGet(); } RestHelper rh = nonSslRestHelper(); @@ -94,69 +106,154 @@ public void testHTTPTraceNoSource() throws Exception { tc.admin().indices().create(new CreateIndexRequest("test")).actionGet(); tc.admin().indices().create(new CreateIndexRequest("u")).actionGet(); - tc.admin().indices().putMapping(new PutMappingRequest("a") - .source("_source","enabled=false","content","store=true,type=text","field1","store=true,type=text", "field2","store=true,type=text", "a","store=true,type=text", "b","store=true,type=text", "my.nested.field","store=true,type=text") - ).actionGet(); - - tc.admin().indices().putMapping(new PutMappingRequest("c") - .source("_source","enabled=false","content","store=true,type=text","field1","store=true,type=text", "field2","store=true,type=text", "a","store=true,type=text", "b","store=true,type=text", "my.nested.field","store=true,type=text") - ).actionGet(); - - tc.admin().indices().putMapping(new PutMappingRequest("test") - .source("_source","enabled=false","content","store=true,type=text","field1","store=true,type=text", "field2","store=true,type=text", "a","store=true,type=text", "b","store=true,type=text", "my.nested.field","store=true,type=text") - ).actionGet(); - - tc.admin().indices().putMapping(new PutMappingRequest("u") - .source("_source","enabled=false","content","store=true,type=text","field1","store=true,type=text", "field2","store=true,type=text", "a","store=true,type=text", "b","store=true,type=text", "my.nested.field","store=true,type=text") - ).actionGet(); - - for(int i=0; i<50;i++) { - tc.index(new IndexRequest("a").id(i+"").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":"+i+"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("c").id(i+"").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":"+i+"}", XContentType.JSON)).actionGet(); + tc.admin() + .indices() + .putMapping( + new PutMappingRequest("a").source( + "_source", + "enabled=false", + "content", + "store=true,type=text", + "field1", + "store=true,type=text", + "field2", + "store=true,type=text", + "a", + "store=true,type=text", + "b", + "store=true,type=text", + "my.nested.field", + "store=true,type=text" + ) + ) + .actionGet(); + + tc.admin() + .indices() + .putMapping( + new PutMappingRequest("c").source( + "_source", + "enabled=false", + "content", + "store=true,type=text", + "field1", + "store=true,type=text", + "field2", + "store=true,type=text", + "a", + "store=true,type=text", + "b", + "store=true,type=text", + "my.nested.field", + "store=true,type=text" + ) + ) + .actionGet(); + + tc.admin() + .indices() + .putMapping( + new PutMappingRequest("test").source( + "_source", + "enabled=false", + "content", + "store=true,type=text", + "field1", + "store=true,type=text", + "field2", + "store=true,type=text", + "a", + "store=true,type=text", + "b", + "store=true,type=text", + "my.nested.field", + "store=true,type=text" + ) + ) + .actionGet(); + + tc.admin() + .indices() + .putMapping( + new PutMappingRequest("u").source( + "_source", + "enabled=false", + "content", + "store=true,type=text", + "field1", + "store=true,type=text", + "field2", + "store=true,type=text", + "a", + "store=true,type=text", + "b", + "store=true,type=text", + "my.nested.field", + "store=true,type=text" + ) + ) + .actionGet(); + + for (int i = 0; i < 50; i++) { + tc.index( + new IndexRequest("a").id(i + "") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":" + i + "}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("c").id(i + "") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":" + i + "}", XContentType.JSON) + ).actionGet(); } } - //setup complex mapping with parent child and nested fields - + // setup complex mapping with parent child and nested fields RestHelper rh = nonSslRestHelper(); System.out.println("############ check shards"); System.out.println(rh.executeGetRequest("_cat/shards?v", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ _bulk"); - String bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator(); + String bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field1\" : \"value1\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator(); System.out.println(rh.executePostRequest("_bulk?refresh=true", bulkBody, encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ _bulk"); - bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator(); + bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field1\" : \"value1\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator(); System.out.println(rh.executePostRequest("_bulk?refresh=true", bulkBody, encodeBasicHeader("nagilum", "nagilum"))); - System.out.println("############ cat indices"); - //cluster:monitor/state - //cluster:monitor/health - //indices:monitor/stats + // cluster:monitor/state + // cluster:monitor/health + // indices:monitor/stats System.out.println(rh.executeGetRequest("_cat/indices", encodeBasicHeader("nagilum", "nagilum"))); - System.out.println("############ _search"); - //indices:data/read/search + // indices:data/read/search System.out.println(rh.executeGetRequest("_search", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ get 1"); - //indices:data/read/get + // indices:data/read/get System.out.println(rh.executeGetRequest("a/b/1", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ get 5"); System.out.println(rh.executeGetRequest("a/b/5", encodeBasicHeader("nagilum", "nagilum"))); @@ -164,71 +261,68 @@ public void testHTTPTraceNoSource() throws Exception { System.out.println(rh.executeGetRequest("a/b/17", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ index (+create index)"); - //indices:data/write/index - //indices:data/write/bulk - //indices:admin/create - //indices:data/write/bulk[s] - System.out.println(rh.executePostRequest("u/b/1?refresh=true", "{}",encodeBasicHeader("nagilum", "nagilum"))); + // indices:data/write/index + // indices:data/write/bulk + // indices:admin/create + // indices:data/write/bulk[s] + System.out.println(rh.executePostRequest("u/b/1?refresh=true", "{}", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ index only"); - //indices:data/write/index - //indices:data/write/bulk - //indices:admin/create - //indices:data/write/bulk[s] - System.out.println(rh.executePostRequest("u/b/2?refresh=true", "{}",encodeBasicHeader("nagilum", "nagilum"))); - + // indices:data/write/index + // indices:data/write/bulk + // indices:admin/create + // indices:data/write/bulk[s] + System.out.println(rh.executePostRequest("u/b/2?refresh=true", "{}", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ delete"); - //indices:data/write/index - //indices:data/write/bulk - //indices:admin/create - //indices:data/write/bulk[s] - System.out.println(rh.executeDeleteRequest("u/b/2?refresh=true",encodeBasicHeader("nagilum", "nagilum"))); + // indices:data/write/index + // indices:data/write/bulk + // indices:admin/create + // indices:data/write/bulk[s] + System.out.println(rh.executeDeleteRequest("u/b/2?refresh=true", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ msearch"); - String msearchBody = - "{\"index\":\"a\", \"type\":\"b\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"a\", \"type\":\"b\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"public\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); - + String msearchBody = "{\"index\":\"a\", \"type\":\"b\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"a\", \"type\":\"b\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"public\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); System.out.println(rh.executePostRequest("_msearch", msearchBody, encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ mget"); - String mgetBody = "{"+ - "\"docs\" : ["+ - "{"+ - "\"_index\" : \"a\","+ - "\"_id\" : \"1\""+ - " },"+ - " {"+ - "\"_index\" : \"a\","+ - " \"_id\" : \"12\""+ - "},"+ - " {"+ - "\"_index\" : \"a\","+ - " \"_id\" : \"13\""+ - "},"+" {"+ - "\"_index\" : \"a\","+ - " \"_id\" : \"14\""+ - "}"+ - "]"+ - "}"; + String mgetBody = "{" + + "\"docs\" : [" + + "{" + + "\"_index\" : \"a\"," + + "\"_id\" : \"1\"" + + " }," + + " {" + + "\"_index\" : \"a\"," + + " \"_id\" : \"12\"" + + "}," + + " {" + + "\"_index\" : \"a\"," + + " \"_id\" : \"13\"" + + "}," + + " {" + + "\"_index\" : \"a\"," + + " \"_id\" : \"14\"" + + "}" + + "]" + + "}"; System.out.println(rh.executePostRequest("_mget?refresh=true", mgetBody, encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ delete by query"); - String dbqBody = "{"+ - ""+ - " \"query\": { "+ - " \"match\": {"+ - " \"content\": 12"+ - " }"+ - " }"+ - "}"; + String dbqBody = "{" + "" + " \"query\": { " + " \"match\": {" + " \"content\": 12" + " }" + " }" + "}"; System.out.println(rh.executePostRequest("a/b/_delete_by_query", dbqBody, encodeBasicHeader("nagilum", "nagilum"))); @@ -237,28 +331,56 @@ public void testHTTPTraceNoSource() throws Exception { @Test public void testHTTPSingle() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".worf", "knuddel","nonexists") + final Settings settings = Settings.builder() + .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".worf", "knuddel", "nonexists") .build(); - setup(settings); - final RestHelper rh = nonSslRestHelper(); + setup(settings); + final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("starfleet","starfleet_academy","starfleet_library").alias("sf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire","vulcangov").alias("nonsf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))).actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add().indices("starfleet", "starfleet_academy", "starfleet_library").alias("sf") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire", "vulcangov").alias("nonsf")) + ) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))) + .actionGet(); } @@ -275,16 +397,24 @@ public void testHTTPSingle() throws Exception { System.out.println("########end pause2"); System.out.println("############ _bulk"); - String bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"myindex\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"myindex\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value1\" }" +System.lineSeparator(); + String bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field1\" : \"value1\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"myindex\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field1\" : \"value1\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"myindex\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field1\" : \"value1\" }" + + System.lineSeparator(); System.out.println(rh.executePostRequest("_bulk?refresh=true", bulkBody, encodeBasicHeader("nagilum", "nagilum")).getBody()); System.out.println("############ _end"); @@ -293,33 +423,42 @@ public void testHTTPSingle() throws Exception { @Test public void testSearchScroll() throws Exception { - final Settings settings = Settings.builder() - .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".worf", "knuddel","nonexists") + final Settings settings = Settings.builder() + .putList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".worf", "knuddel", "nonexists") .build(); - setup(settings); - final RestHelper rh = nonSslRestHelper(); + setup(settings); + final RestHelper rh = nonSslRestHelper(); try (Client tc = getClient()) { - for(int i=0; i<3; i++) - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + for (int i = 0; i < 3; i++) + tc.index( + new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); } - System.out.println("########search"); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executeGetRequest("vulcangov/_search?scroll=1m&pretty=true", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("vulcangov/_search?scroll=1m&pretty=true", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode() + ); System.out.println(res.getBody()); int start = res.getBody().indexOf("_scroll_id") + 15; - String scrollid = res.getBody().substring(start, res.getBody().indexOf("\"", start+1)); + String scrollid = res.getBody().substring(start, res.getBody().indexOf("\"", start + 1)); System.out.println(scrollid); System.out.println("########search scroll"); - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executePostRequest("/_search/scroll?pretty=true", "{\"scroll_id\" : \""+scrollid+"\"}", encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); - + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest( + "/_search/scroll?pretty=true", + "{\"scroll_id\" : \"" + scrollid + "\"}", + encodeBasicHeader("nagilum", "nagilum") + )).getStatusCode() + ); System.out.println("########search done"); - } @Test @@ -329,53 +468,64 @@ public void testHTTPTrace() throws Exception { try (Client tc = getClient()) { - for(int i=0; i<50;i++) { - tc.index(new IndexRequest("a").id(i+"").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":"+i+"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("c").id(i+"").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":"+i+"}", XContentType.JSON)).actionGet(); + for (int i = 0; i < 50; i++) { + tc.index( + new IndexRequest("a").id(i + "") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":" + i + "}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("c").id(i + "") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"content\":" + i + "}", XContentType.JSON) + ).actionGet(); } } - - - RestHelper rh = nonSslRestHelper(); System.out.println("############ check shards"); System.out.println(rh.executeGetRequest("_cat/shards?v", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ _bulk"); - String bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator(); + String bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field1\" : \"value1\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator(); System.out.println(rh.executePostRequest("_bulk?refresh=true", bulkBody, encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ _bulk"); - bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }"+System.lineSeparator(); + bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field1\" : \"value1\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"2\" } }" + + System.lineSeparator(); System.out.println(rh.executePostRequest("_bulk?refresh=true", bulkBody, encodeBasicHeader("nagilum", "nagilum"))); - System.out.println("############ cat indices"); - //cluster:monitor/state - //cluster:monitor/health - //indices:monitor/stats + // cluster:monitor/state + // cluster:monitor/health + // indices:monitor/stats System.out.println(rh.executeGetRequest("_cat/indices", encodeBasicHeader("nagilum", "nagilum"))); - System.out.println("############ _search"); - //indices:data/read/search + // indices:data/read/search System.out.println(rh.executeGetRequest("_search", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ get 1"); - //indices:data/read/get + // indices:data/read/get System.out.println(rh.executeGetRequest("a/b/1", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ get 5"); System.out.println(rh.executeGetRequest("a/b/5", encodeBasicHeader("nagilum", "nagilum"))); @@ -383,106 +533,107 @@ public void testHTTPTrace() throws Exception { System.out.println(rh.executeGetRequest("a/b/17", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ index (+create index)"); - //indices:data/write/index - //indices:data/write/bulk - //indices:admin/create - //indices:data/write/bulk[s] - System.out.println(rh.executePostRequest("u/b/1?refresh=true", "{}",encodeBasicHeader("nagilum", "nagilum"))); + // indices:data/write/index + // indices:data/write/bulk + // indices:admin/create + // indices:data/write/bulk[s] + System.out.println(rh.executePostRequest("u/b/1?refresh=true", "{}", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ index only"); - //indices:data/write/index - //indices:data/write/bulk - //indices:admin/create - //indices:data/write/bulk[s] - System.out.println(rh.executePostRequest("u/b/2?refresh=true", "{}",encodeBasicHeader("nagilum", "nagilum"))); + // indices:data/write/index + // indices:data/write/bulk + // indices:admin/create + // indices:data/write/bulk[s] + System.out.println(rh.executePostRequest("u/b/2?refresh=true", "{}", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ update"); - //indices:data/write/index - //indices:data/write/bulk - //indices:admin/create - //indices:data/write/bulk[s] - System.out.println(rh.executePostRequest("u/b/2/_update?refresh=true", "{\"doc\" : {\"a\":1}}",encodeBasicHeader("nagilum", "nagilum"))); + // indices:data/write/index + // indices:data/write/bulk + // indices:admin/create + // indices:data/write/bulk[s] + System.out.println( + rh.executePostRequest("u/b/2/_update?refresh=true", "{\"doc\" : {\"a\":1}}", encodeBasicHeader("nagilum", "nagilum")) + ); System.out.println("############ update2"); - //indices:data/write/index - //indices:data/write/bulk - //indices:admin/create - //indices:data/write/bulk[s] - System.out.println(rh.executePostRequest("u/b/2/_update?refresh=true", "{\"doc\" : {\"a\":44, \"b\":55}}",encodeBasicHeader("nagilum", "nagilum"))); + // indices:data/write/index + // indices:data/write/bulk + // indices:admin/create + // indices:data/write/bulk[s] + System.out.println( + rh.executePostRequest("u/b/2/_update?refresh=true", "{\"doc\" : {\"a\":44, \"b\":55}}", encodeBasicHeader("nagilum", "nagilum")) + ); System.out.println("############ update3"); - //indices:data/write/index - //indices:data/write/bulk - //indices:admin/create - //indices:data/write/bulk[s] - System.out.println(rh.executePostRequest("u/b/2/_update?refresh=true", "{\"doc\" : {\"b\":66}}",encodeBasicHeader("nagilum", "nagilum"))); - + // indices:data/write/index + // indices:data/write/bulk + // indices:admin/create + // indices:data/write/bulk[s] + System.out.println( + rh.executePostRequest("u/b/2/_update?refresh=true", "{\"doc\" : {\"b\":66}}", encodeBasicHeader("nagilum", "nagilum")) + ); System.out.println("############ delete"); - //indices:data/write/index - //indices:data/write/bulk - //indices:admin/create - //indices:data/write/bulk[s] - System.out.println(rh.executeDeleteRequest("u/b/2?refresh=true",encodeBasicHeader("nagilum", "nagilum"))); + // indices:data/write/index + // indices:data/write/bulk + // indices:admin/create + // indices:data/write/bulk[s] + System.out.println(rh.executeDeleteRequest("u/b/2?refresh=true", encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ reindex"); - String reindex = - "{"+ - " \"source\": {"+ - " \"index\": \"a\""+ - " },"+ - " \"dest\": {"+ - " \"index\": \"new_a\""+ - " }"+ - "}"; + String reindex = "{" + + " \"source\": {" + + " \"index\": \"a\"" + + " }," + + " \"dest\": {" + + " \"index\": \"new_a\"" + + " }" + + "}"; System.out.println(rh.executePostRequest("_reindex", reindex, encodeBasicHeader("nagilum", "nagilum"))); - System.out.println("############ msearch"); - String msearchBody = - "{\"index\":\"a\", \"type\":\"b\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"a\", \"type\":\"b\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"public\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); - + String msearchBody = "{\"index\":\"a\", \"type\":\"b\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"a\", \"type\":\"b\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"public\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); System.out.println(rh.executePostRequest("_msearch", msearchBody, encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ mget"); - String mgetBody = "{"+ - "\"docs\" : ["+ - "{"+ - "\"_index\" : \"a\","+ - "\"_id\" : \"1\""+ - " },"+ - " {"+ - "\"_index\" : \"a\","+ - " \"_id\" : \"12\""+ - "},"+ - " {"+ - "\"_index\" : \"a\","+ - " \"_id\" : \"13\""+ - "},"+" {"+ - "\"_index\" : \"a\","+ - " \"_id\" : \"14\""+ - "}"+ - "]"+ - "}"; + String mgetBody = "{" + + "\"docs\" : [" + + "{" + + "\"_index\" : \"a\"," + + "\"_id\" : \"1\"" + + " }," + + " {" + + "\"_index\" : \"a\"," + + " \"_id\" : \"12\"" + + "}," + + " {" + + "\"_index\" : \"a\"," + + " \"_id\" : \"13\"" + + "}," + + " {" + + "\"_index\" : \"a\"," + + " \"_id\" : \"14\"" + + "}" + + "]" + + "}"; System.out.println(rh.executePostRequest("_mget?refresh=true", mgetBody, encodeBasicHeader("nagilum", "nagilum"))); System.out.println("############ delete by query"); - String dbqBody = "{"+ - ""+ - " \"query\": { "+ - " \"match\": {"+ - " \"content\": 12"+ - " }"+ - " }"+ - "}"; + String dbqBody = "{" + "" + " \"query\": { " + " \"match\": {" + " \"content\": 12" + " }" + " }" + "}"; System.out.println(rh.executePostRequest("a/b/_delete_by_query", dbqBody, encodeBasicHeader("nagilum", "nagilum"))); diff --git a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java index fc61a3f127..8b5259eb81 100644 --- a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java @@ -55,52 +55,69 @@ public UserInjectorPlugin(final Settings settings, final Path configPath) { } @Override - public Collection createComponents(Client client, ClusterService clusterService, ThreadPool threadPool, - ResourceWatcherService resourceWatcherService, ScriptService scriptService, - NamedXContentRegistry xContentRegistry, Environment environment, - NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry, - IndexNameExpressionResolver indexNameExpressionResolver, - Supplier repositoriesServiceSupplier) { - if(injectedUser != null) - threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, injectedUser); + public Collection createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { + if (injectedUser != null) threadPool.getThreadContext() + .putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, injectedUser); return new ArrayList<>(); } } @Test public void testSecurityUserInjection() throws Exception { - final Settings clusterNodeSettings = Settings.builder() - .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) - .build(); + final Settings clusterNodeSettings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true).build(); setup(clusterNodeSettings, new DynamicSecurityConfig().setSecurityRolesMapping("roles_transport_inject_user.yml"), Settings.EMPTY); final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) - .put(minimumSecuritySettings(Settings.EMPTY).get(0)) - .put("cluster.name", clusterInfo.clustername) - .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") - .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") - .put("path.home", "./target") - .put("node.name", "testclient") - .put("discovery.initial_state_timeout", "8s") - .put("plugins.security.allow_default_init_securityindex", "true") - .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) - .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) - .build(); - + .put(minimumSecuritySettings(Settings.EMPTY).get(0)) + .put("cluster.name", clusterInfo.clustername) + .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") + .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") + .put("path.home", "./target") + .put("node.name", "testclient") + .put("discovery.initial_state_timeout", "8s") + .put("plugins.security.allow_default_init_securityindex", "true") + .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) + .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) + .build(); // 1. without user injection - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, - OpenSearchSecurityPlugin.class, UserInjectorPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + UserInjectorPlugin.class + ).start() + ) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-1")).actionGet(); Assert.assertTrue(cir.isAcknowledged()); } - // 2. with invalid backend roles UserInjectorPlugin.injectedUser = "ttt|kkk"; Exception exception = null; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, - OpenSearchSecurityPlugin.class, UserInjectorPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + UserInjectorPlugin.class + ).start() + ) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-2")).actionGet(); Assert.fail("Expecting exception"); @@ -111,11 +128,17 @@ public void testSecurityUserInjection() throws Exception { Assert.assertTrue(exception.getMessage().toString().contains("no permissions for [indices:admin/create]")); } - // 3. with valid backend roles for injected user UserInjectorPlugin.injectedUser = "injectedadmin|injecttest"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, - OpenSearchSecurityPlugin.class, UserInjectorPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + UserInjectorPlugin.class + ).start() + ) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-2")).actionGet(); Assert.assertTrue(cir.isAcknowledged()); @@ -125,25 +148,32 @@ public void testSecurityUserInjection() throws Exception { @Test public void testSecurityUserInjectionWithConfigDisabled() throws Exception { final Settings clusterNodeSettings = Settings.builder() - .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false) - .build(); + .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false) + .build(); setup(clusterNodeSettings, new DynamicSecurityConfig().setSecurityRolesMapping("roles_transport_inject_user.yml"), Settings.EMPTY); final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) - .put(minimumSecuritySettings(Settings.EMPTY).get(0)) - .put("cluster.name", clusterInfo.clustername) - .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") - .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") - .put("path.home", "./target") - .put("node.name", "testclient") - .put("discovery.initial_state_timeout", "8s") - .put("plugins.security.allow_default_init_securityindex", "true") - .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false) - .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) - .build(); + .put(minimumSecuritySettings(Settings.EMPTY).get(0)) + .put("cluster.name", clusterInfo.clustername) + .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") + .put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") + .put("path.home", "./target") + .put("node.name", "testclient") + .put("discovery.initial_state_timeout", "8s") + .put("plugins.security.allow_default_init_securityindex", "true") + .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false) + .putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) + .build(); // 1. without user injection - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, - OpenSearchSecurityPlugin.class, UserInjectorPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + UserInjectorPlugin.class + ).start() + ) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-1")).actionGet(); Assert.assertTrue(cir.isAcknowledged()); @@ -151,8 +181,15 @@ public void testSecurityUserInjectionWithConfigDisabled() throws Exception { // with invalid backend roles UserInjectorPlugin.injectedUser = "ttt|kkk"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, - OpenSearchSecurityPlugin.class, UserInjectorPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + UserInjectorPlugin.class + ).start() + ) { waitForInit(node.client()); CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-2")).actionGet(); // Should pass as the user injection is disabled diff --git a/src/test/java/org/opensearch/security/UtilTests.java b/src/test/java/org/opensearch/security/UtilTests.java index 83728e165d..2127ada55f 100644 --- a/src/test/java/org/opensearch/security/UtilTests.java +++ b/src/test/java/org/opensearch/security/UtilTests.java @@ -86,29 +86,29 @@ public void testWildcardMatcherClassesCaseInsensitive() { @Test public void testWildcardMatchers() { - assertTrue(!WildcardMatcher.from("a*?").test( "a")); - assertTrue(WildcardMatcher.from("a*?").test( "aa")); - assertTrue(WildcardMatcher.from("a*?").test( "ab")); - //assertTrue(WildcardMatcher.pattern("a*?").test( "abb")); - assertTrue(WildcardMatcher.from("*my*index").test( "myindex")); - assertTrue(!WildcardMatcher.from("*my*index").test( "myindex1")); - assertTrue(WildcardMatcher.from("*my*index?").test( "myindex1")); - assertTrue(WildcardMatcher.from("*my*index").test( "this_is_my_great_index")); - assertTrue(!WildcardMatcher.from("*my*index").test( "MYindex")); - assertTrue(!WildcardMatcher.from("?kibana").test( "kibana")); - assertTrue(WildcardMatcher.from("?kibana").test( ".kibana")); - assertTrue(!WildcardMatcher.from("?kibana").test( "kibana.")); - assertTrue(WildcardMatcher.from("?kibana?").test( "?kibana.")); - assertTrue(WildcardMatcher.from("/(\\d{3}-?\\d{2}-?\\d{4})/").test( "123-45-6789")); - assertTrue(!WildcardMatcher.from("(\\d{3}-?\\d{2}-?\\d{4})").test( "123-45-6789")); - assertTrue(WildcardMatcher.from("/\\S*/").test( "abc")); - assertTrue(WildcardMatcher.from("abc").test( "abc")); - assertTrue(!WildcardMatcher.from("ABC").test( "abc")); + assertTrue(!WildcardMatcher.from("a*?").test("a")); + assertTrue(WildcardMatcher.from("a*?").test("aa")); + assertTrue(WildcardMatcher.from("a*?").test("ab")); + // assertTrue(WildcardMatcher.pattern("a*?").test( "abb")); + assertTrue(WildcardMatcher.from("*my*index").test("myindex")); + assertTrue(!WildcardMatcher.from("*my*index").test("myindex1")); + assertTrue(WildcardMatcher.from("*my*index?").test("myindex1")); + assertTrue(WildcardMatcher.from("*my*index").test("this_is_my_great_index")); + assertTrue(!WildcardMatcher.from("*my*index").test("MYindex")); + assertTrue(!WildcardMatcher.from("?kibana").test("kibana")); + assertTrue(WildcardMatcher.from("?kibana").test(".kibana")); + assertTrue(!WildcardMatcher.from("?kibana").test("kibana.")); + assertTrue(WildcardMatcher.from("?kibana?").test("?kibana.")); + assertTrue(WildcardMatcher.from("/(\\d{3}-?\\d{2}-?\\d{4})/").test("123-45-6789")); + assertTrue(!WildcardMatcher.from("(\\d{3}-?\\d{2}-?\\d{4})").test("123-45-6789")); + assertTrue(WildcardMatcher.from("/\\S*/").test("abc")); + assertTrue(WildcardMatcher.from("abc").test("abc")); + assertTrue(!WildcardMatcher.from("ABC").test("abc")); } @Test public void testMapFromArray() { - Map map = SecurityUtils.mapFromArray((Object)null); + Map map = SecurityUtils.mapFromArray((Object) null); assertTrue(map == null); map = SecurityUtils.mapFromArray("key"); @@ -138,32 +138,35 @@ public void testMapFromArray() { @Test public void testEnvReplace() { Settings settings = Settings.EMPTY; - assertEquals("abv${env.MYENV}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV}xyz",settings)); - assertEquals("abv${envbc.MYENV}xyz", SecurityUtils.replaceEnvVars("abv${envbc.MYENV}xyz",settings)); - assertEquals("abvtTtxyz", SecurityUtils.replaceEnvVars("abv${env.MYENV:-tTt}xyz",settings)); - assertTrue(OpenBSDBCrypt.checkPassword(SecurityUtils.replaceEnvVars("${envbc.MYENV:-tTt}",settings), "tTt".toCharArray())); - assertEquals("abvtTtxyzxxx", SecurityUtils.replaceEnvVars("abv${env.MYENV:-tTt}xyz${env.MYENV:-xxx}",settings)); - assertTrue(SecurityUtils.replaceEnvVars("abv${env.MYENV:-tTt}xyz${envbc.MYENV:-xxx}",settings).startsWith("abvtTtxyz$2y$")); - assertEquals("abv${env.MYENV:tTt}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV:tTt}xyz",settings)); - assertEquals("abv${env.MYENV-tTt}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV-tTt}xyz",settings)); - //assertEquals("abvabcdefgxyz", SecurityUtils.replaceEnvVars("abv${envbase64.B64TEST}xyz",settings)); + assertEquals("abv${env.MYENV}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV}xyz", settings)); + assertEquals("abv${envbc.MYENV}xyz", SecurityUtils.replaceEnvVars("abv${envbc.MYENV}xyz", settings)); + assertEquals("abvtTtxyz", SecurityUtils.replaceEnvVars("abv${env.MYENV:-tTt}xyz", settings)); + assertTrue(OpenBSDBCrypt.checkPassword(SecurityUtils.replaceEnvVars("${envbc.MYENV:-tTt}", settings), "tTt".toCharArray())); + assertEquals("abvtTtxyzxxx", SecurityUtils.replaceEnvVars("abv${env.MYENV:-tTt}xyz${env.MYENV:-xxx}", settings)); + assertTrue(SecurityUtils.replaceEnvVars("abv${env.MYENV:-tTt}xyz${envbc.MYENV:-xxx}", settings).startsWith("abvtTtxyz$2y$")); + assertEquals("abv${env.MYENV:tTt}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV:tTt}xyz", settings)); + assertEquals("abv${env.MYENV-tTt}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV-tTt}xyz", settings)); + // assertEquals("abvabcdefgxyz", SecurityUtils.replaceEnvVars("abv${envbase64.B64TEST}xyz",settings)); Map env = System.getenv(); assertTrue(env.size() > 0); boolean checked = false; - for(String k: env.keySet()) { - String val=System.getenv().get(k); - if(val == null || val.isEmpty()) { + for (String k : env.keySet()) { + String val = System.getenv().get(k); + if (val == null || val.isEmpty()) { continue; } - assertEquals("abv"+val+"xyz", SecurityUtils.replaceEnvVars("abv${env."+k+"}xyz",settings)); - assertEquals("abv${"+k+"}xyz", SecurityUtils.replaceEnvVars("abv${"+k+"}xyz",settings)); - assertEquals("abv"+val+"xyz", SecurityUtils.replaceEnvVars("abv${env."+k+":-k182765ggh}xyz",settings)); - assertEquals("abv"+val+"xyzabv"+val+"xyz", SecurityUtils.replaceEnvVars("abv${env."+k+"}xyzabv${env."+k+"}xyz",settings)); - assertEquals("abv"+val+"xyz", SecurityUtils.replaceEnvVars("abv${env."+k+":-k182765ggh}xyz",settings)); - assertTrue(OpenBSDBCrypt.checkPassword(SecurityUtils.replaceEnvVars("${envbc."+k+"}",settings), val.toCharArray())); + assertEquals("abv" + val + "xyz", SecurityUtils.replaceEnvVars("abv${env." + k + "}xyz", settings)); + assertEquals("abv${" + k + "}xyz", SecurityUtils.replaceEnvVars("abv${" + k + "}xyz", settings)); + assertEquals("abv" + val + "xyz", SecurityUtils.replaceEnvVars("abv${env." + k + ":-k182765ggh}xyz", settings)); + assertEquals( + "abv" + val + "xyzabv" + val + "xyz", + SecurityUtils.replaceEnvVars("abv${env." + k + "}xyzabv${env." + k + "}xyz", settings) + ); + assertEquals("abv" + val + "xyz", SecurityUtils.replaceEnvVars("abv${env." + k + ":-k182765ggh}xyz", settings)); + assertTrue(OpenBSDBCrypt.checkPassword(SecurityUtils.replaceEnvVars("${envbc." + k + "}", settings), val.toCharArray())); checked = true; } @@ -173,22 +176,34 @@ public void testEnvReplace() { @Test public void testNoEnvReplace() { Settings settings = Settings.builder().put(ConfigConstants.SECURITY_DISABLE_ENVVAR_REPLACEMENT, true).build(); - assertEquals("abv${env.MYENV}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV}xyz",settings)); - assertEquals("abv${envbc.MYENV}xyz", SecurityUtils.replaceEnvVars("abv${envbc.MYENV}xyz",settings)); - assertEquals("abv${env.MYENV:-tTt}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV:-tTt}xyz",settings)); - assertEquals("abv${env.MYENV:-tTt}xyz${env.MYENV:-xxx}", SecurityUtils.replaceEnvVars("abv${env.MYENV:-tTt}xyz${env.MYENV:-xxx}",settings)); - assertFalse(SecurityUtils.replaceEnvVars("abv${env.MYENV:-tTt}xyz${envbc.MYENV:-xxx}",settings).startsWith("abvtTtxyz$2y$")); - assertEquals("abv${env.MYENV:tTt}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV:tTt}xyz",settings)); - assertEquals("abv${env.MYENV-tTt}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV-tTt}xyz",settings)); + assertEquals("abv${env.MYENV}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV}xyz", settings)); + assertEquals("abv${envbc.MYENV}xyz", SecurityUtils.replaceEnvVars("abv${envbc.MYENV}xyz", settings)); + assertEquals("abv${env.MYENV:-tTt}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV:-tTt}xyz", settings)); + assertEquals( + "abv${env.MYENV:-tTt}xyz${env.MYENV:-xxx}", + SecurityUtils.replaceEnvVars("abv${env.MYENV:-tTt}xyz${env.MYENV:-xxx}", settings) + ); + assertFalse(SecurityUtils.replaceEnvVars("abv${env.MYENV:-tTt}xyz${envbc.MYENV:-xxx}", settings).startsWith("abvtTtxyz$2y$")); + assertEquals("abv${env.MYENV:tTt}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV:tTt}xyz", settings)); + assertEquals("abv${env.MYENV-tTt}xyz", SecurityUtils.replaceEnvVars("abv${env.MYENV-tTt}xyz", settings)); Map env = System.getenv(); assertTrue(env.size() > 0); - for(String k: env.keySet()) { - assertEquals("abv${env."+k+"}xyz", SecurityUtils.replaceEnvVars("abv${env."+k+"}xyz",settings)); - assertEquals("abv${"+k+"}xyz", SecurityUtils.replaceEnvVars("abv${"+k+"}xyz",settings)); - assertEquals("abv${env."+k+":-k182765ggh}xyz", SecurityUtils.replaceEnvVars("abv${env."+k+":-k182765ggh}xyz",settings)); - assertEquals("abv${env."+k+"}xyzabv${env."+k+"}xyz", SecurityUtils.replaceEnvVars("abv${env."+k+"}xyzabv${env."+k+"}xyz",settings)); - assertEquals("abv${env."+k+":-k182765ggh}xyz", SecurityUtils.replaceEnvVars("abv${env."+k+":-k182765ggh}xyz",settings)); + for (String k : env.keySet()) { + assertEquals("abv${env." + k + "}xyz", SecurityUtils.replaceEnvVars("abv${env." + k + "}xyz", settings)); + assertEquals("abv${" + k + "}xyz", SecurityUtils.replaceEnvVars("abv${" + k + "}xyz", settings)); + assertEquals( + "abv${env." + k + ":-k182765ggh}xyz", + SecurityUtils.replaceEnvVars("abv${env." + k + ":-k182765ggh}xyz", settings) + ); + assertEquals( + "abv${env." + k + "}xyzabv${env." + k + "}xyz", + SecurityUtils.replaceEnvVars("abv${env." + k + "}xyzabv${env." + k + "}xyz", settings) + ); + assertEquals( + "abv${env." + k + ":-k182765ggh}xyz", + SecurityUtils.replaceEnvVars("abv${env." + k + ":-k182765ggh}xyz", settings) + ); } } } diff --git a/src/test/java/org/opensearch/security/auditlog/AbstractAuditlogiUnitTest.java b/src/test/java/org/opensearch/security/auditlog/AbstractAuditlogiUnitTest.java index 14ae6aa81e..f567a90ec8 100644 --- a/src/test/java/org/opensearch/security/auditlog/AbstractAuditlogiUnitTest.java +++ b/src/test/java/org/opensearch/security/auditlog/AbstractAuditlogiUnitTest.java @@ -45,8 +45,10 @@ protected final void setup(Settings settings) throws Exception { // Separate the cluster defaults from audit settings that will be applied after the cluster is up settings.keySet().forEach(key -> { final boolean moveToAuditConfig = Arrays.stream(AuditConfig.Filter.FilterEntries.values()) - .anyMatch(entry -> entry.getKeyWithNamespace().equalsIgnoreCase(key) || entry.getLegacyKeyWithNamespace().equalsIgnoreCase(key)) - || DEPRECATED_KEYS.stream().anyMatch(key::equalsIgnoreCase); + .anyMatch( + entry -> entry.getKeyWithNamespace().equalsIgnoreCase(key) || entry.getLegacyKeyWithNamespace().equalsIgnoreCase(key) + ) + || DEPRECATED_KEYS.stream().anyMatch(key::equalsIgnoreCase); if (moveToAuditConfig) { auditConfigSettings.put(key, settings.get(key)); } else { @@ -64,10 +66,8 @@ protected Settings defaultNodeSettings(Settings additionalSettings) { Settings.Builder builder = Settings.builder(); builder.put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.keystore_filepath", - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")); + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")); return builder.put(additionalSettings).build(); } @@ -87,7 +87,7 @@ protected void setupStarfleetIndex() throws Exception { protected boolean validateMsgs(final Collection msgs) { boolean valid = true; - for(AuditMessage msg: msgs) { + for (AuditMessage msg : msgs) { valid = validateMsg(msg) && valid; } return valid; @@ -99,15 +99,15 @@ protected boolean validateMsg(final AuditMessage msg) { protected boolean validateJson(final String json) { - if(json == null || json.isEmpty()) { + if (json == null || json.isEmpty()) { return false; } try { JsonNode node = DefaultObjectMapper.objectMapper.readTree(json); - if(node.get("audit_request_body") != null) { - System.out.println(" Check audit_request_body for validity: "+node.get("audit_request_body").asText()); + if (node.get("audit_request_body") != null) { + System.out.println(" Check audit_request_body for validity: " + node.get("audit_request_body").asText()); DefaultObjectMapper.objectMapper.readTree(node.get("audit_request_body").asText()); } diff --git a/src/test/java/org/opensearch/security/auditlog/AuditTestUtils.java b/src/test/java/org/opensearch/security/auditlog/AuditTestUtils.java index 507ebc1409..98f5fab88e 100644 --- a/src/test/java/org/opensearch/security/auditlog/AuditTestUtils.java +++ b/src/test/java/org/opensearch/security/auditlog/AuditTestUtils.java @@ -62,7 +62,8 @@ public static AbstractAuditLog createAuditLog( final Client clientProvider, final ThreadPool threadPool, final IndexNameExpressionResolver resolver, - final ClusterService clusterService) { + final ClusterService clusterService + ) { AuditLogImpl auditLog = new AuditLogImpl(settings, configPath, clientProvider, threadPool, resolver, clusterService); AuditConfig auditConfig = AuditConfig.from(settings); auditLog.setConfig(auditConfig); 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 96773dfee4..361c3cc313 100644 --- a/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java @@ -54,14 +54,14 @@ 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_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") - .build(); + .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_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") + .build(); setup(additionalSettings); final boolean sendAdminCertificate = rh.sendAdminCertificate; @@ -74,18 +74,18 @@ public void testSourceFilter() throws Exception { rh.sendAdminCertificate = sendAdminCertificate; rh.keystore = keystore; - String search = "{" + - " \"_source\":[" + - " \"Gender\""+ - " ]," + - " \"from\":0," + - " \"size\":3," + - " \"query\":{" + - " \"term\":{" + - " \"Salary\": 300" + - " }" + - " }" + - "}"; + String search = "{" + + " \"_source\":[" + + " \"Gender\"" + + " ]," + + " \"from\":0," + + " \"size\":3," + + " \"query\":{" + + " \"term\":{" + + " \"Salary\": 300" + + " }" + + " }" + + "}"; final AuditMessage message = TestAuditlogImpl.doThenWaitForMessage(() -> { final HttpResponse response = rh.executePostRequest("_search?pretty", search, encodeBasicHeader("admin", "admin")); @@ -102,9 +102,7 @@ public void testSourceFilter() throws Exception { @Test public void testComplianceEnable() throws Exception { - Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .build(); + Settings additionalSettings = Settings.builder().put("plugins.security.audit.type", TestAuditlogImpl.class.getName()).build(); setup(additionalSettings); @@ -112,7 +110,14 @@ public void testComplianceEnable() throws Exception { rh.keystore = "auditlog/kirk-keystore.jks"; // watch emp for write - AuditConfig auditConfig = new AuditConfig(true, AuditConfig.Filter.DEFAULT , ComplianceConfig.from(ImmutableMap.of("enabled", true, "write_watched_indices", Collections.singletonList("emp")), additionalSettings)); + AuditConfig auditConfig = new AuditConfig( + true, + AuditConfig.Filter.DEFAULT, + ComplianceConfig.from( + ImmutableMap.of("enabled", true, "write_watched_indices", Collections.singletonList("emp")), + additionalSettings + ) + ); updateAuditConfig(AuditTestUtils.createAuditPayload(auditConfig)); // make an event happen @@ -122,23 +127,36 @@ public void testComplianceEnable() throws Exception { 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) { + } 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")); + 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()); + 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()); + 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)); + auditConfig = new AuditConfig( + true, + AuditConfig.Filter.DEFAULT, + ComplianceConfig.from( + ImmutableMap.of("enabled", false, "write_watched_indices", Collections.singletonList("emp")), + additionalSettings + ) + ); updateAuditConfig(AuditTestUtils.createAuditPayload(auditConfig)); // trigger an event that it not captured by the audit log @@ -154,15 +172,15 @@ public void testComplianceEnable() throws Exception { public void testSourceFilterMsearch() 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") - .build(); + .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") + .build(); setup(additionalSettings); final boolean sendAdminCertificate = rh.sendAdminCertificate; @@ -175,33 +193,38 @@ public void testSourceFilterMsearch() throws Exception { rh.sendAdminCertificate = sendAdminCertificate; rh.keystore = keystore; - String search = "{}"+System.lineSeparator() - + "{" + - " \"_source\":[" + - " \"Gender\""+ - " ]," + - " \"from\":0," + - " \"size\":3," + - " \"query\":{" + - " \"term\":{" + - " \"Salary\": 300" + - " }" + - " }" + - "}"+System.lineSeparator()+ - - "{}"+System.lineSeparator() - + "{" + - " \"_source\":[" + - " \"Designation\""+ - " ]," + - " \"from\":0," + - " \"size\":3," + - " \"query\":{" + - " \"term\":{" + - " \"Salary\": 200" + - " }" + - " }" + - "}"+System.lineSeparator(); + String search = "{}" + + System.lineSeparator() + + "{" + + " \"_source\":[" + + " \"Gender\"" + + " ]," + + " \"from\":0," + + " \"size\":3," + + " \"query\":{" + + " \"term\":{" + + " \"Salary\": 300" + + " }" + + " }" + + "}" + + System.lineSeparator() + + + + "{}" + + System.lineSeparator() + + "{" + + " \"_source\":[" + + " \"Designation\"" + + " ]," + + " \"from\":0," + + " \"size\":3," + + " \"query\":{" + + " \"term\":{" + + " \"Salary\": 200" + + " }" + + " }" + + "}" + + System.lineSeparator(); final List messages = TestAuditlogImpl.doThenWaitForMessages(() -> { HttpResponse response = rh.executePostRequest("_msearch?pretty", search, encodeBasicHeader("admin", "admin")); @@ -209,8 +232,10 @@ public void testSourceFilterMsearch() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); }, 2); - - final AuditMessage desginationMsg = messages.stream().filter(msg -> msg.getRequestBody().contains("Designation")).findFirst().orElseThrow(); + final AuditMessage desginationMsg = messages.stream() + .filter(msg -> msg.getRequestBody().contains("Designation")) + .findFirst() + .orElseThrow(); assertThat(desginationMsg.getCategory(), equalTo(AuditCategory.COMPLIANCE_DOC_READ)); assertThat(desginationMsg.getRequestBody(), containsString("Designation")); assertThat(desginationMsg.getRequestBody(), not(containsString("Salary"))); @@ -227,23 +252,31 @@ public void testSourceFilterMsearch() throws Exception { public void testInternalConfig() 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, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .build(); + .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, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) + .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .build(); setup(additionalSettings); - final List expectedDocumentsTypes = List.of("config", "actiongroups", "internalusers", "roles", "rolesmapping", "tenants", "audit"); + final List expectedDocumentsTypes = List.of( + "config", + "actiongroups", + "internalusers", + "roles", + "rolesmapping", + "tenants", + "audit" + ); final List messages = TestAuditlogImpl.doThenWaitForMessages(() -> { try (RestHighLevelClient restHighLevelClient = getRestClient(clusterInfo, "kirk-keystore.jks", "truststore.jks")) { - for (IndexRequest ir: new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) { + for (IndexRequest ir : new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) { restHighLevelClient.index(ir, RequestOptions.DEFAULT); GetResponse getDocumentResponse = restHighLevelClient.get(new GetRequest(ir.index(), ir.id()), RequestOptions.DEFAULT); assertThat(getDocumentResponse.isExists(), equalTo(true)); @@ -262,7 +295,8 @@ public void testInternalConfig() throws Exception { 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 read/write config message", + assertThat( + "Doc " + docId + " should have a read/write config message", messagesByDocId.stream().map(AuditMessage::getCategory).collect(Collectors.toList()), equalTo(List.of(AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE, AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ)) ); @@ -275,15 +309,15 @@ public void testInternalConfig() throws Exception { public void testExternalConfig() throws Exception { 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) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .build(); + .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, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .build(); final List messages = TestAuditlogImpl.doThenWaitForMessages(() -> { try { @@ -293,7 +327,7 @@ public void testExternalConfig() throws Exception { } try (Client tc = getClient()) { - for(IndexRequest ir: new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) { + for (IndexRequest ir : new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) { tc.index(ir).actionGet(); } } @@ -319,41 +353,46 @@ public void testExternalConfig() throws Exception { public void testUpdate() 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, "finance") - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, "humanresources,Designation,FirstName,LastName") - .build(); + .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, "finance") + .put( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, + "humanresources,Designation,FirstName,LastName" + ) + .build(); setup(additionalSettings); - try (Client tc = getClient()) { - tc.prepareIndex("humanresources") - .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .setSource("Age", 456) - .execute() - .actionGet(); + tc.prepareIndex("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE).setSource("Age", 456).execute().actionGet(); } 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")); + final HttpResponse response = rh.executePostRequest( + "humanresources/_doc/100?pretty", + body, + encodeBasicHeader("admin", "admin") + ); Assert.assertEquals(HttpStatus.SC_CREATED, 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")); + final HttpResponse response = rh.executePostRequest( + "humanresources/_update/100?pretty", + body, + encodeBasicHeader("admin", "admin") + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); }); }); @@ -367,34 +406,38 @@ public void testUpdate() throws Exception { public void testWriteHistory() 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_WRITE_LOG_DIFFS, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, "humanresources") - .build(); + .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_WRITE_LOG_DIFFS, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, "humanresources") + .build(); setup(additionalSettings); try (Client tc = getClient()) { - tc.prepareIndex("humanresources") - .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .setSource("Age", 456) - .execute() - .actionGet(); + tc.prepareIndex("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE).setSource("Age", 456).execute().actionGet(); } TestAuditlogImpl.doThenWaitForMessage(() -> { final String body = "{\"doc\": {\"Age\":123}}"; - final HttpResponse response = rh.executePostRequest("humanresources/_doc/100?pretty", body, encodeBasicHeader("admin", "admin")); + final HttpResponse response = rh.executePostRequest( + "humanresources/_doc/100?pretty", + body, + encodeBasicHeader("admin", "admin") + ); Assert.assertEquals(HttpStatus.SC_CREATED, 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")); + 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/compliance/ComplianceConfigTest.java b/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceConfigTest.java index a40e94ad28..467475212b 100644 --- a/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceConfigTest.java +++ b/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceConfigTest.java @@ -50,19 +50,21 @@ public void testConfig() { // arrange final String testSalt = "abcdefghijklmnop"; final Settings settings = Settings.builder() - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, true) - .put(ConfigConstants.SECURITY_COMPLIANCE_SALT, testSalt) - .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, "write_index1", "write_index_pattern*") - .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, "read_index1,field1,field2", "read_index_pattern*,field1,field_pattern*") - .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, - "test-user-1", "test-user-2") - .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, - "test-user-3", "test-user-4") - .build(); + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, true) + .put(ConfigConstants.SECURITY_COMPLIANCE_SALT, testSalt) + .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, "write_index1", "write_index_pattern*") + .putList( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, + "read_index1,field1,field2", + "read_index_pattern*,field1,field_pattern*" + ) + .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, "test-user-1", "test-user-2") + .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, "test-user-3", "test-user-4") + .build(); // act final ComplianceConfig complianceConfig = ComplianceConfig.from(settings); @@ -74,8 +76,14 @@ public void testConfig() { assertTrue(complianceConfig.shouldLogReadMetadataOnly()); assertTrue(complianceConfig.shouldLogWriteMetadataOnly()); assertFalse(complianceConfig.shouldLogDiffsForWrite()); - assertEquals(WildcardMatcher.from(ImmutableSet.of("test-user-1", "test-user-2")), complianceConfig.getIgnoredComplianceUsersForReadMatcher()); - assertEquals(WildcardMatcher.from(ImmutableSet.of("test-user-3", "test-user-4")), complianceConfig.getIgnoredComplianceUsersForWriteMatcher()); + assertEquals( + WildcardMatcher.from(ImmutableSet.of("test-user-1", "test-user-2")), + complianceConfig.getIgnoredComplianceUsersForReadMatcher() + ); + assertEquals( + WildcardMatcher.from(ImmutableSet.of("test-user-3", "test-user-4")), + complianceConfig.getIgnoredComplianceUsersForWriteMatcher() + ); // test write history assertTrue(complianceConfig.writeHistoryEnabledForIndex(".opendistro_security")); @@ -105,11 +113,9 @@ public void testConfig() { public void testNone() { // arrange final Settings settings = Settings.builder() - .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, - "NONE") - .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, - "NONE") - .build(); + .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, "NONE") + .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, "NONE") + .build(); // act final ComplianceConfig complianceConfig = ComplianceConfig.from(settings); // assert @@ -121,11 +127,9 @@ public void testNone() { public void testEmpty() { // arrange final Settings settings = Settings.builder() - .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, - Collections.emptyList()) - .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, - Collections.emptyList()) - .build(); + .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, Collections.emptyList()) + .putList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, Collections.emptyList()) + .build(); // act final ComplianceConfig complianceConfig = ComplianceConfig.from(settings); // assert diff --git a/src/test/java/org/opensearch/security/auditlog/compliance/RestApiComplianceAuditlogTest.java b/src/test/java/org/opensearch/security/auditlog/compliance/RestApiComplianceAuditlogTest.java index 37ef283ccb..519237bd8e 100644 --- a/src/test/java/org/opensearch/security/auditlog/compliance/RestApiComplianceAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/compliance/RestApiComplianceAuditlogTest.java @@ -29,25 +29,29 @@ public class RestApiComplianceAuditlogTest extends AbstractAuditlogiUnitTest { public void testRestApiRolesEnabled() throws Exception { Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, "opendistro_security_all_access") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, "opendistro_security_all_access") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) + .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .build(); setup(additionalSettings); TestAuditlogImpl.clear(); String body = "{ \"password\":\"some new password\",\"backend_roles\":[\"role1\",\"role2\"] }"; - HttpResponse response = rh.executePutRequest("_opendistro/_security/api/internalusers/compuser?pretty", body, encodeBasicHeader("admin", "admin")); + HttpResponse response = rh.executePutRequest( + "_opendistro/_security/api/internalusers/compuser?pretty", + body, + encodeBasicHeader("admin", "admin") + ); Thread.sleep(1500); System.out.println(TestAuditlogImpl.sb.toString()); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - Assert.assertTrue(TestAuditlogImpl.messages.size()+"",TestAuditlogImpl.messages.size() == 1); + Assert.assertTrue(TestAuditlogImpl.messages.size() + "", TestAuditlogImpl.messages.size() == 1); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("audit_request_effective_user")); Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_INTERNAL_CONFIG_READ")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_INTERNAL_CONFIG_WRITE")); @@ -59,15 +63,15 @@ public void testRestApiRolesEnabled() throws Exception { public void testRestApiRolesDisabled() 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_ENABLE_REST, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) + .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .build(); setup(additionalSettings); TestAuditlogImpl.clear(); @@ -82,7 +86,7 @@ public void testRestApiRolesDisabled() throws Exception { Thread.sleep(1500); System.out.println(TestAuditlogImpl.sb.toString()); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - Assert.assertTrue(TestAuditlogImpl.messages.size()+"",TestAuditlogImpl.messages.size() == 1); + Assert.assertTrue(TestAuditlogImpl.messages.size() + "", TestAuditlogImpl.messages.size() == 1); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("audit_request_effective_user")); Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_INTERNAL_CONFIG_READ")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_INTERNAL_CONFIG_WRITE")); @@ -95,15 +99,15 @@ public void testRestApiRolesDisabled() throws Exception { public void testRestApiRolesDisabledGet() 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_ENABLE_REST, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) + .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .build(); setup(additionalSettings); TestAuditlogImpl.clear(); @@ -125,21 +129,19 @@ public void testRestApiRolesDisabledGet() throws Exception { Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } - - @Test public void testAutoInit() 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_ENABLE_REST, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .build(); setup(additionalSettings); @@ -157,42 +159,45 @@ public void testAutoInit() throws Exception { public void testRestApiNewUser() throws Exception { Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, "opendistro_security_all_access") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, "admin") - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, "opendistro_security_all_access") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) + .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, "admin") + .build(); setup(additionalSettings); TestAuditlogImpl.clear(); String body = "{ \"password\":\"some new password\",\"backend_roles\":[\"role1\",\"role2\"] }"; System.out.println("exec"); - HttpResponse response = rh.executePutRequest("_opendistro/_security/api/internalusers/compuser?pretty", - body, encodeBasicHeader("admin", "admin")); + HttpResponse response = rh.executePutRequest( + "_opendistro/_security/api/internalusers/compuser?pretty", + body, + encodeBasicHeader("admin", "admin") + ); Thread.sleep(1500); System.out.println(TestAuditlogImpl.sb.toString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); - Assert.assertTrue(TestAuditlogImpl.messages.size()+"", TestAuditlogImpl.messages.isEmpty()); + Assert.assertTrue(TestAuditlogImpl.messages.size() + "", TestAuditlogImpl.messages.isEmpty()); } @Test public void testRestInternalConfigRead() throws Exception { Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .put(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, "opendistro_security_all_access") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, "opendistro_security_all_access") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) + .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .build(); setup(additionalSettings); TestAuditlogImpl.clear(); @@ -206,7 +211,7 @@ public void testRestInternalConfigRead() throws Exception { Thread.sleep(1500); System.out.println(TestAuditlogImpl.sb.toString()); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(TestAuditlogImpl.messages.size()+"",TestAuditlogImpl.messages.size() == 1); + Assert.assertTrue(TestAuditlogImpl.messages.size() + "", TestAuditlogImpl.messages.size() == 1); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("audit_request_effective_user")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_INTERNAL_CONFIG_READ")); Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_INTERNAL_CONFIG_WRITE")); @@ -217,13 +222,13 @@ public void testRestInternalConfigRead() throws Exception { @Test public void testBCryptHashRedaction() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, "opendistro_security_all_access") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, true) - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, "opendistro_security_all_access") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) + .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, true) + .build(); setup(settings); rh.sendAdminCertificate = true; rh.keystore = "kirk-keystore.jks"; @@ -242,7 +247,7 @@ public void testBCryptHashRedaction() throws Exception { // create internal user and verify no BCrypt hash is present in audit logs TestAuditlogImpl.clear(); - rh.executePutRequest("/_opendistro/_security/api/internalusers/test", "{ \"password\":\"some new user password\"}"); + rh.executePutRequest("/_opendistro/_security/api/internalusers/test", "{ \"password\":\"some new user password\"}"); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); Assert.assertFalse(AuditMessage.BCRYPT_HASH.matcher(TestAuditlogImpl.sb.toString()).matches()); } diff --git a/src/test/java/org/opensearch/security/auditlog/config/AuditConfigFilterTest.java b/src/test/java/org/opensearch/security/auditlog/config/AuditConfigFilterTest.java index 4b4676d852..fad4cabbc5 100644 --- a/src/test/java/org/opensearch/security/auditlog/config/AuditConfigFilterTest.java +++ b/src/test/java/org/opensearch/security/auditlog/config/AuditConfigFilterTest.java @@ -65,19 +65,25 @@ public void testDefault() { public void testConfig() { // arrange final Settings settings = Settings.builder() - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS, false) - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, "test-request") - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, "test-user") - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, - BAD_HEADERS.toString(), SSL_EXCEPTION.toString()) - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, - FAILED_LOGIN.toString(), MISSING_PRIVILEGES.toString()) - .build(); + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS, false) + .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, "test-request") + .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, "test-user") + .putList( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, + BAD_HEADERS.toString(), + SSL_EXCEPTION.toString() + ) + .putList( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, + FAILED_LOGIN.toString(), + MISSING_PRIVILEGES.toString() + ) + .build(); // act final AuditConfig.Filter auditConfigFilter = AuditConfig.Filter.from(settings); // assert @@ -97,12 +103,10 @@ public void testConfig() { public void testNone() { // arrange final Settings settings = Settings.builder() - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, "NONE") - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, - "None") - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, - "none") - .build(); + .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, "NONE") + .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "None") + .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "none") + .build(); // act final AuditConfig.Filter auditConfigFilter = AuditConfig.Filter.from(settings); // assert @@ -115,13 +119,11 @@ public void testNone() { public void testEmpty() { // arrange final Settings settings = Settings.builder() - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, Collections.emptyList()) - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, Collections.emptyList()) - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, - Collections.emptyList()) - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, - Collections.emptyList()) - .build(); + .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, Collections.emptyList()) + .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, Collections.emptyList()) + .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, Collections.emptyList()) + .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, Collections.emptyList()) + .build(); // act final AuditConfig.Filter auditConfigFilter = AuditConfig.Filter.from(settings); // assert @@ -130,7 +132,6 @@ public void testEmpty() { assertTrue(auditConfigFilter.getDisabledTransportCategories().isEmpty()); } - @Test public void testFilterEntries() { assertThat(FilterEntries.ENABLE_REST.getKey(), equalTo("enable_rest")); @@ -147,16 +148,14 @@ public void fromSettingBoolean() { .put(entry.getKeyWithNamespace(), false) .put(entry.getLegacyKeyWithNamespace(), true) .build(); - assertThat(AuditConfig.Filter.fromSettingBoolean(settings1, entry, true), equalTo(false)); + assertThat(AuditConfig.Filter.fromSettingBoolean(settings1, entry, true), equalTo(false)); // Use fallback key - final Settings settings2 = Settings.builder() - .put(entry.getLegacyKeyWithNamespace(), false) - .build(); - assertThat(AuditConfig.Filter.fromSettingBoolean(settings2, entry, true), equalTo(false)); + final Settings settings2 = Settings.builder().put(entry.getLegacyKeyWithNamespace(), false).build(); + assertThat(AuditConfig.Filter.fromSettingBoolean(settings2, entry, true), equalTo(false)); // Use default - assertThat(AuditConfig.Filter.fromSettingBoolean(Settings.builder().build(), entry, true), equalTo(true)); + assertThat(AuditConfig.Filter.fromSettingBoolean(Settings.builder().build(), entry, true), equalTo(true)); } @Test @@ -165,67 +164,65 @@ public void fromSettingStringSet() { // Use primary key final Settings settings1 = Settings.builder() - .putList(entry.getKeyWithNamespace(), "abc") - .putList(entry.getLegacyKeyWithNamespace(), "def") - .build(); + .putList(entry.getKeyWithNamespace(), "abc") + .putList(entry.getLegacyKeyWithNamespace(), "def") + .build(); assertThat(AuditConfig.Filter.fromSettingStringSet(settings1, entry, List.of("xyz")), equalTo(ImmutableSet.of("abc"))); // Use fallback key - final Settings settings2 = Settings.builder() - .putList(entry.getLegacyKeyWithNamespace(), "def") - .build(); + final Settings settings2 = Settings.builder().putList(entry.getLegacyKeyWithNamespace(), "def").build(); assertThat(AuditConfig.Filter.fromSettingStringSet(settings2, entry, List.of("xyz")), equalTo(ImmutableSet.of("def"))); // Use default - assertThat(AuditConfig.Filter.fromSettingStringSet(Settings.builder().build(), entry, List.of("xyz")), equalTo(ImmutableSet.of("xyz"))); + assertThat( + AuditConfig.Filter.fromSettingStringSet(Settings.builder().build(), entry, List.of("xyz")), + equalTo(ImmutableSet.of("xyz")) + ); } @Test public void fromSettingParseAuditCategory() { final FilterEntries entry = FilterEntries.DISABLE_REST_CATEGORIES; - final Function> parse = (settings) -> - AuditCategory.parse(AuditConfig.Filter.fromSettingStringSet(settings, entry, ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT)); + final Function> parse = (settings) -> AuditCategory.parse( + AuditConfig.Filter.fromSettingStringSet(settings, entry, ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT) + ); final Settings noValues = Settings.builder().build(); assertThat(parse.apply(noValues), equalTo(ImmutableSet.of(AUTHENTICATED, GRANTED_PRIVILEGES))); - final Settings legacySettingNone = Settings.builder() - .put(entry.getLegacyKeyWithNamespace(), "NONE") - .build(); + final Settings legacySettingNone = Settings.builder().put(entry.getLegacyKeyWithNamespace(), "NONE").build(); assertThat(parse.apply(legacySettingNone), equalTo(ImmutableSet.of())); - final Settings legacySettingValue = Settings.builder() - .put(entry.getLegacyKeyWithNamespace(), AUTHENTICATED.name()) - .build(); + final Settings legacySettingValue = Settings.builder().put(entry.getLegacyKeyWithNamespace(), AUTHENTICATED.name()).build(); assertThat(parse.apply(legacySettingValue), equalTo(ImmutableSet.of(AUTHENTICATED))); final Settings legacySettingMultipleValues = Settings.builder() - .putList(entry.getLegacyKeyWithNamespace(), AUTHENTICATED.name(), BAD_HEADERS.name()) - .build(); + .putList(entry.getLegacyKeyWithNamespace(), AUTHENTICATED.name(), BAD_HEADERS.name()) + .build(); assertThat(parse.apply(legacySettingMultipleValues), equalTo(ImmutableSet.of(AUTHENTICATED, BAD_HEADERS))); final Settings settingNone = Settings.builder() - .put(entry.getKeyWithNamespace(), "NONE") - .put(entry.getLegacyKeyWithNamespace(), FAILED_LOGIN.name()) - .build(); + .put(entry.getKeyWithNamespace(), "NONE") + .put(entry.getLegacyKeyWithNamespace(), FAILED_LOGIN.name()) + .build(); assertThat(parse.apply(settingNone), equalTo(ImmutableSet.of())); final Settings settingValue = Settings.builder() - .put(entry.getKeyWithNamespace(), AUTHENTICATED.name()) - .put(entry.getLegacyKeyWithNamespace(), FAILED_LOGIN.name()) - .build(); + .put(entry.getKeyWithNamespace(), AUTHENTICATED.name()) + .put(entry.getLegacyKeyWithNamespace(), FAILED_LOGIN.name()) + .build(); assertThat(parse.apply(settingValue), equalTo(ImmutableSet.of(AUTHENTICATED))); final Settings settingMultipleValues = Settings.builder() - .putList(entry.getKeyWithNamespace(), AUTHENTICATED.name(), BAD_HEADERS.name()) - .put(entry.getLegacyKeyWithNamespace(), FAILED_LOGIN.name()) - .build(); + .putList(entry.getKeyWithNamespace(), AUTHENTICATED.name(), BAD_HEADERS.name()) + .put(entry.getLegacyKeyWithNamespace(), FAILED_LOGIN.name()) + .build(); assertThat(parse.apply(settingMultipleValues), equalTo(ImmutableSet.of(AUTHENTICATED, BAD_HEADERS))); final Settings settingMultipleValuesString = Settings.builder() - .put(entry.getKeyWithNamespace(), AUTHENTICATED.name() + "," + BAD_HEADERS.name()) - .put(entry.getLegacyKeyWithNamespace(), FAILED_LOGIN.name()) - .build(); + .put(entry.getKeyWithNamespace(), AUTHENTICATED.name() + "," + BAD_HEADERS.name()) + .put(entry.getLegacyKeyWithNamespace(), FAILED_LOGIN.name()) + .build(); assertThat(parse.apply(settingMultipleValues), equalTo(ImmutableSet.of(AUTHENTICATED, BAD_HEADERS))); } } diff --git a/src/test/java/org/opensearch/security/auditlog/config/AuditConfigSerializeTest.java b/src/test/java/org/opensearch/security/auditlog/config/AuditConfigSerializeTest.java index d7ba321ea9..8cc19fa0f5 100644 --- a/src/test/java/org/opensearch/security/auditlog/config/AuditConfigSerializeTest.java +++ b/src/test/java/org/opensearch/security/auditlog/config/AuditConfigSerializeTest.java @@ -60,33 +60,33 @@ public void testDefaultSerialize() throws IOException { final String json = objectMapper.writeValueAsString(audit); final XContentBuilder jsonBuilder = XContentFactory.jsonBuilder() - .startObject() - .field("enabled", true) - .startObject("audit") - .field("enable_rest", true) - .field("disabled_rest_categories", ImmutableList.of( "AUTHENTICATED", "GRANTED_PRIVILEGES")) - .field("enable_transport", true) - .field("disabled_transport_categories", ImmutableList.of( "AUTHENTICATED", "GRANTED_PRIVILEGES")) - .field("resolve_bulk_requests", false) - .field("log_request_body", true) - .field("resolve_indices", true) - .field("exclude_sensitive_headers", true) - .field("ignore_users", Collections.singletonList("kibanaserver")) - .field("ignore_requests", Collections.emptyList()) - .endObject() - .startObject("compliance") - .field("enabled", true) - .field("external_config", false) - .field("internal_config", false) - .field("read_metadata_only", false) - .field("read_watched_fields", Collections.emptyMap()) - .field("read_ignore_users", Collections.singletonList("kibanaserver")) - .field("write_metadata_only", false) - .field("write_log_diffs", false) - .field("write_watched_indices", Collections.emptyList()) - .field("write_ignore_users", Collections.singletonList("kibanaserver")) - .endObject() - .endObject(); + .startObject() + .field("enabled", true) + .startObject("audit") + .field("enable_rest", true) + .field("disabled_rest_categories", ImmutableList.of("AUTHENTICATED", "GRANTED_PRIVILEGES")) + .field("enable_transport", true) + .field("disabled_transport_categories", ImmutableList.of("AUTHENTICATED", "GRANTED_PRIVILEGES")) + .field("resolve_bulk_requests", false) + .field("log_request_body", true) + .field("resolve_indices", true) + .field("exclude_sensitive_headers", true) + .field("ignore_users", Collections.singletonList("kibanaserver")) + .field("ignore_requests", Collections.emptyList()) + .endObject() + .startObject("compliance") + .field("enabled", true) + .field("external_config", false) + .field("internal_config", false) + .field("read_metadata_only", false) + .field("read_watched_fields", Collections.emptyMap()) + .field("read_ignore_users", Collections.singletonList("kibanaserver")) + .field("write_metadata_only", false) + .field("write_log_diffs", false) + .field("write_watched_indices", Collections.emptyList()) + .field("write_ignore_users", Collections.singletonList("kibanaserver")) + .endObject() + .endObject(); assertTrue(compareJson(Strings.toString(jsonBuilder), json)); } @@ -121,33 +121,33 @@ public void testDefaultDeserialize() throws IOException { public void testDeserialize() throws IOException { // arrange final XContentBuilder jsonBuilder = XContentFactory.jsonBuilder() - .startObject() - .field("enabled", true) - .startObject("audit") - .field("enable_rest", true) - .field("disabled_rest_categories", Collections.singletonList("AUTHENTICATED")) - .field("enable_transport", true) - .field("disabled_transport_categories", Collections.singletonList("SSL_EXCEPTION")) - .field("resolve_bulk_requests", true) - .field("log_request_body", true) - .field("resolve_indices", true) - .field("exclude_sensitive_headers", true) - .field("ignore_users", Collections.singletonList("test-user-1")) - .field("ignore_requests", Collections.singletonList("test-request")) - .endObject() - .startObject("compliance") - .field("enabled", true) - .field("external_config", true) - .field("internal_config", true) - .field("read_metadata_only", true) - .field("read_watched_fields", Collections.singletonMap("test-read-watch-field", Collections.singleton("test-field-1"))) - .field("read_ignore_users", Collections.singletonList("test-user-2")) - .field("write_metadata_only", true) - .field("write_log_diffs", false) - .field("write_watched_indices", Collections.singletonList("test-write-watch-index")) - .field("write_ignore_users", Collections.singletonList("test-user-3")) - .endObject() - .endObject(); + .startObject() + .field("enabled", true) + .startObject("audit") + .field("enable_rest", true) + .field("disabled_rest_categories", Collections.singletonList("AUTHENTICATED")) + .field("enable_transport", true) + .field("disabled_transport_categories", Collections.singletonList("SSL_EXCEPTION")) + .field("resolve_bulk_requests", true) + .field("log_request_body", true) + .field("resolve_indices", true) + .field("exclude_sensitive_headers", true) + .field("ignore_users", Collections.singletonList("test-user-1")) + .field("ignore_requests", Collections.singletonList("test-request")) + .endObject() + .startObject("compliance") + .field("enabled", true) + .field("external_config", true) + .field("internal_config", true) + .field("read_metadata_only", true) + .field("read_watched_fields", Collections.singletonMap("test-read-watch-field", Collections.singleton("test-field-1"))) + .field("read_ignore_users", Collections.singletonList("test-user-2")) + .field("write_metadata_only", true) + .field("write_log_diffs", false) + .field("write_watched_indices", Collections.singletonList("test-write-watch-index")) + .field("write_ignore_users", Collections.singletonList("test-user-3")) + .endObject() + .endObject(); final String json = Strings.toString(jsonBuilder); // act @@ -168,48 +168,80 @@ public void testDeserialize() throws IOException { assertEquals(WildcardMatcher.from(Collections.singleton("test-user-1")), audit.getIgnoredAuditUsersMatcher()); assertEquals(WildcardMatcher.from(Collections.singleton("test-request")), audit.getIgnoredAuditRequestsMatcher()); assertTrue(configCompliance.shouldLogReadMetadataOnly()); - assertEquals(WildcardMatcher.from(Collections.singleton("test-user-2")), configCompliance.getIgnoredComplianceUsersForReadMatcher()); - assertEquals(Collections.singletonMap(WildcardMatcher.from("test-read-watch-field"), Collections.singleton("test-field-1")), configCompliance.getReadEnabledFields()); + assertEquals( + WildcardMatcher.from(Collections.singleton("test-user-2")), + configCompliance.getIgnoredComplianceUsersForReadMatcher() + ); + assertEquals( + Collections.singletonMap(WildcardMatcher.from("test-read-watch-field"), Collections.singleton("test-field-1")), + configCompliance.getReadEnabledFields() + ); assertTrue(configCompliance.shouldLogWriteMetadataOnly()); assertFalse(configCompliance.shouldLogDiffsForWrite()); - assertEquals(WildcardMatcher.from(Collections.singleton("test-user-3")), configCompliance.getIgnoredComplianceUsersForWriteMatcher()); + assertEquals( + WildcardMatcher.from(Collections.singleton("test-user-3")), + configCompliance.getIgnoredComplianceUsersForWriteMatcher() + ); assertEquals(WildcardMatcher.from("test-write-watch-index"), configCompliance.getWatchedWriteIndicesMatcher()); } @Test public void testSerialize() throws IOException { // arrange - final AuditConfig.Filter audit = new AuditConfig.Filter(true, true, true, true, true, true, ImmutableSet.of("ignore-user-1", "ignore-user-2"), ImmutableSet.of("ignore-request-1"), EnumSet.of(AuditCategory.FAILED_LOGIN, AuditCategory.GRANTED_PRIVILEGES), EnumSet.of(AUTHENTICATED)); - final ComplianceConfig compliance = new ComplianceConfig(true, true, true, true, Collections.singletonMap("test-read-watch-field-1", Collections.emptyList()), Collections.singleton("test-user-1"), true, false,Collections.singletonList("test-write-watch-index"), Collections.singleton("test-user-2"), Settings.EMPTY); + final AuditConfig.Filter audit = new AuditConfig.Filter( + true, + true, + true, + true, + true, + true, + ImmutableSet.of("ignore-user-1", "ignore-user-2"), + ImmutableSet.of("ignore-request-1"), + EnumSet.of(AuditCategory.FAILED_LOGIN, AuditCategory.GRANTED_PRIVILEGES), + EnumSet.of(AUTHENTICATED) + ); + final ComplianceConfig compliance = new ComplianceConfig( + true, + true, + true, + true, + Collections.singletonMap("test-read-watch-field-1", Collections.emptyList()), + Collections.singleton("test-user-1"), + true, + false, + Collections.singletonList("test-write-watch-index"), + Collections.singleton("test-user-2"), + Settings.EMPTY + ); final AuditConfig auditConfig = new AuditConfig(true, audit, compliance); final XContentBuilder jsonBuilder = XContentFactory.jsonBuilder() - .startObject() - .field("enabled", true) - .startObject("audit") - .field("enable_rest", true) - .field("disabled_rest_categories", ImmutableList.of("FAILED_LOGIN", "GRANTED_PRIVILEGES")) - .field("enable_transport", true) - .field("disabled_transport_categories", Collections.singletonList("AUTHENTICATED")) - .field("resolve_bulk_requests", true) - .field("log_request_body", true) - .field("resolve_indices", true) - .field("exclude_sensitive_headers", true) - .field("ignore_users", ImmutableList.of("ignore-user-1", "ignore-user-2")) - .field("ignore_requests", Collections.singletonList("ignore-request-1")) - .endObject() - .startObject("compliance") - .field("enabled", true) - .field("external_config", true) - .field("internal_config", true) - .field("read_metadata_only", true) - .field("read_watched_fields", Collections.singletonMap("test-read-watch-field-1", Collections.emptyList())) - .field("read_ignore_users", Collections.singletonList("test-user-1")) - .field("write_metadata_only", true) - .field("write_log_diffs", false) - .field("write_watched_indices", Collections.singletonList("test-write-watch-index")) - .field("write_ignore_users", Collections.singletonList("test-user-2")) - .endObject() - .endObject(); + .startObject() + .field("enabled", true) + .startObject("audit") + .field("enable_rest", true) + .field("disabled_rest_categories", ImmutableList.of("FAILED_LOGIN", "GRANTED_PRIVILEGES")) + .field("enable_transport", true) + .field("disabled_transport_categories", Collections.singletonList("AUTHENTICATED")) + .field("resolve_bulk_requests", true) + .field("log_request_body", true) + .field("resolve_indices", true) + .field("exclude_sensitive_headers", true) + .field("ignore_users", ImmutableList.of("ignore-user-1", "ignore-user-2")) + .field("ignore_requests", Collections.singletonList("ignore-request-1")) + .endObject() + .startObject("compliance") + .field("enabled", true) + .field("external_config", true) + .field("internal_config", true) + .field("read_metadata_only", true) + .field("read_watched_fields", Collections.singletonMap("test-read-watch-field-1", Collections.emptyList())) + .field("read_ignore_users", Collections.singletonList("test-user-1")) + .field("write_metadata_only", true) + .field("write_log_diffs", false) + .field("write_watched_indices", Collections.singletonList("test-write-watch-index")) + .field("write_ignore_users", Collections.singletonList("test-user-2")) + .endObject() + .endObject(); // act final String json = objectMapper.writeValueAsString(auditConfig); @@ -225,33 +257,33 @@ public void testNullSerialize() throws IOException { final ComplianceConfig compliance = ComplianceConfig.from(Collections.emptyMap(), Settings.EMPTY); final AuditConfig auditConfig = new AuditConfig(true, audit, compliance); final XContentBuilder jsonBuilder = XContentFactory.jsonBuilder() - .startObject() - .field("enabled", true) - .startObject("audit") - .field("enable_rest", true) - .field("disabled_rest_categories", ImmutableList.of("AUTHENTICATED", "GRANTED_PRIVILEGES")) - .field("enable_transport", true) - .field("disabled_transport_categories", ImmutableList.of("AUTHENTICATED", "GRANTED_PRIVILEGES")) - .field("resolve_bulk_requests", false) - .field("log_request_body", true) - .field("resolve_indices", true) - .field("exclude_sensitive_headers", true) - .field("ignore_users", ImmutableList.of("kibanaserver")) - .field("ignore_requests", Collections.emptyList()) - .endObject() - .startObject("compliance") - .field("enabled", true) - .field("external_config", false) - .field("internal_config", false) - .field("read_metadata_only", false) - .field("read_watched_fields", Collections.emptyMap()) - .field("read_ignore_users", Collections.singletonList("kibanaserver")) - .field("write_metadata_only", false) - .field("write_log_diffs", false) - .field("write_watched_indices", Collections.emptyList()) - .field("write_ignore_users", Collections.singletonList("kibanaserver")) - .endObject() - .endObject(); + .startObject() + .field("enabled", true) + .startObject("audit") + .field("enable_rest", true) + .field("disabled_rest_categories", ImmutableList.of("AUTHENTICATED", "GRANTED_PRIVILEGES")) + .field("enable_transport", true) + .field("disabled_transport_categories", ImmutableList.of("AUTHENTICATED", "GRANTED_PRIVILEGES")) + .field("resolve_bulk_requests", false) + .field("log_request_body", true) + .field("resolve_indices", true) + .field("exclude_sensitive_headers", true) + .field("ignore_users", ImmutableList.of("kibanaserver")) + .field("ignore_requests", Collections.emptyList()) + .endObject() + .startObject("compliance") + .field("enabled", true) + .field("external_config", false) + .field("internal_config", false) + .field("read_metadata_only", false) + .field("read_watched_fields", Collections.emptyMap()) + .field("read_ignore_users", Collections.singletonList("kibanaserver")) + .field("write_metadata_only", false) + .field("write_log_diffs", false) + .field("write_watched_indices", Collections.emptyList()) + .field("write_ignore_users", Collections.singletonList("kibanaserver")) + .endObject() + .endObject(); // act final String json = objectMapper.writeValueAsString(auditConfig); @@ -262,10 +294,7 @@ public void testNullSerialize() throws IOException { @Test public void testNullDeSerialize() throws IOException { // arrange - final String json = "{" + - "\"audit\":{}," + - "\"compliance\":{}" + - "}"; + final String json = "{" + "\"audit\":{}," + "\"compliance\":{}" + "}"; // act final AuditConfig auditConfig = objectMapper.readValue(json, AuditConfig.class); @@ -287,36 +316,38 @@ public void testNullDeSerialize() throws IOException { public void testCustomSettings() throws IOException { // arrange final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, "test-security-index") - .put(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT, "internal_opensearch") - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, - "test-auditlog-index") - .build(); + .put(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, "test-security-index") + .put(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT, "internal_opensearch") + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, + "test-auditlog-index" + ) + .build(); final ObjectMapper customObjectMapper = new ObjectMapper(); InjectableValues.Std iv = new InjectableValues.Std(); iv.addValue(Settings.class, settings); customObjectMapper.setInjectableValues(iv); final XContentBuilder jsonBuilder = XContentFactory.jsonBuilder() - .startObject() - .field("enabled", true) - .startObject("audit") - .field("enable_rest", true) - .field("enable_transport", true) - .field("resolve_bulk_requests", true) - .field("log_request_body", true) - .field("resolve_indices", true) - .field("exclude_sensitive_headers", true) - .endObject() - .startObject("compliance") - .field("enabled", true) - .field("external_config", true) - .field("internal_config", true) - .field("read_metadata_only", true) - .field("write_metadata_only", true) - .field("write_log_diffs", false) - .endObject() - .endObject(); + .startObject() + .field("enabled", true) + .startObject("audit") + .field("enable_rest", true) + .field("enable_transport", true) + .field("resolve_bulk_requests", true) + .field("log_request_body", true) + .field("resolve_indices", true) + .field("exclude_sensitive_headers", true) + .endObject() + .startObject("compliance") + .field("enabled", true) + .field("external_config", true) + .field("internal_config", true) + .field("read_metadata_only", true) + .field("write_metadata_only", true) + .field("write_log_diffs", false) + .endObject() + .endObject(); final String json = Strings.toString(jsonBuilder); // act diff --git a/src/test/java/org/opensearch/security/auditlog/config/ThreadPoolConfigTest.java b/src/test/java/org/opensearch/security/auditlog/config/ThreadPoolConfigTest.java index 914163a7f3..83c5e9ae2a 100644 --- a/src/test/java/org/opensearch/security/auditlog/config/ThreadPoolConfigTest.java +++ b/src/test/java/org/opensearch/security/auditlog/config/ThreadPoolConfigTest.java @@ -74,9 +74,9 @@ public void testConfig() { public void testGenerationFromSettings() { // arrange Settings settings = Settings.builder() - .put("plugins.security.audit.threadpool.size", "8") - .put("plugins.security.audit.threadpool.max_queue_len", "50") - .build(); + .put("plugins.security.audit.threadpool.size", "8") + .put("plugins.security.audit.threadpool.max_queue_len", "50") + .build(); // assert ThreadPoolConfig config = ThreadPoolConfig.getConfig(settings); diff --git a/src/test/java/org/opensearch/security/auditlog/helper/ErroneousHttpHandler.java b/src/test/java/org/opensearch/security/auditlog/helper/ErroneousHttpHandler.java index 120232825b..4ba106ddd9 100644 --- a/src/test/java/org/opensearch/security/auditlog/helper/ErroneousHttpHandler.java +++ b/src/test/java/org/opensearch/security/auditlog/helper/ErroneousHttpHandler.java @@ -17,8 +17,8 @@ import org.apache.hc.core5.http.protocol.HttpContext; public class ErroneousHttpHandler implements HttpRequestHandler { - @Override - public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) { - response.setCode(404); - } + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) { + response.setCode(404); + } } diff --git a/src/test/java/org/opensearch/security/auditlog/helper/FailingSink.java b/src/test/java/org/opensearch/security/auditlog/helper/FailingSink.java index 8557314ab1..0c915810e1 100644 --- a/src/test/java/org/opensearch/security/auditlog/helper/FailingSink.java +++ b/src/test/java/org/opensearch/security/auditlog/helper/FailingSink.java @@ -21,10 +21,10 @@ public FailingSink(String name, Settings settings, String sinkPrefix, AuditLogSi super(name, settings, null, fallbackSink); } - @Override - protected boolean doStore(AuditMessage msg) { - return false; - } + @Override + protected boolean doStore(AuditMessage msg) { + return false; + } @Override public boolean isHandlingBackpressure() { diff --git a/src/test/java/org/opensearch/security/auditlog/helper/LoggingSink.java b/src/test/java/org/opensearch/security/auditlog/helper/LoggingSink.java index 63b4e71958..8125acd8c4 100644 --- a/src/test/java/org/opensearch/security/auditlog/helper/LoggingSink.java +++ b/src/test/java/org/opensearch/security/auditlog/helper/LoggingSink.java @@ -20,16 +20,15 @@ public class LoggingSink extends AuditLogSink { - public List messages = new ArrayList(100); + public List messages = new ArrayList(100); public StringBuffer sb = new StringBuffer(); public LoggingSink(String name, Settings settings, String settingsPrefix, AuditLogSink fallbackSink) { super(name, settings, null, fallbackSink); } - public boolean doStore(AuditMessage msg) { - sb.append(msg.toPrettyString()+System.lineSeparator()); + sb.append(msg.toPrettyString() + System.lineSeparator()); messages.add(msg); return true; } diff --git a/src/test/java/org/opensearch/security/auditlog/helper/MockAuditMessageFactory.java b/src/test/java/org/opensearch/security/auditlog/helper/MockAuditMessageFactory.java index c0c24107ef..7e67fc374c 100644 --- a/src/test/java/org/opensearch/security/auditlog/helper/MockAuditMessageFactory.java +++ b/src/test/java/org/opensearch/security/auditlog/helper/MockAuditMessageFactory.java @@ -26,14 +26,14 @@ public class MockAuditMessageFactory { - public static AuditMessage validAuditMessage() { - return validAuditMessage(AuditCategory.FAILED_LOGIN); - } + public static AuditMessage validAuditMessage() { + return validAuditMessage(AuditCategory.FAILED_LOGIN); + } - public static AuditMessage validAuditMessage(AuditCategory category) { + public static AuditMessage validAuditMessage(AuditCategory category) { - ClusterService cs = mock(ClusterService.class); - DiscoveryNode dn = mock(DiscoveryNode.class); + ClusterService cs = mock(ClusterService.class); + DiscoveryNode dn = mock(DiscoveryNode.class); when(dn.getHostAddress()).thenReturn("hostaddress"); when(dn.getId()).thenReturn("hostaddress"); @@ -41,13 +41,13 @@ public static AuditMessage validAuditMessage(AuditCategory category) { when(cs.localNode()).thenReturn(dn); when(cs.getClusterName()).thenReturn(new ClusterName("testcluster")); - TransportAddress ta = new TransportAddress(new InetSocketAddress("8.8.8.8",80)); + TransportAddress ta = new TransportAddress(new InetSocketAddress("8.8.8.8", 80)); - AuditMessage msg = new AuditMessage(category, cs, Origin.TRANSPORT, Origin.TRANSPORT); - msg.addEffectiveUser("John Doe"); - msg.addRemoteAddress(ta); - msg.addRequestType("IndexRequest"); - return msg; - } + AuditMessage msg = new AuditMessage(category, cs, Origin.TRANSPORT, Origin.TRANSPORT); + msg.addEffectiveUser("John Doe"); + msg.addRemoteAddress(ta); + msg.addRequestType("IndexRequest"); + return msg; + } } diff --git a/src/test/java/org/opensearch/security/auditlog/helper/MockRestRequest.java b/src/test/java/org/opensearch/security/auditlog/helper/MockRestRequest.java index 6328c94352..d2b15750ff 100644 --- a/src/test/java/org/opensearch/security/auditlog/helper/MockRestRequest.java +++ b/src/test/java/org/opensearch/security/auditlog/helper/MockRestRequest.java @@ -20,8 +20,8 @@ public class MockRestRequest extends RestRequest { public MockRestRequest() { - //NamedXContentRegistry xContentRegistry, Map params, String path, - //Map> headers, HttpRequest httpRequest, HttpChannel httpChannel + // NamedXContentRegistry xContentRegistry, Map params, String path, + // Map> headers, HttpRequest httpRequest, HttpChannel httpChannel super(NamedXContentRegistry.EMPTY, Collections.emptyMap(), "", Collections.emptyMap(), null, null); } diff --git a/src/test/java/org/opensearch/security/auditlog/helper/MyOwnAuditLog.java b/src/test/java/org/opensearch/security/auditlog/helper/MyOwnAuditLog.java index d1138c7c9b..8df27f0d1d 100644 --- a/src/test/java/org/opensearch/security/auditlog/helper/MyOwnAuditLog.java +++ b/src/test/java/org/opensearch/security/auditlog/helper/MyOwnAuditLog.java @@ -23,19 +23,26 @@ public class MyOwnAuditLog extends AuditLogSink { - public MyOwnAuditLog(final String name, final Settings settings, final String settingsPrefix, final Path configPath, final ThreadPool threadPool, - final IndexNameExpressionResolver resolver, final ClusterService clusterService, AuditLogSink fallbackSink) { + public MyOwnAuditLog( + final String name, + final Settings settings, + final String settingsPrefix, + final Path configPath, + final ThreadPool threadPool, + final IndexNameExpressionResolver resolver, + final ClusterService clusterService, + AuditLogSink fallbackSink + ) { super(name, settings, settingsPrefix, fallbackSink); } @Override - public void close() throws IOException { - - } + public void close() throws IOException { + } - public boolean doStore(AuditMessage msg) { - return true; - } + public boolean doStore(AuditMessage msg) { + return true; + } } diff --git a/src/test/java/org/opensearch/security/auditlog/helper/RetrySink.java b/src/test/java/org/opensearch/security/auditlog/helper/RetrySink.java index f26188bed7..0d41111f25 100644 --- a/src/test/java/org/opensearch/security/auditlog/helper/RetrySink.java +++ b/src/test/java/org/opensearch/security/auditlog/helper/RetrySink.java @@ -28,8 +28,8 @@ public RetrySink(String name, Settings settings, String sinkPrefix, AuditLogSink @Override protected synchronized boolean doStore(AuditMessage msg) { - if(failCount++ < 5) { - log.debug("Fail "+failCount); + if (failCount++ < 5) { + log.debug("Fail " + failCount); return false; } log.debug("doStore ok"); diff --git a/src/test/java/org/opensearch/security/auditlog/helper/SlowSink.java b/src/test/java/org/opensearch/security/auditlog/helper/SlowSink.java index f291b76cda..376c29cf80 100644 --- a/src/test/java/org/opensearch/security/auditlog/helper/SlowSink.java +++ b/src/test/java/org/opensearch/security/auditlog/helper/SlowSink.java @@ -21,13 +21,12 @@ public SlowSink(String name, Settings settings, Settings sinkSetting, AuditLogSi super(name, settings, null, fallbackSink); } - public boolean doStore(AuditMessage msg) { - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - e.printStackTrace(); - } + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } return true; } diff --git a/src/test/java/org/opensearch/security/auditlog/helper/TestHttpHandler.java b/src/test/java/org/opensearch/security/auditlog/helper/TestHttpHandler.java index d888949e46..d4f68e8291 100644 --- a/src/test/java/org/opensearch/security/auditlog/helper/TestHttpHandler.java +++ b/src/test/java/org/opensearch/security/auditlog/helper/TestHttpHandler.java @@ -23,22 +23,22 @@ import org.apache.hc.core5.http.protocol.HttpContext; public class TestHttpHandler implements HttpRequestHandler { - public String method; - public String uri; - public String body; + public String method; + public String uri; + public String body; - @Override - public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { - this.method = request.getMethod(); - this.uri = request.getRequestUri(); + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException { + this.method = request.getMethod(); + this.uri = request.getRequestUri(); - HttpEntity entity = request.getEntity(); - body = EntityUtils.toString(entity, StandardCharsets.UTF_8); - } + HttpEntity entity = request.getEntity(); + body = EntityUtils.toString(entity, StandardCharsets.UTF_8); + } - public void reset() { - this.body = null; - this.uri = null; - this.method = null; - } + public void reset() { + this.body = null; + this.uri = null; + this.method = null; + } } diff --git a/src/test/java/org/opensearch/security/auditlog/impl/AuditCategoryTest.java b/src/test/java/org/opensearch/security/auditlog/impl/AuditCategoryTest.java index abd8260fa7..18670f42cf 100644 --- a/src/test/java/org/opensearch/security/auditlog/impl/AuditCategoryTest.java +++ b/src/test/java/org/opensearch/security/auditlog/impl/AuditCategoryTest.java @@ -42,19 +42,32 @@ public AuditCategoryParseTest(List input, EnumSet expecte @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[][]{ - {Arrays.asList(), EnumSet.noneOf(AuditCategory.class)}, - {Arrays.asList("BAD_HEADERS"), EnumSet.of(BAD_HEADERS)}, - {Arrays.asList("bad_headers"), EnumSet.of(BAD_HEADERS)}, - {Arrays.asList("bAd_HeAdErS"), EnumSet.of(BAD_HEADERS)}, - {Arrays.asList("bAd_HeAdErS"), EnumSet.of(BAD_HEADERS)}, - {Arrays.asList("BAD_HEADERS", "AUTHENTICATED"), EnumSet.of(BAD_HEADERS, AUTHENTICATED)}, - {Arrays.asList("BAD_HEADERS", "FAILED_LOGIN", "MISSING_PRIVILEGES", "GRANTED_PRIVILEGES", - "OPENDISTRO_SECURITY_INDEX_ATTEMPT", "SSL_EXCEPTION", "AUTHENTICATED", "INDEX_EVENT", - "COMPLIANCE_DOC_READ", "COMPLIANCE_DOC_WRITE", "COMPLIANCE_EXTERNAL_CONFIG", - "COMPLIANCE_INTERNAL_CONFIG_READ", "COMPLIANCE_INTERNAL_CONFIG_WRITE" - ), EnumSet.allOf(AuditCategory.class)}, - }); + return Arrays.asList( + new Object[][] { + { Arrays.asList(), EnumSet.noneOf(AuditCategory.class) }, + { Arrays.asList("BAD_HEADERS"), EnumSet.of(BAD_HEADERS) }, + { Arrays.asList("bad_headers"), EnumSet.of(BAD_HEADERS) }, + { Arrays.asList("bAd_HeAdErS"), EnumSet.of(BAD_HEADERS) }, + { Arrays.asList("bAd_HeAdErS"), EnumSet.of(BAD_HEADERS) }, + { Arrays.asList("BAD_HEADERS", "AUTHENTICATED"), EnumSet.of(BAD_HEADERS, AUTHENTICATED) }, + { + Arrays.asList( + "BAD_HEADERS", + "FAILED_LOGIN", + "MISSING_PRIVILEGES", + "GRANTED_PRIVILEGES", + "OPENDISTRO_SECURITY_INDEX_ATTEMPT", + "SSL_EXCEPTION", + "AUTHENTICATED", + "INDEX_EVENT", + "COMPLIANCE_DOC_READ", + "COMPLIANCE_DOC_WRITE", + "COMPLIANCE_EXTERNAL_CONFIG", + "COMPLIANCE_INTERNAL_CONFIG_READ", + "COMPLIANCE_INTERNAL_CONFIG_WRITE" + ), + EnumSet.allOf(AuditCategory.class) }, } + ); } @Test @@ -75,10 +88,9 @@ public AuditCategoryExceptionTest(List input) { @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[][]{ - {Arrays.asList("BAD_INPUT")}, - {Arrays.asList("BAD_HEADERS", "bad_category", "AUTHENTICATED")}, - }); + return Arrays.asList( + new Object[][] { { Arrays.asList("BAD_INPUT") }, { Arrays.asList("BAD_HEADERS", "bad_category", "AUTHENTICATED") }, } + ); } @Test(expected = IllegalArgumentException.class) diff --git a/src/test/java/org/opensearch/security/auditlog/impl/AuditMessageTest.java b/src/test/java/org/opensearch/security/auditlog/impl/AuditMessageTest.java index da408f1000..6bfaf32816 100644 --- a/src/test/java/org/opensearch/security/auditlog/impl/AuditMessageTest.java +++ b/src/test/java/org/opensearch/security/auditlog/impl/AuditMessageTest.java @@ -38,17 +38,25 @@ public class AuditMessageTest { private static final Map> TEST_REST_HEADERS = ImmutableMap.of( - "authorization", ImmutableList.of("test-1"), - "Authorization", ImmutableList.of("test-2"), - "AuThOrIzAtIoN", ImmutableList.of("test-3"), - "test-header", ImmutableList.of("test-4") + "authorization", + ImmutableList.of("test-1"), + "Authorization", + ImmutableList.of("test-2"), + "AuThOrIzAtIoN", + ImmutableList.of("test-3"), + "test-header", + ImmutableList.of("test-4") ); private static final Map TEST_TRANSPORT_HEADERS = ImmutableMap.of( - "authorization", "test-1", - "Authorization", "test-2", - "AuThOrIzAtIoN","test-3", - "test-header", "test-4" + "authorization", + "test-1", + "Authorization", + "test-2", + "AuThOrIzAtIoN", + "test-3", + "test-header", + "test-4" ); private AuditMessage message; @@ -58,10 +66,7 @@ public void setUp() { final ClusterService clusterServiceMock = mock(ClusterService.class); when(clusterServiceMock.localNode()).thenReturn(mock(DiscoveryNode.class)); when(clusterServiceMock.getClusterName()).thenReturn(mock(ClusterName.class)); - message = new AuditMessage(AuditCategory.AUTHENTICATED, - clusterServiceMock, - AuditLog.Origin.REST, - AuditLog.Origin.REST); + message = new AuditMessage(AuditCategory.AUTHENTICATED, clusterServiceMock, AuditLog.Origin.REST, AuditLog.Origin.REST); } @Test @@ -131,7 +136,7 @@ public void testBCryptHashIsRedacted() { assertEquals("Diff is __HASH__", message.getAsMap().get(AuditMessage.COMPLIANCE_DIFF_CONTENT)); // test tuple redaction - final ByteBuffer[] byteBuffers = new ByteBuffer[]{ ByteBuffer.wrap(("Hash in tuple is " + hash1).getBytes()) }; + final ByteBuffer[] byteBuffers = new ByteBuffer[] { ByteBuffer.wrap(("Hash in tuple is " + hash1).getBytes()) }; BytesReference ref = BytesReference.fromByteBuffers(byteBuffers); message.addSecurityConfigTupleToRequestBody(new Tuple<>(XContentType.JSON, ref), internalUsersDocId); assertEquals("Hash in tuple is __HASH__", message.getAsMap().get(AuditMessage.REQUEST_BODY)); diff --git a/src/test/java/org/opensearch/security/auditlog/impl/AuditlogTest.java b/src/test/java/org/opensearch/security/auditlog/impl/AuditlogTest.java index e72b85694f..cd4ced3523 100644 --- a/src/test/java/org/opensearch/security/auditlog/impl/AuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/impl/AuditlogTest.java @@ -49,9 +49,9 @@ public void setup() { @Test public void testClusterHealthRequest() { Settings settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .build(); AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); TestAuditlogImpl.clear(); al.logGrantedPrivileges("indices:data/read/search", new ClusterHealthRequest(), null); @@ -62,13 +62,13 @@ public void testClusterHealthRequest() { public void testSearchRequest() { SearchRequest sr = new SearchRequest(); - sr.indices("index1","logstash*"); + sr.indices("index1", "logstash*"); Settings settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .build(); - AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .build(); + AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); TestAuditlogImpl.clear(); al.logGrantedPrivileges("indices:data/read/search", sr, null); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); @@ -78,12 +78,12 @@ public void testSearchRequest() { public void testSslException() { Settings settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) - .build(); - AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) + .build(); + AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); TestAuditlogImpl.clear(); al.logSSLException(null, new Exception("test rest")); al.logSSLException(null, new Exception("test rest"), null, null); @@ -97,14 +97,14 @@ public void testRetry() { RetrySink.init(); Settings settings = Settings.builder() - .put("plugins.security.audit.type", RetrySink.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) - .put(ConfigConstants.SECURITY_AUDIT_RETRY_COUNT, 10) - .put(ConfigConstants.SECURITY_AUDIT_RETRY_DELAY_MS, 500) - .build(); - AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); + .put("plugins.security.audit.type", RetrySink.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) + .put(ConfigConstants.SECURITY_AUDIT_RETRY_COUNT, 10) + .put(ConfigConstants.SECURITY_AUDIT_RETRY_DELAY_MS, 500) + .build(); + AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); al.logSSLException(null, new Exception("test retry")); Assert.assertNotNull(RetrySink.getMsg()); Assert.assertTrue(RetrySink.getMsg().toJson().contains("test retry")); @@ -116,36 +116,32 @@ public void testNoRetry() { RetrySink.init(); Settings settings = Settings.builder() - .put("plugins.security.audit.type", RetrySink.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) - .put(ConfigConstants.SECURITY_AUDIT_RETRY_COUNT, 0) - .put(ConfigConstants.SECURITY_AUDIT_RETRY_DELAY_MS, 500) - .build(); - AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); + .put("plugins.security.audit.type", RetrySink.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) + .put(ConfigConstants.SECURITY_AUDIT_RETRY_COUNT, 0) + .put(ConfigConstants.SECURITY_AUDIT_RETRY_DELAY_MS, 500) + .build(); + AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); al.logSSLException(null, new Exception("test retry")); Assert.assertNull(RetrySink.getMsg()); } @Test public void testRestFilterEnabledCheck() { - final Settings settings = Settings.builder() - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) - .build(); - final AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); - for (AuditCategory category: AuditCategory.values()) { + final Settings settings = Settings.builder().put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false).build(); + final AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); + for (AuditCategory category : AuditCategory.values()) { Assert.assertFalse(al.checkRestFilter(category, "user", mock(RestRequest.class))); } } @Test public void testTransportFilterEnabledCheck() { - final Settings settings = Settings.builder() - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) - .build(); - final AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); - for (AuditCategory category: AuditCategory.values()) { + final Settings settings = Settings.builder().put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false).build(); + final AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); + for (AuditCategory category : AuditCategory.values()) { Assert.assertFalse(al.checkTransportFilter(category, "action", "user", mock(TransportRequest.class))); } } @@ -153,11 +149,11 @@ public void testTransportFilterEnabledCheck() { @Test public void testTransportFilterMonitorActionsCheck() { final Settings settings = Settings.builder() - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .build(); - final AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); - for (AuditCategory category: AuditCategory.values()) { + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .build(); + final AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); + for (AuditCategory category : AuditCategory.values()) { Assert.assertTrue(al.checkTransportFilter(category, "cluster:monitor/any", "user", mock(TransportRequest.class))); Assert.assertTrue(al.checkTransportFilter(category, "indices:data/any", "user", mock(TransportRequest.class))); Assert.assertFalse(al.checkTransportFilter(category, "internal:any", "user", mock(TransportRequest.class))); diff --git a/src/test/java/org/opensearch/security/auditlog/impl/DelegateTest.java b/src/test/java/org/opensearch/security/auditlog/impl/DelegateTest.java index a88c73e3bf..fa176a2011 100644 --- a/src/test/java/org/opensearch/security/auditlog/impl/DelegateTest.java +++ b/src/test/java/org/opensearch/security/auditlog/impl/DelegateTest.java @@ -22,28 +22,28 @@ import org.opensearch.security.auditlog.sink.InternalOpenSearchSink; public class DelegateTest { - @Test - public void auditLogTypeTest() throws Exception{ - testAuditType("DeBUg", DebugSink.class); - testAuditType("intERnal_OpenSearch", InternalOpenSearchSink.class); - testAuditType("EXTERnal_OpenSearch", ExternalOpenSearchSink.class); - testAuditType("org.opensearch.security.auditlog.sink.MyOwnAuditLog", MyOwnAuditLog.class); - testAuditType("org.opensearch.security.auditlog.sink.MyOwnAuditLog", null); - testAuditType("idonotexist", null); - } + @Test + public void auditLogTypeTest() throws Exception { + testAuditType("DeBUg", DebugSink.class); + testAuditType("intERnal_OpenSearch", InternalOpenSearchSink.class); + testAuditType("EXTERnal_OpenSearch", ExternalOpenSearchSink.class); + testAuditType("org.opensearch.security.auditlog.sink.MyOwnAuditLog", MyOwnAuditLog.class); + testAuditType("org.opensearch.security.auditlog.sink.MyOwnAuditLog", null); + testAuditType("idonotexist", null); + } - private void testAuditType(String type, Class expectedClass) throws Exception { - Builder settingsBuilder = Settings.builder(); - settingsBuilder.put("plugins.security.audit.type", type); - settingsBuilder.put("path.home", "."); - AuditLogImpl auditLog = new AuditLogImpl(settingsBuilder.build(), null, null, null, null, null); - auditLog.close(); -// if (expectedClass != null) { -// Assert.assertNotNull("delegate is null for type: "+type,auditLog.delegate); -// Assert.assertEquals(expectedClass, auditLog.delegate.getClass()); -// } else { -// Assert.assertNull(auditLog.delegate); -// } + private void testAuditType(String type, Class expectedClass) throws Exception { + Builder settingsBuilder = Settings.builder(); + settingsBuilder.put("plugins.security.audit.type", type); + settingsBuilder.put("path.home", "."); + AuditLogImpl auditLog = new AuditLogImpl(settingsBuilder.build(), null, null, null, null, null); + auditLog.close(); + // if (expectedClass != null) { + // Assert.assertNotNull("delegate is null for type: "+type,auditLog.delegate); + // Assert.assertEquals(expectedClass, auditLog.delegate.getClass()); + // } else { + // Assert.assertNull(auditLog.delegate); + // } - } + } } diff --git a/src/test/java/org/opensearch/security/auditlog/impl/DisabledCategoriesTest.java b/src/test/java/org/opensearch/security/auditlog/impl/DisabledCategoriesTest.java index db7d3da0fb..2a37749fbe 100644 --- a/src/test/java/org/opensearch/security/auditlog/impl/DisabledCategoriesTest.java +++ b/src/test/java/org/opensearch/security/auditlog/impl/DisabledCategoriesTest.java @@ -45,8 +45,8 @@ public class DisabledCategoriesTest { ClusterService cs = mock(ClusterService.class); DiscoveryNode dn = mock(DiscoveryNode.class); - @Rule - public ExpectedException thrown = ExpectedException.none(); + @Rule + public ExpectedException thrown = ExpectedException.none(); @Before public void setup() { @@ -58,195 +58,220 @@ public void setup() { TestAuditlogImpl.clear(); } - @Test - public void invalidRestCategoryConfigurationTest() { - thrown.expect(IllegalArgumentException.class); + @Test + public void invalidRestCategoryConfigurationTest() { + thrown.expect(IllegalArgumentException.class); - Builder settingsBuilder = Settings.builder(); - settingsBuilder.put("plugins.security.audit.type", TestAuditlogImpl.class.getName()); + Builder settingsBuilder = Settings.builder(); + settingsBuilder.put("plugins.security.audit.type", TestAuditlogImpl.class.getName()); settingsBuilder.put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "nonexistent"); - AuditTestUtils.createAuditLog(settingsBuilder.build(), null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); - } - - @Test - public void invalidTransportCategoryConfigurationTest() { - thrown.expect(IllegalArgumentException.class); - - Builder settingsBuilder = Settings.builder(); - settingsBuilder.put("plugins.security.audit.type", TestAuditlogImpl.class.getName()); - settingsBuilder.put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "nonexistent"); - AuditTestUtils.createAuditLog(settingsBuilder.build(), null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); - } - - @Test - public void invalidConfigurationTest() { - Builder settingsBuilder = Settings.builder(); - settingsBuilder.put("plugins.security.audit.type", "debug"); - settingsBuilder.put("plugins.security.audit.config.disabled_categories", "nonexistant, bad_headers"); - AbstractAuditLog auditLog = AuditTestUtils.createAuditLog(settingsBuilder.build(), null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); - logAll(auditLog); - String result = TestAuditlogImpl.sb.toString(); - Assert.assertFalse(categoriesPresentInLog(result, AuditCategory.BAD_HEADERS)); - } - - @Test - public void enableAllCategoryTest() throws Exception { - final Builder settingsBuilder = Settings.builder(); - - settingsBuilder.put("plugins.security.audit.type", TestAuditlogImpl.class.getName()); - settingsBuilder.put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE"); + AuditTestUtils.createAuditLog(settingsBuilder.build(), null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); + } + + @Test + public void invalidTransportCategoryConfigurationTest() { + thrown.expect(IllegalArgumentException.class); + + Builder settingsBuilder = Settings.builder(); + settingsBuilder.put("plugins.security.audit.type", TestAuditlogImpl.class.getName()); + settingsBuilder.put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "nonexistent"); + AuditTestUtils.createAuditLog(settingsBuilder.build(), null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); + } + + @Test + public void invalidConfigurationTest() { + Builder settingsBuilder = Settings.builder(); + settingsBuilder.put("plugins.security.audit.type", "debug"); + settingsBuilder.put("plugins.security.audit.config.disabled_categories", "nonexistant, bad_headers"); + AbstractAuditLog auditLog = AuditTestUtils.createAuditLog( + settingsBuilder.build(), + null, + null, + AbstractSecurityUnitTest.MOCK_POOL, + null, + cs + ); + logAll(auditLog); + String result = TestAuditlogImpl.sb.toString(); + Assert.assertFalse(categoriesPresentInLog(result, AuditCategory.BAD_HEADERS)); + } + + @Test + public void enableAllCategoryTest() throws Exception { + final Builder settingsBuilder = Settings.builder(); + + settingsBuilder.put("plugins.security.audit.type", TestAuditlogImpl.class.getName()); + settingsBuilder.put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE"); settingsBuilder.put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE"); - // we use the debug output, no OpenSearch client is needed. Also, we - // do not need to close. - AbstractAuditLog auditLog = AuditTestUtils.createAuditLog(settingsBuilder.build(), null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); - logAll(auditLog); - - // we're using the ExecutorService in AuditLogImpl, so we need to wait - // until all tasks are finished before we can check the result - auditLog.close(); - - String result = TestAuditlogImpl.sb.toString(); - - Assert.assertTrue(AuditCategory.values()+"#"+result, categoriesPresentInLog(result, filterComplianceCategories(AuditCategory.values()))); - - Assert.assertThat(result, containsString("testuser.rest.succeededlogin")); - Assert.assertThat(result, containsString("testuser.rest.failedlogin")); - Assert.assertThat(result, containsString("privilege.missing")); - Assert.assertThat(result, containsString("action.indexattempt")); - Assert.assertThat(result, containsString("action.transport.ssl")); - Assert.assertThat(result, containsString("action.success")); - Assert.assertThat(result, containsString("Empty")); - } - - @Test - public void disableSingleCategoryTest() throws Exception { - for (AuditCategory category : AuditCategory.values()) { - TestAuditlogImpl.clear(); - checkCategoriesDisabled(category); - } - } - - @Test - public void disableAllCategoryTest() throws Exception{ - checkCategoriesDisabled(AuditCategory.values()); - } - - @Test - public void disableSomeCategoryTest() throws Exception{ - checkCategoriesDisabled(AuditCategory.AUTHENTICATED, AuditCategory.BAD_HEADERS, AuditCategory.FAILED_LOGIN); - } - - /*@After - public void restoreOut() { - System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out))); - }*/ - - protected void checkCategoriesDisabled(AuditCategory... disabledCategories) throws Exception { - - List categoryNames = new LinkedList<>(); - for (AuditCategory category : disabledCategories) { - categoryNames.add(category.name().toLowerCase()); - } - String disabledCategoriesString = Joiner.on(",").join(categoryNames); - - Builder settingsBuilder = Settings.builder(); - settingsBuilder.put("plugins.security.audit.type", TestAuditlogImpl.class.getName()); - settingsBuilder.put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, disabledCategoriesString); - settingsBuilder.put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, disabledCategoriesString); + // we use the debug output, no OpenSearch client is needed. Also, we + // do not need to close. + AbstractAuditLog auditLog = AuditTestUtils.createAuditLog( + settingsBuilder.build(), + null, + null, + AbstractSecurityUnitTest.MOCK_POOL, + null, + cs + ); + logAll(auditLog); + + // we're using the ExecutorService in AuditLogImpl, so we need to wait + // until all tasks are finished before we can check the result + auditLog.close(); + + String result = TestAuditlogImpl.sb.toString(); + + Assert.assertTrue( + AuditCategory.values() + "#" + result, + categoriesPresentInLog(result, filterComplianceCategories(AuditCategory.values())) + ); + + Assert.assertThat(result, containsString("testuser.rest.succeededlogin")); + Assert.assertThat(result, containsString("testuser.rest.failedlogin")); + Assert.assertThat(result, containsString("privilege.missing")); + Assert.assertThat(result, containsString("action.indexattempt")); + Assert.assertThat(result, containsString("action.transport.ssl")); + Assert.assertThat(result, containsString("action.success")); + Assert.assertThat(result, containsString("Empty")); + } + + @Test + public void disableSingleCategoryTest() throws Exception { + for (AuditCategory category : AuditCategory.values()) { + TestAuditlogImpl.clear(); + checkCategoriesDisabled(category); + } + } + + @Test + public void disableAllCategoryTest() throws Exception { + checkCategoriesDisabled(AuditCategory.values()); + } + @Test + public void disableSomeCategoryTest() throws Exception { + checkCategoriesDisabled(AuditCategory.AUTHENTICATED, AuditCategory.BAD_HEADERS, AuditCategory.FAILED_LOGIN); + } - // we use the debug output, no OpenSearch client is needed. Also, we - // do not need to close. - AbstractAuditLog auditLog = AuditTestUtils.createAuditLog(settingsBuilder.build(), null, null, AbstractSecurityUnitTest.MOCK_POOL, null, cs); - logAll(auditLog); + /*@After + public void restoreOut() { + System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out))); + }*/ - auditLog.close(); + protected void checkCategoriesDisabled(AuditCategory... disabledCategories) throws Exception { - String result = TestAuditlogImpl.sb.toString(); + List categoryNames = new LinkedList<>(); + for (AuditCategory category : disabledCategories) { + categoryNames.add(category.name().toLowerCase()); + } + String disabledCategoriesString = Joiner.on(",").join(categoryNames); - List allButDisablesCategories = new LinkedList<>(Arrays.asList(AuditCategory.values())); - allButDisablesCategories.removeAll(Arrays.asList(disabledCategories)); + Builder settingsBuilder = Settings.builder(); + settingsBuilder.put("plugins.security.audit.type", TestAuditlogImpl.class.getName()); + settingsBuilder.put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, disabledCategoriesString); + settingsBuilder.put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, disabledCategoriesString); - System.out.println(result+"###"+disabledCategoriesString); - Assert.assertFalse(categoriesPresentInLog(result, disabledCategories)); - Assert.assertTrue(categoriesPresentInLog(result, filterComplianceCategories(allButDisablesCategories.toArray(new AuditCategory[] {})))); - } + // we use the debug output, no OpenSearch client is needed. Also, we + // do not need to close. + AbstractAuditLog auditLog = AuditTestUtils.createAuditLog( + settingsBuilder.build(), + null, + null, + AbstractSecurityUnitTest.MOCK_POOL, + null, + cs + ); + logAll(auditLog); + + auditLog.close(); + + String result = TestAuditlogImpl.sb.toString(); + + List allButDisablesCategories = new LinkedList<>(Arrays.asList(AuditCategory.values())); + allButDisablesCategories.removeAll(Arrays.asList(disabledCategories)); + + System.out.println(result + "###" + disabledCategoriesString); + Assert.assertFalse(categoriesPresentInLog(result, disabledCategories)); + Assert.assertTrue( + categoriesPresentInLog(result, filterComplianceCategories(allButDisablesCategories.toArray(new AuditCategory[] {}))) + ); + } - protected boolean categoriesPresentInLog(String result, AuditCategory... categories) { - // since we're logging a JSON structure, whitespaces between keys and - // values must not matter - result = result.replaceAll(" ", ""); - for (AuditCategory category : categories) { - if(!result.contains("\""+AuditMessage.CATEGORY+"\":\""+category.name()+"\"")) { - System.out.println("MISSING: "+category.name()); - return false; - } - } - return true; - } + protected boolean categoriesPresentInLog(String result, AuditCategory... categories) { + // since we're logging a JSON structure, whitespaces between keys and + // values must not matter + result = result.replaceAll(" ", ""); + for (AuditCategory category : categories) { + if (!result.contains("\"" + AuditMessage.CATEGORY + "\":\"" + category.name() + "\"")) { + System.out.println("MISSING: " + category.name()); + return false; + } + } + return true; + } - protected void logAll(AuditLog auditLog) { - logRestFailedLogin(auditLog); - logRestBadHeaders(auditLog); - logRestSSLException(auditLog); - logRestSucceededLogin(auditLog); + protected void logAll(AuditLog auditLog) { + logRestFailedLogin(auditLog); + logRestBadHeaders(auditLog); + logRestSSLException(auditLog); + logRestSucceededLogin(auditLog); - logMissingPrivileges(auditLog); - logSecurityIndexAttempt(auditLog); - logAuthenticatedRequest(auditLog); + logMissingPrivileges(auditLog); + logSecurityIndexAttempt(auditLog); + logAuthenticatedRequest(auditLog); - logTransportSSLException(auditLog); - logTransportBadHeaders(auditLog); + logTransportSSLException(auditLog); + logTransportBadHeaders(auditLog); - logIndexEvent(auditLog); + logIndexEvent(auditLog); } - protected void logRestSucceededLogin(AuditLog auditLog) { - auditLog.logSucceededLogin("testuser.rest.succeededlogin", false, "testuser.rest.succeededlogin", new MockRestRequest()); - } + protected void logRestSucceededLogin(AuditLog auditLog) { + auditLog.logSucceededLogin("testuser.rest.succeededlogin", false, "testuser.rest.succeededlogin", new MockRestRequest()); + } protected void logRestFailedLogin(AuditLog auditLog) { - auditLog.logFailedLogin("testuser.rest.failedlogin", false, "testuser.rest.failedlogin", new MockRestRequest()); + auditLog.logFailedLogin("testuser.rest.failedlogin", false, "testuser.rest.failedlogin", new MockRestRequest()); } protected void logMissingPrivileges(AuditLog auditLog) { - auditLog.logMissingPrivileges("privilege.missing", new TransportRequest.Empty(), null); + auditLog.logMissingPrivileges("privilege.missing", new TransportRequest.Empty(), null); } protected void logTransportBadHeaders(AuditLog auditLog) { - auditLog.logBadHeaders(new TransportRequest.Empty(),"action", null); + auditLog.logBadHeaders(new TransportRequest.Empty(), "action", null); } protected void logRestBadHeaders(AuditLog auditLog) { - auditLog.logBadHeaders(new MockRestRequest()); + auditLog.logBadHeaders(new MockRestRequest()); } protected void logSecurityIndexAttempt(AuditLog auditLog) { - auditLog.logSecurityIndexAttempt(new TransportRequest.Empty(), "action.indexattempt", null); + auditLog.logSecurityIndexAttempt(new TransportRequest.Empty(), "action.indexattempt", null); } protected void logRestSSLException(AuditLog auditLog) { - auditLog.logSSLException(new MockRestRequest(), new Exception()); + auditLog.logSSLException(new MockRestRequest(), new Exception()); } protected void logTransportSSLException(AuditLog auditLog) { - auditLog.logSSLException(new TransportRequest.Empty(), new Exception(), "action.transport.ssl", null); + auditLog.logSSLException(new TransportRequest.Empty(), new Exception(), "action.transport.ssl", null); } protected void logAuthenticatedRequest(AuditLog auditLog) { - auditLog.logGrantedPrivileges("action.success", new TransportRequest.Empty(), null); + auditLog.logGrantedPrivileges("action.success", new TransportRequest.Empty(), null); } - protected void logIndexEvent(AuditLog auditLog) { - auditLog.logIndexEvent("indices:admin/test/action", new TransportRequest.Empty(), null); - } + protected void logIndexEvent(AuditLog auditLog) { + auditLog.logIndexEvent("indices:admin/test/action", new TransportRequest.Empty(), null); + } private static final AuditCategory[] filterComplianceCategories(AuditCategory[] cats) { List retval = new ArrayList(); - for(AuditCategory c: cats) { - if(!c.toString().startsWith("COMPLIANCE")) { + for (AuditCategory c : cats) { + if (!c.toString().startsWith("COMPLIANCE")) { retval.add(c); } } diff --git a/src/test/java/org/opensearch/security/auditlog/impl/IgnoreAuditUsersTest.java b/src/test/java/org/opensearch/security/auditlog/impl/IgnoreAuditUsersTest.java index 9939db53d8..036482e8f3 100644 --- a/src/test/java/org/opensearch/security/auditlog/impl/IgnoreAuditUsersTest.java +++ b/src/test/java/org/opensearch/security/auditlog/impl/IgnoreAuditUsersTest.java @@ -61,16 +61,21 @@ public static void initSearchRequest() { sr.indices("index1", "logstash*"); } - - @Test public void testConfiguredIgnoreUser() { Settings settings = Settings.builder() - .put("opendistro_security.audit.ignore_users", ignoreUser) - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .build(); - AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, newThreadPool(ConfigConstants.OPENDISTRO_SECURITY_USER, ignoreUserObj), null, cs); + .put("opendistro_security.audit.ignore_users", ignoreUser) + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .build(); + AbstractAuditLog al = AuditTestUtils.createAuditLog( + settings, + null, + null, + newThreadPool(ConfigConstants.OPENDISTRO_SECURITY_USER, ignoreUserObj), + null, + cs + ); TestAuditlogImpl.clear(); al.logGrantedPrivileges("indices:data/read/search", sr, null); Assert.assertEquals(0, TestAuditlogImpl.messages.size()); @@ -79,12 +84,19 @@ public void testConfiguredIgnoreUser() { @Test public void testNonConfiguredIgnoreUser() { Settings settings = Settings.builder() - .put("opendistro_security.audit.ignore_users", nonIgnoreUser) - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .build(); - AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, newThreadPool(ConfigConstants.OPENDISTRO_SECURITY_USER, ignoreUserObj), null, cs); + .put("opendistro_security.audit.ignore_users", nonIgnoreUser) + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .build(); + AbstractAuditLog al = AuditTestUtils.createAuditLog( + settings, + null, + null, + newThreadPool(ConfigConstants.OPENDISTRO_SECURITY_USER, ignoreUserObj), + null, + cs + ); TestAuditlogImpl.clear(); al.logGrantedPrivileges("indices:data/read/search", sr, null); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); @@ -93,11 +105,18 @@ public void testNonConfiguredIgnoreUser() { @Test public void testNonExistingIgnoreUser() { Settings settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .build(); - AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, newThreadPool(ConfigConstants.OPENDISTRO_SECURITY_USER, ignoreUserObj), null, cs); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .build(); + AbstractAuditLog al = AuditTestUtils.createAuditLog( + settings, + null, + null, + newThreadPool(ConfigConstants.OPENDISTRO_SECURITY_USER, ignoreUserObj), + null, + cs + ); TestAuditlogImpl.clear(); al.logGrantedPrivileges("indices:data/read/search", sr, null); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); @@ -108,78 +127,122 @@ public void testWildcards() { SearchRequest sr = new SearchRequest(); User user = new User("John Doe"); - //sr.putInContext(ConfigConstants.OPENDISTRO_SECURITY_USER, user); - //sr.putInContext(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, "8.8.8.8"); - //sr.putInContext(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL, "CN=kirk,OU=client,O=client,L=test,C=DE"); - //sr.putHeader("myheader", "hval"); - sr.indices("index1","logstash*"); - //sr.source("{\"query\": false}"); + // sr.putInContext(ConfigConstants.OPENDISTRO_SECURITY_USER, user); + // sr.putInContext(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, "8.8.8.8"); + // sr.putInContext(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL, "CN=kirk,OU=client,O=client,L=test,C=DE"); + // sr.putHeader("myheader", "hval"); + sr.indices("index1", "logstash*"); + // sr.source("{\"query\": false}"); Settings settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .putList("opendistro_security.audit.ignore_users", "*") - .build(); - - TransportAddress ta = new TransportAddress(new InetSocketAddress("8.8.8.8",80)); - - AbstractAuditLog al = AuditTestUtils.createAuditLog(settings, null, null, newThreadPool(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, ta, - ConfigConstants.OPENDISTRO_SECURITY_USER, new User("John Doe"), - ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL, "CN=kirk,OU=client,O=client,L=test,C=DE" - ), null, cs); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .putList("opendistro_security.audit.ignore_users", "*") + .build(); + + TransportAddress ta = new TransportAddress(new InetSocketAddress("8.8.8.8", 80)); + + AbstractAuditLog al = AuditTestUtils.createAuditLog( + settings, + null, + null, + newThreadPool( + ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, + ta, + ConfigConstants.OPENDISTRO_SECURITY_USER, + new User("John Doe"), + ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL, + "CN=kirk,OU=client,O=client,L=test,C=DE" + ), + null, + cs + ); TestAuditlogImpl.clear(); al.logGrantedPrivileges("indices:data/read/search", sr, null); Assert.assertEquals(0, TestAuditlogImpl.messages.size()); settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .putList("opendistro_security.audit.ignore_users", "xxx") - .build(); - al = AuditTestUtils.createAuditLog(settings, null, null, newThreadPool(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, ta, - ConfigConstants.OPENDISTRO_SECURITY_USER, new User("John Doe"), - ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL, "CN=kirk,OU=client,O=client,L=test,C=DE" - ), null, cs); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .putList("opendistro_security.audit.ignore_users", "xxx") + .build(); + al = AuditTestUtils.createAuditLog( + settings, + null, + null, + newThreadPool( + ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, + ta, + ConfigConstants.OPENDISTRO_SECURITY_USER, + new User("John Doe"), + ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL, + "CN=kirk,OU=client,O=client,L=test,C=DE" + ), + null, + cs + ); TestAuditlogImpl.clear(); al.logGrantedPrivileges("indices:data/read/search", sr, null); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .putList("opendistro_security.audit.ignore_users", "John Doe","Capatin Kirk") - .build(); - al = AuditTestUtils.createAuditLog(settings, null, null, newThreadPool(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, ta, - ConfigConstants.OPENDISTRO_SECURITY_USER, new User("John Doe"), - ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL, "CN=kirk,OU=client,O=client,L=test,C=DE" - ), null, cs); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .putList("opendistro_security.audit.ignore_users", "John Doe", "Capatin Kirk") + .build(); + al = AuditTestUtils.createAuditLog( + settings, + null, + null, + newThreadPool( + ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, + ta, + ConfigConstants.OPENDISTRO_SECURITY_USER, + new User("John Doe"), + ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL, + "CN=kirk,OU=client,O=client,L=test,C=DE" + ), + null, + cs + ); TestAuditlogImpl.clear(); al.logGrantedPrivileges("indices:data/read/search", sr, null); al.logSecurityIndexAttempt(sr, "indices:data/read/search", null); - al.logMissingPrivileges("indices:data/read/search",sr, null); + al.logMissingPrivileges("indices:data/read/search", sr, null); Assert.assertEquals(TestAuditlogImpl.messages.toString(), 0, TestAuditlogImpl.messages.size()); settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .putList("opendistro_security.audit.ignore_users", "Wil Riker","Capatin Kirk") - .build(); - al = AuditTestUtils.createAuditLog(settings, null, null, newThreadPool(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, ta, - ConfigConstants.OPENDISTRO_SECURITY_USER, new User("John Doe"), - ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL, "CN=kirk,OU=client,O=client,L=test,C=DE" - ), null, cs); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .putList("opendistro_security.audit.ignore_users", "Wil Riker", "Capatin Kirk") + .build(); + al = AuditTestUtils.createAuditLog( + settings, + null, + null, + newThreadPool( + ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, + ta, + ConfigConstants.OPENDISTRO_SECURITY_USER, + new User("John Doe"), + ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL, + "CN=kirk,OU=client,O=client,L=test,C=DE" + ), + null, + cs + ); TestAuditlogImpl.clear(); al.logGrantedPrivileges("indices:data/read/search", sr, null); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); } private static ThreadPool newThreadPool(Object... transients) { - ThreadPool tp = new ThreadPool(Settings.builder().put("node.name", "mock").build()); - for(int i=0;i 0); // disable - auditConfig = new AuditConfig(false, AuditConfig.Filter.from(ImmutableMap.of("disabled_rest_categories", Collections.emptySet(), "disabled_transport_categories", Collections.emptySet())) , ComplianceConfig.DEFAULT); + auditConfig = new AuditConfig( + false, + AuditConfig.Filter.from( + ImmutableMap.of("disabled_rest_categories", Collections.emptySet(), "disabled_transport_categories", Collections.emptySet()) + ), + ComplianceConfig.DEFAULT + ); updateAuditConfig(AuditTestUtils.createAuditPayload(auditConfig)); // assert no auditing @@ -99,13 +109,10 @@ public void testSimpleAuthenticatedLegacySetting() throws Exception { private void verifyAuthenticated(final Settings settings) throws Exception { setup(settings); - - final List messages = TestAuditlogImpl.doThenWaitForMessages( - () -> { - final HttpResponse response = rh.executeGetRequest("_search", encodeBasicHeader("admin", "admin")); - assertThat(response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - }, - /* expectedCount*/ 1); + 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)); @@ -116,22 +123,24 @@ private void verifyAuthenticated(final Settings settings) throws Exception { @Test public void testSSLPlainText() throws Exception { - //if this fails permanently look in the logs for an abstract method error or method not found error. - //needs proper ssl plugin version + // if this fails permanently look in the logs for an abstract method error or method not found error. + // needs proper ssl plugin version Settings additionalSettings = Settings.builder() - .put("plugins.security.ssl.http.enabled",true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .build(); setup(additionalSettings); final List messages = TestAuditlogImpl.doThenWaitForMessages(() -> { - final RuntimeException ex = Assert.assertThrows(RuntimeException.class, - () -> nonSslRestHelper().executeGetRequest("_search", encodeBasicHeader("admin", "admin"))); + final RuntimeException ex = Assert.assertThrows( + RuntimeException.class, + () -> nonSslRestHelper().executeGetRequest("_search", encodeBasicHeader("admin", "admin")) + ); Assert.assertEquals("org.apache.hc.core5.http.NoHttpResponseException", ex.getCause().getClass().getName()); }, 1); /* no retry on NotSslRecordException exceptions */ @@ -139,7 +148,7 @@ public void testSSLPlainText() throws Exception { 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)); } @@ -147,10 +156,10 @@ public void testSSLPlainText() throws Exception { public void testTaskId() throws Exception { Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .build(); setup(additionalSettings); setupStarfleetIndex(); @@ -169,8 +178,10 @@ public void testTaskId() throws Exception { Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("\"audit_request_effective_user\" : \"admin\"")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("REST")); Assert.assertFalse(TestAuditlogImpl.sb.toString().toLowerCase().contains("authorization")); - Assert.assertEquals(TestAuditlogImpl.messages.get(1).getAsMap().get(AuditMessage.TASK_ID), - TestAuditlogImpl.messages.get(1).getAsMap().get(AuditMessage.TASK_ID)); + Assert.assertEquals( + TestAuditlogImpl.messages.get(1).getAsMap().get(AuditMessage.TASK_ID), + TestAuditlogImpl.messages.get(1).getAsMap().get(AuditMessage.TASK_ID) + ); Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } @@ -178,10 +189,10 @@ public void testTaskId() throws Exception { public void testDefaultsRest() throws Exception { Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .build(); setup(additionalSettings); setupStarfleetIndex(); @@ -206,11 +217,11 @@ public void testDefaultsRest() throws Exception { @Test public void testGrantedPrivilegesRest() throws Exception { final Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, "opendistro_security_all_access") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "AUTHENTICATED") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, "opendistro_security_all_access") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "AUTHENTICATED") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .build(); setup(additionalSettings); setupStarfleetIndex(); @@ -221,10 +232,10 @@ public void testGrantedPrivilegesRest() throws Exception { @Test public void testMissingPrivilegesRest() throws Exception { final Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "AUTHENTICATED") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "AUTHENTICATED") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .build(); setup(additionalSettings); setupStarfleetIndex(); @@ -237,7 +248,7 @@ private void testPrivilegeRest(final int expectedStatus, final String endpoint, Assert.assertEquals(expectedStatus, response.getStatusCode()); final String auditlog = TestAuditlogImpl.sb.toString(); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); - Assert.assertTrue(auditlog.contains("\"audit_category\" : \"" + category +"\"")); + Assert.assertTrue(auditlog.contains("\"audit_category\" : \"" + category + "\"")); Assert.assertTrue(auditlog.contains("\"audit_rest_request_path\" : \"" + endpoint + "\"")); Assert.assertTrue(auditlog.contains("\"audit_request_effective_user\" : \"admin\"")); } @@ -246,12 +257,12 @@ private void testPrivilegeRest(final int expectedStatus, final String endpoint, public void testAuthenticated() 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_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .build(); + .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, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .build(); setup(additionalSettings); setupStarfleetIndex(); @@ -273,9 +284,7 @@ public void testAuthenticated() throws Exception { @Test public void testNonAuthenticated() throws Exception { - Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .build(); + Settings additionalSettings = Settings.builder().put("plugins.security.audit.type", TestAuditlogImpl.class.getName()).build(); setup(additionalSettings); setupStarfleetIndex(); @@ -303,28 +312,26 @@ public void testWrongUser() throws Exception { HttpResponse response = rh.executeGetRequest("", encodeBasicHeader("wronguser", "admin")); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusCode()); Thread.sleep(500); - Assert.assertTrue(TestAuditlogImpl.sb.toString(),TestAuditlogImpl.sb.toString().contains("FAILED_LOGIN")); - Assert.assertTrue(TestAuditlogImpl.sb.toString(),TestAuditlogImpl.sb.toString().contains("wronguser")); - Assert.assertTrue(TestAuditlogImpl.sb.toString(),TestAuditlogImpl.sb.toString().contains(AuditMessage.UTC_TIMESTAMP)); - Assert.assertFalse(TestAuditlogImpl.sb.toString(),TestAuditlogImpl.sb.toString().contains("AUTHENTICATED")); + Assert.assertTrue(TestAuditlogImpl.sb.toString(), TestAuditlogImpl.sb.toString().contains("FAILED_LOGIN")); + Assert.assertTrue(TestAuditlogImpl.sb.toString(), TestAuditlogImpl.sb.toString().contains("wronguser")); + Assert.assertTrue(TestAuditlogImpl.sb.toString(), TestAuditlogImpl.sb.toString().contains(AuditMessage.UTC_TIMESTAMP)); + Assert.assertFalse(TestAuditlogImpl.sb.toString(), TestAuditlogImpl.sb.toString().contains("AUTHENTICATED")); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } - public void testUnknownAuthorization() throws Exception { HttpResponse response = rh.executeGetRequest("", encodeBasicHeader("unknown", "unknown")); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusCode()); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("FAILED_LOGIN")); - Assert.assertFalse(TestAuditlogImpl.sb.toString(),TestAuditlogImpl.sb.toString().contains("Basic dW5rbm93bjp1bmtub3du")); + Assert.assertFalse(TestAuditlogImpl.sb.toString(), TestAuditlogImpl.sb.toString().contains("Basic dW5rbm93bjp1bmtub3du")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains(AuditMessage.UTC_TIMESTAMP)); Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("AUTHENTICATED")); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } - public void testUnauthenticated() throws Exception { System.out.println("#### testUnauthenticated"); @@ -349,7 +356,6 @@ public void testJustAuthenticated() throws Exception { Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } - public void testSecurityIndexAttempt() throws Exception { HttpResponse response = rh.executePutRequest(".opendistro_security/_doc/0", "{}", encodeBasicHeader("admin", "admin")); @@ -363,10 +369,13 @@ public void testSecurityIndexAttempt() throws Exception { Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } - public void testBadHeader() throws Exception { - HttpResponse response = rh.executeGetRequest("", new BasicHeader("_opendistro_security_bad", "bad"), encodeBasicHeader("admin", "admin")); + HttpResponse response = rh.executeGetRequest( + "", + new BasicHeader("_opendistro_security_bad", "bad"), + encodeBasicHeader("admin", "admin") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); Assert.assertFalse(TestAuditlogImpl.sb.toString(), TestAuditlogImpl.sb.toString().contains("AUTHENTICATED")); Assert.assertTrue(TestAuditlogImpl.sb.toString(), TestAuditlogImpl.sb.toString().contains("BAD_HEADERS")); @@ -375,7 +384,6 @@ public void testBadHeader() throws Exception { Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } - public void testMissingPriv() throws Exception { HttpResponse response = rh.executeGetRequest("sf/_search", encodeBasicHeader("worf", "worf")); @@ -390,13 +398,16 @@ public void testMissingPriv() throws Exception { Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } - public void testMsearch() throws Exception { + public void testMsearch() throws Exception { - String msearch = - "{\"index\":\"sf\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":0,\"query\":{\"match_all\":{}}}"+System.lineSeparator()+ - "{\"index\":\"sf\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":0,\"query\":{\"match_all\":{}}}"+System.lineSeparator(); + String msearch = "{\"index\":\"sf\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":0,\"query\":{\"match_all\":{}}}" + + System.lineSeparator() + + "{\"index\":\"sf\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":0,\"query\":{\"match_all\":{}}}" + + System.lineSeparator(); System.out.println("##### msaerch"); HttpResponse response = rh.executePostRequest("_msearch?pretty", msearch, encodeBasicHeader("admin", "admin")); @@ -409,24 +420,31 @@ public void testMsearch() throws Exception { Assert.assertEquals(TestAuditlogImpl.sb.toString(), 4, TestAuditlogImpl.messages.size()); Assert.assertFalse(TestAuditlogImpl.sb.toString().toLowerCase().contains("authorization")); Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); - } - + } public void testBulkAuth() throws Exception { System.out.println("#### testBulkAuth"); - String bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"worf\", \"_id\" : \"2\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - - "{ \"update\" : {\"_id\" : \"1\", \"_index\" : \"test\"} }"+System.lineSeparator()+ - "{ \"doc\" : {\"field\" : \"valuex\"} }"+System.lineSeparator()+ - "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"create\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value3x\" }"+System.lineSeparator(); - + String bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field1\" : \"value1\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"worf\", \"_id\" : \"2\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + + + "{ \"update\" : {\"_id\" : \"1\", \"_index\" : \"test\"} }" + + System.lineSeparator() + + "{ \"doc\" : {\"field\" : \"valuex\"} }" + + 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("admin", "admin")); System.out.println(TestAuditlogImpl.sb.toString()); @@ -438,24 +456,33 @@ public void testBulkAuth() throws Exception { Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("IndexRequest")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("audit_trace_task_parent_id")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("audit_trace_task_id")); - //may vary because we log shardrequests which are not predictable here + // may vary because we log shardrequests which are not predictable here Assert.assertTrue(TestAuditlogImpl.messages.size() >= 17); Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } public void testBulkNonAuth() throws Exception { - String bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"worf\", \"_id\" : \"2\" } }"+System.lineSeparator()+ - "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - - "{ \"update\" : {\"_id\" : \"1\", \"_index\" : \"test\"} }"+System.lineSeparator()+ - "{ \"doc\" : {\"field\" : \"valuex\"} }"+System.lineSeparator()+ - "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"create\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"field1\" : \"value3x\" }"+System.lineSeparator(); + String bulkBody = "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }" + + System.lineSeparator() + + "{ \"field1\" : \"value1\" }" + + System.lineSeparator() + + "{ \"index\" : { \"_index\" : \"worf\", \"_id\" : \"2\" } }" + + System.lineSeparator() + + "{ \"field2\" : \"value2\" }" + + System.lineSeparator() + + + + "{ \"update\" : {\"_id\" : \"1\", \"_index\" : \"test\"} }" + + System.lineSeparator() + + "{ \"doc\" : {\"field\" : \"valuex\"} }" + + 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")); System.out.println(response.getBody()); @@ -468,22 +495,21 @@ public void testBulkNonAuth() throws Exception { Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("MISSING_PRIVILEGES")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("indices:data/write/bulk[s]")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("IndexRequest")); - //may vary because we log shardrequests which are not predictable here + // may vary because we log shardrequests which are not predictable here Assert.assertTrue(TestAuditlogImpl.messages.size() >= 7); Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } public void testUpdateSettings() throws Exception { - String json = - "{"+ - "\"persistent\" : {"+ - "\"indices.recovery.*\" : null"+ - "},"+ - "\"transient\" : {"+ - "\"indices.recovery.*\" : null"+ - "}"+ - "}"; + String json = "{" + + "\"persistent\" : {" + + "\"indices.recovery.*\" : null" + + "}," + + "\"transient\" : {" + + "\"indices.recovery.*\" : null" + + "}" + + "}"; HttpResponse response = rh.executePutRequest("_cluster/settings", json, encodeBasicHeader("admin", "admin")); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); @@ -491,7 +517,7 @@ public void testUpdateSettings() throws Exception { Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("AUTHENTICATED")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("cluster:admin/settings/update")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("indices.recovery.*")); - //may vary because we log may hit cluster manager directly or not + // may vary because we log may hit cluster manager directly or not Assert.assertTrue(TestAuditlogImpl.messages.size() > 1); Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } @@ -500,14 +526,14 @@ public void testUpdateSettings() throws Exception { public void testIndexPattern() throws Exception { Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", "internal_opensearch") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .put("plugins.security.audit.threadpool.size", 10) //must be greater 0 - .put("plugins.security.audit.config.index", "'auditlog-'YYYY.MM.dd.ss") - .build(); + .put("plugins.security.audit.type", "internal_opensearch") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .put("plugins.security.audit.threadpool.size", 10) // must be greater 0 + .put("plugins.security.audit.config.index", "'auditlog-'YYYY.MM.dd.ss") + .build(); setup(additionalSettings); setupStarfleetIndex(); @@ -527,31 +553,59 @@ public void testIndexPattern() throws Exception { public void testAliases() 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_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .build(); + .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, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .build(); setup(additionalSettings); try (Client tc = getClient()) { tc.admin().indices().create(new CreateIndexRequest("copysf")).actionGet(); - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("starfleet","starfleet_academy","starfleet_library").alias("sf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire","vulcangov").alias("nonsf"))).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))).actionGet(); + tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("starfleet").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("starfleet_academy").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("starfleet_library").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("klingonempire").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.index(new IndexRequest("public").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + + tc.index(new IndexRequest("spock").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index(new IndexRequest("kirk").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("role01_role02").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + AliasActions.add().indices("starfleet", "starfleet_academy", "starfleet_library").alias("sf") + ) + ) + .actionGet(); + tc.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("klingonempire", "vulcangov").alias("nonsf")) + ) + .actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("public").alias("unrestricted"))) + .actionGet(); } TestAuditlogImpl.clear(); @@ -571,34 +625,56 @@ public void testAliases() throws Exception { public void testScroll() 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_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .build(); + .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, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .build(); setup(additionalSettings); try (Client tc = getClient()) { - for(int i=0; i<3; i++) - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + for (int i = 0; i < 3; i++) + tc.index( + new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); } TestAuditlogImpl.clear(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executeGetRequest("vulcangov/_search?scroll=1m&pretty=true", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("vulcangov/_search?scroll=1m&pretty=true", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); int start = res.getBody().indexOf("_scroll_id") + 15; - String scrollid = res.getBody().substring(start, res.getBody().indexOf("\"", start+1)); - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executePostRequest("/_search/scroll?pretty=true", "{\"scroll_id\" : \""+scrollid+"\"}", encodeBasicHeader("admin", "admin"))).getStatusCode()); + String scrollid = res.getBody().substring(start, res.getBody().indexOf("\"", start + 1)); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest( + "/_search/scroll?pretty=true", + "{\"scroll_id\" : \"" + scrollid + "\"}", + encodeBasicHeader("admin", "admin") + )).getStatusCode() + ); Assert.assertEquals(4, TestAuditlogImpl.messages.size()); - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executeGetRequest("vulcangov/_search?scroll=1m&pretty=true", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("vulcangov/_search?scroll=1m&pretty=true", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); start = res.getBody().indexOf("_scroll_id") + 15; - scrollid = res.getBody().substring(start, res.getBody().indexOf("\"", start+1)); - TestAuditlogImpl.clear(); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, (res=rh.executePostRequest("/_search/scroll?pretty=true", "{\"scroll_id\" : \""+scrollid+"\"}", encodeBasicHeader("admin2", "admin"))).getStatusCode()); + scrollid = res.getBody().substring(start, res.getBody().indexOf("\"", start + 1)); + TestAuditlogImpl.clear(); + Assert.assertEquals( + HttpStatus.SC_FORBIDDEN, + (res = rh.executePostRequest( + "/_search/scroll?pretty=true", + "{\"scroll_id\" : \"" + scrollid + "\"}", + encodeBasicHeader("admin2", "admin") + )).getStatusCode() + ); Thread.sleep(1000); System.out.println(TestAuditlogImpl.sb.toString()); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("InternalScrollSearchRequest")); @@ -611,21 +687,25 @@ public void testScroll() throws Exception { public void testAliasResolution() 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_ENABLE_REST, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .build(); setup(additionalSettings); - try (Client tc = getClient()) { - for(int i=0; i<3; i++) - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().alias("thealias").index("vulcangov"))).actionGet(); + for (int i = 0; i < 3; i++) + tc.index( + new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().alias("thealias").index("vulcangov"))) + .actionGet(); } TestAuditlogImpl.clear(); @@ -644,17 +724,21 @@ public void testAliasResolution() throws Exception { public void testAliasBadHeaders() 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_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .build(); + .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, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .build(); setup(additionalSettings); TestAuditlogImpl.clear(); - HttpResponse response = rh.executeGetRequest("_search?pretty", new BasicHeader("_opendistro_security_user", "xxx"), encodeBasicHeader("admin", "admin")); + HttpResponse response = rh.executeGetRequest( + "_search?pretty", + new BasicHeader("_opendistro_security_user", "xxx"), + encodeBasicHeader("admin", "admin") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); System.out.println(TestAuditlogImpl.sb.toString()); Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("YWRtaW46YWRtaW4")); @@ -669,13 +753,13 @@ public void testAliasBadHeaders() throws Exception { public void testIndexCloseDelete() 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_ENABLE_REST, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .build(); setup(additionalSettings); @@ -701,24 +785,33 @@ public void testIndexCloseDelete() throws Exception { public void testDeleteByQuery() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .build(); setup(settings); try (Client tc = getClient()) { - for(int i=0; i<3; i++) - tc.index(new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON)).actionGet(); + for (int i = 0; i < 3; i++) + tc.index( + new IndexRequest("vulcangov").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"content\":1}", XContentType.JSON) + ).actionGet(); } TestAuditlogImpl.clear(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executePostRequest("/vulcango*/_delete_by_query?refresh=true&wait_for_completion=true&pretty=true", "{\"query\" : {\"match_all\" : {}}}", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest( + "/vulcango*/_delete_by_query?refresh=true&wait_for_completion=true&pretty=true", + "{\"query\" : {\"match_all\" : {}}}", + encodeBasicHeader("admin", "admin") + )).getStatusCode() + ); assertContains(res, "*\"deleted\" : 3,*"); String auditlogContents = TestAuditlogImpl.sb.toString(); Assert.assertTrue(auditlogContents.contains("indices:data/write/delete/byquery")); @@ -729,21 +822,29 @@ public void testDeleteByQuery() throws Exception { @Test public void testIndexRequests() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "AUTHENTICATED,GRANTED_PRIVILEGES") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, true) - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "AUTHENTICATED,GRANTED_PRIVILEGES") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, true) + .build(); setup(settings); // test create index TestAuditlogImpl.clear(); - rh.executePutRequest("/twitter", "{\"settings\":{\"index\":{\"number_of_shards\":3,\"number_of_replicas\":2}}}", encodeBasicHeader("admin", "admin")); + rh.executePutRequest( + "/twitter", + "{\"settings\":{\"index\":{\"number_of_shards\":3,\"number_of_replicas\":2}}}", + encodeBasicHeader("admin", "admin") + ); String auditlogs = TestAuditlogImpl.sb.toString(); Assert.assertTrue(auditlogs.contains("\"audit_category\" : \"INDEX_EVENT\"")); Assert.assertTrue(auditlogs.contains("\"audit_transport_request_type\" : \"CreateIndexRequest\",")); - Assert.assertTrue(auditlogs.contains("\"audit_request_body\" : \"{\\\"index\\\":{\\\"number_of_shards\\\":\\\"3\\\",\\\"number_of_replicas\\\":\\\"2\\\"}}\"")); + Assert.assertTrue( + auditlogs.contains( + "\"audit_request_body\" : \"{\\\"index\\\":{\\\"number_of_shards\\\":\\\"3\\\",\\\"number_of_replicas\\\":\\\"2\\\"}}\"" + ) + ); // test update index TestAuditlogImpl.clear(); @@ -755,7 +856,11 @@ public void testIndexRequests() throws Exception { // test put mapping TestAuditlogImpl.clear(); - rh.executePutRequest("/twitter/_mapping", "{\"properties\":{\"message\":{\"type\":\"keyword\"}}}", encodeBasicHeader("admin", "admin")); + rh.executePutRequest( + "/twitter/_mapping", + "{\"properties\":{\"message\":{\"type\":\"keyword\"}}}", + encodeBasicHeader("admin", "admin") + ); auditlogs = TestAuditlogImpl.sb.toString(); Assert.assertTrue(auditlogs.contains("\"audit_category\" : \"INDEX_EVENT\"")); Assert.assertTrue(auditlogs.contains("\"audit_transport_request_type\" : \"PutMappingRequest\",")); @@ -776,50 +881,41 @@ private String messageRestRequestMethod(AuditMessage msg) { @Test public void testRestMethod() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "NONE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) + .build(); setup(settings); final Header adminHeader = encodeBasicHeader("admin", "admin"); List messages; // test GET - messages = TestAuditlogImpl.doThenWaitForMessages(() -> { - rh.executeGetRequest("test", adminHeader); - }, 1); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { rh.executeGetRequest("test", adminHeader); }, 1); Assert.assertEquals(GET, messages.get(0).getRequestMethod()); // test PUT - messages = TestAuditlogImpl.doThenWaitForMessages(() -> { - rh.executePutRequest("test/_doc/0", "{}", adminHeader); - }, 1); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { rh.executePutRequest("test/_doc/0", "{}", adminHeader); }, 1); Assert.assertEquals(PUT, messages.get(0).getRequestMethod()); // test DELETE - messages = TestAuditlogImpl.doThenWaitForMessages(() -> { - rh.executeDeleteRequest("test", adminHeader); - }, 1); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { rh.executeDeleteRequest("test", adminHeader); }, 1); Assert.assertEquals(DELETE, messages.get(0).getRequestMethod()); // test POST - messages = TestAuditlogImpl.doThenWaitForMessages(() -> { - rh.executePostRequest("test/_doc", "{}", adminHeader); - }, 1); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { rh.executePostRequest("test/_doc", "{}", adminHeader); }, 1); Assert.assertEquals(POST, messages.get(0).getRequestMethod()); // test PATCH - messages = TestAuditlogImpl.doThenWaitForMessages(() -> { - rh.executePatchRequest("/_opendistro/_security/api/audit", "[]"); - }, 1); + 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 - messages = TestAuditlogImpl.doThenWaitForMessages(() -> { - rh.executePatchRequest("/_opendistro/_security/api/audit", "[]", adminHeader); - }, 2); + 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()); @@ -828,16 +924,15 @@ public void testRestMethod() throws Exception { Assert.assertEquals(AuditCategory.MISSING_PRIVILEGES, messages.get(1).getCategory()); // test AUTHENTICATED - messages = TestAuditlogImpl.doThenWaitForMessages(() -> { - rh.executeGetRequest("test", adminHeader); - }, 1); + 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 - messages = TestAuditlogImpl.doThenWaitForMessages(() -> { - rh.executeGetRequest("test", encodeBasicHeader("random", "random")); - }, 1); + 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()); @@ -852,11 +947,11 @@ public void testRestMethod() throws Exception { @Test public void testSensitiveMethodRedaction() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "AUTHENTICATED") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) - .build(); + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "AUTHENTICATED") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) + .build(); setup(settings); rh.sendAdminCertificate = true; final String expectedRequestBody = "\"audit_request_body\" : \"__SENSITIVE__\""; @@ -869,19 +964,28 @@ public void testSensitiveMethodRedaction() throws Exception { // test PUT internal users API TestAuditlogImpl.clear(); - rh.executePutRequest("/_opendistro/_security/api/internalusers/test1", "{\"password\":\"new-pass\", \"backend_roles\":[], \"attributes\": {}}"); + rh.executePutRequest( + "/_opendistro/_security/api/internalusers/test1", + "{\"password\":\"new-pass\", \"backend_roles\":[], \"attributes\": {}}" + ); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains(expectedRequestBody)); // test PATCH internal users API TestAuditlogImpl.clear(); - rh.executePatchRequest("/_opendistro/_security/api/internalusers/test1", "[{\"op\":\"add\", \"path\":\"/password\", \"value\": \"test-pass\"}]"); + rh.executePatchRequest( + "/_opendistro/_security/api/internalusers/test1", + "[{\"op\":\"add\", \"path\":\"/password\", \"value\": \"test-pass\"}]" + ); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains(expectedRequestBody)); // test PUT users API TestAuditlogImpl.clear(); - rh.executePutRequest("/_opendistro/_security/api/user/test2", "{\"password\":\"new-pass\", \"backend_roles\":[], \"attributes\": {}}"); + rh.executePutRequest( + "/_opendistro/_security/api/user/test2", + "{\"password\":\"new-pass\", \"backend_roles\":[], \"attributes\": {}}" + ); Assert.assertEquals(1, TestAuditlogImpl.messages.size()); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains(expectedRequestBody)); } diff --git a/src/test/java/org/opensearch/security/auditlog/integration/SSLAuditlogTest.java b/src/test/java/org/opensearch/security/auditlog/integration/SSLAuditlogTest.java index 56fd17ddff..3eb49dc794 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/SSLAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/SSLAuditlogTest.java @@ -31,7 +31,9 @@ public class SSLAuditlogTest extends AbstractAuditlogiUnitTest { private ClusterInfo monitoringClusterInfo; private RestHelper rhMon; - private final ClusterHelper monitoringCluster = new ClusterHelper("mon_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); + private final ClusterHelper monitoringCluster = new ClusterHelper( + "mon_n" + num.incrementAndGet() + "_f" + System.getProperty("forkno") + "_t" + System.nanoTime() + ); @After @Override @@ -48,36 +50,53 @@ public void tearDown() { private void setupMonitoring() throws Exception { Assert.assertNull("No monitoring cluster", monitoringClusterInfo); - monitoringClusterInfo = monitoringCluster.startCluster(minimumSecuritySettings(defaultNodeSettings(Settings.EMPTY)), ClusterConfiguration.DEFAULT); + monitoringClusterInfo = monitoringCluster.startCluster( + minimumSecuritySettings(defaultNodeSettings(Settings.EMPTY)), + ClusterConfiguration.DEFAULT + ); initialize(monitoringCluster, monitoringClusterInfo, new DynamicSecurityConfig()); rhMon = new RestHelper(monitoringClusterInfo, getResourceFolder()); } - @Test public void testExternalPemUserPass() throws Exception { setupMonitoring(); Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", "external_opensearch") - .put("plugins.security.audit.config.http_endpoints", monitoringClusterInfo.httpHost+":"+monitoringClusterInfo.httpPort) - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, "*spock*","admin", "CN=kirk,OU=client,O=client,L=Test,C=DE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, true) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL_CLIENT_AUTH, false) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/chain-ca.pem")) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/spock.crtfull.pem")) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/spock.key.pem")) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME, - "admin") - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PASSWORD, - "admin") - .build(); + .put("plugins.security.audit.type", "external_opensearch") + .put("plugins.security.audit.config.http_endpoints", monitoringClusterInfo.httpHost + ":" + monitoringClusterInfo.httpPort) + .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, "*spock*", "admin", "CN=kirk,OU=client,O=client,L=Test,C=DE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) + .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, true) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL_CLIENT_AUTH, + false + ) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/chain-ca.pem") + ) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/spock.crtfull.pem") + ) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/spock.key.pem") + ) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME, + "admin" + ) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PASSWORD, + "admin" + ) + .build(); setup(additionalSettings); HttpResponse response = rh.executeGetRequest("_search"); @@ -99,20 +118,31 @@ public void testExternalPemClientAuth() throws Exception { setupMonitoring(); Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", "external_opensearch") - .put("plugins.security.audit.config.http_endpoints", monitoringClusterInfo.httpHost+":"+monitoringClusterInfo.httpPort) - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, "*spock*","admin", "CN=kirk,OU=client,O=client,L=Test,C=DE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, true) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL_CLIENT_AUTH, true) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/chain-ca.pem")) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/kirk.crtfull.pem")) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/kirk.key.pem")) - .build(); + .put("plugins.security.audit.type", "external_opensearch") + .put("plugins.security.audit.config.http_endpoints", monitoringClusterInfo.httpHost + ":" + monitoringClusterInfo.httpPort) + .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, "*spock*", "admin", "CN=kirk,OU=client,O=client,L=Test,C=DE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) + .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, true) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL_CLIENT_AUTH, + true + ) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/chain-ca.pem") + ) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/kirk.crtfull.pem") + ) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/kirk.key.pem") + ) + .build(); setup(additionalSettings); HttpResponse response = rh.executeGetRequest("_search"); @@ -133,19 +163,26 @@ public void testExternalPemUserPassTp() throws Exception { setupMonitoring(); Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", "external_opensearch") - .put("plugins.security.audit.config.http_endpoints", monitoringClusterInfo.httpHost+":"+monitoringClusterInfo.httpPort) - .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, "*spock*","admin", "CN=kirk,OU=client,O=client,L=Test,C=DE") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, true) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/chain-ca.pem")) - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME, - "admin") - .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PASSWORD, - "admin") - .build(); + .put("plugins.security.audit.type", "external_opensearch") + .put("plugins.security.audit.config.http_endpoints", monitoringClusterInfo.httpHost + ":" + monitoringClusterInfo.httpPort) + .putList(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, "*spock*", "admin", "CN=kirk,OU=client,O=client,L=Test,C=DE") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) + .put(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, true) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/chain-ca.pem") + ) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME, + "admin" + ) + .put( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PASSWORD, + "admin" + ) + .build(); setup(additionalSettings); HttpResponse response = rh.executeGetRequest("_search"); 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 ba094e23a1..7a3cd45f5b 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java @@ -42,7 +42,7 @@ public synchronized boolean doStore(AuditMessage msg) { // Ignore any messages that are sent before TestAuditlogImpl is waiting. return true; } - sb.append(msg.toPrettyString()+System.lineSeparator()); + sb.append(msg.toPrettyString() + System.lineSeparator()); messagesRef.get().add(msg); countDownRef.get().countDown(); return true; @@ -81,14 +81,11 @@ public static List doThenWaitForMessages(final Runnable action, fi 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(); + 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); } @@ -134,6 +131,7 @@ 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; @@ -154,14 +152,11 @@ public List getFoundMessages() { } 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(); + 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/routing/FallbackTest.java b/src/test/java/org/opensearch/security/auditlog/routing/FallbackTest.java index 0cd48f6009..cf76dbc343 100644 --- a/src/test/java/org/opensearch/security/auditlog/routing/FallbackTest.java +++ b/src/test/java/org/opensearch/security/auditlog/routing/FallbackTest.java @@ -32,96 +32,99 @@ public class FallbackTest extends AbstractAuditlogiUnitTest { - @Test - public void testFallback() throws Exception { - Settings.Builder settingsBuilder = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/fallback.yml")); - - Settings settings = settingsBuilder.put("path.home", ".").put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE").build(); - - AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); - - AuditMessage msg = MockAuditMessageFactory.validAuditMessage(AuditCategory.MISSING_PRIVILEGES); - router.route(msg); - - // endpoint 1 is failing, endoint2 and default work - List sinks = router.categorySinks.get(AuditCategory.MISSING_PRIVILEGES); - Assert.assertEquals(3, sinks.size()); - // this sink has failed, message must be logged to fallback sink - AuditLogSink sink = sinks.get(0); - Assert.assertEquals("endpoint1", sink.getName()); - Assert.assertEquals(FailingSink.class, sink.getClass()); - sink = sink.getFallbackSink(); - Assert.assertEquals("fallback", sink.getName()); - Assert.assertEquals(LoggingSink.class, sink.getClass()); - LoggingSink loggingSkin = (LoggingSink) sink; - Assert.assertEquals(msg, loggingSkin.messages.get(0)); - // this sink succeeds - sink = sinks.get(1); - Assert.assertEquals("endpoint2", sink.getName()); - Assert.assertEquals(LoggingSink.class, sink.getClass()); - loggingSkin = (LoggingSink) sink; - Assert.assertEquals(msg, loggingSkin.messages.get(0)); - // default sink also succeeds - sink = sinks.get(2); - Assert.assertEquals("default", sink.getName()); - Assert.assertEquals(LoggingSink.class, sink.getClass()); - loggingSkin = (LoggingSink) sink; - Assert.assertEquals(msg, loggingSkin.messages.get(0)); - - // has only one end point which fails - router = createMessageRouterComplianceEnabled(settings); - msg = MockAuditMessageFactory.validAuditMessage(AuditCategory.COMPLIANCE_DOC_READ); - router.route(msg); - sinks = router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_READ); - sink = sinks.get(0); - Assert.assertEquals("endpoint3", sink.getName()); - Assert.assertEquals(FailingSink.class, sink.getClass()); - sink = sink.getFallbackSink(); - Assert.assertEquals("fallback", sink.getName()); - Assert.assertEquals(LoggingSink.class, sink.getClass()); - loggingSkin = (LoggingSink) sink; - Assert.assertEquals(msg, loggingSkin.messages.get(0)); - - // has only default which succeeds - router = createMessageRouterComplianceEnabled(settings); - msg = MockAuditMessageFactory.validAuditMessage(AuditCategory.COMPLIANCE_DOC_WRITE); - router.route(msg); - sinks = router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_WRITE); - sink = sinks.get(0); - Assert.assertEquals("default", sink.getName()); - Assert.assertEquals(LoggingSink.class, sink.getClass()); - loggingSkin = (LoggingSink) sink; - Assert.assertEquals(1, loggingSkin.messages.size()); - Assert.assertEquals(msg, loggingSkin.messages.get(0)); - // fallback must be empty - sink = sink.getFallbackSink(); - Assert.assertEquals("fallback", sink.getName()); - Assert.assertEquals(LoggingSink.class, sink.getClass()); - loggingSkin = (LoggingSink) sink; - Assert.assertEquals(0, loggingSkin.messages.size()); - - // test non configured categories, must be logged to default only - router = createMessageRouterComplianceEnabled(settings); - msg = MockAuditMessageFactory.validAuditMessage(AuditCategory.FAILED_LOGIN); - router.route(msg); - Assert.assertNull(router.categorySinks.get(AuditCategory.FAILED_LOGIN)); - loggingSkin = (LoggingSink) router.defaultSink; - Assert.assertEquals(1, loggingSkin.messages.size()); - Assert.assertEquals(msg, loggingSkin.messages.get(0)); - // all others must be empty - assertLoggingSinksEmpty(router); - - } - - private void assertLoggingSinksEmpty(AuditMessageRouter router) { - // get all sinks - List allSinks = router.categorySinks.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); - allSinks = allSinks.stream().filter(sink -> (sink instanceof LoggingSink)).collect(Collectors.toList()); - allSinks.removeAll(Collections.singleton(router.defaultSink)); - for(AuditLogSink sink : allSinks) { - LoggingSink loggingSink = (LoggingSink)sink; - Assert.assertEquals(0, loggingSink.messages.size()); - } - } + @Test + public void testFallback() throws Exception { + Settings.Builder settingsBuilder = Settings.builder() + .loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/fallback.yml")); + + Settings settings = settingsBuilder.put("path.home", ".") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .build(); + + AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); + + AuditMessage msg = MockAuditMessageFactory.validAuditMessage(AuditCategory.MISSING_PRIVILEGES); + router.route(msg); + + // endpoint 1 is failing, endoint2 and default work + List sinks = router.categorySinks.get(AuditCategory.MISSING_PRIVILEGES); + Assert.assertEquals(3, sinks.size()); + // this sink has failed, message must be logged to fallback sink + AuditLogSink sink = sinks.get(0); + Assert.assertEquals("endpoint1", sink.getName()); + Assert.assertEquals(FailingSink.class, sink.getClass()); + sink = sink.getFallbackSink(); + Assert.assertEquals("fallback", sink.getName()); + Assert.assertEquals(LoggingSink.class, sink.getClass()); + LoggingSink loggingSkin = (LoggingSink) sink; + Assert.assertEquals(msg, loggingSkin.messages.get(0)); + // this sink succeeds + sink = sinks.get(1); + Assert.assertEquals("endpoint2", sink.getName()); + Assert.assertEquals(LoggingSink.class, sink.getClass()); + loggingSkin = (LoggingSink) sink; + Assert.assertEquals(msg, loggingSkin.messages.get(0)); + // default sink also succeeds + sink = sinks.get(2); + Assert.assertEquals("default", sink.getName()); + Assert.assertEquals(LoggingSink.class, sink.getClass()); + loggingSkin = (LoggingSink) sink; + Assert.assertEquals(msg, loggingSkin.messages.get(0)); + + // has only one end point which fails + router = createMessageRouterComplianceEnabled(settings); + msg = MockAuditMessageFactory.validAuditMessage(AuditCategory.COMPLIANCE_DOC_READ); + router.route(msg); + sinks = router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_READ); + sink = sinks.get(0); + Assert.assertEquals("endpoint3", sink.getName()); + Assert.assertEquals(FailingSink.class, sink.getClass()); + sink = sink.getFallbackSink(); + Assert.assertEquals("fallback", sink.getName()); + Assert.assertEquals(LoggingSink.class, sink.getClass()); + loggingSkin = (LoggingSink) sink; + Assert.assertEquals(msg, loggingSkin.messages.get(0)); + + // has only default which succeeds + router = createMessageRouterComplianceEnabled(settings); + msg = MockAuditMessageFactory.validAuditMessage(AuditCategory.COMPLIANCE_DOC_WRITE); + router.route(msg); + sinks = router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_WRITE); + sink = sinks.get(0); + Assert.assertEquals("default", sink.getName()); + Assert.assertEquals(LoggingSink.class, sink.getClass()); + loggingSkin = (LoggingSink) sink; + Assert.assertEquals(1, loggingSkin.messages.size()); + Assert.assertEquals(msg, loggingSkin.messages.get(0)); + // fallback must be empty + sink = sink.getFallbackSink(); + Assert.assertEquals("fallback", sink.getName()); + Assert.assertEquals(LoggingSink.class, sink.getClass()); + loggingSkin = (LoggingSink) sink; + Assert.assertEquals(0, loggingSkin.messages.size()); + + // test non configured categories, must be logged to default only + router = createMessageRouterComplianceEnabled(settings); + msg = MockAuditMessageFactory.validAuditMessage(AuditCategory.FAILED_LOGIN); + router.route(msg); + Assert.assertNull(router.categorySinks.get(AuditCategory.FAILED_LOGIN)); + loggingSkin = (LoggingSink) router.defaultSink; + Assert.assertEquals(1, loggingSkin.messages.size()); + Assert.assertEquals(msg, loggingSkin.messages.get(0)); + // all others must be empty + assertLoggingSinksEmpty(router); + + } + + private void assertLoggingSinksEmpty(AuditMessageRouter router) { + // get all sinks + List allSinks = router.categorySinks.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); + allSinks = allSinks.stream().filter(sink -> (sink instanceof LoggingSink)).collect(Collectors.toList()); + allSinks.removeAll(Collections.singleton(router.defaultSink)); + for (AuditLogSink sink : allSinks) { + LoggingSink loggingSink = (LoggingSink) sink; + Assert.assertEquals(0, loggingSink.messages.size()); + } + } } diff --git a/src/test/java/org/opensearch/security/auditlog/routing/PerfTest.java b/src/test/java/org/opensearch/security/auditlog/routing/PerfTest.java index c71a4dc951..f308db21fc 100644 --- a/src/test/java/org/opensearch/security/auditlog/routing/PerfTest.java +++ b/src/test/java/org/opensearch/security/auditlog/routing/PerfTest.java @@ -24,28 +24,28 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.file.FileHelper; - public class PerfTest extends AbstractAuditlogiUnitTest { - @Test - @Ignore(value="jvm crash on cci") - public void testPerf() throws Exception { - Settings.Builder settingsBuilder = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/perftest.yml")); - - Settings settings = settingsBuilder.put("path.home", ".") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .build(); - - AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); - int limit = 150000; - while(limit > 0) { - AuditMessage msg = MockAuditMessageFactory.validAuditMessage(AuditCategory.MISSING_PRIVILEGES); - router.route(msg); - limit--; - } - LoggingSink loggingSink = (LoggingSink)router.defaultSink.getFallbackSink(); - int currentSize = loggingSink.messages.size(); - Assert.assertTrue(currentSize > 0); - } + @Test + @Ignore(value = "jvm crash on cci") + public void testPerf() throws Exception { + Settings.Builder settingsBuilder = Settings.builder() + .loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/perftest.yml")); + + Settings settings = settingsBuilder.put("path.home", ".") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .build(); + + AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); + int limit = 150000; + while (limit > 0) { + AuditMessage msg = MockAuditMessageFactory.validAuditMessage(AuditCategory.MISSING_PRIVILEGES); + router.route(msg); + limit--; + } + LoggingSink loggingSink = (LoggingSink) router.defaultSink.getFallbackSink(); + int currentSize = loggingSink.messages.size(); + Assert.assertTrue(currentSize > 0); + } } diff --git a/src/test/java/org/opensearch/security/auditlog/routing/RouterTest.java b/src/test/java/org/opensearch/security/auditlog/routing/RouterTest.java index 49d603884f..9ab7c0f93c 100644 --- a/src/test/java/org/opensearch/security/auditlog/routing/RouterTest.java +++ b/src/test/java/org/opensearch/security/auditlog/routing/RouterTest.java @@ -31,45 +31,46 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.file.FileHelper; -public class RouterTest extends AbstractAuditlogiUnitTest{ - - - @Test - public void testValidConfiguration() throws Exception { - Settings settings = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/configuration_valid.yml")).build(); - AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); - // default - Assert.assertEquals("default", router.defaultSink.getName()); - Assert.assertEquals(ExternalOpenSearchSink.class, router.defaultSink.getClass()); - // test category sinks - List sinks = router.categorySinks.get(AuditCategory.MISSING_PRIVILEGES); - Assert.assertNotNull(sinks); - // 3, since we include default as well - Assert.assertEquals(3, sinks.size()); - Assert.assertEquals("endpoint1", sinks.get(0).getName()); - Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(0).getClass()); - Assert.assertEquals("endpoint2", sinks.get(1).getName()); - Assert.assertEquals(ExternalOpenSearchSink.class, sinks.get(1).getClass()); - Assert.assertEquals("default", sinks.get(2).getName()); - Assert.assertEquals(ExternalOpenSearchSink.class, sinks.get(2).getClass()); - sinks = router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_READ); - // 1, since we do not include default - Assert.assertEquals(1, sinks.size()); - Assert.assertEquals("endpoint3", sinks.get(0).getName()); - Assert.assertEquals(DebugSink.class, sinks.get(0).getClass()); - } +public class RouterTest extends AbstractAuditlogiUnitTest { + + @Test + public void testValidConfiguration() throws Exception { + Settings settings = Settings.builder() + .loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/configuration_valid.yml")) + .build(); + AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); + // default + Assert.assertEquals("default", router.defaultSink.getName()); + Assert.assertEquals(ExternalOpenSearchSink.class, router.defaultSink.getClass()); + // test category sinks + List sinks = router.categorySinks.get(AuditCategory.MISSING_PRIVILEGES); + Assert.assertNotNull(sinks); + // 3, since we include default as well + Assert.assertEquals(3, sinks.size()); + Assert.assertEquals("endpoint1", sinks.get(0).getName()); + Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(0).getClass()); + Assert.assertEquals("endpoint2", sinks.get(1).getName()); + Assert.assertEquals(ExternalOpenSearchSink.class, sinks.get(1).getClass()); + Assert.assertEquals("default", sinks.get(2).getName()); + Assert.assertEquals(ExternalOpenSearchSink.class, sinks.get(2).getClass()); + sinks = router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_READ); + // 1, since we do not include default + Assert.assertEquals(1, sinks.size()); + Assert.assertEquals("endpoint3", sinks.get(0).getName()); + Assert.assertEquals(DebugSink.class, sinks.get(0).getClass()); + } @Test public void testMessageRouting() throws Exception { - Settings.Builder settingsBuilder = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/routing.yml")); + Settings.Builder settingsBuilder = Settings.builder() + .loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/routing.yml")); - Settings settings = settingsBuilder - .put("path.home", ".") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") - .build(); + Settings settings = settingsBuilder.put("path.home", ".") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .build(); - AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); + AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); AuditMessage msg = MockAuditMessageFactory.validAuditMessage(AuditCategory.MISSING_PRIVILEGES); router.route(msg); testMessageDeliveredForCategory(router, msg, AuditCategory.MISSING_PRIVILEGES, "endpoint1", "endpoint2", "default"); @@ -96,35 +97,40 @@ public void testMessageRouting() throws Exception { } - private void testMessageDeliveredForCategory(AuditMessageRouter router, AuditMessage msg, AuditCategory categoryToCheck, String ... sinkNames) { - Map> sinksForCategory = router.categorySinks; - for(AuditCategory category : AuditCategory.values()) { - List sinks = sinksForCategory.get(category); - if (sinks == null) { - continue; - } - if (category.equals(categoryToCheck)) { - // each sink must contain our message - for(AuditLogSink sink : sinks) { - LoggingSink logSink = (LoggingSink)sink; - Assert.assertEquals(1, logSink.messages.size()); - Assert.assertEquals(msg, logSink.messages.get(0)); - Assert.assertTrue(logSink.sb.length() > 0); - Assert.assertTrue(Arrays.stream(sinkNames).anyMatch(sink.getName()::equals)); - } - } else { - // make sure sinks are empty for all other categories, exclude default - for(AuditLogSink sink : sinks) { - // default is configured for multiple categories, skip - if (sink.getName().equals("default")) { - continue; - } - LoggingSink logSink = (LoggingSink)sink; - Assert.assertEquals(0, logSink.messages.size()); - Assert.assertTrue(logSink.sb.length() == 0); - } - } - } + private void testMessageDeliveredForCategory( + AuditMessageRouter router, + AuditMessage msg, + AuditCategory categoryToCheck, + String... sinkNames + ) { + Map> sinksForCategory = router.categorySinks; + for (AuditCategory category : AuditCategory.values()) { + List sinks = sinksForCategory.get(category); + if (sinks == null) { + continue; + } + if (category.equals(categoryToCheck)) { + // each sink must contain our message + for (AuditLogSink sink : sinks) { + LoggingSink logSink = (LoggingSink) sink; + Assert.assertEquals(1, logSink.messages.size()); + Assert.assertEquals(msg, logSink.messages.get(0)); + Assert.assertTrue(logSink.sb.length() > 0); + Assert.assertTrue(Arrays.stream(sinkNames).anyMatch(sink.getName()::equals)); + } + } else { + // make sure sinks are empty for all other categories, exclude default + for (AuditLogSink sink : sinks) { + // default is configured for multiple categories, skip + if (sink.getName().equals("default")) { + continue; + } + LoggingSink logSink = (LoggingSink) sink; + Assert.assertEquals(0, logSink.messages.size()); + Assert.assertTrue(logSink.sb.length() == 0); + } + } + } } } diff --git a/src/test/java/org/opensearch/security/auditlog/routing/RoutingConfigurationTest.java b/src/test/java/org/opensearch/security/auditlog/routing/RoutingConfigurationTest.java index f33d52c663..8ddb79bcba 100644 --- a/src/test/java/org/opensearch/security/auditlog/routing/RoutingConfigurationTest.java +++ b/src/test/java/org/opensearch/security/auditlog/routing/RoutingConfigurationTest.java @@ -28,139 +28,155 @@ import org.opensearch.security.auditlog.sink.InternalOpenSearchSink; import org.opensearch.security.test.helper.file.FileHelper; -public class RoutingConfigurationTest extends AbstractAuditlogiUnitTest{ - - @Test - public void testValidConfiguration() throws Exception { - Settings settings = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/configuration_valid.yml")).build(); - AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); - // default - Assert.assertEquals("default", router.defaultSink.getName()); - Assert.assertEquals(ExternalOpenSearchSink.class, router.defaultSink.getClass()); - // test category sinks - List sinks = router.categorySinks.get(AuditCategory.MISSING_PRIVILEGES); - Assert.assertNotNull(sinks); - // 3, since we include default as well - Assert.assertEquals(3, sinks.size()); - Assert.assertEquals("endpoint1", sinks.get(0).getName()); - Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(0).getClass()); - Assert.assertEquals("endpoint2", sinks.get(1).getName()); - Assert.assertEquals(ExternalOpenSearchSink.class, sinks.get(1).getClass()); - Assert.assertEquals("default", sinks.get(2).getName()); - Assert.assertEquals(ExternalOpenSearchSink.class, sinks.get(2).getClass()); - sinks = router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_READ); - // 1, since we do not include default - Assert.assertEquals(1, sinks.size()); - Assert.assertEquals("endpoint3", sinks.get(0).getName()); - Assert.assertEquals(DebugSink.class, sinks.get(0).getClass()); - } - - @Test - public void testNoDefaultSink() throws Exception { - Settings settings = Settings.builder().loadFromPath(Objects.requireNonNull(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/configuration_no_default.yml"))).build(); - AuditMessageRouter router = new AuditMessageRouter(settings, null, null, null); - // no default sink, audit log not enabled - Assert.assertEquals(false, router.isEnabled()); - Assert.assertEquals(null, router.defaultSink); - Assert.assertEquals(null, router.categorySinks); - // make sure no exception is thrown - router.route(MockAuditMessageFactory.validAuditMessage()); - } - - @Test - public void testMissingEndpoints() throws Exception { - Settings settings = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/configuration_wrong_endpoint_names.yml")).build(); - AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); - // fallback to debug sink if no default is given - Assert.assertEquals(InternalOpenSearchSink.class, router.defaultSink.getClass()); - // missing configuration for endpoint2 / External ES. Fallback to - // localhost - List sinks = router.categorySinks.get(AuditCategory.MISSING_PRIVILEGES); - // 2 valid endpoints - Assert.assertEquals(2, sinks.size()); - Assert.assertEquals("endpoint1", sinks.get(0).getName()); - Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(0).getClass()); - Assert.assertEquals("endpoint3", sinks.get(1).getName()); - Assert.assertEquals(DebugSink.class, sinks.get(1).getClass()); - sinks = router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_WRITE); - Assert.assertEquals(1, sinks.size()); - Assert.assertEquals("default", sinks.get(0).getName()); - Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(0).getClass()); - // no valid end points for category, must use default - Assert.assertNull(router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_READ)); - } - - @Test - public void testWrongCategories() throws Exception { - Settings settings = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/configuration_wrong_categories.yml")).build(); - AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); - // no default sink, we fall back to debug sink - Assert.assertEquals(DebugSink.class, router.defaultSink.getClass()); - - List sinks = router.categorySinks.get(AuditCategory.MISSING_PRIVILEGES); - // 3, since default is not valid but replaced with Debug - Assert.assertEquals(3, sinks.size()); - Assert.assertEquals("default", sinks.get(0).getName()); - Assert.assertEquals(DebugSink.class, sinks.get(0).getClass()); - Assert.assertEquals("endpoint1", sinks.get(1).getName()); - Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(1).getClass()); - Assert.assertEquals("endpoint2", sinks.get(2).getName()); - Assert.assertEquals(ExternalOpenSearchSink.class, sinks.get(2).getClass()); - - sinks = router.categorySinks.get(AuditCategory.GRANTED_PRIVILEGES); - Assert.assertEquals(3, sinks.size()); - Assert.assertEquals("endpoint1", sinks.get(0).getName()); - Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(0).getClass()); - Assert.assertEquals("endpoint3", sinks.get(1).getName()); - Assert.assertEquals(DebugSink.class, sinks.get(1).getClass()); - Assert.assertEquals("default", sinks.get(2).getName()); - Assert.assertEquals(DebugSink.class, sinks.get(2).getClass()); - - sinks = router.categorySinks.get(AuditCategory.AUTHENTICATED); - Assert.assertEquals(1, sinks.size()); - Assert.assertEquals("endpoint1", sinks.get(0).getName()); - Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(0).getClass()); - - // bad headers has no valid endpoint, so we use default - Assert.assertNull(router.categorySinks.get(AuditCategory.BAD_HEADERS)); - - // failed login has no endpoint configuration, so we use default - Assert.assertNull(router.categorySinks.get(AuditCategory.FAILED_LOGIN)); - - } - - @Test - public void testWrongEndpointTypes() throws Exception { - Settings settings = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/configuration_wrong_endpoint_types.yml")).build(); - AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); - // debug sink not valid, fallback to debug - Assert.assertEquals(DebugSink.class, router.defaultSink.getClass()); - - List sinks = router.categorySinks.get(AuditCategory.MISSING_PRIVILEGES); - // 2 valid endpoints in config, default falls back to debug - Assert.assertEquals(3, sinks.size()); - Assert.assertEquals("endpoint2", sinks.get(0).getName()); - Assert.assertEquals(ExternalOpenSearchSink.class, sinks.get(0).getClass()); - Assert.assertEquals("endpoint3", sinks.get(1).getName()); - Assert.assertEquals(DebugSink.class, sinks.get(1).getClass()); - Assert.assertEquals("default", sinks.get(2).getName()); - Assert.assertEquals(DebugSink.class, sinks.get(2).getClass()); - - sinks = router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_WRITE); - Assert.assertEquals(1, sinks.size()); - Assert.assertEquals("default", sinks.get(0).getName()); - Assert.assertEquals(DebugSink.class, sinks.get(0).getClass()); - - // no valid endpoints for category, must fallback to default - Assert.assertNull(router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_READ)); - } - - @Test - public void testNoMultipleEndpointsConfiguration() throws Exception { - Settings settings = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/sink/configuration_no_multiple_endpoints.yml")).build(); - AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); - ThreadPoolConfig config = router.storagePool.getConfig(); - Assert.assertEquals(5, config.getThreadPoolSize()); - Assert.assertEquals(200000, config.getThreadPoolMaxQueueLen()); - } +public class RoutingConfigurationTest extends AbstractAuditlogiUnitTest { + + @Test + public void testValidConfiguration() throws Exception { + Settings settings = Settings.builder() + .loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/configuration_valid.yml")) + .build(); + AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); + // default + Assert.assertEquals("default", router.defaultSink.getName()); + Assert.assertEquals(ExternalOpenSearchSink.class, router.defaultSink.getClass()); + // test category sinks + List sinks = router.categorySinks.get(AuditCategory.MISSING_PRIVILEGES); + Assert.assertNotNull(sinks); + // 3, since we include default as well + Assert.assertEquals(3, sinks.size()); + Assert.assertEquals("endpoint1", sinks.get(0).getName()); + Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(0).getClass()); + Assert.assertEquals("endpoint2", sinks.get(1).getName()); + Assert.assertEquals(ExternalOpenSearchSink.class, sinks.get(1).getClass()); + Assert.assertEquals("default", sinks.get(2).getName()); + Assert.assertEquals(ExternalOpenSearchSink.class, sinks.get(2).getClass()); + sinks = router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_READ); + // 1, since we do not include default + Assert.assertEquals(1, sinks.size()); + Assert.assertEquals("endpoint3", sinks.get(0).getName()); + Assert.assertEquals(DebugSink.class, sinks.get(0).getClass()); + } + + @Test + public void testNoDefaultSink() throws Exception { + Settings settings = Settings.builder() + .loadFromPath( + Objects.requireNonNull( + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/configuration_no_default.yml") + ) + ) + .build(); + AuditMessageRouter router = new AuditMessageRouter(settings, null, null, null); + // no default sink, audit log not enabled + Assert.assertEquals(false, router.isEnabled()); + Assert.assertEquals(null, router.defaultSink); + Assert.assertEquals(null, router.categorySinks); + // make sure no exception is thrown + router.route(MockAuditMessageFactory.validAuditMessage()); + } + + @Test + public void testMissingEndpoints() throws Exception { + Settings settings = Settings.builder() + .loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/configuration_wrong_endpoint_names.yml")) + .build(); + AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); + // fallback to debug sink if no default is given + Assert.assertEquals(InternalOpenSearchSink.class, router.defaultSink.getClass()); + // missing configuration for endpoint2 / External ES. Fallback to + // localhost + List sinks = router.categorySinks.get(AuditCategory.MISSING_PRIVILEGES); + // 2 valid endpoints + Assert.assertEquals(2, sinks.size()); + Assert.assertEquals("endpoint1", sinks.get(0).getName()); + Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(0).getClass()); + Assert.assertEquals("endpoint3", sinks.get(1).getName()); + Assert.assertEquals(DebugSink.class, sinks.get(1).getClass()); + sinks = router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_WRITE); + Assert.assertEquals(1, sinks.size()); + Assert.assertEquals("default", sinks.get(0).getName()); + Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(0).getClass()); + // no valid end points for category, must use default + Assert.assertNull(router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_READ)); + } + + @Test + public void testWrongCategories() throws Exception { + Settings settings = Settings.builder() + .loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/configuration_wrong_categories.yml")) + .build(); + AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); + // no default sink, we fall back to debug sink + Assert.assertEquals(DebugSink.class, router.defaultSink.getClass()); + + List sinks = router.categorySinks.get(AuditCategory.MISSING_PRIVILEGES); + // 3, since default is not valid but replaced with Debug + Assert.assertEquals(3, sinks.size()); + Assert.assertEquals("default", sinks.get(0).getName()); + Assert.assertEquals(DebugSink.class, sinks.get(0).getClass()); + Assert.assertEquals("endpoint1", sinks.get(1).getName()); + Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(1).getClass()); + Assert.assertEquals("endpoint2", sinks.get(2).getName()); + Assert.assertEquals(ExternalOpenSearchSink.class, sinks.get(2).getClass()); + + sinks = router.categorySinks.get(AuditCategory.GRANTED_PRIVILEGES); + Assert.assertEquals(3, sinks.size()); + Assert.assertEquals("endpoint1", sinks.get(0).getName()); + Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(0).getClass()); + Assert.assertEquals("endpoint3", sinks.get(1).getName()); + Assert.assertEquals(DebugSink.class, sinks.get(1).getClass()); + Assert.assertEquals("default", sinks.get(2).getName()); + Assert.assertEquals(DebugSink.class, sinks.get(2).getClass()); + + sinks = router.categorySinks.get(AuditCategory.AUTHENTICATED); + Assert.assertEquals(1, sinks.size()); + Assert.assertEquals("endpoint1", sinks.get(0).getName()); + Assert.assertEquals(InternalOpenSearchSink.class, sinks.get(0).getClass()); + + // bad headers has no valid endpoint, so we use default + Assert.assertNull(router.categorySinks.get(AuditCategory.BAD_HEADERS)); + + // failed login has no endpoint configuration, so we use default + Assert.assertNull(router.categorySinks.get(AuditCategory.FAILED_LOGIN)); + + } + + @Test + public void testWrongEndpointTypes() throws Exception { + Settings settings = Settings.builder() + .loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/routing/configuration_wrong_endpoint_types.yml")) + .build(); + AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); + // debug sink not valid, fallback to debug + Assert.assertEquals(DebugSink.class, router.defaultSink.getClass()); + + List sinks = router.categorySinks.get(AuditCategory.MISSING_PRIVILEGES); + // 2 valid endpoints in config, default falls back to debug + Assert.assertEquals(3, sinks.size()); + Assert.assertEquals("endpoint2", sinks.get(0).getName()); + Assert.assertEquals(ExternalOpenSearchSink.class, sinks.get(0).getClass()); + Assert.assertEquals("endpoint3", sinks.get(1).getName()); + Assert.assertEquals(DebugSink.class, sinks.get(1).getClass()); + Assert.assertEquals("default", sinks.get(2).getName()); + Assert.assertEquals(DebugSink.class, sinks.get(2).getClass()); + + sinks = router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_WRITE); + Assert.assertEquals(1, sinks.size()); + Assert.assertEquals("default", sinks.get(0).getName()); + Assert.assertEquals(DebugSink.class, sinks.get(0).getClass()); + + // no valid endpoints for category, must fallback to default + Assert.assertNull(router.categorySinks.get(AuditCategory.COMPLIANCE_DOC_READ)); + } + + @Test + public void testNoMultipleEndpointsConfiguration() throws Exception { + Settings settings = Settings.builder() + .loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/sink/configuration_no_multiple_endpoints.yml")) + .build(); + AuditMessageRouter router = createMessageRouterComplianceEnabled(settings); + ThreadPoolConfig config = router.storagePool.getConfig(); + Assert.assertEquals(5, config.getThreadPoolSize()); + Assert.assertEquals(200000, config.getThreadPoolMaxQueueLen()); + } } diff --git a/src/test/java/org/opensearch/security/auditlog/sink/KafkaSinkTest.java b/src/test/java/org/opensearch/security/auditlog/sink/KafkaSinkTest.java index b074dd2b62..bf567c6ebe 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/KafkaSinkTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/KafkaSinkTest.java @@ -37,6 +37,7 @@ public class KafkaSinkTest extends AbstractAuditlogiUnitTest { public static EmbeddedKafkaRule embeddedKafka = new EmbeddedKafkaRule(1, true, 1, "compliance") { // Prevents test exceptions from randomized runner, see https://bit.ly/3y17IkI private UncaughtExceptionHandler currentHandler; + @Override public void before() { currentHandler = Thread.getDefaultUncaughtExceptionHandler(); @@ -53,9 +54,9 @@ public void after() { @Test public void testKafka() throws Exception { String configYml = FileHelper.loadFile("auditlog/endpoints/sink/configuration_kafka.yml"); - configYml = configYml.replace("_RPLC_BOOTSTRAP_SERVERS_",embeddedKafka.getEmbeddedKafka().getBrokersAsString()); + configYml = configYml.replace("_RPLC_BOOTSTRAP_SERVERS_", embeddedKafka.getEmbeddedKafka().getBrokersAsString()); Settings.Builder settingsBuilder = Settings.builder().loadFromSource(configYml, YamlXContent.yamlXContent.mediaType()); - try(KafkaConsumer consumer = createConsumer()) { + try (KafkaConsumer consumer = createConsumer()) { consumer.subscribe(Arrays.asList("compliance")); Settings settings = settingsBuilder.put("path.home", ".").build(); @@ -78,7 +79,7 @@ private KafkaConsumer createConsumer() { Properties props = new Properties(); props.put("bootstrap.servers", embeddedKafka.getEmbeddedKafka().getBrokersAsString()); props.put("auto.offset.reset", "earliest"); - props.put("group.id", "mygroup"+System.currentTimeMillis()+"_"+new Random().nextDouble()); + props.put("group.id", "mygroup" + System.currentTimeMillis() + "_" + new Random().nextDouble()); props.put("key.deserializer", "org.apache.kafka.common.serialization.LongDeserializer"); props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); return new KafkaConsumer<>(props); diff --git a/src/test/java/org/opensearch/security/auditlog/sink/MockWebhookAuditLog.java b/src/test/java/org/opensearch/security/auditlog/sink/MockWebhookAuditLog.java index b93904574e..6dd019e733 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/MockWebhookAuditLog.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/MockWebhookAuditLog.java @@ -15,23 +15,22 @@ public class MockWebhookAuditLog extends WebhookSink { - public String payload = null; - public String url = null; - - public MockWebhookAuditLog(Settings settings, String settingsPrefix, AuditLogSink fallback) throws Exception { - super("test", settings, settingsPrefix, null, fallback); - } - - @Override - protected boolean doPost(String url, String payload) { - this.payload = payload; - return true; - } - - - @Override - protected boolean doGet(String url) { - this.url = url; - return true; - } + public String payload = null; + public String url = null; + + public MockWebhookAuditLog(Settings settings, String settingsPrefix, AuditLogSink fallback) throws Exception { + super("test", settings, settingsPrefix, null, fallback); + } + + @Override + protected boolean doPost(String url, String payload) { + this.payload = payload; + return true; + } + + @Override + protected boolean doGet(String url) { + this.url = url; + return true; + } } 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 fb0f665b16..467cce5fe9 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java @@ -39,119 +39,132 @@ public class SinkProviderTLSTest { - protected HttpServer server = null; - - @Before - @After - public void tearDown() { - if (server != null) { - try { - server.stop(); - } catch (Exception e) { - // ignore - } - } - } - - @Test - public void testTlsConfigurationNoFallback() throws Exception { - - TestHttpHandler handler = new TestHttpHandler(); - - int port = findFreePort(); - server = ServerBootstrap.bootstrap().setListenerPort(port).setHttpProcessor(HttpProcessors.server("Test/1.1")).setSslContext(createSSLContext()).register("*", handler).create(); - - server.start(); - - Builder builder = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/sink/configuration_tls.yml")); - builder.put("path.home", "/"); - - // replace some values with absolute paths for unit tests - builder.put("plugins.security.audit.config.webhook.ssl.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/root-ca.pem")); - builder.put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/root-ca.pem")); - builder.put("plugins.security.audit.endpoints.endpoint2.config.webhook.ssl.pemtrustedcas_content", FileHelper.loadFile("auditlog/root-ca.pem")); - - builder.put("plugins.security.audit.config.webhook.url", "https://localhost:" + port); - builder.put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", "https://localhost:" + port); - builder.put("plugins.security.audit.endpoints.endpoint2.config.webhook.url", "https://localhost:" + port); - - - SinkProvider provider = new SinkProvider(builder.build(), null, null, null); - WebhookSink defaultSink = (WebhookSink) provider.defaultSink; - Assert.assertEquals(true, defaultSink.verifySSL); - - AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); - provider.allSinks.get("endpoint1").store(msg); - - Assert.assertTrue(handler.method.equals("POST")); + protected HttpServer server = null; + + @Before + @After + public void tearDown() { + if (server != null) { + try { + server.stop(); + } catch (Exception e) { + // ignore + } + } + } + + @Test + public void testTlsConfigurationNoFallback() throws Exception { + + TestHttpHandler handler = new TestHttpHandler(); + + int port = findFreePort(); + server = ServerBootstrap.bootstrap() + .setListenerPort(port) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .setSslContext(createSSLContext()) + .register("*", handler) + .create(); + + server.start(); + + Builder builder = Settings.builder() + .loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/sink/configuration_tls.yml")); + builder.put("path.home", "/"); + + // replace some values with absolute paths for unit tests + builder.put( + "plugins.security.audit.config.webhook.ssl.pemtrustedcas_filepath", + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/root-ca.pem") + ); + builder.put( + "plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.pemtrustedcas_filepath", + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/root-ca.pem") + ); + builder.put( + "plugins.security.audit.endpoints.endpoint2.config.webhook.ssl.pemtrustedcas_content", + FileHelper.loadFile("auditlog/root-ca.pem") + ); + + builder.put("plugins.security.audit.config.webhook.url", "https://localhost:" + port); + builder.put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", "https://localhost:" + port); + builder.put("plugins.security.audit.endpoints.endpoint2.config.webhook.url", "https://localhost:" + port); + + SinkProvider provider = new SinkProvider(builder.build(), null, null, null); + WebhookSink defaultSink = (WebhookSink) provider.defaultSink; + Assert.assertEquals(true, defaultSink.verifySSL); + + AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); + provider.allSinks.get("endpoint1").store(msg); + + Assert.assertTrue(handler.method.equals("POST")); Assert.assertTrue(handler.body != null); Assert.assertTrue(handler.body.contains("{")); assertStringContainsAllKeysAndValues(handler.body); - handler.reset(); + handler.reset(); - provider.allSinks.get("endpoint2").store(msg); + provider.allSinks.get("endpoint2").store(msg); - Assert.assertTrue(handler.method.equals("POST")); + Assert.assertTrue(handler.method.equals("POST")); Assert.assertTrue(handler.body != null); Assert.assertTrue(handler.body.contains("{")); assertStringContainsAllKeysAndValues(handler.body); - handler.reset(); + handler.reset(); - provider.defaultSink.store(msg); + provider.defaultSink.store(msg); - Assert.assertTrue(handler.method.equals("POST")); + Assert.assertTrue(handler.method.equals("POST")); Assert.assertTrue(handler.body != null); Assert.assertTrue(handler.body.contains("{")); assertStringContainsAllKeysAndValues(handler.body); server.stop(); - } - - // for TLS support on our in-memory server - private SSLContext createSSLContext() throws Exception { - final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory - .getDefaultAlgorithm()); - final KeyStore trustStore = KeyStore.getInstance("JKS"); - InputStream trustStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks").toFile()); - trustStore.load(trustStream, "changeit".toCharArray()); - tmf.init(trustStore); - - final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - final KeyStore keyStore = KeyStore.getInstance("JKS"); - InputStream keyStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/node-0-keystore.jks").toFile()); - - keyStore.load(keyStream, "changeit".toCharArray()); - kmf.init(keyStore, "changeit".toCharArray()); - - SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); - sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - return sslContext; - } - - private void assertStringContainsAllKeysAndValues(String in) { - System.out.println(in); - Assert.assertTrue(in, in.contains(AuditMessage.FORMAT_VERSION)); - Assert.assertTrue(in, in.contains(AuditMessage.CATEGORY)); - Assert.assertTrue(in, in.contains(AuditMessage.FORMAT_VERSION)); - Assert.assertTrue(in, in.contains(AuditMessage.REMOTE_ADDRESS)); - Assert.assertTrue(in, in.contains(AuditMessage.ORIGIN)); - Assert.assertTrue(in, in.contains(AuditMessage.REQUEST_LAYER)); - Assert.assertTrue(in, in.contains(AuditMessage.TRANSPORT_REQUEST_TYPE)); - Assert.assertTrue(in, in.contains(AuditMessage.UTC_TIMESTAMP)); - Assert.assertTrue(in, in.contains(AuditCategory.FAILED_LOGIN.name())); - Assert.assertTrue(in, in.contains("FAILED_LOGIN")); - Assert.assertTrue(in, in.contains("John Doe")); - 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); - } - } + } + + // for TLS support on our in-memory server + private SSLContext createSSLContext() throws Exception { + final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + final KeyStore trustStore = KeyStore.getInstance("JKS"); + InputStream trustStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks").toFile()); + trustStore.load(trustStream, "changeit".toCharArray()); + tmf.init(trustStore); + + final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + final KeyStore keyStore = KeyStore.getInstance("JKS"); + InputStream keyStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/node-0-keystore.jks").toFile()); + + keyStore.load(keyStream, "changeit".toCharArray()); + kmf.init(keyStore, "changeit".toCharArray()); + + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + return sslContext; + } + + private void assertStringContainsAllKeysAndValues(String in) { + System.out.println(in); + Assert.assertTrue(in, in.contains(AuditMessage.FORMAT_VERSION)); + Assert.assertTrue(in, in.contains(AuditMessage.CATEGORY)); + Assert.assertTrue(in, in.contains(AuditMessage.FORMAT_VERSION)); + Assert.assertTrue(in, in.contains(AuditMessage.REMOTE_ADDRESS)); + Assert.assertTrue(in, in.contains(AuditMessage.ORIGIN)); + Assert.assertTrue(in, in.contains(AuditMessage.REQUEST_LAYER)); + Assert.assertTrue(in, in.contains(AuditMessage.TRANSPORT_REQUEST_TYPE)); + Assert.assertTrue(in, in.contains(AuditMessage.UTC_TIMESTAMP)); + Assert.assertTrue(in, in.contains(AuditCategory.FAILED_LOGIN.name())); + Assert.assertTrue(in, in.contains("FAILED_LOGIN")); + Assert.assertTrue(in, in.contains("John Doe")); + 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/auditlog/sink/SinkProviderTest.java b/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTest.java index 029192e097..5e3203261f 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTest.java @@ -20,82 +20,85 @@ public class SinkProviderTest { - @Test - public void testConfiguration() throws Exception { - - Settings settings = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/sink/configuration_all_variants.yml")).build(); - SinkProvider provider = new SinkProvider(settings, null, null, null); - - // make sure we have a debug sink as fallback - Assert.assertEquals(DebugSink.class, provider.fallbackSink.getClass() ); - - AuditLogSink sink = provider.getSink("DefaULT"); - Assert.assertEquals(sink.getClass(), DebugSink.class); - - sink = provider.getSink("endpoint1"); - Assert.assertEquals(InternalOpenSearchSink.class, sink.getClass()); - - sink = provider.getSink("endpoint2"); - Assert.assertEquals(ExternalOpenSearchSink.class, sink.getClass()); - // todo: sink does not work - - sink = provider.getSink("endpoinT3"); - Assert.assertEquals(DebugSink.class, sink.getClass()); - - // no valid type - sink = provider.getSink("endpoint4"); - Assert.assertEquals(null, sink); - - sink = provider.getSink("endpoint2"); - Assert.assertEquals(ExternalOpenSearchSink.class, sink.getClass()); - // todo: sink does not work, no valid config - - // no valid type - sink = provider.getSink("endpoint6"); - Assert.assertEquals(null, sink); - - // no valid type - sink = provider.getSink("endpoint7"); - Assert.assertEquals(null, sink); - - sink = provider.getSink("endpoint8"); - Assert.assertEquals(DebugSink.class, sink.getClass()); - - // wrong type in config - sink = provider.getSink("endpoint9"); - Assert.assertEquals(ExternalOpenSearchSink.class, sink.getClass()); - - // log4j, valid configuration - sink = provider.getSink("endpoint10"); - Assert.assertEquals(Log4JSink.class, sink.getClass()); - Log4JSink lsink = (Log4JSink)sink; - Assert.assertEquals("loggername", lsink.loggerName); - Assert.assertEquals(Level.WARN, lsink.logLevel); - - // log4j, no level, fallback to default - sink = provider.getSink("endpoint11"); - Assert.assertEquals(Log4JSink.class, sink.getClass()); - lsink = (Log4JSink)sink; - Assert.assertEquals("loggername", lsink.loggerName); - Assert.assertEquals(Level.INFO, lsink.logLevel); - - // log4j, wrong level, fallback to log4j default - sink = provider.getSink("endpoint12"); - Assert.assertEquals(Log4JSink.class, sink.getClass()); - lsink = (Log4JSink)sink; - Assert.assertEquals("loggername", lsink.loggerName); - Assert.assertEquals(Level.DEBUG, lsink.logLevel); - - } - - @Test - public void testNoMultipleEndpointsConfiguration() throws Exception { - Settings settings = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/sink/configuration_no_multiple_endpoints.yml")).build(); - SinkProvider provider = new SinkProvider(settings, null, null, null); - InternalOpenSearchSink sink = (InternalOpenSearchSink)provider.defaultSink; - Assert.assertEquals("myownindex", sink.index); - Assert.assertEquals("auditevents", sink.type); - } - + @Test + public void testConfiguration() throws Exception { + + Settings settings = Settings.builder() + .loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/sink/configuration_all_variants.yml")) + .build(); + SinkProvider provider = new SinkProvider(settings, null, null, null); + + // make sure we have a debug sink as fallback + Assert.assertEquals(DebugSink.class, provider.fallbackSink.getClass()); + + AuditLogSink sink = provider.getSink("DefaULT"); + Assert.assertEquals(sink.getClass(), DebugSink.class); + + sink = provider.getSink("endpoint1"); + Assert.assertEquals(InternalOpenSearchSink.class, sink.getClass()); + + sink = provider.getSink("endpoint2"); + Assert.assertEquals(ExternalOpenSearchSink.class, sink.getClass()); + // todo: sink does not work + + sink = provider.getSink("endpoinT3"); + Assert.assertEquals(DebugSink.class, sink.getClass()); + + // no valid type + sink = provider.getSink("endpoint4"); + Assert.assertEquals(null, sink); + + sink = provider.getSink("endpoint2"); + Assert.assertEquals(ExternalOpenSearchSink.class, sink.getClass()); + // todo: sink does not work, no valid config + + // no valid type + sink = provider.getSink("endpoint6"); + Assert.assertEquals(null, sink); + + // no valid type + sink = provider.getSink("endpoint7"); + Assert.assertEquals(null, sink); + + sink = provider.getSink("endpoint8"); + Assert.assertEquals(DebugSink.class, sink.getClass()); + + // wrong type in config + sink = provider.getSink("endpoint9"); + Assert.assertEquals(ExternalOpenSearchSink.class, sink.getClass()); + + // log4j, valid configuration + sink = provider.getSink("endpoint10"); + Assert.assertEquals(Log4JSink.class, sink.getClass()); + Log4JSink lsink = (Log4JSink) sink; + Assert.assertEquals("loggername", lsink.loggerName); + Assert.assertEquals(Level.WARN, lsink.logLevel); + + // log4j, no level, fallback to default + sink = provider.getSink("endpoint11"); + Assert.assertEquals(Log4JSink.class, sink.getClass()); + lsink = (Log4JSink) sink; + Assert.assertEquals("loggername", lsink.loggerName); + Assert.assertEquals(Level.INFO, lsink.logLevel); + + // log4j, wrong level, fallback to log4j default + sink = provider.getSink("endpoint12"); + Assert.assertEquals(Log4JSink.class, sink.getClass()); + lsink = (Log4JSink) sink; + Assert.assertEquals("loggername", lsink.loggerName); + Assert.assertEquals(Level.DEBUG, lsink.logLevel); + + } + + @Test + public void testNoMultipleEndpointsConfiguration() throws Exception { + Settings settings = Settings.builder() + .loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/sink/configuration_no_multiple_endpoints.yml")) + .build(); + SinkProvider provider = new SinkProvider(settings, null, null, null); + InternalOpenSearchSink sink = (InternalOpenSearchSink) provider.defaultSink; + Assert.assertEquals("myownindex", sink.index); + Assert.assertEquals("auditevents", sink.type); + } } 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 1e327750b6..2c2af154a0 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java @@ -51,326 +51,354 @@ public class WebhookAuditLogTest { @Before @After public void tearDown() { - if(server != null) { + if (server != null) { try { server.stop(); } catch (Exception e) { - //ignore + // ignore } } } - @Test - public void invalidConfFallbackTest() throws Exception { - AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); - - // provide no settings, fallback must be used - Settings settings = Settings.builder() - .put("path.home", ".") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .build(); - LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); - MockWebhookAuditLog auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, fallback); - auditlog.store(msg); - // Webhook sink has failed ... - Assert.assertEquals(null, auditlog.webhookFormat); - // ... so message must be stored in fallback - Assert.assertEquals(1, fallback.messages.size()); - Assert.assertEquals(msg, fallback.messages.get(0)); - - } - - @Test - public void formatsTest() throws Exception { - - String url = "http://localhost"; - AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); - - // provide no format, defaults to TEXT - Settings settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("path.home", ".") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("plugins.security.ssl.transport.enforce_hostname_verification", false) - .build(); - - MockWebhookAuditLog auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null); - auditlog.store(msg); - Assert.assertEquals(WebhookFormat.TEXT, auditlog.webhookFormat); - Assert.assertEquals(ContentType.TEXT_PLAIN, auditlog.webhookFormat.getContentType()); - Assert.assertTrue(auditlog.payload, !auditlog.payload.startsWith("{\"text\":")); - - // provide faulty format, defaults to TEXT - settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "idonotexist") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("path.home", ".") - .build(); - auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null); - auditlog.store(msg); - Assert.assertEquals(WebhookFormat.TEXT, auditlog.webhookFormat); - Assert.assertEquals(ContentType.TEXT_PLAIN, auditlog.webhookFormat.getContentType()); - Assert.assertTrue(auditlog.payload, !auditlog.payload.startsWith("{\"text\":")); - auditlog.close(); - - // TEXT - settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "text") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("path.home", ".") - .build(); - auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null); - auditlog.store(msg); - Assert.assertEquals(WebhookFormat.TEXT, auditlog.webhookFormat); - Assert.assertEquals(ContentType.TEXT_PLAIN, auditlog.webhookFormat.getContentType()); - Assert.assertTrue(auditlog.payload, !auditlog.payload.startsWith("{\"text\":")); - Assert.assertTrue(auditlog.payload, auditlog.payload.contains(AuditMessage.UTC_TIMESTAMP)); - Assert.assertTrue(auditlog.payload, auditlog.payload.contains("audit_request_remote_address")); - - // JSON - settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "json") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("path.home", ".") - .build(); - auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null); - auditlog.store(msg); - System.out.println(auditlog.payload); - Assert.assertEquals(WebhookFormat.JSON, auditlog.webhookFormat); - Assert.assertEquals(ContentType.APPLICATION_JSON, auditlog.webhookFormat.getContentType()); - Assert.assertTrue(auditlog.payload, !auditlog.payload.startsWith("{\"text\":")); - Assert.assertTrue(auditlog.payload, auditlog.payload.contains(AuditMessage.UTC_TIMESTAMP)); + @Test + public void invalidConfFallbackTest() throws Exception { + AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); + + // provide no settings, fallback must be used + Settings settings = Settings.builder() + .put("path.home", ".") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .build(); + LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); + MockWebhookAuditLog auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, fallback); + auditlog.store(msg); + // Webhook sink has failed ... + Assert.assertEquals(null, auditlog.webhookFormat); + // ... so message must be stored in fallback + Assert.assertEquals(1, fallback.messages.size()); + Assert.assertEquals(msg, fallback.messages.get(0)); + + } + + @Test + public void formatsTest() throws Exception { + + String url = "http://localhost"; + AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); + + // provide no format, defaults to TEXT + Settings settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("path.home", ".") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put("plugins.security.ssl.transport.enforce_hostname_verification", false) + .build(); + + MockWebhookAuditLog auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null); + auditlog.store(msg); + Assert.assertEquals(WebhookFormat.TEXT, auditlog.webhookFormat); + Assert.assertEquals(ContentType.TEXT_PLAIN, auditlog.webhookFormat.getContentType()); + Assert.assertTrue(auditlog.payload, !auditlog.payload.startsWith("{\"text\":")); + + // provide faulty format, defaults to TEXT + settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "idonotexist") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put("path.home", ".") + .build(); + auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null); + auditlog.store(msg); + Assert.assertEquals(WebhookFormat.TEXT, auditlog.webhookFormat); + Assert.assertEquals(ContentType.TEXT_PLAIN, auditlog.webhookFormat.getContentType()); + Assert.assertTrue(auditlog.payload, !auditlog.payload.startsWith("{\"text\":")); + auditlog.close(); + + // TEXT + settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "text") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put("path.home", ".") + .build(); + auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null); + auditlog.store(msg); + Assert.assertEquals(WebhookFormat.TEXT, auditlog.webhookFormat); + Assert.assertEquals(ContentType.TEXT_PLAIN, auditlog.webhookFormat.getContentType()); + Assert.assertTrue(auditlog.payload, !auditlog.payload.startsWith("{\"text\":")); + Assert.assertTrue(auditlog.payload, auditlog.payload.contains(AuditMessage.UTC_TIMESTAMP)); + Assert.assertTrue(auditlog.payload, auditlog.payload.contains("audit_request_remote_address")); + + // JSON + settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "json") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put("path.home", ".") + .build(); + auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null); + auditlog.store(msg); + System.out.println(auditlog.payload); + Assert.assertEquals(WebhookFormat.JSON, auditlog.webhookFormat); + Assert.assertEquals(ContentType.APPLICATION_JSON, auditlog.webhookFormat.getContentType()); + Assert.assertTrue(auditlog.payload, !auditlog.payload.startsWith("{\"text\":")); + Assert.assertTrue(auditlog.payload, auditlog.payload.contains(AuditMessage.UTC_TIMESTAMP)); Assert.assertTrue(auditlog.payload, auditlog.payload.contains("audit_request_remote_address")); - // SLACK - settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "slack") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("path.home", ".") - .build(); - auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null); - auditlog.store(msg); - Assert.assertEquals(WebhookFormat.SLACK, auditlog.webhookFormat); - Assert.assertEquals(ContentType.APPLICATION_JSON, auditlog.webhookFormat.getContentType()); - Assert.assertTrue(auditlog.payload, auditlog.payload.startsWith("{\"text\":")); - Assert.assertTrue(auditlog.payload, auditlog.payload.contains(AuditMessage.UTC_TIMESTAMP)); + // SLACK + settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "slack") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put("path.home", ".") + .build(); + auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null); + auditlog.store(msg); + Assert.assertEquals(WebhookFormat.SLACK, auditlog.webhookFormat); + Assert.assertEquals(ContentType.APPLICATION_JSON, auditlog.webhookFormat.getContentType()); + Assert.assertTrue(auditlog.payload, auditlog.payload.startsWith("{\"text\":")); + Assert.assertTrue(auditlog.payload, auditlog.payload.contains(AuditMessage.UTC_TIMESTAMP)); Assert.assertTrue(auditlog.payload, auditlog.payload.contains("audit_request_remote_address")); - } - - - - @Test - public void invalidUrlTest() throws Exception { - - String url = "faultyurl"; - - final Settings settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "slack") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("path.home", ".") - .build(); - LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null);; - MockWebhookAuditLog auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, fallback); - AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); - auditlog.store(msg); - Assert.assertEquals(null, auditlog.url); - Assert.assertEquals(null, auditlog.payload); - Assert.assertEquals(null, auditlog.webhookUrl); - // message must be stored in fallback - Assert.assertEquals(1, fallback.messages.size()); - Assert.assertEquals(msg, fallback.messages.get(0)); - } - - @Test - public void noServerRunningHttpTest() throws Exception { - String url = "http://localhost:8080/endpoint"; - - Settings settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "slack") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("path.home", ".") - .build(); - - LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null);; - WebhookSink auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); - AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); - auditlog.store(msg); - // can't connect, no server running ... - Assert.assertEquals("http://localhost:8080/endpoint", auditlog.webhookUrl); - // ... message must be stored in fallback - Assert.assertEquals(1, fallback.messages.size()); - Assert.assertEquals(msg, fallback.messages.get(0)); - } - - - @Test - public void postGetHttpTest() throws Exception { - TestHttpHandler handler = new TestHttpHandler(); - - int port = findFreePort(); - server = ServerBootstrap.bootstrap() - .setListenerPort(port) - .setHttpProcessor(HttpProcessors.server("Test/1.1")) - .register("*", handler) - .create(); - - server.start(); - - String url = "http://localhost:" + port + "/endpoint"; - - // SLACK - Settings settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "slack") - .put("path.home", ".") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .build(); - - LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null);; - WebhookSink auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); - AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); - auditlog.store(msg); - Assert.assertTrue(handler.method.equals("POST")); - Assert.assertTrue(handler.body != null); - Assert.assertTrue(handler.body.startsWith("{\"text\":")); - assertStringContainsAllKeysAndValues(handler.body); - // no message stored on fallback - Assert.assertEquals(0, fallback.messages.size()); - handler.reset(); - - // TEXT - settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "texT") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("path.home", ".") - .build(); - - auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); - auditlog.store(msg); - Assert.assertTrue(handler.method.equals("POST")); - Assert.assertTrue(handler.body != null); - System.out.println(handler.body); - Assert.assertFalse(handler.body.contains("{")); - assertStringContainsAllKeysAndValues(handler.body); - handler.reset(); - - // JSON - settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "JSon") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("path.home", ".") - .build(); - - auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); - auditlog.store(msg); - Assert.assertTrue(handler.method.equals("POST")); - Assert.assertTrue(handler.body != null); - Assert.assertTrue(handler.body.contains("{")); - assertStringContainsAllKeysAndValues(handler.body); - handler.reset(); - - // URL POST - settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "URL_PARAMETER_POST") - .put("path.home", ".") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .build(); - - auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); - auditlog.store(msg); - Assert.assertTrue(handler.method.equals("POST")); - Assert.assertTrue(handler.body.equals("")); - Assert.assertTrue(!handler.body.contains("{")); - assertStringContainsAllKeysAndValues(URLDecoder.decode(handler.uri, StandardCharsets.UTF_8.displayName())); - handler.reset(); - - // URL GET - settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "URL_PARAMETER_GET") - .put("path.home", ".") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .build(); - - auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); - auditlog.store(msg); - Assert.assertTrue(handler.method.equals("GET")); - Assert.assertEquals(null, handler.body); - assertStringContainsAllKeysAndValues(URLDecoder.decode(handler.uri, StandardCharsets.UTF_8.displayName())); - server.awaitTermination(TimeValue.ofSeconds(3)); - } - - @Test - public void httpsTestWithoutTLSServer() throws Exception { - - TestHttpHandler handler = new TestHttpHandler(); - - int port = findFreePort(); - server = ServerBootstrap.bootstrap() - .setListenerPort(port) - .setHttpProcessor(HttpProcessors.server("Test/1.1")) - .register("*", handler) - .create(); - - server.start(); - - String url = "https://localhost:" + port + "/endpoint"; - - Settings settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "slack") - .put("path.home", ".") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .build(); - - LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null);; - WebhookSink auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); - AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); - auditlog.store(msg); - Assert.assertTrue(handler.method == null); - Assert.assertTrue(handler.body == null); - Assert.assertTrue(handler.uri == null); - // ... so message must be stored in fallback - Assert.assertEquals(1, fallback.messages.size()); - Assert.assertEquals(msg, fallback.messages.get(0)); - server.awaitTermination(TimeValue.ofSeconds(3)); - } - - - @Test + } + + @Test + public void invalidUrlTest() throws Exception { + + String url = "faultyurl"; + + final Settings settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "slack") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put("path.home", ".") + .build(); + LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); + ; + MockWebhookAuditLog auditlog = new MockWebhookAuditLog(settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, fallback); + AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); + auditlog.store(msg); + Assert.assertEquals(null, auditlog.url); + Assert.assertEquals(null, auditlog.payload); + Assert.assertEquals(null, auditlog.webhookUrl); + // message must be stored in fallback + Assert.assertEquals(1, fallback.messages.size()); + Assert.assertEquals(msg, fallback.messages.get(0)); + } + + @Test + public void noServerRunningHttpTest() throws Exception { + String url = "http://localhost:8080/endpoint"; + + Settings settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "slack") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put("path.home", ".") + .build(); + + LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); + ; + WebhookSink auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); + AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); + auditlog.store(msg); + // can't connect, no server running ... + Assert.assertEquals("http://localhost:8080/endpoint", auditlog.webhookUrl); + // ... message must be stored in fallback + Assert.assertEquals(1, fallback.messages.size()); + Assert.assertEquals(msg, fallback.messages.get(0)); + } + + @Test + public void postGetHttpTest() throws Exception { + TestHttpHandler handler = new TestHttpHandler(); + + int port = findFreePort(); + server = ServerBootstrap.bootstrap() + .setListenerPort(port) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .register("*", handler) + .create(); + + server.start(); + + String url = "http://localhost:" + port + "/endpoint"; + + // SLACK + Settings settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "slack") + .put("path.home", ".") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .build(); + + LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); + ; + WebhookSink auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); + AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); + auditlog.store(msg); + Assert.assertTrue(handler.method.equals("POST")); + Assert.assertTrue(handler.body != null); + Assert.assertTrue(handler.body.startsWith("{\"text\":")); + assertStringContainsAllKeysAndValues(handler.body); + // no message stored on fallback + Assert.assertEquals(0, fallback.messages.size()); + handler.reset(); + + // TEXT + settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "texT") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put("path.home", ".") + .build(); + + auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); + auditlog.store(msg); + Assert.assertTrue(handler.method.equals("POST")); + Assert.assertTrue(handler.body != null); + System.out.println(handler.body); + Assert.assertFalse(handler.body.contains("{")); + assertStringContainsAllKeysAndValues(handler.body); + handler.reset(); + + // JSON + settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "JSon") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put("path.home", ".") + .build(); + + auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); + auditlog.store(msg); + Assert.assertTrue(handler.method.equals("POST")); + Assert.assertTrue(handler.body != null); + Assert.assertTrue(handler.body.contains("{")); + assertStringContainsAllKeysAndValues(handler.body); + handler.reset(); + + // URL POST + settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "URL_PARAMETER_POST") + .put("path.home", ".") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .build(); + + auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); + auditlog.store(msg); + Assert.assertTrue(handler.method.equals("POST")); + Assert.assertTrue(handler.body.equals("")); + Assert.assertTrue(!handler.body.contains("{")); + assertStringContainsAllKeysAndValues(URLDecoder.decode(handler.uri, StandardCharsets.UTF_8.displayName())); + handler.reset(); + + // URL GET + settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "URL_PARAMETER_GET") + .put("path.home", ".") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .build(); + + auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); + auditlog.store(msg); + Assert.assertTrue(handler.method.equals("GET")); + Assert.assertEquals(null, handler.body); + assertStringContainsAllKeysAndValues(URLDecoder.decode(handler.uri, StandardCharsets.UTF_8.displayName())); + server.awaitTermination(TimeValue.ofSeconds(3)); + } + + @Test + public void httpsTestWithoutTLSServer() throws Exception { + + TestHttpHandler handler = new TestHttpHandler(); + + int port = findFreePort(); + server = ServerBootstrap.bootstrap() + .setListenerPort(port) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .register("*", handler) + .create(); + + server.start(); + + String url = "https://localhost:" + port + "/endpoint"; + + Settings settings = Settings.builder() + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "slack") + .put("path.home", ".") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .build(); + + LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); + ; + WebhookSink auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); + AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); + auditlog.store(msg); + Assert.assertTrue(handler.method == null); + Assert.assertTrue(handler.body == null); + Assert.assertTrue(handler.uri == null); + // ... so message must be stored in fallback + Assert.assertEquals(1, fallback.messages.size()); + Assert.assertEquals(msg, fallback.messages.get(0)); + server.awaitTermination(TimeValue.ofSeconds(3)); + } + + @Test public void httpsTest() throws Exception { TestHttpHandler handler = new TestHttpHandler(); - int port = findFreePort(); + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(port) - .setHttpProcessor(HttpProcessors.server("Test/1.1")) - .setSslContext(createSSLContext()) - .register("*", handler) - .create(); + .setListenerPort(port) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .setSslContext(createSSLContext()) + .register("*", handler) + .create(); server.start(); AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); @@ -379,30 +407,30 @@ public void httpsTest() throws Exception { // try with ssl verification on, no trust ca, must fail Settings settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "slack") - .put("path.home", ".") - .put("plugins.security.audit.config.webhook.ssl.verify", true) - .build(); - - LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); - WebhookSink auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "slack") + .put("path.home", ".") + .put("plugins.security.audit.config.webhook.ssl.verify", true) + .build(); + + LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); + WebhookSink auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); auditlog.store(msg); Assert.assertNull(handler.method); Assert.assertNull(handler.body); Assert.assertNull(handler.body); - // message must be stored in fallback - Assert.assertEquals(1, fallback.messages.size()); - Assert.assertEquals(msg, fallback.messages.get(0)); + // message must be stored in fallback + Assert.assertEquals(1, fallback.messages.size()); + Assert.assertEquals(msg, fallback.messages.get(0)); // disable ssl verification, no ca, call must succeed handler.reset(); settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "jSoN") - .put("plugins.security.audit.config.webhook.ssl.verify", false) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "jSoN") + .put("plugins.security.audit.config.webhook.ssl.verify", false) + .put("path.home", ".") + .build(); auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); auditlog.store(msg); Assert.assertTrue(handler.method.equals("POST")); @@ -413,12 +441,15 @@ public void httpsTest() throws Exception { // enable ssl verification, provide correct trust ca, call must succeed handler.reset(); settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "jSoN") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("plugins.security.audit.config.webhook.ssl.verify", true) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "jSoN") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put("plugins.security.audit.config.webhook.ssl.verify", true) + .put("path.home", ".") + .build(); auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); auditlog.store(msg); Assert.assertTrue(handler.method.equals("POST")); @@ -429,48 +460,54 @@ public void httpsTest() throws Exception { // enable ssl verification, provide wrong trust ca, call must succeed handler.reset(); settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "jSoN") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore_fail.jks")) - .put("plugins.security.audit.config.webhook.ssl.verify", true) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "jSoN") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore_fail.jks") + ) + .put("plugins.security.audit.config.webhook.ssl.verify", true) + .put("path.home", ".") + .build(); auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); auditlog.store(msg); Assert.assertNull(handler.method); Assert.assertNull(handler.body); Assert.assertNull(handler.body); - server.awaitTermination(TimeValue.ofSeconds(3)); + server.awaitTermination(TimeValue.ofSeconds(3)); } - @Test + @Test public void httpsTestPemDefault() throws Exception { final int port = findFreePort(); - TestHttpHandler handler = new TestHttpHandler(); + TestHttpHandler handler = new TestHttpHandler(); server = ServerBootstrap.bootstrap() - .setListenerPort(port) - .setHttpProcessor(HttpProcessors.server("Test/1.1")) - .setSslContext(createSSLContext()) - .register("*", handler) - .create(); + .setListenerPort(port) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .setSslContext(createSSLContext()) + .register("*", handler) + .create(); server.start(); AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); - LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); + LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); String url = "https://localhost:" + port + "/endpoint"; // test default with filepath handler.reset(); Settings settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "jSoN") - .put("plugins.security.audit.config.webhook.ssl.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/root-ca.pem")) - .put("plugins.security.audit.config.webhook.ssl.verify", true) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "jSoN") + .put( + "plugins.security.audit.config.webhook.ssl.pemtrustedcas_filepath", + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/root-ca.pem") + ) + .put("plugins.security.audit.config.webhook.ssl.verify", true) + .put("path.home", ".") + .build(); AuditLogSink auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); auditlog.store(msg); Assert.assertTrue(handler.method.equals("POST")); @@ -481,12 +518,15 @@ public void httpsTestPemDefault() throws Exception { // test default with missing filepath and fallback to correct Security settings handler.reset(); settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "jSoN") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("plugins.security.audit.config.webhook.ssl.verify", true) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "jSoN") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put("plugins.security.audit.config.webhook.ssl.verify", true) + .put("path.home", ".") + .build(); auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); auditlog.store(msg); Assert.assertTrue(handler.method.equals("POST")); @@ -497,13 +537,16 @@ public void httpsTestPemDefault() throws Exception { // test default with wrong filepath and fallback to wrong Security settings handler.reset(); settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "jSoN") - .put("plugins.security.audit.config.webhook.ssl.pemtrustedcas_filepath", "wrong") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore_fail.jks")) - .put("plugins.security.audit.config.webhook.ssl.verify", true) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "jSoN") + .put("plugins.security.audit.config.webhook.ssl.pemtrustedcas_filepath", "wrong") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore_fail.jks") + ) + .put("plugins.security.audit.config.webhook.ssl.verify", true) + .put("path.home", ".") + .build(); auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); auditlog.store(msg); Assert.assertNull(handler.method); @@ -513,12 +556,12 @@ public void httpsTestPemDefault() throws Exception { // test default with wrong/no filepath and no fallback to Security settings, must fail handler.reset(); settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.ssl.pemtrustedcas_filepath", "wrong") - .put("plugins.security.audit.config.webhook.format", "jSoN") - .put("plugins.security.audit.config.webhook.ssl.verify", true) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.ssl.pemtrustedcas_filepath", "wrong") + .put("plugins.security.audit.config.webhook.format", "jSoN") + .put("plugins.security.audit.config.webhook.ssl.verify", true) + .put("path.home", ".") + .build(); auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); auditlog.store(msg); Assert.assertNull(handler.method); @@ -528,12 +571,15 @@ public void httpsTestPemDefault() throws Exception { // test default with existing but wrong PEM, no fallback handler.reset(); settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "jSoN") - .put("plugins.security.audit.config.webhook.ssl.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/spock.crt.pem")) - .put("plugins.security.audit.config.webhook.ssl.verify", true) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "jSoN") + .put( + "plugins.security.audit.config.webhook.ssl.pemtrustedcas_filepath", + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/spock.crt.pem") + ) + .put("plugins.security.audit.config.webhook.ssl.verify", true) + .put("path.home", ".") + .build(); auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); auditlog.store(msg); Assert.assertNull(handler.method); @@ -543,49 +589,58 @@ public void httpsTestPemDefault() throws Exception { // test default with existing but wrong PEM, fallback present but pemtrustedcas_filepath takes precedence and must fail handler.reset(); settings = Settings.builder() - .put("plugins.security.audit.config.webhook.url", url) - .put("plugins.security.audit.config.webhook.format", "jSoN") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("plugins.security.audit.config.webhook.ssl.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/spock.crt.pem")) - .put("plugins.security.audit.config.webhook.ssl.verify", true) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.config.webhook.url", url) + .put("plugins.security.audit.config.webhook.format", "jSoN") + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put( + "plugins.security.audit.config.webhook.ssl.pemtrustedcas_filepath", + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/spock.crt.pem") + ) + .put("plugins.security.audit.config.webhook.ssl.verify", true) + .put("path.home", ".") + .build(); auditlog = new WebhookSink("name", settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT, null, fallback); auditlog.store(msg); Assert.assertNull(handler.method); Assert.assertNull(handler.body); Assert.assertNull(handler.body); - server.awaitTermination(TimeValue.ofSeconds(3)); - } + server.awaitTermination(TimeValue.ofSeconds(3)); + } - @Test + @Test public void httpsTestPemEndpoint() throws Exception { TestHttpHandler handler = new TestHttpHandler(); - int port = findFreePort(); + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(port) - .setHttpProcessor(HttpProcessors.server("Test/1.1")) - .setSslContext(createSSLContext()) - .register("*", handler) - .create(); + .setListenerPort(port) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .setSslContext(createSSLContext()) + .register("*", handler) + .create(); server.start(); AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); - LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); + LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); String url = "https://localhost:" + port + "/endpoint"; // test default with filepath handler.reset(); Settings settings = Settings.builder() - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", url) - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.format", "jSoN") - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/root-ca.pem")) - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.verify", true) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", url) + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.format", "jSoN") + .put( + "plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.pemtrustedcas_filepath", + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/root-ca.pem") + ) + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.verify", true) + .put("path.home", ".") + .build(); AuditLogSink auditlog = new WebhookSink("name", settings, "plugins.security.audit.endpoints.endpoint1.config", null, fallback); auditlog.store(msg); Assert.assertTrue(handler.method.equals("POST")); @@ -596,12 +651,15 @@ public void httpsTestPemEndpoint() throws Exception { // test default with missing filepath and fallback to correct Security settings handler.reset(); settings = Settings.builder() - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", url) - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.format", "jSoN") - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.verify", true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", url) + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.format", "jSoN") + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.verify", true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks") + ) + .put("path.home", ".") + .build(); auditlog = new WebhookSink("name", settings, "plugins.security.audit.endpoints.endpoint1.config", null, fallback); auditlog.store(msg); Assert.assertTrue(handler.method.equals("POST")); @@ -612,12 +670,15 @@ public void httpsTestPemEndpoint() throws Exception { // test default with wrong filepath and fallback to wrong Security settings handler.reset(); settings = Settings.builder() - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", url) - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.format", "jSoN") - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.verify", true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore_fail.jks")) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", url) + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.format", "jSoN") + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.verify", true) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore_fail.jks") + ) + .put("path.home", ".") + .build(); auditlog = new WebhookSink("name", settings, "plugins.security.audit.endpoints.endpoint1.config", null, fallback); auditlog.store(msg); Assert.assertNull(handler.method); @@ -627,11 +688,11 @@ public void httpsTestPemEndpoint() throws Exception { // test default with wrong/no filepath and no fallback to Security settings, must fail handler.reset(); settings = Settings.builder() - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", url) - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.format", "jSoN") - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.verify", true) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", url) + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.format", "jSoN") + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.verify", true) + .put("path.home", ".") + .build(); auditlog = new WebhookSink("name", settings, "plugins.security.audit.endpoints.endpoint1.config", null, fallback); auditlog.store(msg); Assert.assertNull(handler.method); @@ -641,49 +702,55 @@ public void httpsTestPemEndpoint() throws Exception { // test default with existing but wrong PEM, no fallback handler.reset(); settings = Settings.builder() - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", url) - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.format", "jSoN") - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.verify", true) - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/spock.crt.pem")) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", url) + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.format", "jSoN") + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.verify", true) + .put( + "plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.pemtrustedcas_filepath", + FileHelper.getAbsoluteFilePathFromClassPath("auditlog/spock.crt.pem") + ) + .put("path.home", ".") + .build(); auditlog = new WebhookSink("name", settings, "plugins.security.audit.endpoints.endpoint1.config", null, fallback); auditlog.store(msg); Assert.assertNull(handler.method); Assert.assertNull(handler.body); Assert.assertNull(handler.body); - server.awaitTermination(TimeValue.ofSeconds(3)); - } + server.awaitTermination(TimeValue.ofSeconds(3)); + } - @Test + @Test public void httpsTestPemContentEndpoint() throws Exception { TestHttpHandler handler = new TestHttpHandler(); - int port = findFreePort(); + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(port) - .setHttpProcessor(HttpProcessors.server("Test/1.1")) - .setSslContext(createSSLContext()) - .register("*", handler) - .create(); + .setListenerPort(port) + .setHttpProcessor(HttpProcessors.server("Test/1.1")) + .setSslContext(createSSLContext()) + .register("*", handler) + .create(); server.start(); AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); - LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); + LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); String url = "https://localhost:" + port + "/endpoint"; - // test with filecontent + // test with filecontent handler.reset(); Settings settings = Settings.builder() - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", url) - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.format", "jSoN") - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.pemtrustedcas_content", FileHelper.loadFile("auditlog/root-ca.pem")) - .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.verify", true) - .put("path.home", ".") - .build(); + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", url) + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.format", "jSoN") + .put( + "plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.pemtrustedcas_content", + FileHelper.loadFile("auditlog/root-ca.pem") + ) + .put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.verify", true) + .put("path.home", ".") + .build(); AuditLogSink auditlog = new WebhookSink("name", settings, "plugins.security.audit.endpoints.endpoint1.config", null, fallback); auditlog.store(msg); @@ -692,52 +759,51 @@ public void httpsTestPemContentEndpoint() throws Exception { Assert.assertTrue(handler.body.contains("{")); assertStringContainsAllKeysAndValues(handler.body); - server.awaitTermination(TimeValue.ofSeconds(3)); - } - - // for TLS support on our in-memory server - private SSLContext createSSLContext() throws Exception { - final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory - .getDefaultAlgorithm()); - final KeyStore trustStore = KeyStore.getInstance("JKS"); - InputStream trustStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks").toFile()); - trustStore.load(trustStream, "changeit".toCharArray()); - tmf.init(trustStore); - - final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - final KeyStore keyStore = KeyStore.getInstance("JKS"); - InputStream keyStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/node-0-keystore.jks").toFile()); - - keyStore.load(keyStream, "changeit".toCharArray()); - kmf.init(keyStore, "changeit".toCharArray()); - - SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); - sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - return sslContext; - } - - private void assertStringContainsAllKeysAndValues(String in) { - System.out.println(in); - Assert.assertTrue(in, in.contains(AuditMessage.FORMAT_VERSION)); - Assert.assertTrue(in, in.contains(AuditMessage.CATEGORY)); - Assert.assertTrue(in, in.contains(AuditMessage.FORMAT_VERSION)); - Assert.assertTrue(in, in.contains(AuditMessage.REMOTE_ADDRESS)); - Assert.assertTrue(in, in.contains(AuditMessage.ORIGIN)); - Assert.assertTrue(in, in.contains(AuditMessage.REQUEST_LAYER)); - Assert.assertTrue(in, in.contains(AuditMessage.TRANSPORT_REQUEST_TYPE)); - Assert.assertTrue(in, in.contains(AuditMessage.UTC_TIMESTAMP)); - Assert.assertTrue(in, in.contains(AuditCategory.FAILED_LOGIN.name())); - Assert.assertTrue(in, in.contains("FAILED_LOGIN")); - Assert.assertTrue(in, in.contains("John Doe")); - 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); - } - } + server.awaitTermination(TimeValue.ofSeconds(3)); + } + + // for TLS support on our in-memory server + private SSLContext createSSLContext() throws Exception { + final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + final KeyStore trustStore = KeyStore.getInstance("JKS"); + InputStream trustStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks").toFile()); + trustStore.load(trustStream, "changeit".toCharArray()); + tmf.init(trustStore); + + final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + final KeyStore keyStore = KeyStore.getInstance("JKS"); + InputStream keyStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/node-0-keystore.jks").toFile()); + + keyStore.load(keyStream, "changeit".toCharArray()); + kmf.init(keyStore, "changeit".toCharArray()); + + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + return sslContext; + } + + private void assertStringContainsAllKeysAndValues(String in) { + System.out.println(in); + Assert.assertTrue(in, in.contains(AuditMessage.FORMAT_VERSION)); + Assert.assertTrue(in, in.contains(AuditMessage.CATEGORY)); + Assert.assertTrue(in, in.contains(AuditMessage.FORMAT_VERSION)); + Assert.assertTrue(in, in.contains(AuditMessage.REMOTE_ADDRESS)); + Assert.assertTrue(in, in.contains(AuditMessage.ORIGIN)); + Assert.assertTrue(in, in.contains(AuditMessage.REQUEST_LAYER)); + Assert.assertTrue(in, in.contains(AuditMessage.TRANSPORT_REQUEST_TYPE)); + Assert.assertTrue(in, in.contains(AuditMessage.UTC_TIMESTAMP)); + Assert.assertTrue(in, in.contains(AuditCategory.FAILED_LOGIN.name())); + Assert.assertTrue(in, in.contains("FAILED_LOGIN")); + Assert.assertTrue(in, in.contains("John Doe")); + 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/InternalAuthBackendTests.java b/src/test/java/org/opensearch/security/auth/InternalAuthBackendTests.java index 3821be7038..c059c890ab 100644 --- a/src/test/java/org/opensearch/security/auth/InternalAuthBackendTests.java +++ b/src/test/java/org/opensearch/security/auth/InternalAuthBackendTests.java @@ -51,7 +51,7 @@ private char[] createArrayFromPasswordBytes(byte[] password) { CharBuffer buf = StandardCharsets.UTF_8.decode(wrap); char[] array = new char[buf.limit()]; buf.get(array); - Arrays.fill(password, (byte)0); + Arrays.fill(password, (byte) 0); return array; } @@ -68,12 +68,11 @@ public void testHashActionWithValidUserValidPassword() { char[] array = createArrayFromPasswordBytes(validPasswordBytes); - when(internalUsersModel.getHash(validUsernameAuth.getUsername())).thenReturn(hash); when(internalUsersModel.exists(validUsernameAuth.getUsername())).thenReturn(true); doReturn(true).when(internalAuthenticationBackend).passwordMatchesHash(Mockito.any(String.class), Mockito.any(char[].class)); - //Act + // Act internalAuthenticationBackend.authenticate(validUsernameAuth); verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array); @@ -95,9 +94,11 @@ public void testHashActionWithValidUserInvalidPassword() { when(internalUsersModel.getHash("admin")).thenReturn(hash); when(internalUsersModel.exists("admin")).thenReturn(true); - OpenSearchSecurityException ex = Assert.assertThrows(OpenSearchSecurityException.class, - () -> internalAuthenticationBackend.authenticate(validUsernameAuth)); - assert(ex.getMessage().contains("password does not match")); + OpenSearchSecurityException ex = Assert.assertThrows( + OpenSearchSecurityException.class, + () -> internalAuthenticationBackend.authenticate(validUsernameAuth) + ); + assert (ex.getMessage().contains("password does not match")); verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array); } @@ -114,11 +115,13 @@ public void testHashActionWithInvalidUserValidPassword() { char[] array = createArrayFromPasswordBytes(validPasswordBytes); when(internalUsersModel.exists("ertyuiykgjjfguyifdghc")).thenReturn(false); - when(internalAuthenticationBackend.passwordMatchesHash(hash, array)).thenReturn(true); //Say that the password is correct + when(internalAuthenticationBackend.passwordMatchesHash(hash, array)).thenReturn(true); // Say that the password is correct - OpenSearchSecurityException ex = Assert.assertThrows(OpenSearchSecurityException.class, - () -> internalAuthenticationBackend.authenticate(invalidUsernameAuth)); - assert(ex.getMessage().contains("not found")); + OpenSearchSecurityException ex = Assert.assertThrows( + OpenSearchSecurityException.class, + () -> internalAuthenticationBackend.authenticate(invalidUsernameAuth) + ); + assert (ex.getMessage().contains("not found")); verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array); } @@ -136,10 +139,11 @@ public void testHashActionWithInvalidUserInvalidPassword() { when(internalUsersModel.exists("ertyuiykgjjfguyifdghc")).thenReturn(false); - - OpenSearchSecurityException ex = Assert.assertThrows(OpenSearchSecurityException.class, - () -> internalAuthenticationBackend.authenticate(invalidUsernameAuth)); + OpenSearchSecurityException ex = Assert.assertThrows( + OpenSearchSecurityException.class, + () -> internalAuthenticationBackend.authenticate(invalidUsernameAuth) + ); verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array); - assert(ex.getMessage().contains("not found")); + assert (ex.getMessage().contains("not found")); } } diff --git a/src/test/java/org/opensearch/security/auth/RolesInjectorTest.java b/src/test/java/org/opensearch/security/auth/RolesInjectorTest.java index 0656f9ccd1..63eb32f862 100644 --- a/src/test/java/org/opensearch/security/auth/RolesInjectorTest.java +++ b/src/test/java/org/opensearch/security/auth/RolesInjectorTest.java @@ -34,7 +34,6 @@ import static org.mockito.Mockito.mock; import static org.opensearch.security.support.ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES; - public class RolesInjectorTest { private TransportRequest transportRequest; @@ -76,13 +75,7 @@ public void testInjected() { @Test public void testCorruptedInjection() { - List corruptedStrs = Arrays.asList( - "invalid", - "role_1,role_2", - " | ", - " ", - "|" - ); + List corruptedStrs = Arrays.asList("invalid", "role_1,role_2", " | ", " ", "|"); corruptedStrs.forEach(name -> { ThreadContext threadContext = new ThreadContext(Settings.EMPTY); diff --git a/src/test/java/org/opensearch/security/auth/UserInjectorTest.java b/src/test/java/org/opensearch/security/auth/UserInjectorTest.java index 09bc1653a4..e9570b1455 100644 --- a/src/test/java/org/opensearch/security/auth/UserInjectorTest.java +++ b/src/test/java/org/opensearch/security/auth/UserInjectorTest.java @@ -43,9 +43,7 @@ public class UserInjectorTest { @Before public void setup() { threadPool = mock(ThreadPool.class); - Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) - .build(); + Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true).build(); threadContext = new ThreadContext(settings); Mockito.when(threadPool.getThreadContext()).thenReturn(threadContext); transportRequest = mock(TransportRequest.class); 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 2e8ddec15b..c92c328564 100644 --- a/src/test/java/org/opensearch/security/auth/limiting/HeapBasedRateTrackerTest.java +++ b/src/test/java/org/opensearch/security/auth/limiting/HeapBasedRateTrackerTest.java @@ -76,7 +76,6 @@ public void expiryTest() throws Exception { assertFalse(tracker.track("c")); assertTrue(tracker.track("c")); - } @Test diff --git a/src/test/java/org/opensearch/security/cache/CachingTest.java b/src/test/java/org/opensearch/security/cache/CachingTest.java index 5276196856..4bff91a1f3 100644 --- a/src/test/java/org/opensearch/security/cache/CachingTest.java +++ b/src/test/java/org/opensearch/security/cache/CachingTest.java @@ -23,7 +23,7 @@ import org.opensearch.security.test.helper.rest.RestHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class CachingTest extends SingleClusterTest{ +public class CachingTest extends SingleClusterTest { @Override protected String getResourceFolder() { @@ -84,16 +84,28 @@ public void testRestCachingWithImpersonation() throws Exception { final Settings settings = Settings.builder().putList("plugins.security.authcz.rest_impersonation_user.dummy", "*").build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings); final RestHelper rh = nonSslRestHelper(); - HttpResponse res = rh.executeGetRequest("_opendistro/_security/authinfo?pretty", new BasicHeader("opendistro_security_impersonate_as", "impuser")); + HttpResponse res = rh.executeGetRequest( + "_opendistro/_security/authinfo?pretty", + new BasicHeader("opendistro_security_impersonate_as", "impuser") + ); System.out.println(res.getBody()); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - res = rh.executeGetRequest("_opendistro/_security/authinfo?pretty", new BasicHeader("opendistro_security_impersonate_as", "impuser")); + res = rh.executeGetRequest( + "_opendistro/_security/authinfo?pretty", + new BasicHeader("opendistro_security_impersonate_as", "impuser") + ); System.out.println(res.getBody()); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - res = rh.executeGetRequest("_opendistro/_security/authinfo?pretty", new BasicHeader("opendistro_security_impersonate_as", "impuser")); + res = rh.executeGetRequest( + "_opendistro/_security/authinfo?pretty", + new BasicHeader("opendistro_security_impersonate_as", "impuser") + ); System.out.println(res.getBody()); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); - res = rh.executeGetRequest("_opendistro/_security/authinfo?pretty", new BasicHeader("opendistro_security_impersonate_as", "impuser2")); + res = rh.executeGetRequest( + "_opendistro/_security/authinfo?pretty", + new BasicHeader("opendistro_security_impersonate_as", "impuser2") + ); System.out.println(res.getBody()); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); diff --git a/src/test/java/org/opensearch/security/cache/DummyAuthenticationBackend.java b/src/test/java/org/opensearch/security/cache/DummyAuthenticationBackend.java index f41ddade65..5e7980f431 100644 --- a/src/test/java/org/opensearch/security/cache/DummyAuthenticationBackend.java +++ b/src/test/java/org/opensearch/security/cache/DummyAuthenticationBackend.java @@ -19,14 +19,12 @@ import org.opensearch.security.user.AuthCredentials; import org.opensearch.security.user.User; - public class DummyAuthenticationBackend implements AuthenticationBackend { private static volatile long authCount; private static volatile long existsCount; - public DummyAuthenticationBackend(final Settings settings, final Path configPath) { - } + public DummyAuthenticationBackend(final Settings settings, final Path configPath) {} @Override public String getType() { @@ -54,7 +52,7 @@ public static long getExistsCount() { } public static void reset() { - authCount=0; - existsCount=0; + authCount = 0; + existsCount = 0; } } diff --git a/src/test/java/org/opensearch/security/cache/DummyAuthorizer.java b/src/test/java/org/opensearch/security/cache/DummyAuthorizer.java index 4c489f0c00..8f8a507cb8 100644 --- a/src/test/java/org/opensearch/security/cache/DummyAuthorizer.java +++ b/src/test/java/org/opensearch/security/cache/DummyAuthorizer.java @@ -19,13 +19,11 @@ import org.opensearch.security.user.AuthCredentials; import org.opensearch.security.user.User; - public class DummyAuthorizer implements AuthorizationBackend { private static volatile long count; - public DummyAuthorizer(final Settings settings, final Path configPath) { - } + public DummyAuthorizer(final Settings settings, final Path configPath) {} @Override public String getType() { @@ -44,7 +42,7 @@ public static long getCount() { } public static void reset() { - count=0; + count = 0; } } diff --git a/src/test/java/org/opensearch/security/cache/DummyHTTPAuthenticator.java b/src/test/java/org/opensearch/security/cache/DummyHTTPAuthenticator.java index 48bead257b..55c2e789c6 100644 --- a/src/test/java/org/opensearch/security/cache/DummyHTTPAuthenticator.java +++ b/src/test/java/org/opensearch/security/cache/DummyHTTPAuthenticator.java @@ -25,8 +25,7 @@ public class DummyHTTPAuthenticator implements HTTPAuthenticator { private static volatile long count; - public DummyHTTPAuthenticator(final Settings settings, final Path configPath) { - } + public DummyHTTPAuthenticator(final Settings settings, final Path configPath) {} @Override public String getType() { @@ -49,6 +48,6 @@ public static long getCount() { } public static void reset() { - count=0; + count = 0; } } diff --git a/src/test/java/org/opensearch/security/ccstest/CrossClusterMinimalRoundtripSearchTests.java b/src/test/java/org/opensearch/security/ccstest/CrossClusterMinimalRoundtripSearchTests.java index 292a0d38d8..db948b77df 100644 --- a/src/test/java/org/opensearch/security/ccstest/CrossClusterMinimalRoundtripSearchTests.java +++ b/src/test/java/org/opensearch/security/ccstest/CrossClusterMinimalRoundtripSearchTests.java @@ -13,5 +13,7 @@ public class CrossClusterMinimalRoundtripSearchTests extends CrossClusterSearchTests { @Override - protected boolean ccsMinimizeRoundtrips() { return true; } + protected boolean ccsMinimizeRoundtrips() { + return true; + } } diff --git a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java index b9b4d22d49..fad6c77a1a 100644 --- a/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java +++ b/src/test/java/org/opensearch/security/ccstest/CrossClusterSearchTests.java @@ -68,14 +68,20 @@ public class CrossClusterSearchTests extends AbstractSecurityUnitTest { - private final ClusterHelper cl1 = new ClusterHelper("crl1_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); - private final ClusterHelper cl2 = new ClusterHelper("crl2_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); + private final ClusterHelper cl1 = new ClusterHelper( + "crl1_n" + num.incrementAndGet() + "_f" + System.getProperty("forkno") + "_t" + System.nanoTime() + ); + private final ClusterHelper cl2 = new ClusterHelper( + "crl2_n" + num.incrementAndGet() + "_f" + System.getProperty("forkno") + "_t" + System.nanoTime() + ); private ClusterInfo cl1Info; private ClusterInfo cl2Info; private RestHelper rh1; private RestHelper rh2; - protected boolean ccsMinimizeRoundtrips() { return false; }; + protected boolean ccsMinimizeRoundtrips() { + return false; + }; private static class ClusterTransportClientSettings extends Tuple { @@ -84,9 +90,7 @@ public ClusterTransportClientSettings() { } public ClusterTransportClientSettings(Settings clusterSettings, Settings transportSettings) { - super(Settings.builder() - .put(clusterSettings) - .putList("node.roles", "remote_cluster_client").build(), transportSettings); + super(Settings.builder().put(clusterSettings).putList("node.roles", "remote_cluster_client").build(), transportSettings); } public Settings clusterSettings() { @@ -106,10 +110,13 @@ private void setupCcs(DynamicSecurityConfig dynamicSecurityConfig) throws Except setupCcs(dynamicSecurityConfig, new ClusterTransportClientSettings(), new ClusterTransportClientSettings()); } - private void setupCcs(DynamicSecurityConfig dynamicSecurityConfig, - ClusterTransportClientSettings cluster1Settings, ClusterTransportClientSettings cluster2Settings) throws Exception { + private void setupCcs( + DynamicSecurityConfig dynamicSecurityConfig, + ClusterTransportClientSettings cluster1Settings, + ClusterTransportClientSettings cluster2Settings + ) throws Exception { - System.setProperty("security.display_lic_none","true"); + System.setProperty("security.display_lic_none", "true"); Tuple cluster2 = setupCluster(cl2, cluster2Settings, dynamicSecurityConfig); cl2Info = cluster2.v1(); @@ -120,19 +127,17 @@ private void setupCcs(DynamicSecurityConfig dynamicSecurityConfig, rh1 = cluster1.v2(); final String seed = cl2Info.nodeHost + ":" + cl2Info.nodePort; - String json = - "{" + - "\"persistent\" : {" + - "\"cluster.remote.cross_cluster_two.seeds\" : [\"" + seed + "\"]" + - "}" + - "}"; - + String json = "{" + "\"persistent\" : {" + "\"cluster.remote.cross_cluster_two.seeds\" : [\"" + seed + "\"]" + "}" + "}"; HttpResponse response = rh1.executePutRequest("_cluster/settings", json, encodeBasicHeader("sarek", "sarek")); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); } - private Tuple setupCluster(ClusterHelper ch, ClusterTransportClientSettings cluster, DynamicSecurityConfig dynamicSecurityConfig) throws Exception { + private Tuple setupCluster( + ClusterHelper ch, + ClusterTransportClientSettings cluster, + DynamicSecurityConfig dynamicSecurityConfig + ) throws Exception { NodeSettingsSupplier settings = minimumSecuritySettings(cluster.clusterSettings()); ClusterInfo clusterInfo = ch.startCluster(settings, ClusterConfiguration.DEFAULT); initialize(ch, clusterInfo, dynamicSecurityConfig); @@ -154,52 +159,82 @@ public void tearDown() throws Exception { public void testCcs() throws Exception { setupCcs(); - final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("nagilum","nagilum")).getBody(); + final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("nagilum", "nagilum") + ).getBody(); Assert.assertTrue(cl1BodyMain.contains("crl1")); try (Client tc = cl1.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } - final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("nagilum","nagilum")).getBody(); + final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("nagilum", "nagilum") + ).getBody(); Assert.assertTrue(cl2BodyMain.contains("crl2")); try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } HttpResponse ccs = null; System.out.println("###################### query 1"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("nagilum", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("crl1")); Assert.assertTrue(ccs.getBody().contains("crl2")); Assert.assertTrue(ccs.getBody().contains("twitter")); - System.out.println("###################### query 4"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:xx,xx/xx/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:xx,xx/xx/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("nagilum", "nagilum") + ); System.out.println(ccs.getBody()); - //TODO fix exception nesting - //Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, ccs.getStatusCode()); - //Assert.assertTrue(ccs.getBody().contains("Can not filter indices; index cross_cluster_two:xx exists but there is also a remote cluster named: cross_cluster_two")); + // TODO fix exception nesting + // Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, ccs.getStatusCode()); + // Assert.assertTrue(ccs.getBody().contains("Can not filter indices; index cross_cluster_two:xx exists but there is also a remote + // cluster named: cross_cluster_two")); System.out.println("###################### query 5"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:abcnonext/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:abcnonext/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("nagilum", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("index_not_found_exception")); System.out.println("###################### query 6"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twutter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twitter,twutter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("nagilum", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); @@ -213,168 +248,279 @@ public void testCcs() throws Exception { public void testCcsNonadmin() throws Exception { setupCcs(); - final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl1BodyMain.contains("crl1")); try (Client tc = cl1.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("twitter").alias("coordalias"))).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("twitter").alias("coordalias"))) + .actionGet(); } - final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl2BodyMain.contains("crl2")); try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("twitter").alias("remotealias"))).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("twitter").alias("remotealias"))) + .actionGet(); } HttpResponse ccs = null; System.out.println("###################### query 1"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); System.out.println("###################### query 2"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twit*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twit*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - System.out.println("###################### query 3"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter,twutter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twitter,twitter,twutter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); System.out.println("###################### query 4"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("crl1_")); Assert.assertTrue(ccs.getBody().contains("crl2_")); System.out.println("###################### query 5"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twutter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twutter,twitter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); System.out.println("###################### query 6"); - String msearchBody = - "{}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); + String msearchBody = "{}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:twitter,twitter/_msearch?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), msearchBody, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:twitter,twitter/_msearch?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + msearchBody, + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); System.out.println("###################### query 7"); - msearchBody = - "{}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); + msearchBody = "{}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:twitter/_msearch?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), msearchBody, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:twitter/_msearch?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + msearchBody, + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("_all/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "_all/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "*:*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("hfghgtdhfhuth/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "hfghgtdhfhuth/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("hfghgtdhfhuth*/_search", encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "hfghgtdhfhuth*/_search", + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - Assert.assertTrue(ccs.getBody().contains("\"hits\":[]")); //TODO: Change for 25.0 to be forbidden (Indices options) + Assert.assertTrue(ccs.getBody().contains("\"hits\":[]")); // TODO: Change for 25.0 to be forbidden (Indices options) - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest(":*/_search", encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest(":*/_search", encodeBasicHeader("worf", "worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - Assert.assertTrue(ccs.getBody().contains("\"hits\":[]")); //TODO: Change for 25.0 to be forbidden (Indices options) + Assert.assertTrue(ccs.getBody().contains("\"hits\":[]")); // TODO: Change for 25.0 to be forbidden (Indices options) - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*:/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "*:/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E,%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E,%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips=" + + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "coordalias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "coordalias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); System.out.println("#### Alias both"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("notexist,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "notexist,coordalias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - //TODO Fix for 25.0 to resolve coordalias (Indices options) + // TODO Fix for 25.0 to resolve coordalias (Indices options) - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("crusherw","crusherw")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("crusherw", "crusherw") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); @@ -384,176 +530,291 @@ public void testCcsNonadmin() throws Exception { public void testCcsNonadminDnfof() throws Exception { setupCcs(new DynamicSecurityConfig().setConfig("config_dnfof.yml")); - final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl1BodyMain.contains("crl1")); try (Client tc = cl1.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("twitter").alias("coordalias"))).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("twitter").alias("coordalias"))) + .actionGet(); } - final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl2BodyMain.contains("crl2")); try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("twitter").alias("remotealias"))).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("twitter").alias("remotealias"))) + .actionGet(); } HttpResponse ccs = null; System.out.println("###################### query 1"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("crl1_")); Assert.assertTrue(ccs.getBody().contains("crl2_")); System.out.println("###################### query 2"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twit*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twit*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - System.out.println("###################### query 3"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter,twutter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twitter,twitter,twutter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("twutter")); System.out.println("###################### query 4"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("crl1_")); Assert.assertTrue(ccs.getBody().contains("crl2_")); System.out.println("###################### query 5"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twutter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twutter,twitter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); System.out.println("###################### query 6"); - String msearchBody = - "{}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); + String msearchBody = "{}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:twitter,twitter/_msearch?pretty", msearchBody, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:twitter,twitter/_msearch?pretty", + msearchBody, + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); System.out.println("###################### query 7"); - msearchBody = - "{}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); + msearchBody = "{}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:twitter/_msearch?pretty", msearchBody, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:twitter/_msearch?pretty", + msearchBody, + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("_all/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "_all/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twitter,twitter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); System.out.println("#####*"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:*,*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:*,*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("crl1_")); Assert.assertTrue(ccs.getBody().contains("crl2_")); - //wildcard in remote cluster names - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*cross*:*twit*,*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + // wildcard in remote cluster names + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "*cross*:*twit*,*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter,t*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twitter,t*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*:*/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "*:*/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("hfghgtdhfhuth/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "hfghgtdhfhuth/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("hfghgtdhfhuth*/_search", encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "hfghgtdhfhuth*/_search", + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - Assert.assertTrue(ccs.getBody().contains("\"hits\":[]")); //TODO: Change for 25.0 to be forbidden (Indices options) + Assert.assertTrue(ccs.getBody().contains("\"hits\":[]")); // TODO: Change for 25.0 to be forbidden (Indices options) - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest(":*/_search", encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest(":*/_search", encodeBasicHeader("worf", "worf")); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - Assert.assertTrue(ccs.getBody().contains("\"hits\":[]")); //TODO: Change for 25.0 to be forbidden (Indices options) + Assert.assertTrue(ccs.getBody().contains("\"hits\":[]")); // TODO: Change for 25.0 to be forbidden (Indices options) - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("*:/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "*:/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E,%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:%3Clogstash-%7Bnow%2Fd%7D%3E,%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty&ccs_minimize_roundtrips=" + + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "coordalias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("worf","worf")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("worf", "worf") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:remotealias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "coordalias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:remotealias,coordalias/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("crusherw","crusherw")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("crusherw", "crusherw") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); } @@ -562,21 +823,33 @@ public void testCcsNonadminDnfof() throws Exception { public void testCcsEmptyCoord() throws Exception { setupCcs(); - final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl1BodyMain.contains("crl1")); - final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl2BodyMain.contains("crl2")); try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } HttpResponse ccs = null; System.out.println("###################### query 1"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:twitter/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("twitter", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); @@ -590,74 +863,120 @@ public void testCcsEmptyCoord() throws Exception { public void testCcsDashboardsAggregations() throws Exception { setupCcs(); - final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl1BodyMain.contains("crl1")); - final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl2BodyMain.contains("crl2")); try (Client tc = cl1.nodeClient()) { - tc.index(new IndexRequest("coordinating").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("abc").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("coordinating").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("abc").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } - try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("remote").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("remote").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } HttpResponse ccs = null; System.out.println("###################### kibana indices agg"); String dashboardsIndicesAgg = "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":100}}}}"; - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertFalse(ccs.getBody().contains("cross_cluster_two")); Assert.assertTrue(ccs.getBody().contains("coordinating")); Assert.assertTrue(ccs.getBody().contains("abc")); Assert.assertFalse(ccs.getBody().contains("remote")); - ccs = new RestHelper(cl2Info, false, false, getResourceFolder()).executePostRequest("*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl2Info, false, false, getResourceFolder()).executePostRequest( + "*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertFalse(ccs.getBody().contains("cross_cluster_two")); Assert.assertFalse(ccs.getBody().contains("coordinating")); Assert.assertFalse(ccs.getBody().contains("abc")); Assert.assertTrue(ccs.getBody().contains("remote")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:remo*,coo*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:remo*,coo*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("cross_cluster_two")); Assert.assertTrue(ccs.getBody().contains("remote")); Assert.assertTrue(ccs.getBody().contains("coordinating")); Assert.assertFalse(ccs.getBody().contains("abc")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:remote/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:remote/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("cross_cluster_two")); Assert.assertTrue(ccs.getBody().contains("remote")); Assert.assertFalse(ccs.getBody().contains("coordinating")); Assert.assertFalse(ccs.getBody().contains("abc")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("cross_cluster_two")); Assert.assertTrue(ccs.getBody().contains("remote")); Assert.assertFalse(ccs.getBody().contains("coordinating")); Assert.assertFalse(ccs.getBody().contains("abc")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:*,*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:*,*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("cross_cluster_two")); Assert.assertTrue(ccs.getBody().contains("remote")); Assert.assertTrue(ccs.getBody().contains("coordinating")); Assert.assertTrue(ccs.getBody().contains("abc")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:remo*,ab*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:remo*,ab*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("cross_cluster_two")); @@ -670,35 +989,59 @@ public void testCcsDashboardsAggregations() throws Exception { public void testCcsDashboardsAggregationsNonAdminDnfof() throws Exception { setupCcs(new DynamicSecurityConfig().setConfig("config_dnfof.yml")); - final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl1BodyMain.contains("crl1")); - final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl2BodyMain.contains("crl2")); try (Client tc = cl1.nodeClient()) { - tc.index(new IndexRequest("coordinating").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("abc").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("coordinating").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("abc").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } - try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("remote").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("analytics").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("remote").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + + tc.index( + new IndexRequest("analytics").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } HttpResponse ccs = null; System.out.println("###################### kibana indices agg"); String dashboardsIndicesAgg = "{\"size\":0,\"aggs\":{\"indices\":{\"terms\":{\"field\":\"_index\",\"size\":100}}}}"; - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertFalse(ccs.getBody().contains("cross_cluster_two")); @@ -708,7 +1051,11 @@ public void testCcsDashboardsAggregationsNonAdminDnfof() throws Exception { Assert.assertFalse(ccs.getBody().contains("coordinating")); Assert.assertFalse(ccs.getBody().contains("abc")); Assert.assertFalse(ccs.getBody().contains("remote")); - ccs = new RestHelper(cl2Info, false, false, getResourceFolder()).executePostRequest("*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl2Info, false, false, getResourceFolder()).executePostRequest( + "*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertFalse(ccs.getBody().contains("cross_cluster_two")); @@ -718,7 +1065,11 @@ public void testCcsDashboardsAggregationsNonAdminDnfof() throws Exception { Assert.assertFalse(ccs.getBody().contains("coordinating")); Assert.assertFalse(ccs.getBody().contains("abc")); Assert.assertFalse(ccs.getBody().contains("remote")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:*,*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:*,*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("cross_cluster_two:analytics")); @@ -726,9 +1077,17 @@ public void testCcsDashboardsAggregationsNonAdminDnfof() throws Exception { Assert.assertFalse(ccs.getBody().contains("coordinating")); Assert.assertFalse(ccs.getBody().contains("abc")); Assert.assertFalse(ccs.getBody().contains("remote")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:remo*,coo*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:remo*,coo*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:ana*,twi*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:ana*,twi*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("cross_cluster_two:analytics")); @@ -736,7 +1095,11 @@ public void testCcsDashboardsAggregationsNonAdminDnfof() throws Exception { Assert.assertFalse(ccs.getBody().contains("coordinating")); Assert.assertFalse(ccs.getBody().contains("abc")); Assert.assertFalse(ccs.getBody().contains("remote")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:ana*,xyz*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:ana*,xyz*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("cross_cluster_two:analytics")); @@ -744,9 +1107,17 @@ public void testCcsDashboardsAggregationsNonAdminDnfof() throws Exception { Assert.assertFalse(ccs.getBody().contains("coordinating")); Assert.assertFalse(ccs.getBody().contains("abc")); Assert.assertFalse(ccs.getBody().contains("remote")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:ana*,xyz/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:ana*,xyz/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:*/_search?pretty", dashboardsIndicesAgg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:*/_search?pretty", + dashboardsIndicesAgg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("cross_cluster_two:analytics")); @@ -760,30 +1131,48 @@ public void testCcsDashboardsAggregationsNonAdminDnfof() throws Exception { public void testCcsAggregations() throws Exception { setupCcs(); - final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl1BodyMain.contains("crl1")); - final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl2BodyMain.contains("crl2")); try (Client tc = cl1.nodeClient()) { - tc.index(new IndexRequest("coordinating").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("abc").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("coordinating").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("abc").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } - try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("remote").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("remote").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } HttpResponse ccs = null; System.out.println("###################### aggs"); final String agg = "{\"size\":0,\"aggs\":{\"clusteragg\":{\"terms\":{\"field\":\"cluster.keyword\",\"size\":100}}}}"; - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("*:*,*/_search?pretty", agg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "*:*,*/_search?pretty", + agg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("\"timed_out\" : false")); @@ -791,7 +1180,11 @@ public void testCcsAggregations() throws Exception { Assert.assertTrue(ccs.getBody().contains("crl2")); Assert.assertTrue(ccs.getBody().contains("\"doc_count\" : 2")); Assert.assertTrue(ccs.getBody().contains("\"doc_count\" : 1")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("coordin*/_search?pretty", agg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "coordin*/_search?pretty", + agg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("\"timed_out\" : false")); @@ -799,7 +1192,11 @@ public void testCcsAggregations() throws Exception { Assert.assertFalse(ccs.getBody().contains("crl2")); Assert.assertFalse(ccs.getBody().contains("\"doc_count\" : 2")); Assert.assertTrue(ccs.getBody().contains("\"doc_count\" : 1")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:remo*/_search?pretty", agg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:remo*/_search?pretty", + agg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("\"timed_out\" : false")); @@ -807,55 +1204,107 @@ public void testCcsAggregations() throws Exception { Assert.assertTrue(ccs.getBody().contains("crl2")); Assert.assertFalse(ccs.getBody().contains("\"doc_count\" : 2")); Assert.assertTrue(ccs.getBody().contains("\"doc_count\" : 1")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:notfound,*/_search?pretty", agg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:notfound,*/_search?pretty", + agg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:*,notfound/_search?pretty", agg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:*,notfound/_search?pretty", + agg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:notfound,notfound/_search?pretty", agg, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:notfound,notfound/_search?pretty", + agg, + encodeBasicHeader("nagilum", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:notfou*,*/_search?pretty", agg, encodeBasicHeader("nagilum","nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode());//TODO: Change for 25.0 to be forbidden (Indices options) - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:*,notfou*/_search?pretty", agg, encodeBasicHeader("nagilum","nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode());//TODO: Change for 25.0 to be forbidden (Indices options) - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:not*,notf*/_search?pretty", agg, encodeBasicHeader("nagilum","nagilum")); - Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode());//TODO: Change for 25.0 to be forbidden (Indices options) + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:notfou*,*/_search?pretty", + agg, + encodeBasicHeader("nagilum", "nagilum") + ); + Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode());// TODO: Change for 25.0 to be forbidden (Indices options) + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:*,notfou*/_search?pretty", + agg, + encodeBasicHeader("nagilum", "nagilum") + ); + Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode());// TODO: Change for 25.0 to be forbidden (Indices options) + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:not*,notf*/_search?pretty", + agg, + encodeBasicHeader("nagilum", "nagilum") + ); + Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode());// TODO: Change for 25.0 to be forbidden (Indices options) } @Test public void testCcsAggregationsDnfof() throws Exception { setupCcs(new DynamicSecurityConfig().setConfig("config_dnfof.yml")); - final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl1BodyMain.contains("crl1")); - final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("twitter", "nagilum") + ).getBody(); Assert.assertTrue(cl2BodyMain.contains("crl2")); try (Client tc = cl1.nodeClient()) { - tc.index(new IndexRequest("coordinating").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("abc").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("coordinating").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("abc").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } - try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("remote").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("analytics").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("remote").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + + tc.index( + new IndexRequest("analytics").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } HttpResponse ccs = null; System.out.println("###################### aggs"); final String agg = "{\"size\":0,\"aggs\":{\"clusteragg\":{\"terms\":{\"field\":\"cluster.keyword\",\"size\":100}}}}"; - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:notfound,*/_search?pretty", agg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:notfound,*/_search?pretty", + agg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:notfound*,*/_search?pretty", agg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:notfound*,*/_search?pretty", + agg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("security_exception")); Assert.assertTrue(ccs.getBody().contains("\"timed_out\" : false")); @@ -863,58 +1312,97 @@ public void testCcsAggregationsDnfof() throws Exception { Assert.assertFalse(ccs.getBody().contains("crl2")); Assert.assertFalse(ccs.getBody().contains("\"doc_count\" : 2")); Assert.assertTrue(ccs.getBody().contains("\"doc_count\" : 1")); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:*,notfound/_search?pretty", agg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:*,notfound/_search?pretty", + agg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:notfound,notfound/_search?pretty", agg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:notfound,notfound/_search?pretty", + agg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:notfou*,*/_search?pretty", agg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:notfou*,*/_search?pretty", + agg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:*,notfou*/_search?pretty", agg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:*,notfou*/_search?pretty", + agg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("cross_cluster_two:not*,notf*/_search?pretty", agg, encodeBasicHeader("twitter","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "cross_cluster_two:not*,notf*/_search?pretty", + agg, + encodeBasicHeader("twitter", "nagilum") + ); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); } - private ClusterTransportClientSettings getBaseSettingsWithDifferentCert() { Settings cluster = Settings.builder() .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("restapi/node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath("restapi/truststore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("node-untspec5-keystore.p12")) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("restapi/node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("restapi/truststore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("node-untspec5-keystore.p12") + ) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "1") .put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "PKCS12") - .putList(ConfigConstants.SECURITY_NODES_DN, - "EMAILADDRESS=unt@tst.com,CN=node-untspec5.example.com,OU=SSL,O=Te\\, st,L=Test,C=DE")//, "CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") - .putList(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, + .putList( + ConfigConstants.SECURITY_NODES_DN, + "EMAILADDRESS=unt@tst.com,CN=node-untspec5.example.com,OU=SSL,O=Te\\, st,L=Test,C=DE" + )// , "CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + .putList( + ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, "EMAILADDRESS=unt@xxx.com,CN=node-untspec6.example.com,OU=SSL,O=Te\\, st,L=Test,C=DE", - "CN=kirk,OU=client,O=client,l=tEst, C=De") - .put(ConfigConstants.SECURITY_CERT_OID,"1.2.3.4.5.6") + "CN=kirk,OU=client,O=client,l=tEst, C=De" + ) + .put(ConfigConstants.SECURITY_CERT_OID, "1.2.3.4.5.6") .build(); Settings transport = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath("node-untspec6-keystore.p12")) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath("node-untspec6-keystore.p12") + ) .build(); return new ClusterTransportClientSettings(cluster, transport); } private void populateBaseData(ClusterTransportClientSettings cluster1, ClusterTransportClientSettings cluster2) throws Exception { - final String cl1BodyMain = rh1.executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl1BodyMain = rh1.executeGetRequest("", encodeBasicHeader("twitter", "nagilum")).getBody(); Assert.assertTrue(cl1BodyMain, cl1BodyMain.contains("crl1")); - final String cl2BodyMain = rh2.executeGetRequest("", encodeBasicHeader("twitter","nagilum")).getBody(); + final String cl2BodyMain = rh2.executeGetRequest("", encodeBasicHeader("twitter", "nagilum")).getBody(); Assert.assertTrue(cl2BodyMain.contains("crl2")); try (Client tc = cl1.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } } @@ -939,9 +1427,11 @@ public void testCcsWithDiffCertsWithNodesDnStaticallyAdded() throws Exception { ClusterTransportClientSettings cluster2 = getBaseSettingsWithDifferentCert(); Settings updatedCluster2 = Settings.builder() .put(cluster2.clusterSettings()) - .putList(ConfigConstants.SECURITY_NODES_DN, + .putList( + ConfigConstants.SECURITY_NODES_DN, "EMAILADDRESS=unt@tst.com,CN=node-untspec5.example.com,OU=SSL,O=Te\\, st,L=Test,C=DE", - "CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE") + "CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE" + ) .build(); cluster2 = new ClusterTransportClientSettings(updatedCluster2, cluster2.transportClientSettings()); @@ -966,9 +1456,11 @@ public void testCcsWithDiffCertsWithNodesDnDynamicallyAdded() throws Exception { setupCcs(new DynamicSecurityConfig().setSecurityNodesDn("nodes_dn_empty.yml"), cluster1, cluster2); - HttpResponse response = rh2.executePutRequest("_opendistro/_security/api/nodesdn/connection1", + HttpResponse response = rh2.executePutRequest( + "_opendistro/_security/api/nodesdn/connection1", "{\"nodes_dn\": [\"CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE\"]}", - encodeBasicHeader("sarek", "sarek")); + encodeBasicHeader("sarek", "sarek") + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); populateBaseData(cluster1, cluster2); @@ -988,41 +1480,59 @@ public void testCcsWithDiffCertsWithNodesDnDynamicallyAdded() throws Exception { public void testCcsWithRoleInjection() throws Exception { setupCcs(new DynamicSecurityConfig().setSecurityRoles("roles.yml")); - Assert.assertEquals(cl1Info.numNodes, cl1.nodeClient().admin().cluster().health( - new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); - Assert.assertEquals(ClusterHealthStatus.GREEN, cl1.nodeClient().admin().cluster(). - health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); - - Assert.assertEquals(cl2Info.numNodes, cl2.nodeClient().admin().cluster().health( - new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); - Assert.assertEquals(ClusterHealthStatus.GREEN, cl2.nodeClient().admin().cluster(). - health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); + Assert.assertEquals( + cl1Info.numNodes, + cl1.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes() + ); + Assert.assertEquals( + ClusterHealthStatus.GREEN, + cl1.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus() + ); + + Assert.assertEquals( + cl2Info.numNodes, + cl2.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes() + ); + Assert.assertEquals( + ClusterHealthStatus.GREEN, + cl2.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus() + ); try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } final Settings.Builder clusterClientSettings = Settings.builder().putList("node.roles", "remote_cluster_client"); final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(clusterClientSettings, false, false) - .put(minimumSecuritySettings(Settings.EMPTY).get(0)) - .put("cluster.name", cl1Info.clustername) - .put("path.data", "./target/data/" + cl1Info.clustername + "/cert/data") - .put("path.logs", "./target/data/" + cl1Info.clustername + "/cert/logs") - .put("path.home", "./target") - .put("node.name", "testclient") - .put("discovery.initial_state_timeout", "8s") - .put("plugins.security.allow_default_init_securityindex", "true") - .putList("discovery.zen.ping.unicast.hosts", cl1Info.nodeHost + ":" + cl1Info.nodePort) - .build(); + .put(minimumSecuritySettings(Settings.EMPTY).get(0)) + .put("cluster.name", cl1Info.clustername) + .put("path.data", "./target/data/" + cl1Info.clustername + "/cert/data") + .put("path.logs", "./target/data/" + cl1Info.clustername + "/cert/logs") + .put("path.home", "./target") + .put("node.name", "testclient") + .put("discovery.initial_state_timeout", "8s") + .put("plugins.security.allow_default_init_securityindex", "true") + .putList("discovery.zen.ping.unicast.hosts", cl1Info.nodeHost + ":" + cl1Info.nodePort) + .build(); OpenSearchSecurityException exception = null; System.out.println("###################### with invalid role injection"); - //1. With invalid roles injection + // 1. With invalid roles injection RolesInjectorIntegTest.RolesInjectorPlugin.injectedRoles = "invalid_user|invalid_role"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, - OpenSearchSecurityPlugin.class, RolesInjectorIntegTest.RolesInjectorPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + RolesInjectorIntegTest.RolesInjectorPlugin.class + ).start() + ) { waitForInit(node.client()); Client remoteClient = node.client().getRemoteClusterClient("cross_cluster_two"); GetRequest getReq = new GetRequest("twitter", "0"); @@ -1039,10 +1549,17 @@ public void testCcsWithRoleInjection() throws Exception { Assert.assertTrue(exception.getMessage().contains("no permissions for")); System.out.println("###################### with valid role injection"); - //2. With valid roles injection + // 2. With valid roles injection RolesInjectorIntegTest.RolesInjectorPlugin.injectedRoles = "valid_user|opendistro_security_all_access"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, - OpenSearchSecurityPlugin.class, RolesInjectorIntegTest.RolesInjectorPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + RolesInjectorIntegTest.RolesInjectorPlugin.class + ).start() + ) { waitForInit(node.client()); Client remoteClient = node.client().getRemoteClusterClient("cross_cluster_two"); GetRequest getReq = new GetRequest("twitter", "0"); diff --git a/src/test/java/org/opensearch/security/ccstest/RemoteReindexTests.java b/src/test/java/org/opensearch/security/ccstest/RemoteReindexTests.java index ad449aa20b..3c49548cf3 100644 --- a/src/test/java/org/opensearch/security/ccstest/RemoteReindexTests.java +++ b/src/test/java/org/opensearch/security/ccstest/RemoteReindexTests.java @@ -46,14 +46,18 @@ public class RemoteReindexTests extends AbstractSecurityUnitTest { - private final ClusterHelper cl1 = new ClusterHelper("crl1_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); - private final ClusterHelper cl2 = new ClusterHelper("crl2_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); + private final ClusterHelper cl1 = new ClusterHelper( + "crl1_n" + num.incrementAndGet() + "_f" + System.getProperty("forkno") + "_t" + System.nanoTime() + ); + private final ClusterHelper cl2 = new ClusterHelper( + "crl2_n" + num.incrementAndGet() + "_f" + System.getProperty("forkno") + "_t" + System.nanoTime() + ); private ClusterInfo cl1Info; private ClusterInfo cl2Info; private void setupReindex() throws Exception { - System.setProperty("security.display_lic_none","true"); + System.setProperty("security.display_lic_none", "true"); cl2Info = cl2.startCluster(minimumSecuritySettings(Settings.EMPTY), ClusterConfiguration.DEFAULT); initialize(cl2, cl2Info); @@ -69,54 +73,70 @@ public void tearDown() throws Exception { } private Settings crossClusterNodeSettings(ClusterInfo remote) { - Settings.Builder builder = Settings.builder() - .putList("reindex.remote.whitelist", remote.httpHost+":"+remote.httpPort); + Settings.Builder builder = Settings.builder().putList("reindex.remote.whitelist", remote.httpHost + ":" + remote.httpPort); return builder.build(); } - //TODO add ssl tests - //https://github.com/elastic/elasticsearch/issues/27267 + // TODO add ssl tests + // https://github.com/elastic/elasticsearch/issues/27267 @Test public void testNonSSLReindex() throws Exception { setupReindex(); - final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("nagilum","nagilum")).getBody(); + final String cl1BodyMain = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("nagilum", "nagilum") + ).getBody(); Assert.assertTrue(cl1BodyMain.contains("crl1")); try (Client tc = cl1.nodeClient()) { tc.admin().indices().create(new CreateIndexRequest("twutter")).actionGet(); } - final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest("", encodeBasicHeader("nagilum","nagilum")).getBody(); + final String cl2BodyMain = new RestHelper(cl2Info, false, false, getResourceFolder()).executeGetRequest( + "", + encodeBasicHeader("nagilum", "nagilum") + ).getBody(); Assert.assertTrue(cl2BodyMain.contains("crl2")); try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } - String reindex = "{"+ - "\"source\": {"+ - "\"remote\": {"+ - "\"host\": \"http://"+cl2Info.httpHost+":"+cl2Info.httpPort+"\","+ - "\"username\": \"nagilum\","+ - "\"password\": \"nagilum\""+ - "},"+ - "\"index\": \"twitter\","+ - "\"size\": 10"+ - "},"+ - "\"dest\": {"+ - "\"index\": \"twutter\""+ - "}"+ - "}"; + String reindex = "{" + + "\"source\": {" + + "\"remote\": {" + + "\"host\": \"http://" + + cl2Info.httpHost + + ":" + + cl2Info.httpPort + + "\"," + + "\"username\": \"nagilum\"," + + "\"password\": \"nagilum\"" + + "}," + + "\"index\": \"twitter\"," + + "\"size\": 10" + + "}," + + "\"dest\": {" + + "\"index\": \"twutter\"" + + "}" + + "}"; System.out.println(reindex); HttpResponse ccs = null; System.out.println("###################### reindex"); - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest("_reindex?pretty", reindex, encodeBasicHeader("nagilum","nagilum")); + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executePostRequest( + "_reindex?pretty", + reindex, + encodeBasicHeader("nagilum", "nagilum") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("created\" : 1")); diff --git a/src/test/java/org/opensearch/security/configuration/SaltTest.java b/src/test/java/org/opensearch/security/configuration/SaltTest.java index 01ddf3de56..8af7501810 100644 --- a/src/test/java/org/opensearch/security/configuration/SaltTest.java +++ b/src/test/java/org/opensearch/security/configuration/SaltTest.java @@ -44,9 +44,7 @@ public void testDefault() { public void testConfig() { // arrange final String testSalt = "abcdefghijklmnop"; - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_COMPLIANCE_SALT, testSalt) - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_COMPLIANCE_SALT, testSalt).build(); // act final Salt salt = Salt.from(settings); @@ -60,9 +58,7 @@ public void testConfig() { public void testSaltUsesOnlyFirst16Bytes() { // arrange final String testSalt = "abcdefghijklmnopqrstuvwxyz"; - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_COMPLIANCE_SALT, testSalt) - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_COMPLIANCE_SALT, testSalt).build(); // act final Salt salt = Salt.from(settings); @@ -79,9 +75,7 @@ public void testSaltThrowsExceptionWhenInsufficientBytesProvided() { // arrange final String testSalt = "abcd"; - final Settings settings = Settings.builder() - .put(ConfigConstants.SECURITY_COMPLIANCE_SALT, testSalt) - .build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_COMPLIANCE_SALT, testSalt).build(); // act final Salt salt = Salt.from(settings); } @@ -93,7 +87,7 @@ public void testSaltThrowsExceptionWhenInsufficientBytesArrayProvided() { thrown.expectMessage("Provided compliance salt must contain 16 bytes"); // act - new Salt(new byte[]{1, 2, 3, 4, 5}); + new Salt(new byte[] { 1, 2, 3, 4, 5 }); } @Test @@ -103,12 +97,12 @@ public void testSaltThrowsExceptionWhenExcessBytesArrayProvided() { thrown.expectMessage("Provided compliance salt must contain 16 bytes"); // act - new Salt(new byte[]{1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5}); + new Salt(new byte[] { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 }); } @Test public void testSaltThrowsNoExceptionWhenCorrectBytesArrayProvided() { // act - new Salt(new byte[]{1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1}); + new Salt(new byte[] { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1 }); } } diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/AbstractDlsFlsTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/AbstractDlsFlsTest.java index 2e1eedcf57..dfe96bb55d 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/AbstractDlsFlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/AbstractDlsFlsTest.java @@ -58,7 +58,7 @@ protected final void setup(Settings override, DynamicSecurityConfig dynamicSecur Settings settings = Settings.builder().put(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT, "debug").put(override).build(); setup(Settings.EMPTY, dynamicSecurityConfig, settings, true); - try(Client tc = getClient()) { + try (Client tc = getClient()) { populateData(tc); } @@ -66,50 +66,48 @@ protected final void setup(Settings override, DynamicSecurityConfig dynamicSecur } protected SearchResponse executeSearch(String indexName, String user, String password) throws Exception { - HttpResponse response = rh.executeGetRequest("/"+indexName+"/_search?from=0&size=50&pretty", - encodeBasicHeader(user, password)); - Assert.assertEquals(200, response.getStatusCode()); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, - LoggingDeprecationHandler.INSTANCE, response.getBody()); - return SearchResponse.fromXContent(xcp); + HttpResponse response = rh.executeGetRequest("/" + indexName + "/_search?from=0&size=50&pretty", encodeBasicHeader(user, password)); + Assert.assertEquals(200, response.getStatusCode()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + return SearchResponse.fromXContent(xcp); } protected GetResponse executeGet(String indexName, String id, String user, String password) throws Exception { - HttpResponse response = rh.executeGetRequest("/"+indexName+"/_doc/"+id, encodeBasicHeader(user, password)); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, - LoggingDeprecationHandler.INSTANCE, response.getBody()); - return GetResponse.fromXContent(xcp); + HttpResponse response = rh.executeGetRequest("/" + indexName + "/_doc/" + id, encodeBasicHeader(user, password)); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + return GetResponse.fromXContent(xcp); } - protected MultiSearchResponse executeMSearchMatchAll(String user, String password, String ... indexName) throws Exception { - StringBuilder body = new StringBuilder(); + protected MultiSearchResponse executeMSearchMatchAll(String user, String password, String... indexName) throws Exception { + StringBuilder body = new StringBuilder(); - for (String index : indexName) { - body.append("{\"index\": \"").append(index).append("\"}\n"); - body.append("{\"query\" : {\"match_all\" : {}}}\n"); - } + for (String index : indexName) { + body.append("{\"index\": \"").append(index).append("\"}\n"); + body.append("{\"query\" : {\"match_all\" : {}}}\n"); + } - HttpResponse response = rh.executePostRequest("/_msearch?pretty", body.toString(), - encodeBasicHeader(user, password)); - Assert.assertEquals(200, response.getStatusCode()); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, - LoggingDeprecationHandler.INSTANCE, response.getBody()); - return MultiSearchResponse.fromXContext(xcp); + HttpResponse response = rh.executePostRequest("/_msearch?pretty", body.toString(), encodeBasicHeader(user, password)); + Assert.assertEquals(200, response.getStatusCode()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + return MultiSearchResponse.fromXContext(xcp); } protected MultiGetResponse executeMGet(String user, String password, Map indicesAndIds) throws Exception { - Set indexAndIdJson = new HashSet<>(); - for (Map.Entry indexAndId : indicesAndIds.entrySet()) { - indexAndIdJson.add("{ \"_index\": \""+indexAndId.getKey()+"\", \"_id\": \""+indexAndId.getValue()+"\" }"); - } - String body = "{ \"docs\": ["+ String.join(",", indexAndIdJson) +"] }"; - - HttpResponse response = rh.executePostRequest("/_mget?pretty", body,encodeBasicHeader(user, password)); - Assert.assertEquals(200, response.getStatusCode()); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, - LoggingDeprecationHandler.INSTANCE, response.getBody()); - return MultiGetResponse.fromXContent(xcp); + Set indexAndIdJson = new HashSet<>(); + for (Map.Entry indexAndId : indicesAndIds.entrySet()) { + indexAndIdJson.add("{ \"_index\": \"" + indexAndId.getKey() + "\", \"_id\": \"" + indexAndId.getValue() + "\" }"); + } + String body = "{ \"docs\": [" + String.join(",", indexAndIdJson) + "] }"; + + HttpResponse response = rh.executePostRequest("/_mget?pretty", body, encodeBasicHeader(user, password)); + Assert.assertEquals(200, response.getStatusCode()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + return MultiGetResponse.fromXContent(xcp); } abstract void populateData(Client tc); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java index 4ac8077c34..079afa65bd 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/CCReplicationTest.java @@ -73,18 +73,24 @@ public class CCReplicationTest extends AbstractDlsFlsTest { public static class MockReplicationPlugin extends Plugin implements ActionPlugin { public static String injectedRoles = null; - public MockReplicationPlugin() { - } + public MockReplicationPlugin() {} @Override - public Collection createComponents(Client client, ClusterService clusterService, ThreadPool threadPool, - ResourceWatcherService resourceWatcherService, ScriptService scriptService, - NamedXContentRegistry xContentRegistry, Environment environment, - NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry, + public Collection createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, IndexNameExpressionResolver indexNameExpressionResolver, - Supplier repositoriesServiceSupplier) { - if(injectedRoles != null) - threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, injectedRoles); + Supplier repositoriesServiceSupplier + ) { + if (injectedRoles != null) threadPool.getThreadContext() + .putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, injectedRoles); return new ArrayList<>(); } @@ -97,6 +103,7 @@ public Collection createComponents(Client client, ClusterService cluster public static class MockReplicationAction extends ActionType { public static final MockReplicationAction INSTANCE = new MockReplicationAction(); public static final String NAME = "indices:admin/plugins/replication/file_chunk"; + private MockReplicationAction() { super(NAME, AcknowledgedResponse::new); } @@ -104,6 +111,7 @@ private MockReplicationAction() { public static class MockReplicationRequest extends AcknowledgedRequest implements Replaceable { private String index; + public MockReplicationRequest(String index) { this.index = index; } @@ -131,7 +139,7 @@ public IndicesRequest indices(String... strings) { @Override public String[] indices() { - return new String[]{index}; + return new String[] { index }; } @Override @@ -148,8 +156,7 @@ public boolean includeDataStreams() { public static class TransportMockReplicationAction extends HandledTransportAction { @Inject - public TransportMockReplicationAction(TransportService transportService, - ActionFilters actionFilters) { + public TransportMockReplicationAction(TransportService transportService, ActionFilters actionFilters) { super(MockReplicationAction.NAME, transportService, actionFilters, MockReplicationRequest::new); } @@ -159,31 +166,52 @@ protected void doExecute(Task task, MockReplicationRequest request, ActionListen } } - //Wait for the security plugin to load roles. + // Wait for the security plugin to load roles. private void waitOrThrow(Client client, String index) throws Exception { waitForInit(client); client.execute(MockReplicationAction.INSTANCE, new MockReplicationRequest(index)).actionGet(); } void populateData(Client tc) { - tc.index(new IndexRequest("hr-dls").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"User\": \"testuser\",\"Date\":\"2021-01-18T17:27:20Z\",\"Designation\":\"HR\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("hr-fls").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"User\": \"adminuser\",\"Date\":\"2021-01-18T17:27:20Z\",\"Designation\":\"CEO\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("hr-masking").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"User\": \"maskeduser\",\"Date\":\"2021-01-18T17:27:20Z\",\"Designation\":\"CEO\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("hr-normal").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"User\": \"employee1\",\"Date\":\"2021-01-18T17:27:20Z\",\"Designation\":\"EMPLOYEE\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("hr-dls").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"User\": \"testuser\",\"Date\":\"2021-01-18T17:27:20Z\",\"Designation\":\"HR\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("hr-fls").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"User\": \"adminuser\",\"Date\":\"2021-01-18T17:27:20Z\",\"Designation\":\"CEO\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("hr-masking").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"User\": \"maskeduser\",\"Date\":\"2021-01-18T17:27:20Z\",\"Designation\":\"CEO\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("hr-normal").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"User\": \"employee1\",\"Date\":\"2021-01-18T17:27:20Z\",\"Designation\":\"EMPLOYEE\"}", XContentType.JSON) + ).actionGet(); } @Test public void testReplication() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityRoles("roles_ccreplication.yml"), Settings.EMPTY); - Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health( - new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); - Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster(). - health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); + Assert.assertEquals( + clusterInfo.numNodes, + clusterHelper.nodeClient() + .admin() + .cluster() + .health(new ClusterHealthRequest().waitForGreenStatus()) + .actionGet() + .getNumberOfNodes() + ); + Assert.assertEquals( + ClusterHealthStatus.GREEN, + clusterHelper.nodeClient().admin().cluster().health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus() + ); final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) @@ -199,39 +227,79 @@ public void testReplication() throws Exception { // Set roles for the user MockReplicationPlugin.injectedRoles = "ccr_user|opendistro_security_human_resources_trainee"; - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, MockReplicationPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + MockReplicationPlugin.class + ).start() + ) { waitOrThrow(node.client(), "hr-dls"); Assert.fail("Expecting exception"); } catch (OpenSearchSecurityException ex) { log.warn(ex.getMessage()); Assert.assertNotNull(ex); - Assert.assertTrue(ex.getMessage().contains("Cross Cluster Replication is not supported when FLS or DLS or Fieldmasking is activated")); + Assert.assertTrue( + ex.getMessage().contains("Cross Cluster Replication is not supported when FLS or DLS or Fieldmasking is activated") + ); Assert.assertEquals(ex.status(), RestStatus.FORBIDDEN); } - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, MockReplicationPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + MockReplicationPlugin.class + ).start() + ) { waitOrThrow(node.client(), "hr-fls"); Assert.fail("Expecting exception"); } catch (OpenSearchSecurityException ex) { log.warn(ex.getMessage()); Assert.assertNotNull(ex); - Assert.assertTrue(ex.getMessage().contains("Cross Cluster Replication is not supported when FLS or DLS or Fieldmasking is activated")); + Assert.assertTrue( + ex.getMessage().contains("Cross Cluster Replication is not supported when FLS or DLS or Fieldmasking is activated") + ); Assert.assertEquals(ex.status(), RestStatus.FORBIDDEN); } - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, MockReplicationPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + MockReplicationPlugin.class + ).start() + ) { waitOrThrow(node.client(), "hr-masking"); Assert.fail("Expecting exception"); } catch (OpenSearchSecurityException ex) { log.warn(ex.getMessage()); Assert.assertNotNull(ex); - Assert.assertTrue(ex.getMessage().contains("Cross Cluster Replication is not supported when FLS or DLS or Fieldmasking is activated")); + Assert.assertTrue( + ex.getMessage().contains("Cross Cluster Replication is not supported when FLS or DLS or Fieldmasking is activated") + ); Assert.assertEquals(ex.status(), RestStatus.FORBIDDEN); } - try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, MockReplicationPlugin.class).start()) { + try ( + Node node = new PluginAwareNode( + false, + tcSettings, + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + MockReplicationPlugin.class + ).start() + ) { waitOrThrow(node.client(), "hr-normal"); - AcknowledgedResponse res = node.client().execute(MockReplicationAction.INSTANCE, new MockReplicationRequest("hr-normal")).actionGet(); + AcknowledgedResponse res = node.client() + .execute(MockReplicationAction.INSTANCE, new MockReplicationRequest("hr-normal")) + .actionGet(); Assert.assertTrue(res.isAcknowledged()); } } diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedComplexMappingTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedComplexMappingTest.java index bbc7bd5479..9be61b30b7 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedComplexMappingTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedComplexMappingTest.java @@ -25,19 +25,20 @@ import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class CustomFieldMaskedComplexMappingTest extends AbstractDlsFlsTest{ - +public class CustomFieldMaskedComplexMappingTest extends AbstractDlsFlsTest { @Override protected void populateData(Client tc) { try { - tc.admin().indices().create(new CreateIndexRequest("logs").mapping(FileHelper.loadFile("dlsfls/masked_field_mapping.json"), XContentType.JSON)).actionGet(); - + tc.admin() + .indices() + .create(new CreateIndexRequest("logs").mapping(FileHelper.loadFile("dlsfls/masked_field_mapping.json"), XContentType.JSON)) + .actionGet(); byte[] data = FileHelper.loadFile("dlsfls/logs_bulk_data.json").getBytes(StandardCharsets.UTF_8); BulkRequest br = new BulkRequest().add(data, 0, data.length, XContentType.JSON).setRefreshPolicy(RefreshPolicy.IMMEDIATE); - if(tc.bulk(br).actionGet().hasFailures()) { + if (tc.bulk(br).actionGet().hasFailures()) { Assert.fail("bulk import failed"); } Thread.sleep(1000); @@ -54,17 +55,17 @@ public void testComplexMappingAggregationsRace() throws Exception { setup(); - - String query = "{"+ - "\"aggs\" : {"+ - "\"ips\" : { \"terms\" : { \"field\" : \"machine.os.keyword\", \"size\": 1002, \"show_term_doc_count_error\": true } }"+ - "}"+ - "}"; - - + String query = "{" + + "\"aggs\" : {" + + "\"ips\" : { \"terms\" : { \"field\" : \"machine.os.keyword\", \"size\": 1002, \"show_term_doc_count_error\": true } }" + + "}" + + "}"; HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/logs/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/logs/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("win 8")); @@ -84,16 +85,22 @@ public void testComplexMappingAggregationsRace() throws Exception { Assert.assertFalse(res.getBody().contains("88783587fef7")); Assert.assertFalse(res.getBody().contains("c1f04335d9f41")); - for(int i=0;i<10;i++) { - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/logs/_search?pretty&size=0", query, encodeBasicHeader("user_masked_nowc1", "password"))).getStatusCode()); + for (int i = 0; i < 10; i++) { + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/logs/_search?pretty&size=0", query, encodeBasicHeader("user_masked_nowc1", "password"))) + .getStatusCode() + ); System.out.println(res.getBody()); } + for (int i = 0; i < 10; i++) { - - for(int i=0;i<10;i++) { - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/logs/_search?pretty&size=0", query, encodeBasicHeader("user_masked_nowc", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/logs/_search?pretty&size=0", query, encodeBasicHeader("user_masked_nowc", "password"))) + .getStatusCode() + ); System.out.println(res.getBody()); Assert.assertFalse(res.getBody().contains("\"aaa")); @@ -115,21 +122,18 @@ public void testComplexMappingAggregationsRace() throws Exception { Assert.assertFalse(res.getBody().contains("osx")); Assert.assertFalse(res.getBody().contains("win 7")); - - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/logs/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); - - + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/logs/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); } - - - - - - for(int i=0;i<10;i++) { - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/logs/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + for (int i = 0; i < 10; i++) { + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/logs/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("win 8")); Assert.assertTrue(res.getBody().contains("win xp")); Assert.assertTrue(res.getBody().contains("ios")); @@ -157,14 +161,27 @@ public void testComplexMappingSearch() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logs/_search?pretty&size=100", encodeBasicHeader("admin", "admin"))).getStatusCode()); - Assert.assertFalse(res.getBody().contains("88783587fef740690c4fa39476fb86314d034fa3370e1a1fa186f6d9d4644a18ad85063c1e3161f8929f7ca019bb8740611eaf337709113901e7c3a6b59f4166")); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logs/_search?pretty&size=100", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); + Assert.assertFalse( + res.getBody() + .contains( + "88783587fef740690c4fa39476fb86314d034fa3370e1a1fa186f6d9d4644a18ad85063c1e3161f8929f7ca019bb8740611eaf337709113901e7c3a6b59f4166" + ) + ); Assert.assertFalse(res.getBody().contains("e90a2fdf7b1939ec06e294321fd7d23e1a70d8fc080a3f85d0f3bf08c205b53")); Assert.assertFalse(res.getBody().contains("*.*.*.*")); Assert.assertFalse(res.getBody().contains("430a65d4b9c51de7192e048b2639db0de5c56f1901afccc2a01ef97f6a769a38")); Assert.assertFalse(res.getBody().contains("7f48bb3636edf546a75968ca7cd0bdfe63e9ce7af04ef7cb642931fa15d2d7a3")); Assert.assertFalse(res.getBody().contains("https://www.static.co/downloads/beats/metricbeat")); - Assert.assertFalse(res.getBody().contains("eb551beb79792f3366b3623495bb0d9acf85055e63d4f48ade024289f9aa782fc7bd215b6ed3452d3d3ff3eccd8a7f5e8f55b8d0ef245c7ccbf8b747e0be9807")); + Assert.assertFalse( + res.getBody() + .contains( + "eb551beb79792f3366b3623495bb0d9acf85055e63d4f48ade024289f9aa782fc7bd215b6ed3452d3d3ff3eccd8a7f5e8f55b8d0ef245c7ccbf8b747e0be9807" + ) + ); Assert.assertFalse(res.getBody().contains("XXX.XXX.XXX.XXX")); Assert.assertFalse(res.getBody().contains("ANONYMIZED_BROWSER")); Assert.assertFalse(res.getBody().contains("69ce5643cf2abe2dec163330161e669")); @@ -172,32 +189,59 @@ public void testComplexMappingSearch() throws Exception { Assert.assertTrue(res.getBody().contains("win xp")); Assert.assertTrue(res.getBody().contains("\"timestamp\" : \"2018-07-22T20:45:16.163Z")); - for(int i=0;i<10;i++) { - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logs/_search?pretty&size=100", encodeBasicHeader("user_masked_nowc", "password"))).getStatusCode()); - Assert.assertTrue(res.getBody().contains("88783587fef740690c4fa39476fb86314d034fa3370e1a1fa186f6d9d4644a18ad85063c1e3161f8929f7ca019bb8740611eaf337709113901e7c3a6b59f4166")); - Assert.assertTrue(res.getBody().contains("e90a2fdf7b1939ec06e294321fd7d23e1a70d8fc080a3f85d0f3bf08c205b53")); - Assert.assertTrue(res.getBody().contains("*.*.*.*")); - Assert.assertTrue(res.getBody().contains("430a65d4b9c51de7192e048b2639db0de5c56f1901afccc2a01ef97f6a769a38")); - Assert.assertTrue(res.getBody().contains("7f48bb3636edf546a75968ca7cd0bdfe63e9ce7af04ef7cb642931fa15d2d7a3")); - Assert.assertTrue(res.getBody().contains("https://www.static.co/downloads/beats/metricbeat")); - Assert.assertTrue(res.getBody().contains("eb551beb79792f3366b3623495bb0d9acf85055e63d4f48ade024289f9aa782fc7bd215b6ed3452d3d3ff3eccd8a7f5e8f55b8d0ef245c7ccbf8b747e0be9807")); - Assert.assertTrue(res.getBody().contains("XXX.XXX.XXX.XXX")); - Assert.assertTrue(res.getBody().contains("ANONYMIZED_BROWSER")); - Assert.assertTrue(res.getBody().contains("69ce5643cf2abe2dec163330161e669")); - Assert.assertTrue(res.getBody().contains("0b50856e97a54df444ff8f7c73c67fc3109aa234")); - Assert.assertFalse(res.getBody().contains("win xp")); - Assert.assertFalse(res.getBody().contains("\"timestamp\" : \"2018-07-22T20:45:16.163Z")); + for (int i = 0; i < 10; i++) { + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logs/_search?pretty&size=100", encodeBasicHeader("user_masked_nowc", "password"))) + .getStatusCode() + ); + Assert.assertTrue( + res.getBody() + .contains( + "88783587fef740690c4fa39476fb86314d034fa3370e1a1fa186f6d9d4644a18ad85063c1e3161f8929f7ca019bb8740611eaf337709113901e7c3a6b59f4166" + ) + ); + Assert.assertTrue(res.getBody().contains("e90a2fdf7b1939ec06e294321fd7d23e1a70d8fc080a3f85d0f3bf08c205b53")); + Assert.assertTrue(res.getBody().contains("*.*.*.*")); + Assert.assertTrue(res.getBody().contains("430a65d4b9c51de7192e048b2639db0de5c56f1901afccc2a01ef97f6a769a38")); + Assert.assertTrue(res.getBody().contains("7f48bb3636edf546a75968ca7cd0bdfe63e9ce7af04ef7cb642931fa15d2d7a3")); + Assert.assertTrue(res.getBody().contains("https://www.static.co/downloads/beats/metricbeat")); + Assert.assertTrue( + res.getBody() + .contains( + "eb551beb79792f3366b3623495bb0d9acf85055e63d4f48ade024289f9aa782fc7bd215b6ed3452d3d3ff3eccd8a7f5e8f55b8d0ef245c7ccbf8b747e0be9807" + ) + ); + Assert.assertTrue(res.getBody().contains("XXX.XXX.XXX.XXX")); + Assert.assertTrue(res.getBody().contains("ANONYMIZED_BROWSER")); + Assert.assertTrue(res.getBody().contains("69ce5643cf2abe2dec163330161e669")); + Assert.assertTrue(res.getBody().contains("0b50856e97a54df444ff8f7c73c67fc3109aa234")); + Assert.assertFalse(res.getBody().contains("win xp")); + Assert.assertFalse(res.getBody().contains("\"timestamp\" : \"2018-07-22T20:45:16.163Z")); } - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logs/_search?pretty&size=100", encodeBasicHeader("admin", "admin"))).getStatusCode()); - Assert.assertFalse(res.getBody().contains("88783587fef740690c4fa39476fb86314d034fa3370e1a1fa186f6d9d4644a18ad85063c1e3161f8929f7ca019bb8740611eaf337709113901e7c3a6b59f4166")); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logs/_search?pretty&size=100", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); + Assert.assertFalse( + res.getBody() + .contains( + "88783587fef740690c4fa39476fb86314d034fa3370e1a1fa186f6d9d4644a18ad85063c1e3161f8929f7ca019bb8740611eaf337709113901e7c3a6b59f4166" + ) + ); Assert.assertFalse(res.getBody().contains("e90a2fdf7b1939ec06e294321fd7d23e1a70d8fc080a3f85d0f3bf08c205b53")); Assert.assertFalse(res.getBody().contains("*.*.*.*")); Assert.assertFalse(res.getBody().contains("430a65d4b9c51de7192e048b2639db0de5c56f1901afccc2a01ef97f6a769a38")); Assert.assertFalse(res.getBody().contains("7f48bb3636edf546a75968ca7cd0bdfe63e9ce7af04ef7cb642931fa15d2d7a3")); Assert.assertFalse(res.getBody().contains("https://www.static.co/downloads/beats/metricbeat")); - Assert.assertFalse(res.getBody().contains("eb551beb79792f3366b3623495bb0d9acf85055e63d4f48ade024289f9aa782fc7bd215b6ed3452d3d3ff3eccd8a7f5e8f55b8d0ef245c7ccbf8b747e0be9807")); + Assert.assertFalse( + res.getBody() + .contains( + "eb551beb79792f3366b3623495bb0d9acf85055e63d4f48ade024289f9aa782fc7bd215b6ed3452d3d3ff3eccd8a7f5e8f55b8d0ef245c7ccbf8b747e0be9807" + ) + ); Assert.assertFalse(res.getBody().contains("XXX.XXX.XXX.XXX")); Assert.assertFalse(res.getBody().contains("ANONYMIZED_BROWSER")); Assert.assertFalse(res.getBody().contains("69ce5643cf2abe2dec163330161e669")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedTest.java index 9d48e0309f..91c6115695 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/CustomFieldMaskedTest.java @@ -25,18 +25,35 @@ public class CustomFieldMaskedTest extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.index(new IndexRequest("deals").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust1\", \"street\":\"testroad\"}, \"ip_source\": \"100.100.1.1\",\"ip_dest\": \"123.123.1.1\",\"amount\": 10, \"mynum\": 1000000000000000000}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("deals").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust2\", \"street\":\"testroad\"}, \"ip_source\": \"100.100.2.2\",\"ip_dest\": \"123.123.2.2\",\"amount\": 20, \"mynum\": 1000000000000000000}", XContentType.JSON)).actionGet(); - - - for (int i=0; i<30;i++) { - tc.index(new IndexRequest("deals").id("a"+i).setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust1\", \"street\":\"testroad\"}, \"ip_source\": \"200.100.1.1\",\"ip_dest\": \"123.123.1.1\",\"amount\": 10, \"mynum\": 1000000000000000000}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("deals").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"customer\": {\"name\":\"cust1\", \"street\":\"testroad\"}, \"ip_source\": \"100.100.1.1\",\"ip_dest\": \"123.123.1.1\",\"amount\": 10, \"mynum\": 1000000000000000000}", + XContentType.JSON + ) + ).actionGet(); + tc.index( + new IndexRequest("deals").id("2") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"customer\": {\"name\":\"cust2\", \"street\":\"testroad\"}, \"ip_source\": \"100.100.2.2\",\"ip_dest\": \"123.123.2.2\",\"amount\": 20, \"mynum\": 1000000000000000000}", + XContentType.JSON + ) + ).actionGet(); + + for (int i = 0; i < 30; i++) { + tc.index( + new IndexRequest("deals").id("a" + i) + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"customer\": {\"name\":\"cust1\", \"street\":\"testroad\"}, \"ip_source\": \"200.100.1.1\",\"ip_dest\": \"123.123.1.1\",\"amount\": 10, \"mynum\": 1000000000000000000}", + XContentType.JSON + ) + ).actionGet(); } - } + } @Test public void testMaskedAggregations() throws Exception { @@ -45,23 +62,23 @@ public void testMaskedAggregations() throws Exception { String query; HttpResponse res; - //Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); - //Assert.assertTrue(res.getBody().contains("100.100")); - - query = - "{" + - "\"query\" : {" + - "\"match_all\": {" + - "}" + - "}," + - "\"aggs\" : {" + - "\"ips\" : {" + - "\"terms\" : {" + - "\"field\" : \"ip_source.keyword\"" + - "}" + - "}" + - "}"+ - "}"; + // Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, + // encodeBasicHeader("admin", "admin"))).getStatusCode()); + // Assert.assertTrue(res.getBody().contains("100.100")); + + query = "{" + + "\"query\" : {" + + "\"match_all\": {" + + "}" + + "}," + + "\"aggs\" : {" + + "\"ips\" : {" + + "\"terms\" : {" + + "\"field\" : \"ip_source.keyword\"" + + "}" + + "}" + + "}" + + "}"; res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked_custom", "password")); System.out.println(res.getBody()); Assert.assertEquals(HttpStatus.SC_OK, res.getStatusCode()); @@ -69,23 +86,22 @@ public void testMaskedAggregations() throws Exception { Assert.assertTrue(res.getBody().contains("***")); Assert.assertTrue(res.getBody().contains("XXX")); - query = - "{" + - "\"query\" : {" + - "\"match_all\": {" + - "}" + - "}," + - "\"aggs\": {" + - "\"ips\" : {" + - "\"terms\" : {" + - "\"field\" : \"ip_source.keyword\"," + - "\"order\": {" + - "\"_term\" : \"asc\"" + - "}" + - "}" + - "}" + - "}" + - "}"; + query = "{" + + "\"query\" : {" + + "\"match_all\": {" + + "}" + + "}," + + "\"aggs\": {" + + "\"ips\" : {" + + "\"terms\" : {" + + "\"field\" : \"ip_source.keyword\"," + + "\"order\": {" + + "\"_term\" : \"asc\"" + + "}" + + "}" + + "}" + + "}" + + "}"; res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked_custom", "password")); System.out.println(res.getBody()); @@ -94,23 +110,22 @@ public void testMaskedAggregations() throws Exception { Assert.assertTrue(res.getBody().contains("***")); Assert.assertTrue(res.getBody().contains("XXX")); - query = - "{" + - "\"query\" : {" + - "\"match_all\": {" + - "}" + - "}," + - "\"aggs\": {" + - "\"ips\" : {" + - "\"terms\" : {" + - "\"field\" : \"ip_source.keyword\"," + - "\"order\": {" + - "\"_term\" : \"desc\"" + - "}" + - "}" + - "}" + - "}" + - "}"; + query = "{" + + "\"query\" : {" + + "\"match_all\": {" + + "}" + + "}," + + "\"aggs\": {" + + "\"ips\" : {" + + "\"terms\" : {" + + "\"field\" : \"ip_source.keyword\"," + + "\"order\": {" + + "\"_term\" : \"desc\"" + + "}" + + "}" + + "}" + + "}" + + "}"; res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked_custom", "password")); System.out.println(res.getBody()); @@ -126,36 +141,41 @@ public void testCustomMaskedAggregationsRace() throws Exception { setup(); + String query = "{" + + "\"aggs\" : {" + + "\"ips\" : { \"terms\" : { \"field\" : \"ip_source.keyword\", \"size\": 1002, \"show_term_doc_count_error\": true } }" + + "}" + + "}"; - String query = "{"+ - "\"aggs\" : {"+ - "\"ips\" : { \"terms\" : { \"field\" : \"ip_source.keyword\", \"size\": 1002, \"show_term_doc_count_error\": true } }"+ - "}"+ - "}"; - - - - HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); - Assert.assertTrue(res.getBody().contains("100.100")); - Assert.assertTrue(res.getBody().contains("200.100")); - Assert.assertTrue(res.getBody().contains("\"doc_count\" : 30")); - Assert.assertTrue(res.getBody().contains("\"doc_count\" : 1")); - Assert.assertFalse(res.getBody().contains("***")); - Assert.assertFalse(res.getBody().contains("XXX")); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked_custom", "password"))).getStatusCode()); - Assert.assertTrue(res.getBody().contains("\"doc_count\" : 31")); - Assert.assertTrue(res.getBody().contains("\"doc_count\" : 1")); - Assert.assertFalse(res.getBody().contains("100.100")); - Assert.assertFalse(res.getBody().contains("200.100")); - Assert.assertTrue(res.getBody().contains("***.100.1.XXX")); - Assert.assertTrue(res.getBody().contains("***.100.2.XXX")); - + HttpResponse res; + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); + Assert.assertTrue(res.getBody().contains("100.100")); + Assert.assertTrue(res.getBody().contains("200.100")); + Assert.assertTrue(res.getBody().contains("\"doc_count\" : 30")); + Assert.assertTrue(res.getBody().contains("\"doc_count\" : 1")); + Assert.assertFalse(res.getBody().contains("***")); + Assert.assertFalse(res.getBody().contains("XXX")); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked_custom", "password"))) + .getStatusCode() + ); + Assert.assertTrue(res.getBody().contains("\"doc_count\" : 31")); + Assert.assertTrue(res.getBody().contains("\"doc_count\" : 1")); + Assert.assertFalse(res.getBody().contains("100.100")); + Assert.assertFalse(res.getBody().contains("200.100")); + Assert.assertTrue(res.getBody().contains("***.100.1.XXX")); + Assert.assertTrue(res.getBody().contains("***.100.2.XXX")); - for(int i=0;i<10;i++) { - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + for (int i = 0; i < 10; i++) { + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("100.100")); Assert.assertTrue(res.getBody().contains("200.100")); Assert.assertTrue(res.getBody().contains("\"doc_count\" : 30")); @@ -173,18 +193,30 @@ public void testCustomMaskedSearch() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=100", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=100", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 32,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); Assert.assertTrue(res.getBody().contains("cust1")); Assert.assertTrue(res.getBody().contains("cust2")); Assert.assertTrue(res.getBody().contains("100.100.1.1")); Assert.assertTrue(res.getBody().contains("100.100.2.2")); - Assert.assertFalse(res.getBody().contains("8976994d0491e35f74fcac67ede9c83334a6ad34dae07c176df32f10225f93c5077ddd302c02ddd618b2406b1e4dfe50a727cbc880cfe264c552decf2d224ffc")); + Assert.assertFalse( + res.getBody() + .contains( + "8976994d0491e35f74fcac67ede9c83334a6ad34dae07c176df32f10225f93c5077ddd302c02ddd618b2406b1e4dfe50a727cbc880cfe264c552decf2d224ffc" + ) + ); Assert.assertFalse(res.getBody().contains("***")); Assert.assertFalse(res.getBody().contains("XXX")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=100", encodeBasicHeader("user_masked_custom", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=100", encodeBasicHeader("user_masked_custom", "password"))) + .getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 32,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -192,7 +224,12 @@ public void testCustomMaskedSearch() throws Exception { Assert.assertFalse(res.getBody().contains("cust2")); Assert.assertFalse(res.getBody().contains("100.100.1.1")); Assert.assertFalse(res.getBody().contains("100.100.2.2")); - Assert.assertTrue(res.getBody().contains("8976994d0491e35f74fcac67ede9c83334a6ad34dae07c176df32f10225f93c5077ddd302c02ddd618b2406b1e4dfe50a727cbc880cfe264c552decf2d224ffc")); + Assert.assertTrue( + res.getBody() + .contains( + "8976994d0491e35f74fcac67ede9c83334a6ad34dae07c176df32f10225f93c5077ddd302c02ddd618b2406b1e4dfe50a727cbc880cfe264c552decf2d224ffc" + ) + ); Assert.assertTrue(res.getBody().contains("***.100.1.XXX")); Assert.assertTrue(res.getBody().contains("123.123.1.XXX")); @@ -205,27 +242,41 @@ public void testCustomMaskedGet() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_doc/0?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_doc/0?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"found\" : true")); Assert.assertTrue(res.getBody().contains("cust1")); Assert.assertFalse(res.getBody().contains("cust2")); Assert.assertTrue(res.getBody().contains("100.100.1.1")); Assert.assertFalse(res.getBody().contains("100.100.2.2")); - Assert.assertFalse(res.getBody().contains("8976994d0491e35f74fcac67ede9c83334a6ad34dae07c176df32f10225f93c5077ddd302c02ddd618b2406b1e4dfe50a727cbc880cfe264c552decf2d224ffc")); + Assert.assertFalse( + res.getBody() + .contains( + "8976994d0491e35f74fcac67ede9c83334a6ad34dae07c176df32f10225f93c5077ddd302c02ddd618b2406b1e4dfe50a727cbc880cfe264c552decf2d224ffc" + ) + ); Assert.assertFalse(res.getBody().contains("***")); Assert.assertFalse(res.getBody().contains("XXX")); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_doc/0?pretty", encodeBasicHeader("user_masked_custom", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_doc/0?pretty", encodeBasicHeader("user_masked_custom", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"found\" : true")); Assert.assertFalse(res.getBody().contains("cust1")); Assert.assertFalse(res.getBody().contains("cust2")); Assert.assertFalse(res.getBody().contains("100.100.1.1")); Assert.assertFalse(res.getBody().contains("100.100.2.2")); - Assert.assertTrue(res.getBody().contains("8976994d0491e35f74fcac67ede9c83334a6ad34dae07c176df32f10225f93c5077ddd302c02ddd618b2406b1e4dfe50a727cbc880cfe264c552decf2d224ffc")); + Assert.assertTrue( + res.getBody() + .contains( + "8976994d0491e35f74fcac67ede9c83334a6ad34dae07c176df32f10225f93c5077ddd302c02ddd618b2406b1e4dfe50a727cbc880cfe264c552decf2d224ffc" + ) + ); Assert.assertTrue(res.getBody().contains("***.100.1.XXX")); Assert.assertTrue(res.getBody().contains("123.123.1.XXX")); } - } diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DateMathTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DateMathTest.java index 54110e911f..da4d9db867 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DateMathTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DateMathTest.java @@ -26,8 +26,7 @@ import org.opensearch.security.support.SecurityUtils; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class DateMathTest extends AbstractDlsFlsTest{ - +public class DateMathTest extends AbstractDlsFlsTest { protected void populateData(Client tc) { @@ -35,17 +34,25 @@ protected void populateData(Client tc) { sdf.setTimeZone(TimeZone.getTimeZone("UTC")); String date = sdf.format(new Date()); - tc.index(new IndexRequest("logstash-"+date).setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"message\":\"mymsg1a\", \"ipaddr\": \"10.0.0.0\",\"msgid\": \"12\"}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("logstash-"+date).setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"message\":\"mymsg1b\", \"ipaddr\": \"10.0.0.1\",\"msgid\": \"14\"}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("logstash-1-"+date).setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"message\":\"mymsg1c\", \"ipaddr\": \"10.0.0.2\",\"msgid\": \"12\"}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("logstash-1-"+date).setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"message\":\"mymsg1d\", \"ipaddr\": \"10.0.0.3\",\"msgid\": \"14\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("logstash-" + date).setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"message\":\"mymsg1a\", \"ipaddr\": \"10.0.0.0\",\"msgid\": \"12\"}", XContentType.JSON) + ).actionGet(); + + tc.index( + new IndexRequest("logstash-" + date).setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"message\":\"mymsg1b\", \"ipaddr\": \"10.0.0.1\",\"msgid\": \"14\"}", XContentType.JSON) + ).actionGet(); + + tc.index( + new IndexRequest("logstash-1-" + date).setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"message\":\"mymsg1c\", \"ipaddr\": \"10.0.0.2\",\"msgid\": \"12\"}", XContentType.JSON) + ).actionGet(); + + tc.index( + new IndexRequest("logstash-1-" + date).setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"message\":\"mymsg1d\", \"ipaddr\": \"10.0.0.3\",\"msgid\": \"14\"}", XContentType.JSON) + ).actionGet(); } @Test @@ -55,7 +62,10 @@ public void testSearch() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -64,7 +74,13 @@ public void testSearch() throws Exception { Assert.assertTrue(res.getBody().contains("mymsg")); Assert.assertTrue(res.getBody().contains("msgid")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty", encodeBasicHeader("opendistro_security_logstash", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + "%3Clogstash-%7Bnow%2Fd%7D%3E/_search?pretty", + encodeBasicHeader("opendistro_security_logstash", "password") + )).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -81,13 +97,23 @@ public void testFieldCaps() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("%3Clogstash-%7Bnow%2Fd%7D%3E/_field_caps?fields=*&pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("%3Clogstash-%7Bnow%2Fd%7D%3E/_field_caps?fields=*&pretty", encodeBasicHeader("admin", "admin"))) + .getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("ipaddr")); Assert.assertTrue(res.getBody().contains("message")); Assert.assertTrue(res.getBody().contains("msgid")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("%3Clogstash-%7Bnow%2Fd%7D%3E/_field_caps?fields=*&pretty", encodeBasicHeader("opendistro_security_logstash", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + "%3Clogstash-%7Bnow%2Fd%7D%3E/_field_caps?fields=*&pretty", + encodeBasicHeader("opendistro_security_logstash", "password") + )).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertFalse(res.getBody().contains("ipaddr")); Assert.assertFalse(res.getBody().contains("message")); @@ -101,7 +127,10 @@ public void testSearchWc() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("logstash-*/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("logstash-*/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 4,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -110,7 +139,11 @@ public void testSearchWc() throws Exception { Assert.assertTrue(res.getBody().contains("mymsg")); Assert.assertTrue(res.getBody().contains("msgid")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("logstash-*/_search?pretty", encodeBasicHeader("opendistro_security_logstash", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("logstash-*/_search?pretty", encodeBasicHeader("opendistro_security_logstash", "password"))) + .getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -127,7 +160,10 @@ public void testSearchWc2() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("logstash-1-*,logstash-20*/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("logstash-1-*,logstash-20*/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 4,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -136,7 +172,10 @@ public void testSearchWc2() throws Exception { Assert.assertTrue(res.getBody().contains("mymsg")); Assert.assertTrue(res.getBody().contains("msgid")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("logstash-1-*,logstash-20*/_search?pretty", encodeBasicHeader("regex", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("logstash-1-*,logstash-20*/_search?pretty", encodeBasicHeader("regex", "password"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DfmOverwritesAllTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DfmOverwritesAllTest.java index aafffb141f..580cabc66b 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DfmOverwritesAllTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DfmOverwritesAllTest.java @@ -37,17 +37,41 @@ public class DfmOverwritesAllTest extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.index(new IndexRequest("index1-1").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"field1\": 1, \"field2\": \"value-2-1\", \"field3\": \"value-3-1\", \"field4\": \"value-4-1\" }", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("index1-2").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"field1\": 2, \"field2\": \"value-2-2\", \"field3\": \"value-3-2\", \"field4\": \"value-4-2\" }", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("index1-3").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"field1\": 3, \"field2\": \"value-2-3\", \"field3\": \"value-3-3\", \"field4\": \"value-4-3\" }", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("index1-4").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"field1\": 4, \"field2\": \"value-2-4\", \"field3\": \"value-3-4\", \"field4\": \"value-4-4\" }", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("index1-1").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"field1\": 1, \"field2\": \"value-2-1\", \"field3\": \"value-3-1\", \"field4\": \"value-4-1\" }", + XContentType.JSON + ) + ).actionGet(); + + tc.index( + new IndexRequest("index1-2").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"field1\": 2, \"field2\": \"value-2-2\", \"field3\": \"value-3-2\", \"field4\": \"value-4-2\" }", + XContentType.JSON + ) + ).actionGet(); + + tc.index( + new IndexRequest("index1-3").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"field1\": 3, \"field2\": \"value-2-3\", \"field3\": \"value-3-3\", \"field4\": \"value-4-3\" }", + XContentType.JSON + ) + ).actionGet(); + + tc.index( + new IndexRequest("index1-4").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"field1\": 4, \"field2\": \"value-2-4\", \"field3\": \"value-3-4\", \"field4\": \"value-4-4\" }", + XContentType.JSON + ) + ).actionGet(); } @@ -59,15 +83,17 @@ protected void populateData(Client tc) { public void testDFMUnrestrictedUser() throws Exception { final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, true).build(); - setup(settings, new DynamicSecurityConfig().setConfig("securityconfig_dfm_empty_overwrites_all.yml") + setup( + settings, + new DynamicSecurityConfig().setConfig("securityconfig_dfm_empty_overwrites_all.yml") .setSecurityInternalUsers("internal_users_dfm_empty_overwrites_all.yml") .setSecurityRoles("roles_dfm_empty_overwrites_all.yml") - .setSecurityRolesMapping("rolesmapping_dfm_empty_overwrites_all.yml")); + .setSecurityRolesMapping("rolesmapping_dfm_empty_overwrites_all.yml") + ); HttpResponse response; - response = rh.executeGetRequest("/index1-*/_search?pretty", - encodeBasicHeader("admin", "password")); + response = rh.executeGetRequest("/index1-*/_search?pretty", encodeBasicHeader("admin", "password")); Assert.assertEquals(200, response.getStatusCode()); // the only document in index1-1 is filtered by DLS query, so normally no hit in index-1-1 @@ -99,21 +125,23 @@ public void testDFMUnrestrictedUser() throws Exception { public void testDFMRestrictedUser() throws Exception { final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, true).build(); - setup(settings, new DynamicSecurityConfig().setConfig("securityconfig_dfm_empty_overwrites_all.yml") + setup( + settings, + new DynamicSecurityConfig().setConfig("securityconfig_dfm_empty_overwrites_all.yml") .setSecurityInternalUsers("internal_users_dfm_empty_overwrites_all.yml") .setSecurityRoles("roles_dfm_empty_overwrites_all.yml") - .setSecurityRolesMapping("rolesmapping_dfm_empty_overwrites_all.yml")); + .setSecurityRolesMapping("rolesmapping_dfm_empty_overwrites_all.yml") + ); HttpResponse response; - response = rh.executeGetRequest("/index1-*/_search?pretty", - encodeBasicHeader("dfm_restricted_role", "password")); + response = rh.executeGetRequest("/index1-*/_search?pretty", encodeBasicHeader("dfm_restricted_role", "password")); Assert.assertEquals(200, response.getStatusCode()); - // the only document in index1-1 is filtered by DLS query, so no hit in index-1-1 + // the only document in index1-1 is filtered by DLS query, so no hit in index-1-1 Assert.assertFalse(response.getBody().contains("index1-1")); - // field3 and field4 - filtered out by FLS + // field3 and field4 - filtered out by FLS Assert.assertFalse(response.getBody().contains("value-3-1")); Assert.assertFalse(response.getBody().contains("value-4-1")); Assert.assertFalse(response.getBody().contains("value-3-2")); @@ -146,15 +174,20 @@ public void testDFMRestrictedAndUnrestrictedAllIndices() throws Exception { final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, true).build(); - setup(settings, new DynamicSecurityConfig().setConfig("securityconfig_dfm_empty_overwrites_all.yml") + setup( + settings, + new DynamicSecurityConfig().setConfig("securityconfig_dfm_empty_overwrites_all.yml") .setSecurityInternalUsers("internal_users_dfm_empty_overwrites_all.yml") .setSecurityRoles("roles_dfm_empty_overwrites_all.yml") - .setSecurityRolesMapping("rolesmapping_dfm_empty_overwrites_all.yml")); + .setSecurityRolesMapping("rolesmapping_dfm_empty_overwrites_all.yml") + ); HttpResponse response; - response = rh.executeGetRequest("/index1-*/_search?pretty", - encodeBasicHeader("dfm_restricted_and_unrestricted_all_indices_role", "password")); + response = rh.executeGetRequest( + "/index1-*/_search?pretty", + encodeBasicHeader("dfm_restricted_and_unrestricted_all_indices_role", "password") + ); Assert.assertEquals(200, response.getStatusCode()); // the only document in index1-1 is filtered by DLS query, so normally no hit in index-1-1 @@ -187,18 +220,25 @@ public void testDFMRestrictedAndUnrestrictedAllIndices() throws Exception { @Test public void testDFMRestrictedAndUnrestrictedOneIndex() throws Exception { final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, true).build(); - setup(settings, new DynamicSecurityConfig().setConfig("securityconfig_dfm_empty_overwrites_all.yml") + setup( + settings, + new DynamicSecurityConfig().setConfig("securityconfig_dfm_empty_overwrites_all.yml") .setSecurityInternalUsers("internal_users_dfm_empty_overwrites_all.yml") .setSecurityRoles("roles_dfm_empty_overwrites_all.yml") - .setSecurityRolesMapping("rolesmapping_dfm_empty_overwrites_all.yml")); + .setSecurityRolesMapping("rolesmapping_dfm_empty_overwrites_all.yml") + ); HttpResponse response; - response = rh.executeGetRequest("/_plugins/_security/authinfo?pretty", - encodeBasicHeader("dfm_restricted_and_unrestricted_one_index_role", "password")); + response = rh.executeGetRequest( + "/_plugins/_security/authinfo?pretty", + encodeBasicHeader("dfm_restricted_and_unrestricted_one_index_role", "password") + ); - response = rh.executeGetRequest("/index1-*/_search?pretty", - encodeBasicHeader("dfm_restricted_and_unrestricted_one_index_role", "password")); + response = rh.executeGetRequest( + "/index1-*/_search?pretty", + encodeBasicHeader("dfm_restricted_and_unrestricted_one_index_role", "password") + ); Assert.assertEquals(200, response.getStatusCode()); // we have a role that places no restrictions on index-1-1, lifting the DLS from the restricted role diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsDateMathTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsDateMathTest.java index bfd0773f44..cbd1e09e56 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsDateMathTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsDateMathTest.java @@ -27,41 +27,52 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class DlsDateMathTest extends AbstractDlsFlsTest{ - +public class DlsDateMathTest extends AbstractDlsFlsTest { @Override protected void populateData(Client tc) { - - LocalDateTime yesterday = LocalDateTime.now(ZoneId.of("UTC")).minusDays(1); LocalDateTime today = LocalDateTime.now(ZoneId.of("UTC")); LocalDateTime tomorrow = LocalDateTime.now(ZoneId.of("UTC")).plusDays(1); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); - tc.index(new IndexRequest("logstash").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"@timestamp\": \""+formatter.format(yesterday)+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"@timestamp\": \""+formatter.format(today)+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash").id("3").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"@timestamp\": \""+formatter.format(tomorrow)+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("logstash").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"@timestamp\": \"" + formatter.format(yesterday) + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("logstash").id("2") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"@timestamp\": \"" + formatter.format(today) + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("logstash").id("3") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"@timestamp\": \"" + formatter.format(tomorrow) + "\"}", XContentType.JSON) + ).actionGet(); } - @Test public void testDlsDateMathQuery() throws Exception { - final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_ALLOW_NOW_IN_DLS,true).build(); + final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_ALLOW_NOW_IN_DLS, true).build(); setup(settings); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash/_search?pretty", encodeBasicHeader("date_math", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash/_search?pretty", encodeBasicHeader("date_math", "password"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 3,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -73,12 +84,18 @@ public void testDlsDateMathQueryNotAllowed() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, (res = rh.executeGetRequest("/logstash/_search?pretty", encodeBasicHeader("date_math", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_BAD_REQUEST, + (res = rh.executeGetRequest("/logstash/_search?pretty", encodeBasicHeader("date_math", "password"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("'now' is not allowed in DLS queries")); Assert.assertTrue(res.getBody().contains("error")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 3,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterMinimalRoundtripSearchTests.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterMinimalRoundtripSearchTests.java index 86b97c1afe..81610b0ee4 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterMinimalRoundtripSearchTests.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterMinimalRoundtripSearchTests.java @@ -13,5 +13,7 @@ public class DlsFlsCrossClusterMinimalRoundtripSearchTests extends DlsFlsCrossClusterSearchTest { @Override - protected boolean ccsMinimizeRoundtrips() { return true; } + protected boolean ccsMinimizeRoundtrips() { + return true; + } } diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java index 8fcd85f873..a86c49d21d 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsFlsCrossClusterSearchTest.java @@ -31,12 +31,18 @@ public class DlsFlsCrossClusterSearchTest extends AbstractSecurityUnitTest { - private final ClusterHelper cl1 = new ClusterHelper("crl1_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); - private final ClusterHelper cl2 = new ClusterHelper("crl2_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); + private final ClusterHelper cl1 = new ClusterHelper( + "crl1_n" + num.incrementAndGet() + "_f" + System.getProperty("forkno") + "_t" + System.nanoTime() + ); + private final ClusterHelper cl2 = new ClusterHelper( + "crl2_n" + num.incrementAndGet() + "_f" + System.getProperty("forkno") + "_t" + System.nanoTime() + ); private ClusterInfo cl1Info; private ClusterInfo cl2Info; - protected boolean ccsMinimizeRoundtrips() { return false; }; + protected boolean ccsMinimizeRoundtrips() { + return false; + }; @Override protected String getResourceFolder() { @@ -45,13 +51,16 @@ protected String getResourceFolder() { private void setupCcs(String remoteRoles) throws Exception { - System.setProperty("security.display_lic_none","true"); + System.setProperty("security.display_lic_none", "true"); - cl2Info = cl2.startCluster(minimumSecuritySettings(Settings.builder().putList("node.roles", "remote_cluster_client").build()), ClusterConfiguration.DEFAULT); + cl2Info = cl2.startCluster( + minimumSecuritySettings(Settings.builder().putList("node.roles", "remote_cluster_client").build()), + ClusterConfiguration.DEFAULT + ); initialize(cl2, cl2Info, new DynamicSecurityConfig().setSecurityRoles(remoteRoles)); System.out.println("### cl2 complete ###"); - //cl1 is coordinating + // cl1 is coordinating cl1Info = cl1.startCluster(minimumSecuritySettings(crossClusterNodeSettings(cl2Info)), ClusterConfiguration.DEFAULT); System.out.println("### cl1 start ###"); initialize(cl1, cl1Info, new DynamicSecurityConfig().setSecurityRoles("roles_983.yml")); @@ -66,8 +75,8 @@ public void tearDown() throws Exception { private Settings crossClusterNodeSettings(ClusterInfo remote) { Settings.Builder builder = Settings.builder() - .putList("cluster.remote.cross_cluster_two.seeds", remote.nodeHost+":"+remote.nodePort) - .putList("node.roles", "remote_cluster_client"); + .putList("cluster.remote.cross_cluster_two.seeds", remote.nodeHost + ":" + remote.nodePort) + .putList("node.roles", "remote_cluster_client"); return builder.build(); } @@ -76,42 +85,71 @@ public void testCcs() throws Exception { setupCcs("roles_983.yml"); try (Client tc = cl1.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\","+ - "\"Designation\": \"CEO\","+ - "\"FirstName\": \"__fn__"+cl2Info.clustername+"\","+ - "\"LastName\": \"lastname0\","+ - "\"Salary\": \"salary0\","+ - "\"SecretFiled\": \"secret0\","+ - "\"AnotherSecredField\": \"anothersecret0\","+ - "\"XXX\": \"xxx0\"" - + "}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("1") - .source("{\"cluster\": \""+cl2Info.clustername+"\","+ - "\"Designation\": \"someoneelse\","+ - "\"FirstName\": \"__fn__"+cl2Info.clustername+"\","+ - "\"LastName\": \"lastname1\","+ - "\"Salary\": \"salary1\","+ - "\"SecretFiled\": \"secret1\","+ - "\"AnotherSecredField\": \"anothersecret1\","+ - "\"XXX\": \"xxx1\"" - + "}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source( + "{\"cluster\": \"" + + cl2Info.clustername + + "\"," + + "\"Designation\": \"CEO\"," + + "\"FirstName\": \"__fn__" + + cl2Info.clustername + + "\"," + + "\"LastName\": \"lastname0\"," + + "\"Salary\": \"salary0\"," + + "\"SecretFiled\": \"secret0\"," + + "\"AnotherSecredField\": \"anothersecret0\"," + + "\"XXX\": \"xxx0\"" + + "}", + XContentType.JSON + ) + ).actionGet(); + + tc.index( + new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("1") + .source( + "{\"cluster\": \"" + + cl2Info.clustername + + "\"," + + "\"Designation\": \"someoneelse\"," + + "\"FirstName\": \"__fn__" + + cl2Info.clustername + + "\"," + + "\"LastName\": \"lastname1\"," + + "\"Salary\": \"salary1\"," + + "\"SecretFiled\": \"secret1\"," + + "\"AnotherSecredField\": \"anothersecret1\"," + + "\"XXX\": \"xxx1\"" + + "}", + XContentType.JSON + ) + ).actionGet(); } HttpResponse ccs = null; System.out.println("###################### query 1"); - //on coordinating cluster - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:humanresources/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("human_resources_trainee", "password")); + // on coordinating cluster + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:humanresources/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("human_resources_trainee", "password") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("crl1")); @@ -134,42 +172,71 @@ public void testCcsDifferentConfig() throws Exception { setupCcs("roles_ccs2.yml"); try (Client tc = cl1.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); } try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\","+ - "\"Designation\": \"CEO\","+ - "\"FirstName\": \"__fn__"+cl2Info.clustername+"\","+ - "\"LastName\": \"lastname0\","+ - "\"Salary\": \"salary0\","+ - "\"SecretFiled\": \"secret0\","+ - "\"AnotherSecredField\": \"anothersecret0\","+ - "\"XXX\": \"xxx0\"" - + "}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("1") - .source("{\"cluster\": \""+cl2Info.clustername+"\","+ - "\"Designation\": \"someoneelse\","+ - "\"FirstName\": \"__fn__"+cl2Info.clustername+"\","+ - "\"LastName\": \"lastname1\","+ - "\"Salary\": \"salary1\","+ - "\"SecretFiled\": \"secret1\","+ - "\"AnotherSecredField\": \"anothersecret1\","+ - "\"XXX\": \"xxx1\"" - + "}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source( + "{\"cluster\": \"" + + cl2Info.clustername + + "\"," + + "\"Designation\": \"CEO\"," + + "\"FirstName\": \"__fn__" + + cl2Info.clustername + + "\"," + + "\"LastName\": \"lastname0\"," + + "\"Salary\": \"salary0\"," + + "\"SecretFiled\": \"secret0\"," + + "\"AnotherSecredField\": \"anothersecret0\"," + + "\"XXX\": \"xxx0\"" + + "}", + XContentType.JSON + ) + ).actionGet(); + + tc.index( + new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("1") + .source( + "{\"cluster\": \"" + + cl2Info.clustername + + "\"," + + "\"Designation\": \"someoneelse\"," + + "\"FirstName\": \"__fn__" + + cl2Info.clustername + + "\"," + + "\"LastName\": \"lastname1\"," + + "\"Salary\": \"salary1\"," + + "\"SecretFiled\": \"secret1\"," + + "\"AnotherSecredField\": \"anothersecret1\"," + + "\"XXX\": \"xxx1\"" + + "}", + XContentType.JSON + ) + ).actionGet(); } HttpResponse ccs = null; System.out.println("###################### query 1"); - //on coordinating cluster - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:humanresources/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("human_resources_trainee", "password")); + // on coordinating cluster + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:humanresources/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("human_resources_trainee", "password") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertFalse(ccs.getBody().contains("crl1")); @@ -192,64 +259,113 @@ public void testCcsDifferentConfigBoth() throws Exception { setupCcs("roles_ccs2.yml"); try (Client tc = cl1.nodeClient()) { - tc.index(new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\"}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl1Info.clustername+"\","+ - "\"Designation\": \"CEO\","+ - "\"FirstName\": \"__fn__"+cl1Info.clustername+"\","+ - "\"LastName\": \"lastname0\","+ - "\"Salary\": \"salary0\","+ - "\"SecretFiled\": \"secret3\","+ - "\"AnotherSecredField\": \"anothersecret3\","+ - "\"XXX\": \"xxx0\"" - + "}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("1") - .source("{\"cluster\": \""+cl1Info.clustername+"\","+ - "\"Designation\": \"someoneelse\","+ - "\"FirstName\": \"__fn__"+cl1Info.clustername+"\","+ - "\"LastName\": \"lastname1\","+ - "\"Salary\": \"salary1\","+ - "\"SecretFiled\": \"secret4\","+ - "\"AnotherSecredField\": \"anothersecret4\","+ - "\"XXX\": \"xxx1\"" - + "}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twitter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl1Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + + tc.index( + new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source( + "{\"cluster\": \"" + + cl1Info.clustername + + "\"," + + "\"Designation\": \"CEO\"," + + "\"FirstName\": \"__fn__" + + cl1Info.clustername + + "\"," + + "\"LastName\": \"lastname0\"," + + "\"Salary\": \"salary0\"," + + "\"SecretFiled\": \"secret3\"," + + "\"AnotherSecredField\": \"anothersecret3\"," + + "\"XXX\": \"xxx0\"" + + "}", + XContentType.JSON + ) + ).actionGet(); + + tc.index( + new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("1") + .source( + "{\"cluster\": \"" + + cl1Info.clustername + + "\"," + + "\"Designation\": \"someoneelse\"," + + "\"FirstName\": \"__fn__" + + cl1Info.clustername + + "\"," + + "\"LastName\": \"lastname1\"," + + "\"Salary\": \"salary1\"," + + "\"SecretFiled\": \"secret4\"," + + "\"AnotherSecredField\": \"anothersecret4\"," + + "\"XXX\": \"xxx1\"" + + "}", + XContentType.JSON + ) + ).actionGet(); } try (Client tc = cl2.nodeClient()) { - tc.index(new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("0") - .source("{\"cluster\": \""+cl2Info.clustername+"\","+ - "\"Designation\": \"CEO\","+ - "\"FirstName\": \"__fn__"+cl2Info.clustername+"\","+ - "\"LastName\": \"lastname0\","+ - "\"Salary\": \"salary0\","+ - "\"SecretFiled\": \"secret0\","+ - "\"AnotherSecredField\": \"anothersecret0\","+ - "\"XXX\": \"xxx0\"" - + "}", XContentType.JSON)).actionGet(); - - tc.index(new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE).id("1") - .source("{\"cluster\": \""+cl2Info.clustername+"\","+ - "\"Designation\": \"someoneelse\","+ - "\"FirstName\": \"__fn__"+cl2Info.clustername+"\","+ - "\"LastName\": \"lastname1\","+ - "\"Salary\": \"salary1\","+ - "\"SecretFiled\": \"secret1\","+ - "\"AnotherSecredField\": \"anothersecret1\","+ - "\"XXX\": \"xxx1\"" - + "}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("twutter").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source("{\"cluster\": \"" + cl2Info.clustername + "\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("0") + .source( + "{\"cluster\": \"" + + cl2Info.clustername + + "\"," + + "\"Designation\": \"CEO\"," + + "\"FirstName\": \"__fn__" + + cl2Info.clustername + + "\"," + + "\"LastName\": \"lastname0\"," + + "\"Salary\": \"salary0\"," + + "\"SecretFiled\": \"secret0\"," + + "\"AnotherSecredField\": \"anothersecret0\"," + + "\"XXX\": \"xxx0\"" + + "}", + XContentType.JSON + ) + ).actionGet(); + + tc.index( + new IndexRequest("humanresources").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .id("1") + .source( + "{\"cluster\": \"" + + cl2Info.clustername + + "\"," + + "\"Designation\": \"someoneelse\"," + + "\"FirstName\": \"__fn__" + + cl2Info.clustername + + "\"," + + "\"LastName\": \"lastname1\"," + + "\"Salary\": \"salary1\"," + + "\"SecretFiled\": \"secret1\"," + + "\"AnotherSecredField\": \"anothersecret1\"," + + "\"XXX\": \"xxx1\"" + + "}", + XContentType.JSON + ) + ).actionGet(); } HttpResponse ccs = null; System.out.println("###################### query 1"); - //on coordinating cluster - ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest("cross_cluster_two:humanresources,humanresources/_search?pretty&ccs_minimize_roundtrips="+ccsMinimizeRoundtrips(), encodeBasicHeader("human_resources_trainee", "password")); + // on coordinating cluster + ccs = new RestHelper(cl1Info, false, false, getResourceFolder()).executeGetRequest( + "cross_cluster_two:humanresources,humanresources/_search?pretty&ccs_minimize_roundtrips=" + ccsMinimizeRoundtrips(), + encodeBasicHeader("human_resources_trainee", "password") + ); System.out.println(ccs.getBody()); Assert.assertEquals(HttpStatus.SC_OK, ccs.getStatusCode()); Assert.assertTrue(ccs.getBody().contains("crl1")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsNestedTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsNestedTest.java index a89d12770d..afb42d9a4a 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsNestedTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsNestedTest.java @@ -23,35 +23,47 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class DlsNestedTest extends AbstractDlsFlsTest{ +public class DlsNestedTest extends AbstractDlsFlsTest { @Override protected void populateData(Client tc) { - String mapping = "{" + - " \"mytype\" : {" + - " \"properties\" : {" + - " \"amount\" : {\"type\": \"integer\"}," + - " \"owner\" : {\"type\": \"text\"}," + - " \"my_nested_object\" : {\"type\" : \"nested\"}" + - " }" + - " }" + - " }" + - ""; - - tc.admin().indices().create(new CreateIndexRequest("deals") - .simpleMapping("amount", "type=integer", "owner", "type=text", "my_nested_object", "type=nested") - .settings(Settings.builder().put("number_of_shards", 1).put("number_of_replicas", 0).build())) - .actionGet(); - - //tc.index(new IndexRequest("deals").id("3").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - // .source("{\"amount\": 7,\"owner\": \"a\", \"my_nested_object\" : {\"name\": \"spock\"}}", XContentType.JSON)).actionGet(); - //tc.index(new IndexRequest("deals").id("4").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - // .source("{\"amount\": 8, \"my_nested_object\" : {\"name\": \"spock\"}}", XContentType.JSON)).actionGet(); - //tc.index(new IndexRequest("deals").id("5").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - // .source("{\"amount\": 1400,\"owner\": \"a\", \"my_nested_object\" : {\"name\": \"spock\"}}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("deals").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"amount\": 1500,\"owner\": \"b\", \"my_nested_object\" : {\"name\": \"spock\"}}", XContentType.JSON)).actionGet(); + String mapping = "{" + + " \"mytype\" : {" + + " \"properties\" : {" + + " \"amount\" : {\"type\": \"integer\"}," + + " \"owner\" : {\"type\": \"text\"}," + + " \"my_nested_object\" : {\"type\" : \"nested\"}" + + " }" + + " }" + + " }" + + ""; + + tc.admin() + .indices() + .create( + new CreateIndexRequest("deals").simpleMapping( + "amount", + "type=integer", + "owner", + "type=text", + "my_nested_object", + "type=nested" + ).settings(Settings.builder().put("number_of_shards", 1).put("number_of_replicas", 0).build()) + ) + .actionGet(); + + // tc.index(new IndexRequest("deals").id("3").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + // .source("{\"amount\": 7,\"owner\": \"a\", \"my_nested_object\" : {\"name\": \"spock\"}}", XContentType.JSON)).actionGet(); + // tc.index(new IndexRequest("deals").id("4").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + // .source("{\"amount\": 8, \"my_nested_object\" : {\"name\": \"spock\"}}", XContentType.JSON)).actionGet(); + // tc.index(new IndexRequest("deals").id("5").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + // .source("{\"amount\": 1400,\"owner\": \"a\", \"my_nested_object\" : {\"name\": \"spock\"}}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("deals").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"amount\": 1500,\"owner\": \"b\", \"my_nested_object\" : {\"name\": \"spock\"}}", XContentType.JSON) + ).actionGet(); } @Test @@ -59,34 +71,35 @@ public void testNestedQuery() throws Exception { setup(); - - String query = "{" + - " \"query\": {" + - " \"nested\": {" + - " \"path\": \"my_nested_object\"," + - " \"query\": {" + - " \"match\": {\"my_nested_object.name\" : \"spock\"}" + - " }," + - " \"inner_hits\": {} " + - " }" + - " }" + - "}"; - + String query = "{" + + " \"query\": {" + + " \"nested\": {" + + " \"path\": \"my_nested_object\"," + + " \"query\": {" + + " \"match\": {\"my_nested_object.name\" : \"spock\"}" + + " }," + + " \"inner_hits\": {} " + + " }" + + " }" + + "}"; HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager", "password"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"my_nested_object\" : {")); Assert.assertTrue(res.getBody().contains("\"field\" : \"my_nested_object\",")); Assert.assertTrue(res.getBody().contains("\"offset\" : 0")); - //Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); - //System.out.println(res.getBody()); - //Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); - //Assert.assertTrue(res.getBody().contains("\"value\" : 1510.0")); - //Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); + // Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", + // "admin"))).getStatusCode()); + // System.out.println(res.getBody()); + // Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); + // Assert.assertTrue(res.getBody().contains("\"value\" : 1510.0")); + // Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); } - } diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsPropsReplaceTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsPropsReplaceTest.java index 43d5ecfc5f..2e23a11d2d 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsPropsReplaceTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsPropsReplaceTest.java @@ -21,28 +21,40 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class DlsPropsReplaceTest extends AbstractDlsFlsTest{ - +public class DlsPropsReplaceTest extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.index(new IndexRequest("prop1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"prop_replace\": \"yes\", \"amount\": 1010}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("prop1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"prop_replace\": \"no\", \"amount\": 2020}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("prop2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"role\": \"prole1\", \"amount\": 3030}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("prop2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"role\": \"prole2\", \"amount\": 4040}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("prop2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"role\": \"prole3\", \"amount\": 5050}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("prop-mapped").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"securityRole\": \"opendistro_security_mapped\", \"amount\": 6060}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("prop-mapped").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"securityRole\": \"not_assigned\", \"amount\": 7070}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("prop1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"prop_replace\": \"yes\", \"amount\": 1010}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("prop1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"prop_replace\": \"no\", \"amount\": 2020}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("prop2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"role\": \"prole1\", \"amount\": 3030}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("prop2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"role\": \"prole2\", \"amount\": 4040}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("prop2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"role\": \"prole3\", \"amount\": 5050}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("prop-mapped").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"securityRole\": \"opendistro_security_mapped\", \"amount\": 6060}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("prop-mapped").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"securityRole\": \"not_assigned\", \"amount\": 7070}", XContentType.JSON) + ).actionGet(); } - @Test public void testDlsProps() throws Exception { @@ -50,15 +62,26 @@ public void testDlsProps() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/prop1,prop2/_search?pretty&size=100", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/prop1,prop2/_search?pretty&size=100", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 5,\n \"relation")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/prop1,prop2/_search?pretty&size=100", encodeBasicHeader("prop_replace", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/prop1,prop2/_search?pretty&size=100", encodeBasicHeader("prop_replace", "password"))) + .getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 3,\n \"relation")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/prop-mapped/_search?pretty&size=100", encodeBasicHeader("prop_replace", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/prop-mapped/_search?pretty&size=100", encodeBasicHeader("prop_replace", "password"))) + .getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"amount\" : 6060")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsScrollTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsScrollTest.java index b1d87734e5..cc7b9e305d 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsScrollTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsScrollTest.java @@ -21,53 +21,67 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class DlsScrollTest extends AbstractDlsFlsTest{ - +public class DlsScrollTest extends AbstractDlsFlsTest { @Override protected void populateData(Client tc) { - tc.index(new IndexRequest("deals").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"amount\": 3}", XContentType.JSON)).actionGet(); //not in + tc.index(new IndexRequest("deals").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"amount\": 3}", XContentType.JSON)) + .actionGet(); // not in - tc.index(new IndexRequest("deals").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"amount\": 10}", XContentType.JSON)).actionGet(); //not in + tc.index(new IndexRequest("deals").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"amount\": 10}", XContentType.JSON)) + .actionGet(); // not in - tc.index(new IndexRequest("deals").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"amount\": 1500}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("deals").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"amount\": 1500}", XContentType.JSON) + ).actionGet(); - tc.index(new IndexRequest("deals").id("4").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"amount\": 21500}", XContentType.JSON)).actionGet(); //not in + tc.index( + new IndexRequest("deals").id("4").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"amount\": 21500}", XContentType.JSON) + ).actionGet(); // not in - for(int i=0; i<100; i++) { - tc.index(new IndexRequest("deals").id("gen"+i).setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"amount\": 1500}", XContentType.JSON)).actionGet(); + for (int i = 0; i < 100; i++) { + tc.index( + new IndexRequest("deals").id("gen" + i) + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"amount\": 1500}", XContentType.JSON) + ).actionGet(); } } - @Test public void testDlsScroll() throws Exception { setup(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executeGetRequest("/deals/_search?scroll=1m&pretty=true&size=5", encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?scroll=1m&pretty=true&size=5", encodeBasicHeader("dept_manager", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 101,")); - int c=0; + int c = 0; - while(true) { + while (true) { int start = res.getBody().indexOf("_scroll_id") + 15; - String scrollid = res.getBody().substring(start, res.getBody().indexOf("\"", start+1)); - Assert.assertEquals(HttpStatus.SC_OK, (res=rh.executePostRequest("/_search/scroll?pretty=true", "{\"scroll\" : \"1m\", \"scroll_id\" : \""+scrollid+"\"}", encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + String scrollid = res.getBody().substring(start, res.getBody().indexOf("\"", start + 1)); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest( + "/_search/scroll?pretty=true", + "{\"scroll\" : \"1m\", \"scroll_id\" : \"" + scrollid + "\"}", + encodeBasicHeader("dept_manager", "password") + )).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 101,")); Assert.assertFalse(res.getBody().contains("\"amount\" : 3")); Assert.assertFalse(res.getBody().contains("\"amount\" : 10")); Assert.assertFalse(res.getBody().contains("\"amount\" : 21500")); c++; - if(res.getBody().contains("\"hits\" : [ ]")) { + if (res.getBody().contains("\"hits\" : [ ]")) { break; } } diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTermLookupQueryTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTermLookupQueryTest.java index 098d659d88..76112f533c 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTermLookupQueryTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTermLookupQueryTest.java @@ -54,543 +54,701 @@ public class DlsTermLookupQueryTest extends AbstractDlsFlsTest { - protected void populateData(Client client) { - // user access codes, basis for TLQ query - client.index(new IndexRequest("user_access_codes").id("tlq_1337").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"access_codes\": [1337] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("user_access_codes").id("tlq_42").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"access_codes\": [42] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("user_access_codes").id("tlq_1337_42").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"access_codes\": [1337, 42] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("user_access_codes").id("tlq_999").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"access_codes\": [999] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("user_access_codes").id("tlq_empty_access_codes") - .setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{ \"access_codes\": [] }", XContentType.JSON)) - .actionGet(); - client.index(new IndexRequest("user_access_codes").id("tlq_no_codes").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bla\": \"blub\" }", XContentType.JSON)).actionGet(); - - // need to have keyword for bu field since we're testing aggregations - client.admin().indices().create(new CreateIndexRequest("tlqdocuments")).actionGet(); - client.admin().indices() - .putMapping(new PutMappingRequest("tlqdocuments").source("bu", "type=keyword")) - .actionGet(); - - // tlqdocuments, protected by TLQ - client.index(new IndexRequest("tlqdocuments").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"AAA\", \"access_codes\": [1337] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"AAA\", \"access_codes\": [42] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("3").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"AAA\", \"access_codes\": [1337, 42] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("4").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"BBB\", \"access_codes\": [1337] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("5").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"BBB\", \"access_codes\": [42] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("6").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"BBB\", \"access_codes\": [1337, 42] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("7").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"CCC\", \"access_codes\": [1337] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("8").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"CCC\", \"access_codes\": [42] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("9").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"CCC\", \"access_codes\": [1337, 42] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("10").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"DDD\", \"access_codes\": [1337] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("11").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"DDD\", \"access_codes\": [42] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("12").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"DDD\", \"access_codes\": [1337, 42] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("13").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"EEE\", \"access_codes\": [1337] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("14").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"EEE\", \"access_codes\": [42] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("15").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"EEE\", \"access_codes\": [1337, 42] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("16").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"FFF\" }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("17").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"FFF\", \"access_codes\": [12345] }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdocuments").id("18").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"bu\": \"FFF\", \"access_codes\": [12345, 6789] }", XContentType.JSON)).actionGet(); - - // we use a "bu" field here as well to test aggregations over multiple indices - client.admin().indices().create(new CreateIndexRequest("tlqdummy")).actionGet(); - client.admin().indices().putMapping(new PutMappingRequest("tlqdummy").source("bu", "type=keyword")) - .actionGet(); - - // tlqdummy, not protected by TLQ - client.index(new IndexRequest("tlqdummy").id("101").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"mykey\": \"101\", \"bu\": \"GGG\" }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdummy").id("102").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"mykey\": \"102\", \"bu\": \"GGG\" }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdummy").id("103").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"mykey\": \"103\", \"bu\": \"GGG\" }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdummy").id("104").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"mykey\": \"104\", \"bu\": \"GGG\" }", XContentType.JSON)).actionGet(); - client.index(new IndexRequest("tlqdummy").id("105").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{ \"mykey\": \"105\", \"bu\": \"GGG\" }", XContentType.JSON)).actionGet(); - - } - - // ------------------------ - // Test search and msearch - // ------------------------ - - @Test - public void testSimpleSearch_AccessCode_1337() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - HttpResponse response = rh.executeGetRequest("/tlqdocuments/_search?pretty", - encodeBasicHeader("tlq_1337", "password")); - Assert.assertEquals(200, response.getStatusCode()); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, - LoggingDeprecationHandler.INSTANCE, response.getBody()); - SearchResponse searchResponse = SearchResponse.fromXContent(xcp); - // 10 docs, all need to have access code 1337 - Assert.assertEquals(searchResponse.toString(), 10, searchResponse.getHits().getTotalHits().value); - // fields need to have 1337 access code - assertAccessCodesMatch(searchResponse.getHits().getHits(), new Integer[] { 1337 }); - } - - @Test - public void testSimpleSearch_AccessCode_42() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - HttpResponse response = rh.executeGetRequest("/tlqdocuments/_search?pretty", - encodeBasicHeader("tlq_42", "password")); - Assert.assertEquals(200, response.getStatusCode()); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, - LoggingDeprecationHandler.INSTANCE, response.getBody()); - SearchResponse searchResponse = SearchResponse.fromXContent(xcp); - - // 10 docs, all need to have access code 42 - Assert.assertEquals(searchResponse.toString(), 10, searchResponse.getHits().getTotalHits().value); - // fields need to have 42 access code - assertAccessCodesMatch(searchResponse.getHits().getHits(), new Integer[] { 42 }); - - } - - @Test - public void testSimpleSearch_AccessCodes_1337_42() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - HttpResponse response = rh.executeGetRequest("/tlqdocuments/_search?pretty", - encodeBasicHeader("tlq_1337_42", "password")); - Assert.assertEquals(200, response.getStatusCode()); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, - LoggingDeprecationHandler.INSTANCE, response.getBody()); - SearchResponse searchResponse = SearchResponse.fromXContent(xcp); - - // 15 docs, all need to have either access code 1337 or 42 - Assert.assertEquals(searchResponse.toString(), 15, searchResponse.getHits().getTotalHits().value); - // fields need to have 42 or 1337 access code - assertAccessCodesMatch(searchResponse.getHits().getHits(), new Integer[] { 42, 1337 }); - - } - - @Test - public void testSimpleSearch_AccessCodes_999() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - HttpResponse response = rh.executeGetRequest("/tlqdocuments/_search?pretty", - encodeBasicHeader("tlq_999", "password")); - Assert.assertEquals(200, response.getStatusCode()); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, - LoggingDeprecationHandler.INSTANCE, response.getBody()); - SearchResponse searchResponse = SearchResponse.fromXContent(xcp); - - Assert.assertEquals(searchResponse.toString(), 0, searchResponse.getHits().getTotalHits().value); - } - - @Test - public void testSimpleSearch_AccessCodes_emptyAccessCodes() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - SearchResponse searchResponse = executeSearch("tlqdocuments", "tlq_empty_access_codes", "password"); - Assert.assertEquals(searchResponse.toString(), 0, searchResponse.getHits().getTotalHits().value); - } - - @Test - public void testSimpleSearch_AccessCodes_noAccessCodes() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - SearchResponse searchResponse = executeSearch("tlqdocuments", "tlq_no_codes", "password"); - - Assert.assertEquals(searchResponse.toString(), 0, searchResponse.getHits().getTotalHits().value); - } - - @Test - public void testSimpleSearch_AllIndices_All_AccessCodes_1337() throws Exception { - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - SearchResponse searchResponse = executeSearch("_all", "tlq_1337", "password"); - - // assume hits from 2 indices: - // - tlqdocuments, must contain only docs with access code 1337 - // - tlqdummy, contains all documents - // no access to user_access_codes must be granted - - // check all 5 tlqdummy entries present, index is not protected by DLS - Set tlqdummyHits = Arrays.asList(searchResponse.getHits().getHits()).stream() - .filter((h) -> h.getIndex().equals("tlqdummy")).collect(Collectors.toSet()); - Assert.assertEquals(searchResponse.toString(), 5, tlqdummyHits.size()); - - // check 10 hits with code 1337 from tlqdocuments index. All other documents - // must be filtered - Set tlqdocumentHits = Arrays.asList(searchResponse.getHits().getHits()).stream() - .filter((h) -> h.getIndex().equals("tlqdocuments")).collect(Collectors.toSet()); - Assert.assertEquals(searchResponse.toString(), 10, tlqdocumentHits.size()); - assertAccessCodesMatch(tlqdocumentHits, new Integer[] { 1337 }); - - // check no access to user_access_codes index - Set userAccessCodesHits = Arrays.asList(searchResponse.getHits().getHits()).stream() - .filter((h) -> h.getIndex().equals("user_access_codes")).collect(Collectors.toSet()); - Assert.assertEquals(searchResponse.toString(), 0, userAccessCodesHits.size()); - } - - @Test - public void testSimpleSearch_AllIndicesWildcard_AccessCodes_1337() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - SearchResponse searchResponse = executeSearch("*", "tlq_1337", "password"); - - // assume hits from 2 indices: - // - tlqdocuments, must contain only docs with access code 1337 - // - tlqdummy, contains all documents - // no access to user_access_codes must be granted - - // check all 5 tlqdummy entries present, index is not protected by DLS - Set tlqdummyHits = Arrays.asList(searchResponse.getHits().getHits()).stream() - .filter((h) -> h.getIndex().equals("tlqdummy")).collect(Collectors.toSet()); - Assert.assertEquals(searchResponse.toString(), 5, tlqdummyHits.size()); - - // check 10 hits with code 1337 from tlqdocuments index. All other documents - // must be filtered - Set tlqdocumentHits = Arrays.asList(searchResponse.getHits().getHits()).stream() - .filter((h) -> h.getIndex().equals("tlqdocuments")).collect(Collectors.toSet()); - Assert.assertEquals(searchResponse.toString(), 10, tlqdocumentHits.size()); - assertAccessCodesMatch(tlqdocumentHits, new Integer[] { 1337 }); - - // check no access to user_access_codes index - Set userAccessCodesHits = Arrays.asList(searchResponse.getHits().getHits()).stream() - .filter((h) -> h.getIndex().equals("user_access_codes")).collect(Collectors.toSet()); - Assert.assertEquals(searchResponse.toString(), 0, userAccessCodesHits.size()); - } - - @Test - public void testSimpleSearch_ThreeIndicesWildcard_AccessCodes_1337() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - SearchResponse searchResponse = executeSearch("tlq*,user*", "tlq_1337", "password"); - - // assume hits from 2 indices: - // - tlqdocuments, must contain only docs with access code 1337 - // - tlqdummy, contains all documents - // no access to user_access_codes must be granted - - // check all 5 tlqdummy entries present, index is not protected by DLS - Set tlqdummyHits = Arrays.asList(searchResponse.getHits().getHits()).stream() - .filter((h) -> h.getIndex().equals("tlqdummy")).collect(Collectors.toSet()); - Assert.assertEquals(searchResponse.toString(), 5, tlqdummyHits.size()); - - // check 10 hits with code 1337 from tlqdocuments index. All other documents - // must be filtered - Set tlqdocumentHits = Arrays.asList(searchResponse.getHits().getHits()).stream() - .filter((h) -> h.getIndex().equals("tlqdocuments")).collect(Collectors.toSet()); - Assert.assertEquals(searchResponse.toString(), 10, tlqdocumentHits.size()); - assertAccessCodesMatch(tlqdocumentHits, new Integer[] { 1337 }); - - // check no access to user_access_codes index - Set userAccessCodesHits = Arrays.asList(searchResponse.getHits().getHits()).stream() - .filter((h) -> h.getIndex().equals("user_access_codes")).collect(Collectors.toSet()); - Assert.assertEquals(searchResponse.toString(), 0, userAccessCodesHits.size()); - - } - - @Test - public void testSimpleSearch_TwoIndicesConcreteNames_AccessCodes_1337() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - SearchResponse searchResponse = executeSearch("tlqdocuments,tlqdummy", "tlq_1337", "password"); - - // assume hits from 2 indices: - // - tlqdocuments, must contains only 10 docs with access code 1337 - // - tlqdummy, must contains all 5 documents - - // check all 5 tlqdummy entries present, index is not protected by DLS - Set tlqdummyHits = Arrays.asList(searchResponse.getHits().getHits()).stream() - .filter((h) -> h.getIndex().equals("tlqdummy")).collect(Collectors.toSet()); - Assert.assertEquals(searchResponse.toString(), 5, tlqdummyHits.size()); - - // ccheck 10 hits with code 1337 from tlqdocuments index. All other documents - // must be filtered - Set tlqdocumentHits = Arrays.asList(searchResponse.getHits().getHits()).stream() - .filter((h) -> h.getIndex().equals("tlqdocuments")).collect(Collectors.toSet()); - Assert.assertEquals(searchResponse.toString(), 10, tlqdocumentHits.size()); - assertAccessCodesMatch(tlqdocumentHits, new Integer[] { 1337 }); - } - - @Test - public void testMSearch_ThreeIndices_AccessCodes_1337() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - MultiSearchResponse searchResponse = executeMSearchMatchAll("tlq_1337", "password", "tlqdummy", "tlqdocuments", - "user_access_codes"); - - Item[] responseItems = searchResponse.getResponses(); - - // as per API order in response is the same as in the msearch request - - // check all 5 tlqdummy entries present - List tlqdummyHits = Arrays.asList(responseItems[0].getResponse().getHits().getHits()); - Assert.assertEquals(searchResponse.toString(), 5, tlqdummyHits.size()); - - // check 10 hits with code 1337 from tlqdocuments index. All other documents - // must be filtered - List tlqdocumentHits = Arrays.asList(responseItems[1].getResponse().getHits().getHits()); - Assert.assertEquals(searchResponse.toString(), 10, tlqdocumentHits.size()); - assertAccessCodesMatch(tlqdocumentHits, new Integer[] { 1337 }); - - // check no access to user_access_codes index, just two indices in the response - Assert.assertTrue(responseItems[2].getResponse() == null); - Assert.assertTrue(responseItems[2].getFailure() != null); - - } - - // ------------------------ - // Test get and mget - // ------------------------ - - @Test - public void testGet_TlqDocumentsIndex_1337() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - // user has 1337, document has 1337 - GetResponse searchResponse = executeGet("tlqdocuments", "1", "tlq_1337", "password"); - Assert.assertTrue(searchResponse != null); - Assert.assertTrue(searchResponse.isExists()); - assertAccessCodesMatch(searchResponse.getSourceAsMap(), "access_codes", new Integer[] { 1337 }); - - // user has 1337, document has 42, not visible - searchResponse = executeGet("tlqdocuments", "2", "tlq_1337", "password"); - Assert.assertFalse(searchResponse.isExists()); - - // user has 1337, document has 42 and 1337 - searchResponse = executeGet("tlqdocuments", "3", "tlq_1337", "password"); - Assert.assertTrue(searchResponse != null); - Assert.assertTrue(searchResponse.isExists()); - assertAccessCodesMatch(searchResponse.getSourceAsMap(), "access_codes", new Integer[] { 1337 }); - - // user has 1337, document has no access codes, not visible - searchResponse = executeGet("tlqdocuments", "16", "tlq_1337", "password"); - Assert.assertFalse(searchResponse.isExists()); - - // user has 1337, document has 12345, not visible - searchResponse = executeGet("tlqdocuments", "17", "tlq_1337", "password"); - Assert.assertFalse(searchResponse.isExists()); - - // user has 1337, document has 12345 and 6789, not visible - searchResponse = executeGet("tlqdocuments", "18", "tlq_1337", "password"); - Assert.assertFalse(searchResponse.isExists()); - - } - - @Test - public void testGet_TlqDocumentsIndex_1337_42() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - // user has 1337 and 42, document has 1337 - GetResponse searchResponse = executeGet("tlqdocuments", "1", "tlq_1337_42", "password"); - Assert.assertTrue(searchResponse != null); - Assert.assertTrue(searchResponse.isExists()); - assertAccessCodesMatch(searchResponse.getSourceAsMap(), "access_codes", new Integer[] { 1337, 42 }); - - // user has 1337 and 42, document has 42 - searchResponse = executeGet("tlqdocuments", "2", "tlq_1337_42", "password"); - Assert.assertTrue(searchResponse != null); - Assert.assertTrue(searchResponse.isExists()); - assertAccessCodesMatch(searchResponse.getSourceAsMap(), "access_codes", new Integer[] { 1337, 42 }); - - // user has 1337 and 42, document has 42 and 1337 - searchResponse = executeGet("tlqdocuments", "3", "tlq_1337_42", "password"); - Assert.assertTrue(searchResponse != null); - Assert.assertTrue(searchResponse.isExists()); - assertAccessCodesMatch(searchResponse.getSourceAsMap(), "access_codes", new Integer[] { 1337, 42 }); - - // user has 1337 and 42, document has no access codes, not visible - searchResponse = executeGet("tlqdocuments", "16", "tlq_1337_42", "password"); - Assert.assertFalse(searchResponse.isExists()); - - // user has 1337 and 42, document has 12345, not visible - searchResponse = executeGet("tlqdocuments", "17", "tlq_1337_42", "password"); - Assert.assertFalse(searchResponse.isExists()); - - // user has 1337 and 42, document has 12345 and 6789, not visible - searchResponse = executeGet("tlqdocuments", "18", "tlq_1337_42", "password"); - Assert.assertFalse(searchResponse.isExists()); - - } - - @Test - public void testGet_TlqDummyIndex_1337() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - // no restrictions on this index - GetResponse searchResponse = executeGet("tlqdummy", "101", "tlq_1337", "password"); - Assert.assertTrue(searchResponse != null); - Assert.assertTrue(searchResponse.isExists()); - - searchResponse = executeGet("tlqdummy", "102", "tlq_1337", "password"); - Assert.assertTrue(searchResponse != null); - Assert.assertTrue(searchResponse.isExists()); - - } - - @Test - public void testGet_UserAccessCodesIndex_1337() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - // we expect a security exception here, user has no direct access to - // user_access_codes index - HttpResponse response = rh.executeGetRequest("/user_access_codes/_doc/tlq_1337", - encodeBasicHeader("tlq_1337", "password")); - Assert.assertEquals(403, response.getStatusCode()); - } - - @Test - public void testMGet_1337() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - Map indicesAndIds = new HashMap<>(); - indicesAndIds.put("tlqdocuments", "1"); - indicesAndIds.put("tlqdocuments", "2"); - indicesAndIds.put("tlqdocuments", "3"); - indicesAndIds.put("tlqdocuments", "16"); - indicesAndIds.put("tlqdocuments", "17"); - indicesAndIds.put("tlqdocuments", "18"); - indicesAndIds.put("tlqdummy", "101"); - indicesAndIds.put("user_access_codes", "tlq_1337"); - - MultiGetResponse searchResponse = executeMGet("tlq_1337", "password", indicesAndIds); - - for (MultiGetItemResponse response : searchResponse.getResponses()) { - // no response from index "user_access_codes" - Assert.assertFalse(response.getIndex().equals("user_access_codes")); - switch (response.getIndex()) { - case "tlqdocuments": - Assert.assertTrue(response.getId(), response.getId().equals("1") | response.getId().equals("3")); - break; - case "tlqdummy": - Assert.assertTrue(response.getId(), response.getId().equals("101")); - break; - default: - Assert.fail("Index " + response.getIndex() + " present in mget response, but should not"); - } - } - } - -// ------------------------ -// Test aggregations -// ------------------------ - - @Test - public void testSimpleAggregation_tlqdocuments_AccessCode_1337() throws Exception { - - setup(new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") - .setSecurityInternalUsers("internal_users_tlq.yml").setSecurityRoles("roles_tlq.yml") - .setSecurityRolesMapping("roles_mapping_tlq.yml")); - - String body = "" - + " {\n" - + " \"aggs\": {\n" - + " \"buaggregation\": {\n" - + " \"terms\": {\n" - + " \"field\": \"bu\"\n" - + " }\n" - + " }\n" - + " }\n" - + " }\n" - + ""; - - // need to add typed_keys so aggregations can be parsed - // see for example: - // https://stackoverflow.com/questions/49798654/how-do-you-convert-an-elasticsearch-json-string-response-with-an-aggregation-t - HttpResponse response = rh.executePostRequest("/tlqdocuments/_search?pretty&typed_keys", body, - encodeBasicHeader("tlq_1337", "password")); - Assert.assertTrue(response.getStatusCode() == 200); - NamedXContentRegistry registry = new NamedXContentRegistry(getDefaultNamedXContents()); - XContentParser xcp = XContentType.JSON.xContent().createParser(registry, LoggingDeprecationHandler.INSTANCE, - response.getBody()); - SearchResponse searchResponse = SearchResponse.fromXContent(xcp); - - Aggregations aggs = searchResponse.getAggregations(); - Assert.assertNotNull(searchResponse.toString(), aggs); - Terms agg = aggs.get("buaggregation"); - Assert.assertTrue("Expected aggregation with name 'buaggregation'", agg != null); - // expect AAA - EEE (FFF does not match) with 2 docs each - for (String bucketName : new String[] { "AAA", "BBB", "CCC", "DDD", "EEE" }) { - Bucket bucket = agg.getBucketByKey(bucketName); - Assert.assertNotNull("Expected bucket " + bucketName + " to be present in agregations", bucket); - Assert.assertTrue("Expected doc count in bucket " + bucketName + " to be 2", bucket.getDocCount() == 2); - } - // expect FFF to be absent - Assert.assertNull("Expected bucket FFF to be absent", agg.getBucketByKey("FFF")); - } - - - public static List getDefaultNamedXContents() { - Map> map = new HashMap<>(); - map.put(TopHitsAggregationBuilder.NAME, (p, c) -> ParsedTopHits.fromXContent(p, (String) c)); - map.put(StringTerms.NAME, (p, c) -> ParsedStringTerms.fromXContent(p, (String) c)); - List entries = map.entrySet().stream() - .map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue())) - .collect(Collectors.toList()); - return entries; - } + protected void populateData(Client client) { + // user access codes, basis for TLQ query + client.index( + new IndexRequest("user_access_codes").id("tlq_1337") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"access_codes\": [1337] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("user_access_codes").id("tlq_42") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"access_codes\": [42] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("user_access_codes").id("tlq_1337_42") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"access_codes\": [1337, 42] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("user_access_codes").id("tlq_999") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"access_codes\": [999] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("user_access_codes").id("tlq_empty_access_codes") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"access_codes\": [] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("user_access_codes").id("tlq_no_codes") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bla\": \"blub\" }", XContentType.JSON) + ).actionGet(); + + // need to have keyword for bu field since we're testing aggregations + client.admin().indices().create(new CreateIndexRequest("tlqdocuments")).actionGet(); + client.admin().indices().putMapping(new PutMappingRequest("tlqdocuments").source("bu", "type=keyword")).actionGet(); + + // tlqdocuments, protected by TLQ + client.index( + new IndexRequest("tlqdocuments").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"AAA\", \"access_codes\": [1337] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("2") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"AAA\", \"access_codes\": [42] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("3") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"AAA\", \"access_codes\": [1337, 42] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("4") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"BBB\", \"access_codes\": [1337] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("5") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"BBB\", \"access_codes\": [42] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("6") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"BBB\", \"access_codes\": [1337, 42] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("7") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"CCC\", \"access_codes\": [1337] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("8") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"CCC\", \"access_codes\": [42] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("9") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"CCC\", \"access_codes\": [1337, 42] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("10") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"DDD\", \"access_codes\": [1337] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("11") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"DDD\", \"access_codes\": [42] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("12") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"DDD\", \"access_codes\": [1337, 42] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("13") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"EEE\", \"access_codes\": [1337] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("14") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"EEE\", \"access_codes\": [42] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("15") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"EEE\", \"access_codes\": [1337, 42] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("16") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"FFF\" }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("17") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"FFF\", \"access_codes\": [12345] }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdocuments").id("18") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"bu\": \"FFF\", \"access_codes\": [12345, 6789] }", XContentType.JSON) + ).actionGet(); + + // we use a "bu" field here as well to test aggregations over multiple indices + client.admin().indices().create(new CreateIndexRequest("tlqdummy")).actionGet(); + client.admin().indices().putMapping(new PutMappingRequest("tlqdummy").source("bu", "type=keyword")).actionGet(); + + // tlqdummy, not protected by TLQ + client.index( + new IndexRequest("tlqdummy").id("101") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"mykey\": \"101\", \"bu\": \"GGG\" }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdummy").id("102") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"mykey\": \"102\", \"bu\": \"GGG\" }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdummy").id("103") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"mykey\": \"103\", \"bu\": \"GGG\" }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdummy").id("104") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"mykey\": \"104\", \"bu\": \"GGG\" }", XContentType.JSON) + ).actionGet(); + client.index( + new IndexRequest("tlqdummy").id("105") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{ \"mykey\": \"105\", \"bu\": \"GGG\" }", XContentType.JSON) + ).actionGet(); + + } + + // ------------------------ + // Test search and msearch + // ------------------------ + + @Test + public void testSimpleSearch_AccessCode_1337() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + HttpResponse response = rh.executeGetRequest("/tlqdocuments/_search?pretty", encodeBasicHeader("tlq_1337", "password")); + Assert.assertEquals(200, response.getStatusCode()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + SearchResponse searchResponse = SearchResponse.fromXContent(xcp); + // 10 docs, all need to have access code 1337 + Assert.assertEquals(searchResponse.toString(), 10, searchResponse.getHits().getTotalHits().value); + // fields need to have 1337 access code + assertAccessCodesMatch(searchResponse.getHits().getHits(), new Integer[] { 1337 }); + } + + @Test + public void testSimpleSearch_AccessCode_42() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + HttpResponse response = rh.executeGetRequest("/tlqdocuments/_search?pretty", encodeBasicHeader("tlq_42", "password")); + Assert.assertEquals(200, response.getStatusCode()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + SearchResponse searchResponse = SearchResponse.fromXContent(xcp); + + // 10 docs, all need to have access code 42 + Assert.assertEquals(searchResponse.toString(), 10, searchResponse.getHits().getTotalHits().value); + // fields need to have 42 access code + assertAccessCodesMatch(searchResponse.getHits().getHits(), new Integer[] { 42 }); + + } + + @Test + public void testSimpleSearch_AccessCodes_1337_42() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + HttpResponse response = rh.executeGetRequest("/tlqdocuments/_search?pretty", encodeBasicHeader("tlq_1337_42", "password")); + Assert.assertEquals(200, response.getStatusCode()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + SearchResponse searchResponse = SearchResponse.fromXContent(xcp); + + // 15 docs, all need to have either access code 1337 or 42 + Assert.assertEquals(searchResponse.toString(), 15, searchResponse.getHits().getTotalHits().value); + // fields need to have 42 or 1337 access code + assertAccessCodesMatch(searchResponse.getHits().getHits(), new Integer[] { 42, 1337 }); + + } + + @Test + public void testSimpleSearch_AccessCodes_999() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + HttpResponse response = rh.executeGetRequest("/tlqdocuments/_search?pretty", encodeBasicHeader("tlq_999", "password")); + Assert.assertEquals(200, response.getStatusCode()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + SearchResponse searchResponse = SearchResponse.fromXContent(xcp); + + Assert.assertEquals(searchResponse.toString(), 0, searchResponse.getHits().getTotalHits().value); + } + + @Test + public void testSimpleSearch_AccessCodes_emptyAccessCodes() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + SearchResponse searchResponse = executeSearch("tlqdocuments", "tlq_empty_access_codes", "password"); + Assert.assertEquals(searchResponse.toString(), 0, searchResponse.getHits().getTotalHits().value); + } + + @Test + public void testSimpleSearch_AccessCodes_noAccessCodes() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + SearchResponse searchResponse = executeSearch("tlqdocuments", "tlq_no_codes", "password"); + + Assert.assertEquals(searchResponse.toString(), 0, searchResponse.getHits().getTotalHits().value); + } + + @Test + public void testSimpleSearch_AllIndices_All_AccessCodes_1337() throws Exception { + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + SearchResponse searchResponse = executeSearch("_all", "tlq_1337", "password"); + + // assume hits from 2 indices: + // - tlqdocuments, must contain only docs with access code 1337 + // - tlqdummy, contains all documents + // no access to user_access_codes must be granted + + // check all 5 tlqdummy entries present, index is not protected by DLS + Set tlqdummyHits = Arrays.asList(searchResponse.getHits().getHits()) + .stream() + .filter((h) -> h.getIndex().equals("tlqdummy")) + .collect(Collectors.toSet()); + Assert.assertEquals(searchResponse.toString(), 5, tlqdummyHits.size()); + + // check 10 hits with code 1337 from tlqdocuments index. All other documents + // must be filtered + Set tlqdocumentHits = Arrays.asList(searchResponse.getHits().getHits()) + .stream() + .filter((h) -> h.getIndex().equals("tlqdocuments")) + .collect(Collectors.toSet()); + Assert.assertEquals(searchResponse.toString(), 10, tlqdocumentHits.size()); + assertAccessCodesMatch(tlqdocumentHits, new Integer[] { 1337 }); + + // check no access to user_access_codes index + Set userAccessCodesHits = Arrays.asList(searchResponse.getHits().getHits()) + .stream() + .filter((h) -> h.getIndex().equals("user_access_codes")) + .collect(Collectors.toSet()); + Assert.assertEquals(searchResponse.toString(), 0, userAccessCodesHits.size()); + } + + @Test + public void testSimpleSearch_AllIndicesWildcard_AccessCodes_1337() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + SearchResponse searchResponse = executeSearch("*", "tlq_1337", "password"); + + // assume hits from 2 indices: + // - tlqdocuments, must contain only docs with access code 1337 + // - tlqdummy, contains all documents + // no access to user_access_codes must be granted + + // check all 5 tlqdummy entries present, index is not protected by DLS + Set tlqdummyHits = Arrays.asList(searchResponse.getHits().getHits()) + .stream() + .filter((h) -> h.getIndex().equals("tlqdummy")) + .collect(Collectors.toSet()); + Assert.assertEquals(searchResponse.toString(), 5, tlqdummyHits.size()); + + // check 10 hits with code 1337 from tlqdocuments index. All other documents + // must be filtered + Set tlqdocumentHits = Arrays.asList(searchResponse.getHits().getHits()) + .stream() + .filter((h) -> h.getIndex().equals("tlqdocuments")) + .collect(Collectors.toSet()); + Assert.assertEquals(searchResponse.toString(), 10, tlqdocumentHits.size()); + assertAccessCodesMatch(tlqdocumentHits, new Integer[] { 1337 }); + + // check no access to user_access_codes index + Set userAccessCodesHits = Arrays.asList(searchResponse.getHits().getHits()) + .stream() + .filter((h) -> h.getIndex().equals("user_access_codes")) + .collect(Collectors.toSet()); + Assert.assertEquals(searchResponse.toString(), 0, userAccessCodesHits.size()); + } + + @Test + public void testSimpleSearch_ThreeIndicesWildcard_AccessCodes_1337() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + SearchResponse searchResponse = executeSearch("tlq*,user*", "tlq_1337", "password"); + + // assume hits from 2 indices: + // - tlqdocuments, must contain only docs with access code 1337 + // - tlqdummy, contains all documents + // no access to user_access_codes must be granted + + // check all 5 tlqdummy entries present, index is not protected by DLS + Set tlqdummyHits = Arrays.asList(searchResponse.getHits().getHits()) + .stream() + .filter((h) -> h.getIndex().equals("tlqdummy")) + .collect(Collectors.toSet()); + Assert.assertEquals(searchResponse.toString(), 5, tlqdummyHits.size()); + + // check 10 hits with code 1337 from tlqdocuments index. All other documents + // must be filtered + Set tlqdocumentHits = Arrays.asList(searchResponse.getHits().getHits()) + .stream() + .filter((h) -> h.getIndex().equals("tlqdocuments")) + .collect(Collectors.toSet()); + Assert.assertEquals(searchResponse.toString(), 10, tlqdocumentHits.size()); + assertAccessCodesMatch(tlqdocumentHits, new Integer[] { 1337 }); + + // check no access to user_access_codes index + Set userAccessCodesHits = Arrays.asList(searchResponse.getHits().getHits()) + .stream() + .filter((h) -> h.getIndex().equals("user_access_codes")) + .collect(Collectors.toSet()); + Assert.assertEquals(searchResponse.toString(), 0, userAccessCodesHits.size()); + + } + + @Test + public void testSimpleSearch_TwoIndicesConcreteNames_AccessCodes_1337() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + SearchResponse searchResponse = executeSearch("tlqdocuments,tlqdummy", "tlq_1337", "password"); + + // assume hits from 2 indices: + // - tlqdocuments, must contains only 10 docs with access code 1337 + // - tlqdummy, must contains all 5 documents + + // check all 5 tlqdummy entries present, index is not protected by DLS + Set tlqdummyHits = Arrays.asList(searchResponse.getHits().getHits()) + .stream() + .filter((h) -> h.getIndex().equals("tlqdummy")) + .collect(Collectors.toSet()); + Assert.assertEquals(searchResponse.toString(), 5, tlqdummyHits.size()); + + // ccheck 10 hits with code 1337 from tlqdocuments index. All other documents + // must be filtered + Set tlqdocumentHits = Arrays.asList(searchResponse.getHits().getHits()) + .stream() + .filter((h) -> h.getIndex().equals("tlqdocuments")) + .collect(Collectors.toSet()); + Assert.assertEquals(searchResponse.toString(), 10, tlqdocumentHits.size()); + assertAccessCodesMatch(tlqdocumentHits, new Integer[] { 1337 }); + } + + @Test + public void testMSearch_ThreeIndices_AccessCodes_1337() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + MultiSearchResponse searchResponse = executeMSearchMatchAll( + "tlq_1337", + "password", + "tlqdummy", + "tlqdocuments", + "user_access_codes" + ); + + Item[] responseItems = searchResponse.getResponses(); + + // as per API order in response is the same as in the msearch request + + // check all 5 tlqdummy entries present + List tlqdummyHits = Arrays.asList(responseItems[0].getResponse().getHits().getHits()); + Assert.assertEquals(searchResponse.toString(), 5, tlqdummyHits.size()); + + // check 10 hits with code 1337 from tlqdocuments index. All other documents + // must be filtered + List tlqdocumentHits = Arrays.asList(responseItems[1].getResponse().getHits().getHits()); + Assert.assertEquals(searchResponse.toString(), 10, tlqdocumentHits.size()); + assertAccessCodesMatch(tlqdocumentHits, new Integer[] { 1337 }); + + // check no access to user_access_codes index, just two indices in the response + Assert.assertTrue(responseItems[2].getResponse() == null); + Assert.assertTrue(responseItems[2].getFailure() != null); + + } + + // ------------------------ + // Test get and mget + // ------------------------ + + @Test + public void testGet_TlqDocumentsIndex_1337() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + // user has 1337, document has 1337 + GetResponse searchResponse = executeGet("tlqdocuments", "1", "tlq_1337", "password"); + Assert.assertTrue(searchResponse != null); + Assert.assertTrue(searchResponse.isExists()); + assertAccessCodesMatch(searchResponse.getSourceAsMap(), "access_codes", new Integer[] { 1337 }); + + // user has 1337, document has 42, not visible + searchResponse = executeGet("tlqdocuments", "2", "tlq_1337", "password"); + Assert.assertFalse(searchResponse.isExists()); + + // user has 1337, document has 42 and 1337 + searchResponse = executeGet("tlqdocuments", "3", "tlq_1337", "password"); + Assert.assertTrue(searchResponse != null); + Assert.assertTrue(searchResponse.isExists()); + assertAccessCodesMatch(searchResponse.getSourceAsMap(), "access_codes", new Integer[] { 1337 }); + + // user has 1337, document has no access codes, not visible + searchResponse = executeGet("tlqdocuments", "16", "tlq_1337", "password"); + Assert.assertFalse(searchResponse.isExists()); + + // user has 1337, document has 12345, not visible + searchResponse = executeGet("tlqdocuments", "17", "tlq_1337", "password"); + Assert.assertFalse(searchResponse.isExists()); + + // user has 1337, document has 12345 and 6789, not visible + searchResponse = executeGet("tlqdocuments", "18", "tlq_1337", "password"); + Assert.assertFalse(searchResponse.isExists()); + + } + + @Test + public void testGet_TlqDocumentsIndex_1337_42() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + // user has 1337 and 42, document has 1337 + GetResponse searchResponse = executeGet("tlqdocuments", "1", "tlq_1337_42", "password"); + Assert.assertTrue(searchResponse != null); + Assert.assertTrue(searchResponse.isExists()); + assertAccessCodesMatch(searchResponse.getSourceAsMap(), "access_codes", new Integer[] { 1337, 42 }); + + // user has 1337 and 42, document has 42 + searchResponse = executeGet("tlqdocuments", "2", "tlq_1337_42", "password"); + Assert.assertTrue(searchResponse != null); + Assert.assertTrue(searchResponse.isExists()); + assertAccessCodesMatch(searchResponse.getSourceAsMap(), "access_codes", new Integer[] { 1337, 42 }); + + // user has 1337 and 42, document has 42 and 1337 + searchResponse = executeGet("tlqdocuments", "3", "tlq_1337_42", "password"); + Assert.assertTrue(searchResponse != null); + Assert.assertTrue(searchResponse.isExists()); + assertAccessCodesMatch(searchResponse.getSourceAsMap(), "access_codes", new Integer[] { 1337, 42 }); + + // user has 1337 and 42, document has no access codes, not visible + searchResponse = executeGet("tlqdocuments", "16", "tlq_1337_42", "password"); + Assert.assertFalse(searchResponse.isExists()); + + // user has 1337 and 42, document has 12345, not visible + searchResponse = executeGet("tlqdocuments", "17", "tlq_1337_42", "password"); + Assert.assertFalse(searchResponse.isExists()); + + // user has 1337 and 42, document has 12345 and 6789, not visible + searchResponse = executeGet("tlqdocuments", "18", "tlq_1337_42", "password"); + Assert.assertFalse(searchResponse.isExists()); + + } + + @Test + public void testGet_TlqDummyIndex_1337() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + // no restrictions on this index + GetResponse searchResponse = executeGet("tlqdummy", "101", "tlq_1337", "password"); + Assert.assertTrue(searchResponse != null); + Assert.assertTrue(searchResponse.isExists()); + + searchResponse = executeGet("tlqdummy", "102", "tlq_1337", "password"); + Assert.assertTrue(searchResponse != null); + Assert.assertTrue(searchResponse.isExists()); + + } + + @Test + public void testGet_UserAccessCodesIndex_1337() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + // we expect a security exception here, user has no direct access to + // user_access_codes index + HttpResponse response = rh.executeGetRequest("/user_access_codes/_doc/tlq_1337", encodeBasicHeader("tlq_1337", "password")); + Assert.assertEquals(403, response.getStatusCode()); + } + + @Test + public void testMGet_1337() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + Map indicesAndIds = new HashMap<>(); + indicesAndIds.put("tlqdocuments", "1"); + indicesAndIds.put("tlqdocuments", "2"); + indicesAndIds.put("tlqdocuments", "3"); + indicesAndIds.put("tlqdocuments", "16"); + indicesAndIds.put("tlqdocuments", "17"); + indicesAndIds.put("tlqdocuments", "18"); + indicesAndIds.put("tlqdummy", "101"); + indicesAndIds.put("user_access_codes", "tlq_1337"); + + MultiGetResponse searchResponse = executeMGet("tlq_1337", "password", indicesAndIds); + + for (MultiGetItemResponse response : searchResponse.getResponses()) { + // no response from index "user_access_codes" + Assert.assertFalse(response.getIndex().equals("user_access_codes")); + switch (response.getIndex()) { + case "tlqdocuments": + Assert.assertTrue(response.getId(), response.getId().equals("1") | response.getId().equals("3")); + break; + case "tlqdummy": + Assert.assertTrue(response.getId(), response.getId().equals("101")); + break; + default: + Assert.fail("Index " + response.getIndex() + " present in mget response, but should not"); + } + } + } + + // ------------------------ + // Test aggregations + // ------------------------ + + @Test + public void testSimpleAggregation_tlqdocuments_AccessCode_1337() throws Exception { + + setup( + new DynamicSecurityConfig().setConfig("securityconfig_tlq.yml") + .setSecurityInternalUsers("internal_users_tlq.yml") + .setSecurityRoles("roles_tlq.yml") + .setSecurityRolesMapping("roles_mapping_tlq.yml") + ); + + String body = "" + + " {\n" + + " \"aggs\": {\n" + + " \"buaggregation\": {\n" + + " \"terms\": {\n" + + " \"field\": \"bu\"\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + ""; + + // need to add typed_keys so aggregations can be parsed + // see for example: + // https://stackoverflow.com/questions/49798654/how-do-you-convert-an-elasticsearch-json-string-response-with-an-aggregation-t + HttpResponse response = rh.executePostRequest( + "/tlqdocuments/_search?pretty&typed_keys", + body, + encodeBasicHeader("tlq_1337", "password") + ); + Assert.assertTrue(response.getStatusCode() == 200); + NamedXContentRegistry registry = new NamedXContentRegistry(getDefaultNamedXContents()); + XContentParser xcp = XContentType.JSON.xContent().createParser(registry, LoggingDeprecationHandler.INSTANCE, response.getBody()); + SearchResponse searchResponse = SearchResponse.fromXContent(xcp); + + Aggregations aggs = searchResponse.getAggregations(); + Assert.assertNotNull(searchResponse.toString(), aggs); + Terms agg = aggs.get("buaggregation"); + Assert.assertTrue("Expected aggregation with name 'buaggregation'", agg != null); + // expect AAA - EEE (FFF does not match) with 2 docs each + for (String bucketName : new String[] { "AAA", "BBB", "CCC", "DDD", "EEE" }) { + Bucket bucket = agg.getBucketByKey(bucketName); + Assert.assertNotNull("Expected bucket " + bucketName + " to be present in agregations", bucket); + Assert.assertTrue("Expected doc count in bucket " + bucketName + " to be 2", bucket.getDocCount() == 2); + } + // expect FFF to be absent + Assert.assertNull("Expected bucket FFF to be absent", agg.getBucketByKey("FFF")); + } + + public static List getDefaultNamedXContents() { + Map> map = new HashMap<>(); + map.put(TopHitsAggregationBuilder.NAME, (p, c) -> ParsedTopHits.fromXContent(p, (String) c)); + map.put(StringTerms.NAME, (p, c) -> ParsedStringTerms.fromXContent(p, (String) c)); + List entries = map.entrySet() + .stream() + .map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue())) + .collect(Collectors.toList()); + return entries; + } } 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 5405146263..a9361d275a 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java @@ -24,16 +24,16 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class DlsTest extends AbstractDlsFlsTest{ - +public class DlsTest extends AbstractDlsFlsTest { @Override protected void populateData(Client tc) { - tc.index(new IndexRequest("deals").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"amount\": 10}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("deals").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"amount\": 1500}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("deals").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"amount\": 10}", XContentType.JSON)) + .actionGet(); + tc.index( + new IndexRequest("deals").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{\"amount\": 1500}", XContentType.JSON) + ).actionGet(); try { Thread.sleep(3000); @@ -51,23 +51,28 @@ public void testDlsAggregations() throws Exception { setup(); - - String query = "{"+ - "\"query\" : {"+ - "\"match_all\": {}"+ - "},"+ - "\"aggs\" : {"+ - "\"thesum\" : { \"sum\" : { \"field\" : \"amount\" } }"+ - "}"+ - "}"; + String query = "{" + + "\"query\" : {" + + "\"match_all\": {}" + + "}," + + "\"aggs\" : {" + + "\"thesum\" : { \"sum\" : { \"field\" : \"amount\" } }" + + "}" + + "}"; HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"value\" : 1500.0")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"value\" : 1510.0")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -93,67 +98,88 @@ public void testDls() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=0", encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=0", encodeBasicHeader("dept_manager", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("dept_manager", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); Assert.assertEquals(res.getHeaders().toString(), 2, res.getHeaders().size()); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=0", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=0", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - String query = - "{"+ - "\"query\": {"+ - "\"range\" : {"+ - "\"amount\" : {"+ - "\"gte\" : 8,"+ - "\"lte\" : 20,"+ - "\"boost\" : 3.0"+ - "}"+ - "}"+ - "}"+ - "}"; - + "{" + + "\"query\": {" + + "\"range\" : {" + + "\"amount\" : {" + + "\"gte\" : 8," + + "\"lte\" : 20," + + "\"boost\" : 3.0" + + "}" + + "}" + + "}" + + "}"; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 0,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); query = - "{"+ - "\"query\": {"+ - "\"range\" : {"+ - "\"amount\" : {"+ - "\"gte\" : 100,"+ - "\"lte\" : 2000,"+ - "\"boost\" : 2.0"+ - "}"+ - "}"+ - "}"+ - "}"; - + "{" + + "\"query\": {" + + "\"range\" : {" + + "\"amount\" : {" + + "\"gte\" : 100," + + "\"lte\" : 2000," + + "\"boost\" : 2.0" + + "}" + + "}" + + "}" + + "}"; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?q=amount:10&pretty", encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?q=amount:10&pretty", encodeBasicHeader("dept_manager", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 0,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -166,45 +192,57 @@ public void testDls() throws Exception { res = rh.executeGetRequest("/deals/_doc/1?pretty", encodeBasicHeader("dept_manager", "password")); Assert.assertTrue(res.getBody().contains("\"found\" : true")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_count?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_count?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"count\" : 2,")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_count?pretty", encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_count?pretty", encodeBasicHeader("dept_manager", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"count\" : 1,")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - - //mget - //msearch - String msearchBody = - "{\"index\":\"deals\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"deals\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); - - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + // mget + // msearch + String msearchBody = "{\"index\":\"deals\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"deals\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("dept_manager", "password"))).getStatusCode() + ); Assert.assertFalse(res.getBody().contains("_opendistro_security_dls_query")); Assert.assertFalse(res.getBody().contains("_opendistro_security_fls_fields")); Assert.assertTrue(res.getBody().contains("\"amount\" : 1500")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - - String mgetBody = "{"+ - "\"docs\" : ["+ - "{"+ - "\"_index\" : \"deals\","+ - "\"_id\" : \"1\""+ - " },"+ - " {"+ - "\"_index\" : \"deals\","+ - " \"_id\" : \"2\""+ - "}"+ - "]"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + String mgetBody = "{" + + "\"docs\" : [" + + "{" + + "\"_index\" : \"deals\"," + + "\"_id\" : \"1\"" + + " }," + + " {" + + "\"_index\" : \"deals\"," + + " \"_id\" : \"2\"" + + "}" + + "]" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("dept_manager", "password"))).getStatusCode() + ); Assert.assertFalse(res.getBody().contains("_opendistro_security_dls_query")); Assert.assertFalse(res.getBody().contains("_opendistro_security_fls_fields")); Assert.assertTrue(res.getBody().contains("amount")); @@ -220,26 +258,26 @@ public void testNonDls() throws Exception { HttpResponse res; String query = - "{"+ - "\"_source\": false,"+ - "\"query\": {"+ - "\"range\" : {"+ - "\"amount\" : {"+ - "\"gte\" : 100,"+ - "\"lte\" : 2000,"+ - "\"boost\" : 2.0"+ - "}"+ - "}"+ - "}"+ - "}"; - - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + "{" + + "\"_source\": false," + + "\"query\": {" + + "\"range\" : {" + + "\"amount\" : {" + + "\"gte\" : 100," + + "\"lte\" : 2000," + + "\"boost\" : 2.0" + + "}" + + "}" + + "}" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - - } @Test @@ -248,11 +286,17 @@ public void testDlsCache() throws Exception { setup(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("dept_manager", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("dept_manager", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -271,69 +315,86 @@ public void testDlsWithMinDocCountZeroAggregations() throws Exception { client.admin().indices().create(new CreateIndexRequest("logs").simpleMapping("termX", "type=keyword")).actionGet(); for (int i = 0; i < 3; i++) { - client.index(new IndexRequest("logs").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("amount", i, "termX", "A", "timestamp", "2022-01-06T09:05:00Z")).actionGet(); - client.index(new IndexRequest("logs").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("amount", i, "termX", "B", "timestamp", "2022-01-06T09:08:00Z")).actionGet(); - client.index(new IndexRequest("logs").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("amount", i, "termX", "C", "timestamp", "2022-01-06T09:09:00Z")).actionGet(); - client.index(new IndexRequest("logs").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("amount", i, "termX", "D", "timestamp", "2022-01-06T09:10:00Z")).actionGet(); + client.index( + new IndexRequest("logs").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("amount", i, "termX", "A", "timestamp", "2022-01-06T09:05:00Z") + ).actionGet(); + client.index( + new IndexRequest("logs").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("amount", i, "termX", "B", "timestamp", "2022-01-06T09:08:00Z") + ).actionGet(); + client.index( + new IndexRequest("logs").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("amount", i, "termX", "C", "timestamp", "2022-01-06T09:09:00Z") + ).actionGet(); + client.index( + new IndexRequest("logs").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("amount", i, "termX", "D", "timestamp", "2022-01-06T09:10:00Z") + ).actionGet(); } - client.index(new IndexRequest("logs").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("amount", 0, "termX", "E", "timestamp", "2022-01-06T09:11:00Z")).actionGet(); + client.index( + new IndexRequest("logs").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("amount", 0, "termX", "E", "timestamp", "2022-01-06T09:11:00Z") + ).actionGet(); } // Terms Aggregation - // Non-admin user with setting "min_doc_count":0. Expected to get error message "min_doc_count 0 is not supported when DLS is activated". + // Non-admin user with setting "min_doc_count":0. Expected to get error message "min_doc_count 0 is not supported when DLS is + // activated". String query1 = "{\n" - + " \"size\":0,\n" - + " \"query\":{\n" - + " \"bool\":{\n" - + " \"must\":[\n" - + " {\n" - + " \"range\":{\n" - + " \"amount\":{\"gte\":1,\"lte\":100}\n" - + " }\n" - + " }\n" - + " ]\n" - + " }\n" - + " },\n" - + " \"aggs\":{\n" - + " \"a\": {\n" - + " \"terms\": {\n" - + " \"field\": \"termX\",\n" - + " \"min_doc_count\":0,\n" - + "\"size\": 10,\n" - + "\"order\": { \"_count\": \"desc\" }\n" - + " }\n" - + " }\n" - + " }\n" - + "}"; + + " \"size\":0,\n" + + " \"query\":{\n" + + " \"bool\":{\n" + + " \"must\":[\n" + + " {\n" + + " \"range\":{\n" + + " \"amount\":{\"gte\":1,\"lte\":100}\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"aggs\":{\n" + + " \"a\": {\n" + + " \"terms\": {\n" + + " \"field\": \"termX\",\n" + + " \"min_doc_count\":0,\n" + + "\"size\": 10,\n" + + "\"order\": { \"_count\": \"desc\" }\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; HttpResponse response1 = rh.executePostRequest("logs*/_search", query1, encodeBasicHeader("dept_manager", "password")); Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response1.getStatusCode()); Assert.assertTrue(response1.getBody(), response1.getBody().contains("min_doc_count 0 is not supported when DLS is activated")); - // Non-admin user without setting "min_doc_count". Expected to only have access to buckets for dept_manager excluding E with 0 doc_count". + // Non-admin user without setting "min_doc_count". Expected to only have access to buckets for dept_manager excluding E with 0 + // doc_count". String query2 = "{\n" - + " \"size\":0,\n" - + " \"query\":{\n" - + " \"bool\":{\n" - + " \"must\":[\n" - + " {\n" - + " \"range\":{\n" - + " \"amount\":{\"gte\":1,\"lte\":100}\n" - + " }\n" - + " }\n" - + " ]\n" - + " }\n" - + " },\n" - + " \"aggs\":{\n" - + " \"a\": {\n" - + " \"terms\": {\n" - + " \"field\": \"termX\",\n" - + "\"size\": 10,\n" - + "\"order\": { \"_count\": \"desc\" }\n" - + " }\n" - + " }\n" - + " }\n" - + "}"; + + " \"size\":0,\n" + + " \"query\":{\n" + + " \"bool\":{\n" + + " \"must\":[\n" + + " {\n" + + " \"range\":{\n" + + " \"amount\":{\"gte\":1,\"lte\":100}\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"aggs\":{\n" + + " \"a\": {\n" + + " \"terms\": {\n" + + " \"field\": \"termX\",\n" + + "\"size\": 10,\n" + + "\"order\": { \"_count\": \"desc\" }\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; HttpResponse response2 = rh.executePostRequest("logs*/_search", query2, encodeBasicHeader("dept_manager", "password")); @@ -366,7 +427,8 @@ 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 = "{\"size\":100,\"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()); @@ -443,7 +505,6 @@ public void testDlsWithMinDocCountZeroAggregations() throws Exception { Assert.assertTrue(response11.getBody(), response11.getBody().contains("\"termX\":\"D\"")); Assert.assertTrue(response11.getBody(), response11.getBody().contains("\"termX\":\"E\"")); - // Admin without setting "min_doc_count". Expected to have access to all buckets". HttpResponse response12 = rh.executePostRequest("logs*/_search", query6, encodeBasicHeader("admin", "admin")); @@ -456,7 +517,8 @@ 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 = "{\"size\":100,\"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")); @@ -468,7 +530,8 @@ 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 = "{\"size\":100,\"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/dlic/dlsfls/FieldMaskedTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FieldMaskedTest.java index 40542e76b7..2628bebbc0 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FieldMaskedTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FieldMaskedTest.java @@ -23,21 +23,37 @@ public class FieldMaskedTest extends AbstractDlsFlsTest { - protected void populateData(Client tc) { - tc.index(new IndexRequest("deals").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust1\"}, \"ip_source\": \"100.100.1.1\",\"ip_dest\": \"123.123.1.1\",\"amount\": 10}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("deals").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust2\"}, \"ip_source\": \"100.100.2.2\",\"ip_dest\": \"123.123.2.2\",\"amount\": 20}", XContentType.JSON)).actionGet(); - - - for (int i=0; i<30;i++) { - tc.index(new IndexRequest("deals").id("a"+i).setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust1\"}, \"ip_source\": \"200.100.1.1\",\"ip_dest\": \"123.123.1.1\",\"amount\": 10}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("deals").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"customer\": {\"name\":\"cust1\"}, \"ip_source\": \"100.100.1.1\",\"ip_dest\": \"123.123.1.1\",\"amount\": 10}", + XContentType.JSON + ) + ).actionGet(); + tc.index( + new IndexRequest("deals").id("2") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"customer\": {\"name\":\"cust2\"}, \"ip_source\": \"100.100.2.2\",\"ip_dest\": \"123.123.2.2\",\"amount\": 20}", + XContentType.JSON + ) + ).actionGet(); + + for (int i = 0; i < 30; i++) { + tc.index( + new IndexRequest("deals").id("a" + i) + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"customer\": {\"name\":\"cust1\"}, \"ip_source\": \"200.100.1.1\",\"ip_dest\": \"123.123.1.1\",\"amount\": 10}", + XContentType.JSON + ) + ).actionGet(); } - } + } @Test public void testMaskedAggregations() throws Exception { @@ -46,61 +62,72 @@ public void testMaskedAggregations() throws Exception { String query; HttpResponse res; - query = "{"+ - "\"query\" : {"+ - "\"match_all\": {}"+ - "},"+ - "\"aggs\" : {"+ - "\"ips\" : { \"terms\" : { \"field\" : \"ip_source.keyword\" } }"+ - "}"+ - "}"; - - //Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); - //Assert.assertTrue(res.getBody().contains("100.100")); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked", "password"))).getStatusCode()); + query = "{" + + "\"query\" : {" + + "\"match_all\": {}" + + "}," + + "\"aggs\" : {" + + "\"ips\" : { \"terms\" : { \"field\" : \"ip_source.keyword\" } }" + + "}" + + "}"; + + // Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, + // encodeBasicHeader("admin", "admin"))).getStatusCode()); + // Assert.assertTrue(res.getBody().contains("100.100")); + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked", "password"))) + .getStatusCode() + ); Assert.assertFalse(res.getBody().contains("100.100")); - query = - "{" + - "\"query\" : {" + - "\"match_all\": {" + - "}" + - "}," + - "\"aggs\": {" + - "\"ips\" : {" + - "\"terms\" : {" + - "\"field\" : \"ip_source.keyword\"," + - "\"order\": {" + - "\"_term\" : \"asc\"" + - "}" + - "}" + - "}" + - "}" + - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked", "password"))).getStatusCode()); + query = "{" + + "\"query\" : {" + + "\"match_all\": {" + + "}" + + "}," + + "\"aggs\": {" + + "\"ips\" : {" + + "\"terms\" : {" + + "\"field\" : \"ip_source.keyword\"," + + "\"order\": {" + + "\"_term\" : \"asc\"" + + "}" + + "}" + + "}" + + "}" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked", "password"))) + .getStatusCode() + ); Assert.assertFalse(res.getBody().contains("100.100")); - query = - "{" + - "\"query\" : {" + - "\"match_all\": {" + - "}" + - "}," + - "\"aggs\": {" + - "\"ips\" : {" + - "\"terms\" : {" + - "\"field\" : \"ip_source.keyword\"," + - "\"order\": {" + - "\"_term\" : \"desc\"" + - "}" + - "}" + - "}" + - "}" + - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked", "password"))).getStatusCode()); + query = "{" + + "\"query\" : {" + + "\"match_all\": {" + + "}" + + "}," + + "\"aggs\": {" + + "\"ips\" : {" + + "\"terms\" : {" + + "\"field\" : \"ip_source.keyword\"," + + "\"order\": {" + + "\"_term\" : \"desc\"" + + "}" + + "}" + + "}" + + "}" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked", "password"))) + .getStatusCode() + ); Assert.assertFalse(res.getBody().contains("100.100")); } @@ -109,15 +136,17 @@ public void testMaskedAggregationsRace() throws Exception { setup(); - - String query = "{"+ - "\"aggs\" : {"+ - "\"ips\" : { \"terms\" : { \"field\" : \"ip_source.keyword\", \"size\": 1002, \"show_term_doc_count_error\": true } }"+ - "}"+ - "}"; + String query = "{" + + "\"aggs\" : {" + + "\"ips\" : { \"terms\" : { \"field\" : \"ip_source.keyword\", \"size\": 1002, \"show_term_doc_count_error\": true } }" + + "}" + + "}"; HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("100.100")); Assert.assertTrue(res.getBody().contains("200.100")); Assert.assertTrue(res.getBody().contains("\"doc_count\" : 30")); @@ -126,7 +155,11 @@ public void testMaskedAggregationsRace() throws Exception { Assert.assertFalse(res.getBody().contains("26a8671e57fefc13504f8c61ced67ac98338261ace1e5bf462038b2f2caae16e")); Assert.assertFalse(res.getBody().contains("87873bdb698e5f0f60e0b02b76dad1ec11b2787c628edbc95b7ff0e82274b140")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("user_masked", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"doc_count\" : 30")); Assert.assertTrue(res.getBody().contains("\"doc_count\" : 1")); Assert.assertFalse(res.getBody().contains("100.100")); @@ -135,8 +168,11 @@ public void testMaskedAggregationsRace() throws Exception { Assert.assertTrue(res.getBody().contains("26a8671e57fefc13504f8c61ced67ac98338261ace1e5bf462038b2f2caae16e")); Assert.assertTrue(res.getBody().contains("87873bdb698e5f0f60e0b02b76dad1ec11b2787c628edbc95b7ff0e82274b140")); - for(int i=0;i<10;i++) { - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + for (int i = 0; i < 10; i++) { + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty&size=0", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("100.100")); Assert.assertTrue(res.getBody().contains("200.100")); Assert.assertTrue(res.getBody().contains("\"doc_count\" : 30")); @@ -155,7 +191,10 @@ public void testMaskedSearch() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=100", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=100", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 32,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); Assert.assertTrue(res.getBody().contains("cust1")); @@ -164,8 +203,10 @@ public void testMaskedSearch() throws Exception { Assert.assertTrue(res.getBody().contains("100.100.2.2")); Assert.assertFalse(res.getBody().contains("87873bdb698e5f0f60e0b02b76dad1ec11b2787c628edbc95b7ff0e82274b140")); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=100", encodeBasicHeader("user_masked", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=100", encodeBasicHeader("user_masked", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 32,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); Assert.assertTrue(res.getBody().contains("cust1")); @@ -183,7 +224,10 @@ public void testMaskedGet() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_doc/0?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_doc/0?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"found\" : true")); Assert.assertTrue(res.getBody().contains("cust1")); Assert.assertFalse(res.getBody().contains("cust2")); @@ -191,8 +235,10 @@ public void testMaskedGet() throws Exception { Assert.assertFalse(res.getBody().contains("100.100.2.2")); Assert.assertFalse(res.getBody().contains("87873bdb698e5f0f60e0b02b76dad1ec11b2787c628edbc95b7ff0e82274b140")); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_doc/0?pretty", encodeBasicHeader("user_masked", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_doc/0?pretty", encodeBasicHeader("user_masked", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"found\" : true")); Assert.assertTrue(res.getBody().contains("cust1")); Assert.assertFalse(res.getBody().contains("cust2")); @@ -201,5 +247,4 @@ public void testMaskedGet() throws Exception { Assert.assertTrue(res.getBody().contains("87873bdb698e5f0f60e0b02b76dad1ec11b2787c628edbc95b7ff0e82274b140")); } - } diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/Fls983Test.java b/src/test/java/org/opensearch/security/dlic/dlsfls/Fls983Test.java index 6f00dfd348..61bc9b53b3 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/Fls983Test.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/Fls983Test.java @@ -22,13 +22,11 @@ import org.opensearch.security.test.DynamicSecurityConfig; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class Fls983Test extends AbstractDlsFlsTest{ - +public class Fls983Test extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.index(new IndexRequest(".kibana").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{}", XContentType.JSON)).actionGet(); + tc.index(new IndexRequest(".kibana").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{}", XContentType.JSON)).actionGet(); } @Test @@ -38,11 +36,13 @@ public void test() throws Exception { HttpResponse res; - String doc = "{\"doc\" : {"+ - "\"x\" : \"y\""+ - "}}"; + String doc = "{\"doc\" : {" + "\"x\" : \"y\"" + "}}"; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/.kibana/_update/0?pretty", doc, encodeBasicHeader("human_resources_trainee", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/.kibana/_update/0?pretty", doc, encodeBasicHeader("human_resources_trainee", "password"))) + .getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("updated")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestAB.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestAB.java index 9cc5cc8b3b..aabe2e4add 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestAB.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestAB.java @@ -23,28 +23,48 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class FlsDlsTestAB extends AbstractDlsFlsTest{ - +public class FlsDlsTestAB extends AbstractDlsFlsTest { protected void populateData(Client tc) { - //aaa - tc.index(new IndexRequest("aaa").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"f1\": \"f1_a0\", \"f2\": \"f2_a0\", \"f3\": \"f3_a0\", \"f4\": \"f4_a0\",\"type\": \"a\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("aaa").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"f1\": \"f1_a1\", \"f2\": \"f2_a1\", \"f3\": \"f3_a1\", \"f4\": \"f4_a1\",\"type\": \"a\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("aaa").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"f1\": \"f1_a2\", \"f2\": \"f2_a2\", \"f3\": \"f3_a2\", \"f4\": \"f4_a2\",\"type\": \"x\"}", XContentType.JSON)).actionGet(); - - //bbb - tc.index(new IndexRequest("bbb").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"f1\": \"f1_b0\", \"f2\": \"f2_b0\", \"f3\": \"f3_b0\", \"f4\": \"f4_b0\",\"type\": \"b\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("bbb").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"f1\": \"f1_b1\", \"f2\": \"f2_b1\", \"f3\": \"f3_b1\", \"f4\": \"f4_b1\",\"type\": \"b\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("bbb").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"f1\": \"f1_b2\", \"f2\": \"f2_b2\", \"f3\": \"f3_b2\", \"f4\": \"f4_b2\",\"type\": \"x\"}", XContentType.JSON)).actionGet(); - - tc.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("aaa","bbb").alias("abalias"))).actionGet(); + // aaa + tc.index( + new IndexRequest("aaa").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"f1\": \"f1_a0\", \"f2\": \"f2_a0\", \"f3\": \"f3_a0\", \"f4\": \"f4_a0\",\"type\": \"a\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("aaa").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"f1\": \"f1_a1\", \"f2\": \"f2_a1\", \"f3\": \"f3_a1\", \"f4\": \"f4_a1\",\"type\": \"a\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("aaa").id("2") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"f1\": \"f1_a2\", \"f2\": \"f2_a2\", \"f3\": \"f3_a2\", \"f4\": \"f4_a2\",\"type\": \"x\"}", XContentType.JSON) + ).actionGet(); + + // bbb + tc.index( + new IndexRequest("bbb").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"f1\": \"f1_b0\", \"f2\": \"f2_b0\", \"f3\": \"f3_b0\", \"f4\": \"f4_b0\",\"type\": \"b\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("bbb").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"f1\": \"f1_b1\", \"f2\": \"f2_b1\", \"f3\": \"f3_b1\", \"f4\": \"f4_b1\",\"type\": \"b\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("bbb").id("2") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"f1\": \"f1_b2\", \"f2\": \"f2_b2\", \"f3\": \"f3_b2\", \"f4\": \"f4_b2\",\"type\": \"x\"}", XContentType.JSON) + ).actionGet(); + + tc.admin() + .indices() + .aliases(new IndicesAliasesRequest().addAliasAction(AliasActions.add().indices("aaa", "bbb").alias("abalias"))) + .actionGet(); } @@ -55,7 +75,10 @@ public void testDlsFlsAB() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/aaa,bbb/_search?pretty", encodeBasicHeader("user_aaa", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/aaa,bbb/_search?pretty", encodeBasicHeader("user_aaa", "password"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 4,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -69,8 +92,10 @@ public void testDlsFlsAB() throws Exception { Assert.assertTrue(res.getBody().contains("f3_b")); Assert.assertFalse(res.getBody().contains("f1_b")); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/abalias/_search?pretty", encodeBasicHeader("user_aaa", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/abalias/_search?pretty", encodeBasicHeader("user_aaa", "password"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 4,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -84,7 +109,10 @@ public void testDlsFlsAB() throws Exception { Assert.assertTrue(res.getBody().contains("f3_b")); Assert.assertFalse(res.getBody().contains("f1_b")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/aaa,bbb/_search?pretty", encodeBasicHeader("user_bbb", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/aaa,bbb/_search?pretty", encodeBasicHeader("user_bbb", "password"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 4,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -98,8 +126,10 @@ public void testDlsFlsAB() throws Exception { Assert.assertFalse(res.getBody().contains("f3_b")); Assert.assertTrue(res.getBody().contains("f1_b")); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/abalias/_search?pretty", encodeBasicHeader("user_bbb", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/abalias/_search?pretty", encodeBasicHeader("user_bbb", "password"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 4,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestForbiddenField.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestForbiddenField.java index 6df02c2e22..840f574a9f 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestForbiddenField.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestForbiddenField.java @@ -21,15 +21,23 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class FlsDlsTestForbiddenField extends AbstractDlsFlsTest{ - +public class FlsDlsTestForbiddenField extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.index(new IndexRequest("deals").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust1\"}, \"zip\": \"12345\",\"secret\": \"tellnoone\",\"amount\": 10}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("deals").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust2\", \"ctype\":\"industry\"}, \"amount\": 1500}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("deals").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"customer\": {\"name\":\"cust1\"}, \"zip\": \"12345\",\"secret\": \"tellnoone\",\"amount\": 10}", + XContentType.JSON + ) + ).actionGet(); + tc.index( + new IndexRequest("deals").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"customer\": {\"name\":\"cust2\", \"ctype\":\"industry\"}, \"amount\": 1500}", XContentType.JSON) + ).actionGet(); } @@ -38,23 +46,29 @@ public void testDlsAggregations() throws Exception { setup(); - - String query = "{"+ - "\"query\" : {"+ - "\"match_all\": {}"+ - "},"+ - "\"aggs\" : {"+ - "\"thesum\" : { \"sum\" : { \"field\" : \"amount\" } }"+ - "}"+ - "}"; + String query = "{" + + "\"query\" : {" + + "\"match_all\": {}" + + "}," + + "\"aggs\" : {" + + "\"thesum\" : { \"sum\" : { \"field\" : \"amount\" } }" + + "}" + + "}"; HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager_fls_dls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager_fls_dls", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 0,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"value\" : 0")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"value\" : 1510.0")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -66,58 +80,77 @@ public void testDls() throws Exception { setup(); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=0", encodeBasicHeader("dept_manager_fls_dls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=0", encodeBasicHeader("dept_manager_fls_dls", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 0,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=0", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=0", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - String query = - "{"+ - "\"query\": {"+ - "\"range\" : {"+ - "\"amount\" : {"+ - "\"gte\" : 8,"+ - "\"lte\" : 20,"+ - "\"boost\" : 3.0"+ - "}"+ - "}"+ - "}"+ - "}"; - - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("dept_manager_fls_dls", "password"))).getStatusCode()); + "{" + + "\"query\": {" + + "\"range\" : {" + + "\"amount\" : {" + + "\"gte\" : 8," + + "\"lte\" : 20," + + "\"boost\" : 3.0" + + "}" + + "}" + + "}" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager_fls_dls", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 0,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); query = - "{"+ - "\"query\": {"+ - "\"range\" : {"+ - "\"amount\" : {"+ - "\"gte\" : 100,"+ - "\"lte\" : 2000,"+ - "\"boost\" : 2.0"+ - "}"+ - "}"+ - "}"+ - "}"; - - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("dept_manager_fls_dls", "password"))).getStatusCode()); + "{" + + "\"query\": {" + + "\"range\" : {" + + "\"amount\" : {" + + "\"gte\" : 100," + + "\"lte\" : 2000," + + "\"boost\" : 2.0" + + "}" + + "}" + + "}" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager_fls_dls", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 0,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?q=amount:10&pretty", encodeBasicHeader("dept_manager_fls_dls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?q=amount:10&pretty", encodeBasicHeader("dept_manager_fls_dls", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 0,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -133,11 +166,17 @@ public void testDls() throws Exception { res = rh.executeGetRequest("/deals/_doc/1?pretty", encodeBasicHeader("dept_manager_fls_dls", "password")); Assert.assertTrue(res.getBody().contains("\"found\" : false")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_count?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_count?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"count\" : 2,")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_count?pretty", encodeBasicHeader("dept_manager_fls_dls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_count?pretty", encodeBasicHeader("dept_manager_fls_dls", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"count\" : 0,")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); } @@ -149,7 +188,10 @@ public void testCombined() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("user_combined", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("user_combined", "password"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestMulti.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestMulti.java index b177f1d346..e9d32f18ea 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestMulti.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsDlsTestMulti.java @@ -21,21 +21,33 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class FlsDlsTestMulti extends AbstractDlsFlsTest{ - +public class FlsDlsTestMulti extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.index(new IndexRequest("deals").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust1\"}, \"zip\": \"12345\",\"secret\": \"tellnoone\",\"amount\": 10}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("deals").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust2\", \"ctype\":\"industry\"}, \"amount\": 1500}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("deals").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust3\", \"ctype\":\"industry\"}, \"amount\": 200}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("deals").id("3").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust4\", \"ctype\":\"industry\"}, \"amount\": 20001}", XContentType.JSON)).actionGet(); - - + tc.index( + new IndexRequest("deals").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"customer\": {\"name\":\"cust1\"}, \"zip\": \"12345\",\"secret\": \"tellnoone\",\"amount\": 10}", + XContentType.JSON + ) + ).actionGet(); + tc.index( + new IndexRequest("deals").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"customer\": {\"name\":\"cust2\", \"ctype\":\"industry\"}, \"amount\": 1500}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("deals").id("2") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"customer\": {\"name\":\"cust3\", \"ctype\":\"industry\"}, \"amount\": 200}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("deals").id("3") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"customer\": {\"name\":\"cust4\", \"ctype\":\"industry\"}, \"amount\": 20001}", XContentType.JSON) + ).actionGet(); } @@ -44,29 +56,34 @@ public void testDlsAggregations() throws Exception { setup(); - - String query = "{"+ - "\"query\" : {"+ - "\"match_all\": {}"+ - "},"+ - "\"aggs\" : {"+ - "\"thesum\" : { \"sum\" : { \"field\" : \"amount\" } }"+ - "}"+ - "}"; + String query = "{" + + "\"query\" : {" + + "\"match_all\": {}" + + "}," + + "\"aggs\" : {" + + "\"thesum\" : { \"sum\" : { \"field\" : \"amount\" } }" + + "}" + + "}"; HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager_multi", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 3,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"value\" : 1710.0")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 4,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"value\" : 21711.0")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); } - @Test public void testDlsFls() throws Exception { @@ -74,63 +91,85 @@ public void testDlsFls() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("ctype")); Assert.assertFalse(res.getBody().contains("secret")); Assert.assertTrue(res.getBody().contains("zip")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=0", encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=0", encodeBasicHeader("dept_manager_multi", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 3,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=0", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=0", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 4,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - String query = - "{"+ - "\"query\": {"+ - "\"range\" : {"+ - "\"amount\" : {"+ - "\"gte\" : 8,"+ - "\"lte\" : 20,"+ - "\"boost\" : 3.0"+ - "}"+ - "}"+ - "}"+ - "}"; - - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode()); + "{" + + "\"query\": {" + + "\"range\" : {" + + "\"amount\" : {" + + "\"gte\" : 8," + + "\"lte\" : 20," + + "\"boost\" : 3.0" + + "}" + + "}" + + "}" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager_multi", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); query = - "{"+ - "\"query\": {"+ - "\"range\" : {"+ - "\"amount\" : {"+ - "\"gte\" : 100,"+ - "\"lte\" : 2000,"+ - "\"boost\" : 2.0"+ - "}"+ - "}"+ - "}"+ - "}"; - - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode()); + "{" + + "\"query\": {" + + "\"range\" : {" + + "\"amount\" : {" + + "\"gte\" : 100," + + "\"lte\" : 2000," + + "\"boost\" : 2.0" + + "}" + + "}" + + "}" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager_multi", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query,encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?q=amount:10&pretty", encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?q=amount:10&pretty", encodeBasicHeader("dept_manager_multi", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -143,23 +182,34 @@ public void testDlsFls() throws Exception { res = rh.executeGetRequest("/deals/_doc/1?pretty", encodeBasicHeader("dept_manager_multi", "password")); Assert.assertTrue(res.getBody().contains("\"found\" : true")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_count?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_count?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"count\" : 4,")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_count?pretty", encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_count?pretty", encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"count\" : 3,")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - //mget - //msearch - String msearchBody = - "{\"index\":\"deals\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); - //"{\"index\":\".opendistro_security\", \"type\":\"_doc\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - //"{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode()); + // mget + // msearch + String msearchBody = "{\"index\":\"deals\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); + // "{\"index\":\".opendistro_security\", \"type\":\"_doc\", \"ignore_unavailable\": true}"+System.lineSeparator()+ + // "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("dept_manager_multi", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody(), res.getBody().contains("\"value\" : 3,\n \"relation")); Assert.assertFalse(res.getBody().contains("_opendistro_security_dls_query")); Assert.assertFalse(res.getBody().contains("_opendistro_security_fls_fields")); @@ -169,29 +219,31 @@ public void testDlsFls() throws Exception { Assert.assertTrue(res.getBody().contains("\"amount\" : 20")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); - - String mgetBody = "{"+ - "\"docs\" : ["+ - "{"+ - "\"_index\" : \"deals\","+ - "\"_id\" : \"0\""+ - " },"+ - " {"+ - "\"_index\" : \"deals\","+ - " \"_id\" : \"1\""+ - "},"+ - " {"+ - "\"_index\" : \"deals\","+ - " \"_id\" : \"2\""+ - "},"+ - " {"+ - "\"_index\" : \"deals\","+ - " \"_id\" : \"3\""+ - "}"+ - "]"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode()); + String mgetBody = "{" + + "\"docs\" : [" + + "{" + + "\"_index\" : \"deals\"," + + "\"_id\" : \"0\"" + + " }," + + " {" + + "\"_index\" : \"deals\"," + + " \"_id\" : \"1\"" + + "}," + + " {" + + "\"_index\" : \"deals\"," + + " \"_id\" : \"2\"" + + "}," + + " {" + + "\"_index\" : \"deals\"," + + " \"_id\" : \"3\"" + + "}" + + "]" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode() + ); Assert.assertFalse(res.getBody().contains("_opendistro_security_dls_query")); Assert.assertFalse(res.getBody().contains("_opendistro_security_fls_fields")); Assert.assertTrue(res.getBody().contains("\"amount\" : 1500")); @@ -209,30 +261,37 @@ public void testDlsSuggest() throws Exception { HttpResponse res; String query = - "{"+ - "\"query\": {"+ - "\"range\" : {"+ - "\"amount\" : {"+ - "\"gte\" : 11,"+ - "\"lte\" : 50000,"+ - "\"boost\" : 1.0"+ - "}"+ - "}"+ - "},"+ - "\"suggest\" : {\n" + - " \"thesuggestion\" : {\n" + - " \"text\" : \"cust\",\n" + - " \"term\" : {\n" + - " \"field\" : \"customer.name\"\n" + - " }\n" + - " }\n" + - " }"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + "{" + + "\"query\": {" + + "\"range\" : {" + + "\"amount\" : {" + + "\"gte\" : 11," + + "\"lte\" : 50000," + + "\"boost\" : 1.0" + + "}" + + "}" + + "}," + + "\"suggest\" : {\n" + + " \"thesuggestion\" : {\n" + + " \"text\" : \"cust\",\n" + + " \"term\" : {\n" + + " \"field\" : \"customer.name\"\n" + + " }\n" + + " }\n" + + " }" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("thesuggestion")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager_multi", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("thesuggestion")); } @@ -244,21 +303,28 @@ public void testDlsSuggestOnly() throws Exception { HttpResponse res; String query = - "{"+ - "\"suggest\" : {\n" + - " \"thesuggestion\" : {\n" + - " \"text\" : \"cust\",\n" + - " \"term\" : {\n" + - " \"field\" : \"customer.name\"\n" + - " }\n" + - " }\n" + - " }"+ - "}"; - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + "{" + + "\"suggest\" : {\n" + + " \"thesuggestion\" : {\n" + + " \"text\" : \"cust\",\n" + + " \"term\" : {\n" + + " \"field\" : \"customer.name\"\n" + + " }\n" + + " }\n" + + " }" + + "}"; + + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("thesuggestion")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager_multi", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("dept_manager_multi", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("thesuggestion")); } diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsExistsFieldsTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsExistsFieldsTest.java index 8f056c8244..32cc44efc0 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsExistsFieldsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsExistsFieldsTest.java @@ -26,33 +26,49 @@ public class FlsExistsFieldsTest extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.admin().indices().create(new CreateIndexRequest("data") - .simpleMapping("@timestamp", "type=date", "host", "type=text,norms=false", "response", "type=text,norms=false", "non-existing", "type=text,norms=false")) - .actionGet(); + tc.admin() + .indices() + .create( + new CreateIndexRequest("data").simpleMapping( + "@timestamp", + "type=date", + "host", + "type=text,norms=false", + "response", + "type=text,norms=false", + "non-existing", + "type=text,norms=false" + ) + ) + .actionGet(); for (int i = 0; i < 1; i++) { - String doc = "{\"host\" : \"myhost"+i+"\",\n" + - " \"@timestamp\" : \"2018-01-18T09:03:25.877Z\",\n" + - " \"response\": \"404\"}"; - tc.index(new IndexRequest("data").id("a-normal-" + i).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(doc, - XContentType.JSON)).actionGet(); + String doc = "{\"host\" : \"myhost" + + i + + "\",\n" + + " \"@timestamp\" : \"2018-01-18T09:03:25.877Z\",\n" + + " \"response\": \"404\"}"; + tc.index(new IndexRequest("data").id("a-normal-" + i).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(doc, XContentType.JSON)) + .actionGet(); } for (int i = 0; i < 1; i++) { - String doc = "{" + - " \"@timestamp\" : \"2017-01-18T09:03:25.877Z\",\n" + - " \"response\": \"200\"}"; - tc.index(new IndexRequest("data").id("b-missing1-" + i).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(doc, - XContentType.JSON)).actionGet(); + String doc = "{" + " \"@timestamp\" : \"2017-01-18T09:03:25.877Z\",\n" + " \"response\": \"200\"}"; + tc.index( + new IndexRequest("data").id("b-missing1-" + i).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(doc, XContentType.JSON) + ).actionGet(); } for (int i = 0; i < 1; i++) { - String doc = "{\"host\" : \"myhost"+i+"\",\n" + - " \"@timestamp\" : \"2018-01-18T09:03:25.877Z\",\n" + - " \"non-existing\": \"xxx\","+ - " \"response\": \"403\"}"; - tc.index(new IndexRequest("data").id("c-missing2-" + i).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(doc, - XContentType.JSON)).actionGet(); + String doc = "{\"host\" : \"myhost" + + i + + "\",\n" + + " \"@timestamp\" : \"2018-01-18T09:03:25.877Z\",\n" + + " \"non-existing\": \"xxx\"," + + " \"response\": \"403\"}"; + tc.index( + new IndexRequest("data").id("c-missing2-" + i).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(doc, XContentType.JSON) + ).actionGet(); } } @@ -61,47 +77,50 @@ protected void populateData(Client tc) { public void testExistsField() throws Exception { setup(); - String query = "{\n" + - " \"query\": {\n" + - " \"bool\": {\n" + - - " \"must_not\": \n" + - " {\n" + - " \"exists\": {\n" + - " \"field\": \"non-existing\"\n" + - " \n" + - " }\n" + - " },\n" + - - " \"must\": [\n" + - " {\n" + - " \"exists\": {\n" + - " \"field\": \"@timestamp\"\n" + - " }\n" + - " },\n" + - " {\n" + - " \"exists\": {\n" + - " \"field\": \"host\"\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " }\n" + - "}"; + String query = "{\n" + " \"query\": {\n" + " \"bool\": {\n" + + + " \"must_not\": \n" + + " {\n" + + " \"exists\": {\n" + + " \"field\": \"non-existing\"\n" + + " \n" + + " }\n" + + " },\n" + + + + " \"must\": [\n" + + " {\n" + + " \"exists\": {\n" + + " \"field\": \"@timestamp\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"exists\": {\n" + + " \"field\": \"host\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + "}"; HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, - (res = rh.executePostRequest("/data/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/data/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("a-normal-0")); Assert.assertTrue(res.getBody().contains("response")); Assert.assertTrue(res.getBody().contains("404")); - //only see's - timestamp and host field - //therefore non-existing does not exist so we expect c-missing2-0 to be returned - Assert.assertEquals(HttpStatus.SC_OK, - (res = rh.executePostRequest("/data/_search?pretty", query, encodeBasicHeader("fls_exists", "password"))).getStatusCode()); + // only see's - timestamp and host field + // therefore non-existing does not exist so we expect c-missing2-0 to be returned + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/data/_search?pretty", query, encodeBasicHeader("fls_exists", "password"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("a-normal-0")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsTest.java index d4826222fa..a910cf5663 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsTest.java @@ -25,19 +25,22 @@ import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class FlsFieldsTest extends AbstractDlsFlsTest{ - +public class FlsFieldsTest extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.admin().indices().create(new CreateIndexRequest("deals").simpleMapping("timestamp", "type=date", "@timestamp", "type=date")).actionGet(); + tc.admin() + .indices() + .create(new CreateIndexRequest("deals").simpleMapping("timestamp", "type=date", "@timestamp", "type=date")) + .actionGet(); try { String doc = FileHelper.loadFile("dlsfls/doc1.json"); for (int i = 0; i < 10; i++) { final String moddoc = doc.replace("", "cust" + i).replace("", "" + i).replace("", "1970-01-02"); - tc.index(new IndexRequest("deals").id("0" + i).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(moddoc, XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("deals").id("0" + i).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(moddoc, XContentType.JSON)) + .actionGet(); } } catch (IOException e) { @@ -46,7 +49,6 @@ protected void populateData(Client tc) { } - @Test public void testFields() throws Exception { setup(); @@ -54,13 +56,19 @@ public void testFields() throws Exception { String query = FileHelper.loadFile("dlsfls/flsquery.json"); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("secret")); Assert.assertTrue(res.getBody().contains("@timestamp")); Assert.assertTrue(res.getBody().contains("\"timestamp")); Assert.assertTrue(res.getBody().contains("numfield5")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("fls_fields", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("fls_fields", "password"))).getStatusCode() + ); Assert.assertFalse(res.getBody().contains("customer")); Assert.assertFalse(res.getBody().contains("secret")); Assert.assertFalse(res.getBody().contains("timestamp")); @@ -74,12 +82,18 @@ public void testFields2() throws Exception { String query = FileHelper.loadFile("dlsfls/flsquery2.json"); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty=true", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty=true", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("secret")); Assert.assertTrue(res.getBody().contains("@timestamp")); Assert.assertTrue(res.getBody().contains("\"timestamp")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty=true", query, encodeBasicHeader("fls_fields", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty=true", query, encodeBasicHeader("fls_fields", "password"))).getStatusCode() + ); Assert.assertFalse(res.getBody().contains("customer")); Assert.assertFalse(res.getBody().contains("secret")); Assert.assertFalse(res.getBody().contains("timestamp")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsWcTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsWcTest.java index 951e786891..f6cfd036fd 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsWcTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFieldsWcTest.java @@ -25,19 +25,22 @@ import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class FlsFieldsWcTest extends AbstractDlsFlsTest{ - +public class FlsFieldsWcTest extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.admin().indices().create(new CreateIndexRequest("deals").simpleMapping("timestamp", "type=date", "@timestamp", "type=date")).actionGet(); + tc.admin() + .indices() + .create(new CreateIndexRequest("deals").simpleMapping("timestamp", "type=date", "@timestamp", "type=date")) + .actionGet(); try { String doc = FileHelper.loadFile("dlsfls/doc1.json"); for (int i = 0; i < 10; i++) { final String moddoc = doc.replace("", "cust" + i).replace("", "" + i).replace("", "1970-01-02"); - tc.index(new IndexRequest("deals").id("0" + i).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(moddoc, XContentType.JSON)).actionGet(); + tc.index(new IndexRequest("deals").id("0" + i).setRefreshPolicy(RefreshPolicy.IMMEDIATE).source(moddoc, XContentType.JSON)) + .actionGet(); } } catch (IOException e) { @@ -46,7 +49,6 @@ protected void populateData(Client tc) { } - @Test public void testFields() throws Exception { setup(); @@ -54,12 +56,18 @@ public void testFields() throws Exception { String query = FileHelper.loadFile("dlsfls/flsquery.json"); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("secret")); Assert.assertTrue(res.getBody().contains("@timestamp")); Assert.assertTrue(res.getBody().contains("\"timestamp")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("fls_fields_wc", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("fls_fields_wc", "password"))).getStatusCode() + ); Assert.assertFalse(res.getBody().contains("customer")); Assert.assertFalse(res.getBody().contains("secret")); Assert.assertFalse(res.getBody().contains("timestamp")); @@ -73,12 +81,18 @@ public void testFields2() throws Exception { String query = FileHelper.loadFile("dlsfls/flsquery2.json"); HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("secret")); Assert.assertTrue(res.getBody().contains("@timestamp")); Assert.assertTrue(res.getBody().contains("\"timestamp")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("fls_fields_wc", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_search?pretty", query, encodeBasicHeader("fls_fields_wc", "password"))).getStatusCode() + ); Assert.assertFalse(res.getBody().contains("customer")); Assert.assertFalse(res.getBody().contains("secret")); Assert.assertFalse(res.getBody().contains("timestamp")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java index a3b5d2809e..2d7ed0efcf 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsIndexingTests.java @@ -32,16 +32,24 @@ public class FlsIndexingTests extends AbstractDlsFlsTest { protected void populateData(final Client tc) { // Create several documents in different indices with shared field names, // different roles will have different levels of FLS restrictions - tc.index(new IndexRequest("yellow-pages").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"phone-all\":1001,\"phone-some\":1002,\"phone-one\":1003}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("green-pages").id("2").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"phone-all\":2001,\"phone-some\":2002,\"phone-one\":2003}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("blue-book").id("3").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"phone-all\":3001,\"phone-some\":3002,\"phone-one\":3003}", XContentType.JSON)).actionGet(); - - // Seperate index used to test aliasing - tc.index(new IndexRequest(".hidden").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("yellow-pages").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"phone-all\":1001,\"phone-some\":1002,\"phone-one\":1003}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("green-pages").id("2") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"phone-all\":2001,\"phone-some\":2002,\"phone-one\":2003}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("blue-book").id("3") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"phone-all\":3001,\"phone-some\":3002,\"phone-one\":3003}", XContentType.JSON) + ).actionGet(); + + // Seperate index used to test aliasing + tc.index(new IndexRequest(".hidden").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE).source("{}", XContentType.JSON)).actionGet(); } private Header asPhoneOneUser = encodeBasicHeader("user_aaa", "password"); @@ -52,9 +60,9 @@ protected void populateData(final Client tc) { @Test public void testSingleIndexFlsApplied() throws Exception { - setup(new DynamicSecurityConfig() - .setSecurityRoles("roles_fls_indexing.yml") - .setSecurityRolesMapping("roles_mapping_fls_indexing.yml")); + setup( + new DynamicSecurityConfig().setSecurityRoles("roles_fls_indexing.yml").setSecurityRolesMapping("roles_mapping_fls_indexing.yml") + ); final HttpResponse phoneOneFilteredResponse = rh.executeGetRequest(searchQuery, asPhoneOneUser); assertThat(phoneOneFilteredResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); @@ -73,11 +81,14 @@ public void testSingleIndexFlsApplied() throws Exception { @Test public void testSingleIndexFlsAppliedForLimitedResults() throws Exception { - setup(new DynamicSecurityConfig() - .setSecurityRoles("roles_fls_indexing.yml") - .setSecurityRolesMapping("roles_mapping_fls_indexing.yml")); - - final HttpResponse phoneOneFilteredResponse = rh.executeGetRequest("/yellow-pages/_search?filter_path=hits.hits&pretty", asPhoneOneUser); + setup( + new DynamicSecurityConfig().setSecurityRoles("roles_fls_indexing.yml").setSecurityRolesMapping("roles_mapping_fls_indexing.yml") + ); + + final HttpResponse phoneOneFilteredResponse = rh.executeGetRequest( + "/yellow-pages/_search?filter_path=hits.hits&pretty", + asPhoneOneUser + ); assertThat(phoneOneFilteredResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(phoneOneFilteredResponse.getBody(), not(containsString("1003"))); assertThat(phoneOneFilteredResponse.getBody(), containsString("1002")); @@ -94,9 +105,9 @@ public void testSingleIndexFlsAppliedForLimitedResults() throws Exception { @Test public void testSeveralIndexFlsApplied() throws Exception { - setup(new DynamicSecurityConfig() - .setSecurityRoles("roles_fls_indexing.yml") - .setSecurityRolesMapping("roles_mapping_fls_indexing.yml")); + setup( + new DynamicSecurityConfig().setSecurityRoles("roles_fls_indexing.yml").setSecurityRolesMapping("roles_mapping_fls_indexing.yml") + ); final HttpResponse phoneSomeFilteredResponse = rh.executeGetRequest(searchQuery, asPhoneSomeUser); assertThat(phoneSomeFilteredResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); @@ -115,9 +126,9 @@ public void testSeveralIndexFlsApplied() throws Exception { @Test public void testAllIndexFlsApplied() throws Exception { - setup(new DynamicSecurityConfig() - .setSecurityRoles("roles_fls_indexing.yml") - .setSecurityRolesMapping("roles_mapping_fls_indexing.yml")); + setup( + new DynamicSecurityConfig().setSecurityRoles("roles_fls_indexing.yml").setSecurityRolesMapping("roles_mapping_fls_indexing.yml") + ); final HttpResponse phoneAllFilteredResponse = rh.executeGetRequest(searchQuery, asPhoneAllUser); assertThat(phoneAllFilteredResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); @@ -136,11 +147,15 @@ public void testAllIndexFlsApplied() throws Exception { @Test public void testAllIndexFlsAppliedWithAlias() throws Exception { - setup(new DynamicSecurityConfig() - .setSecurityRoles("roles_fls_indexing.yml") - .setSecurityRolesMapping("roles_mapping_fls_indexing.yml")); - - final HttpResponse createAlias = rh.executePostRequest("_aliases", "{\"actions\":[{\"add\":{\"index\":\".hidden\",\"alias\":\"ducky\"}}]}", asPhoneAllUser); + setup( + new DynamicSecurityConfig().setSecurityRoles("roles_fls_indexing.yml").setSecurityRolesMapping("roles_mapping_fls_indexing.yml") + ); + + final HttpResponse createAlias = rh.executePostRequest( + "_aliases", + "{\"actions\":[{\"add\":{\"index\":\".hidden\",\"alias\":\"ducky\"}}]}", + asPhoneAllUser + ); assertThat(createAlias.getStatusCode(), equalTo(HttpStatus.SC_OK)); final HttpResponse phoneAllFilteredResponse = rh.executeGetRequest(searchQuery, asPhoneAllUser); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsKeywordTests.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsKeywordTests.java index 25069e48ef..1c51ec99b7 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsKeywordTests.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsKeywordTests.java @@ -32,20 +32,24 @@ public class FlsKeywordTests extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.index(new IndexRequest("movies").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"year\": 2013, \"title\": \"Rush\", \"actors\": [\"Daniel Br\u00FChl\", \"Chris Hemsworth\", \"Olivia Wilde\"]}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("movies").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"year\": 2013, \"title\": \"Rush\", \"actors\": [\"Daniel Br\u00FChl\", \"Chris Hemsworth\", \"Olivia Wilde\"]}", + XContentType.JSON + ) + ).actionGet(); } private Header movieUser = encodeBasicHeader("user_aaa", "password"); private Header movieNoActorUser = encodeBasicHeader("user_bbb", "password"); - private String[] actors = new String[] {"Daniel Br\u00FChl", "Chris Hemsworth", "Olivia Wilde"}; + private String[] actors = new String[] { "Daniel Br\u00FChl", "Chris Hemsworth", "Olivia Wilde" }; @Test public void testKeywordsAreAutomaticallyFiltered() throws Exception { - setup(new DynamicSecurityConfig() - .setSecurityRoles("roles_keyword.yml") - .setSecurityRolesMapping("roles_mappings_keyword.yml")); + setup(new DynamicSecurityConfig().setSecurityRoles("roles_keyword.yml").setSecurityRolesMapping("roles_mappings_keyword.yml")); final String searchQuery = "/movies/_search?filter_path=hits.hits._source"; final String aggQuery = "/movies/_search?filter_path=aggregations.actors.buckets.key"; @@ -73,14 +77,10 @@ public void testKeywordsAreAutomaticallyFiltered() throws Exception { } private void assertActorsPresent(final HttpResponse response) { - Arrays.stream(actors).forEach(actor -> { - assertThat(response.getBody(), containsString(actor)); - }); + Arrays.stream(actors).forEach(actor -> { assertThat(response.getBody(), containsString(actor)); }); } private void assertActorsNotPresent(final HttpResponse response) { - Arrays.stream(actors).forEach(actor -> { - assertThat(response.getBody(), not(containsString(actor))); - }); + Arrays.stream(actors).forEach(actor -> { assertThat(response.getBody(), not(containsString(actor))); }); } } diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsPerfTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsPerfTest.java index 565dbdde9c..4fde195860 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsPerfTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsPerfTest.java @@ -30,30 +30,27 @@ import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; @Ignore -public class FlsPerfTest extends AbstractDlsFlsTest{ - +public class FlsPerfTest extends AbstractDlsFlsTest { protected void populateData(Client tc) { Map indexSettings = new HashMap<>(3); - indexSettings.put("index.mapping.total_fields.limit",50000); + indexSettings.put("index.mapping.total_fields.limit", 50000); indexSettings.put("number_of_shards", 10); indexSettings.put("number_of_replicas", 0); - tc.admin().indices().create(new CreateIndexRequest("deals") - .settings(indexSettings)) - .actionGet(); + tc.admin().indices().create(new CreateIndexRequest("deals").settings(indexSettings)).actionGet(); try { - IndexRequest ir = new IndexRequest("deals").id("idx1"); + IndexRequest ir = new IndexRequest("deals").id("idx1"); XContentBuilder b = XContentBuilder.builder(JsonXContent.jsonXContent); b.startObject(); - b.field("amount",1000); + b.field("amount", 1000); b.startObject("xyz"); - b.field("abc","val"); + b.field("abc", "val"); b.endObject(); b.endObject(); @@ -61,13 +58,13 @@ protected void populateData(Client tc) { tc.index(ir).actionGet(); - for(int i=0; i<1500; i++) { + for (int i = 0; i < 1500; i++) { - ir = new IndexRequest("deals").id("id"+i); + ir = new IndexRequest("deals").id("id" + i); b = XContentBuilder.builder(JsonXContent.jsonXContent); b.startObject(); - for(int j=0; j<2000;j++) { - b.field("field"+j,"val"+j); + for (int j = 0; j < 2000; j++) { + b.field("field" + j, "val" + j); } b.endObject(); @@ -94,7 +91,10 @@ public void testFlsPerfNamed() throws Exception { StopWatch sw = new StopWatch("testFlsPerfNamed"); sw.start("non fls"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); sw.stop(); Assert.assertTrue(res.getBody().contains("field1\"")); Assert.assertTrue(res.getBody().contains("field2\"")); @@ -102,7 +102,11 @@ public void testFlsPerfNamed() throws Exception { Assert.assertTrue(res.getBody().contains("field997\"")); sw.start("with fls"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_named_only", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_named_only", "password"))) + .getStatusCode() + ); sw.stop(); Assert.assertFalse(res.getBody().contains("field1\"")); Assert.assertFalse(res.getBody().contains("field2\"")); @@ -111,7 +115,11 @@ public void testFlsPerfNamed() throws Exception { sw.start("with fls 2 after warmup"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_named_only", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_named_only", "password"))) + .getStatusCode() + ); sw.stop(); Assert.assertFalse(res.getBody().contains("field1\"")); @@ -121,7 +129,11 @@ public void testFlsPerfNamed() throws Exception { sw.start("with fls 3 after warmup"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_named_only", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_named_only", "password"))) + .getStatusCode() + ); sw.stop(); Assert.assertFalse(res.getBody().contains("field1\"")); @@ -141,7 +153,10 @@ public void testFlsPerfWcEx() throws Exception { StopWatch sw = new StopWatch("testFlsPerfWcEx"); sw.start("non fls"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); sw.stop(); Assert.assertTrue(res.getBody().contains("field1\"")); Assert.assertTrue(res.getBody().contains("field2\"")); @@ -149,7 +164,10 @@ public void testFlsPerfWcEx() throws Exception { Assert.assertTrue(res.getBody().contains("field997\"")); sw.start("with fls"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_wc_ex", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_wc_ex", "password"))).getStatusCode() + ); sw.stop(); Assert.assertTrue(res.getBody().contains("field1\"")); Assert.assertTrue(res.getBody().contains("field2\"")); @@ -158,7 +176,10 @@ public void testFlsPerfWcEx() throws Exception { sw.start("with fls 2 after warmup"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_wc_ex", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_wc_ex", "password"))).getStatusCode() + ); sw.stop(); Assert.assertTrue(res.getBody().contains("field1\"")); @@ -168,7 +189,10 @@ public void testFlsPerfWcEx() throws Exception { sw.start("with fls 3 after warmup"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_wc_ex", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_wc_ex", "password"))).getStatusCode() + ); sw.stop(); Assert.assertTrue(res.getBody().contains("field1\"")); @@ -188,7 +212,10 @@ public void testFlsPerfNamedEx() throws Exception { StopWatch sw = new StopWatch("testFlsPerfNamedEx"); sw.start("non fls"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); sw.stop(); Assert.assertTrue(res.getBody().contains("field1\"")); Assert.assertTrue(res.getBody().contains("field2\"")); @@ -196,7 +223,10 @@ public void testFlsPerfNamedEx() throws Exception { Assert.assertTrue(res.getBody().contains("field997\"")); sw.start("with fls"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_named_ex", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_named_ex", "password"))).getStatusCode() + ); sw.stop(); Assert.assertTrue(res.getBody().contains("field1\"")); Assert.assertTrue(res.getBody().contains("field2\"")); @@ -205,7 +235,10 @@ public void testFlsPerfNamedEx() throws Exception { sw.start("with fls 2 after warmup"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_named_ex", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_named_ex", "password"))).getStatusCode() + ); sw.stop(); Assert.assertTrue(res.getBody().contains("field1\"")); @@ -215,7 +248,10 @@ public void testFlsPerfNamedEx() throws Exception { sw.start("with fls 3 after warmup"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_named_ex", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_named_ex", "password"))).getStatusCode() + ); sw.stop(); Assert.assertTrue(res.getBody().contains("field1\"")); @@ -235,7 +271,10 @@ public void testFlsWcIn() throws Exception { StopWatch sw = new StopWatch("testFlsWcIn"); sw.start("non fls"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); sw.stop(); Assert.assertTrue(res.getBody().contains("field1\"")); Assert.assertTrue(res.getBody().contains("field2\"")); @@ -243,7 +282,10 @@ public void testFlsWcIn() throws Exception { Assert.assertTrue(res.getBody().contains("field997\"")); sw.start("with fls"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_wc_in", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_wc_in", "password"))).getStatusCode() + ); sw.stop(); Assert.assertFalse(res.getBody().contains("field0\"")); Assert.assertTrue(res.getBody().contains("field50\"")); @@ -251,7 +293,10 @@ public void testFlsWcIn() throws Exception { sw.start("with fls 2 after warmup"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_wc_in", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_wc_in", "password"))).getStatusCode() + ); sw.stop(); Assert.assertFalse(res.getBody().contains("field0\"")); @@ -260,7 +305,10 @@ public void testFlsWcIn() throws Exception { sw.start("with fls 3 after warmup"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_wc_in", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty&size=1000", encodeBasicHeader("perf_wc_in", "password"))).getStatusCode() + ); sw.stop(); Assert.assertFalse(res.getBody().contains("field0\"")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsTest.java index c31650e734..180256efd9 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsTest.java @@ -21,15 +21,23 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class FlsTest extends AbstractDlsFlsTest{ - +public class FlsTest extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.index(new IndexRequest("deals").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust1\"}, \"zip\": \"12345\",\"secret\": \"tellnoone\",\"amount\": 10}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("deals").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust2\", \"ctype\":\"industry\"}, \"amount\": 1500}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("deals").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"customer\": {\"name\":\"cust1\"}, \"zip\": \"12345\",\"secret\": \"tellnoone\",\"amount\": 10}", + XContentType.JSON + ) + ).actionGet(); + tc.index( + new IndexRequest("deals").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"customer\": {\"name\":\"cust2\", \"ctype\":\"industry\"}, \"amount\": 1500}", XContentType.JSON) + ).actionGet(); } @Test @@ -39,8 +47,10 @@ public void testFieldCapabilities() throws Exception { HttpResponse res; - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_field_caps?fields=*&pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_field_caps?fields=*&pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("customer")); Assert.assertTrue(res.getBody().contains("customer.name")); Assert.assertTrue(res.getBody().contains("zip")); @@ -48,7 +58,11 @@ public void testFieldCapabilities() throws Exception { Assert.assertTrue(res.getBody().contains("amount")); Assert.assertTrue(res.getBody().contains("secret")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_field_caps?fields=*&pretty", encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_field_caps?fields=*&pretty", encodeBasicHeader("dept_manager_fls", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("customer")); Assert.assertTrue(res.getBody().contains("customer.name")); Assert.assertTrue(res.getBody().contains("zip")); @@ -56,7 +70,13 @@ public void testFieldCapabilities() throws Exception { Assert.assertFalse(res.getBody().contains("amount")); Assert.assertFalse(res.getBody().contains("secret")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_field_caps?fields=*&pretty", encodeBasicHeader("dept_manager_fls_reversed_fields", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + "/deals/_field_caps?fields=*&pretty", + encodeBasicHeader("dept_manager_fls_reversed_fields", "password") + )).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertFalse(res.getBody().contains("customer")); Assert.assertFalse(res.getBody().contains("customer.name")); @@ -73,8 +93,10 @@ public void testMapping() throws Exception { HttpResponse res; - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_mapping?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_mapping?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("customer")); Assert.assertTrue(res.getBody().contains("name")); Assert.assertTrue(res.getBody().contains("zip")); @@ -82,7 +104,10 @@ public void testMapping() throws Exception { Assert.assertTrue(res.getBody().contains("amount")); Assert.assertTrue(res.getBody().contains("secret")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_mapping?pretty", encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_mapping?pretty", encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("customer")); Assert.assertTrue(res.getBody().contains("name")); Assert.assertTrue(res.getBody().contains("zip")); @@ -90,7 +115,11 @@ public void testMapping() throws Exception { Assert.assertFalse(res.getBody().contains("amount")); Assert.assertFalse(res.getBody().contains("secret")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_mapping?pretty", encodeBasicHeader("dept_manager_fls_reversed_fields", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_mapping?pretty", encodeBasicHeader("dept_manager_fls_reversed_fields", "password"))) + .getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("customer")); Assert.assertFalse(res.getBody().contains("name")); @@ -100,7 +129,6 @@ public void testMapping() throws Exception { Assert.assertTrue(res.getBody().contains("secret")); } - @Test public void testFlsSearch() throws Exception { @@ -108,7 +136,10 @@ public void testFlsSearch() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); Assert.assertTrue(res.getBody().contains("cust1")); @@ -118,7 +149,10 @@ public void testFlsSearch() throws Exception { Assert.assertTrue(res.getBody().contains("amount")); Assert.assertTrue(res.getBody().contains("secret")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); Assert.assertTrue(res.getBody().contains("cust1")); @@ -128,7 +162,11 @@ public void testFlsSearch() throws Exception { Assert.assertFalse(res.getBody().contains("amount")); Assert.assertFalse(res.getBody().contains("secret")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("dept_manager_fls_reversed_fields", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_search?pretty", encodeBasicHeader("dept_manager_fls_reversed_fields", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); Assert.assertFalse(res.getBody().contains("cust1")); @@ -146,7 +184,10 @@ public void testFlsGet() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_doc/0?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_doc/0?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"found\" : true")); Assert.assertTrue(res.getBody().contains("cust1")); Assert.assertFalse(res.getBody().contains("cust2")); @@ -154,7 +195,10 @@ public void testFlsGet() throws Exception { Assert.assertFalse(res.getBody().contains("ctype")); Assert.assertTrue(res.getBody().contains("amount")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_doc/0?pretty", encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_doc/0?pretty", encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"found\" : true")); Assert.assertTrue(res.getBody().contains("cust1")); Assert.assertFalse(res.getBody().contains("cust2")); @@ -162,7 +206,11 @@ public void testFlsGet() throws Exception { Assert.assertFalse(res.getBody().contains("ctype")); Assert.assertFalse(res.getBody().contains("amount")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_doc/0?realtime=true&pretty", encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/deals/_doc/0?realtime=true&pretty", encodeBasicHeader("dept_manager_fls", "password"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"found\" : true")); Assert.assertTrue(res.getBody().contains("cust1")); Assert.assertFalse(res.getBody().contains("cust2")); @@ -170,7 +218,13 @@ public void testFlsGet() throws Exception { Assert.assertFalse(res.getBody().contains("ctype")); Assert.assertFalse(res.getBody().contains("amount")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/deals/_doc/0?realtime=true&pretty", encodeBasicHeader("dept_manager_fls_reversed_fields", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + "/deals/_doc/0?realtime=true&pretty", + encodeBasicHeader("dept_manager_fls_reversed_fields", "password") + )).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"found\" : true")); Assert.assertFalse(res.getBody().contains("cust1")); Assert.assertFalse(res.getBody().contains("cust2")); @@ -187,11 +241,22 @@ public void testFlsUpdate() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("/deals/_update/0?pretty", "{\"doc\": {\"zip\": \"98765\"}}", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("/deals/_update/0?pretty", "{\"doc\": {\"zip\": \"98765\"}}", encodeBasicHeader("admin", "admin"))) + .getStatusCode() + ); Assert.assertTrue(res.getBody().contains("\"_version\" : 2")); Assert.assertFalse(res.getBody(), res.getBody().contains("\"successful\" : 0")); - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, (res = rh.executePostRequest("/deals/_update/0?pretty", "{\"doc\": {\"zip\": \"98765000\"}}", encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_INTERNAL_SERVER_ERROR, + (res = rh.executePostRequest( + "/deals/_update/0?pretty", + "{\"doc\": {\"zip\": \"98765000\"}}", + encodeBasicHeader("dept_manager_fls", "password") + )).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("Update is not supported")); } @@ -202,7 +267,14 @@ public void testFlsUpdateIndex() throws Exception { HttpResponse res = null; - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, (res = rh.executePostRequest("/deals/_update/0?pretty", "{\"doc\": {\"zip\": \"98765000\"}}", encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_INTERNAL_SERVER_ERROR, + (res = rh.executePostRequest( + "/deals/_update/0?pretty", + "{\"doc\": {\"zip\": \"98765000\"}}", + encodeBasicHeader("dept_manager_fls", "password") + )).getStatusCode() + ); Assert.assertTrue(res.getBody().contains("Update is not supported")); } } diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/IndexPatternTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/IndexPatternTest.java index 7348b11341..9b075e0600 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/IndexPatternTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/IndexPatternTest.java @@ -21,20 +21,27 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class IndexPatternTest extends AbstractDlsFlsTest{ - +public class IndexPatternTest extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.index(new IndexRequest("logstash-2016").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"message\":\"mymsg1a\", \"ipaddr\": \"10.0.0.0\",\"msgid\": \"12\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash-2016").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"message\":\"mymsg1b\", \"ipaddr\": \"10.0.0.1\",\"msgid\": \"14\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash-2018").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"message\":\"mymsg1c\", \"ipaddr\": \"10.0.0.2\",\"msgid\": \"12\"}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("logstash-2018").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"message\":\"mymsg1d\", \"ipaddr\": \"10.0.0.3\",\"msgid\": \"14\"}", XContentType.JSON)).actionGet(); - } + tc.index( + new IndexRequest("logstash-2016").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"message\":\"mymsg1a\", \"ipaddr\": \"10.0.0.0\",\"msgid\": \"12\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("logstash-2016").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"message\":\"mymsg1b\", \"ipaddr\": \"10.0.0.1\",\"msgid\": \"14\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("logstash-2018").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"message\":\"mymsg1c\", \"ipaddr\": \"10.0.0.2\",\"msgid\": \"12\"}", XContentType.JSON) + ).actionGet(); + tc.index( + new IndexRequest("logstash-2018").setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source("{\"message\":\"mymsg1d\", \"ipaddr\": \"10.0.0.3\",\"msgid\": \"14\"}", XContentType.JSON) + ).actionGet(); + } @Test public void testSearch() throws Exception { @@ -43,7 +50,10 @@ public void testSearch() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-2016/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash-2016/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -52,7 +62,11 @@ public void testSearch() throws Exception { Assert.assertTrue(res.getBody().contains("mymsg")); Assert.assertTrue(res.getBody().contains("msgid")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-2016/_search?pretty", encodeBasicHeader("opendistro_security_logstash", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash-2016/_search?pretty", encodeBasicHeader("opendistro_security_logstash", "password"))) + .getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 1,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -69,13 +83,22 @@ public void testFieldCaps() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-2016/_field_caps?fields=*&pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash-2016/_field_caps?fields=*&pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("ipaddr")); Assert.assertTrue(res.getBody().contains("message")); Assert.assertTrue(res.getBody().contains("msgid")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-2016/_field_caps?fields=*&pretty", encodeBasicHeader("opendistro_security_logstash", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest( + "/logstash-2016/_field_caps?fields=*&pretty", + encodeBasicHeader("opendistro_security_logstash", "password") + )).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertFalse(res.getBody().contains("ipaddr")); Assert.assertFalse(res.getBody().contains("message")); @@ -89,7 +112,10 @@ public void testSearchWc() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-20*/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash-20*/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 4,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -98,7 +124,11 @@ public void testSearchWc() throws Exception { Assert.assertTrue(res.getBody().contains("mymsg")); Assert.assertTrue(res.getBody().contains("msgid")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-20*/_search?pretty", encodeBasicHeader("opendistro_security_logstash", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash-20*/_search?pretty", encodeBasicHeader("opendistro_security_logstash", "password"))) + .getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -115,7 +145,10 @@ public void testSearchWcRegex() throws Exception { HttpResponse res; - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-20*/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash-20*/_search?pretty", encodeBasicHeader("admin", "admin"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 4,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); @@ -124,7 +157,10 @@ public void testSearchWcRegex() throws Exception { Assert.assertTrue(res.getBody().contains("mymsg")); Assert.assertTrue(res.getBody().contains("msgid")); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("/logstash-20*/_search?pretty", encodeBasicHeader("regex", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("/logstash-20*/_search?pretty", encodeBasicHeader("regex", "password"))).getStatusCode() + ); System.out.println(res.getBody()); Assert.assertTrue(res.getBody().contains("\"value\" : 2,\n \"relation")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/MFlsTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/MFlsTest.java index b7305ee48c..81a2c50fc6 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/MFlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/MFlsTest.java @@ -21,15 +21,26 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -public class MFlsTest extends AbstractDlsFlsTest{ - +public class MFlsTest extends AbstractDlsFlsTest { protected void populateData(Client tc) { - tc.index(new IndexRequest("deals").id("0").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"customer\": {\"name\":\"cust1\"}, \"zip\": \"12345\",\"secret\": \"tellnoone\",\"amount\": 10}", XContentType.JSON)).actionGet(); - tc.index(new IndexRequest("finance").id("1").setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source("{\"finfield2\":\"fff\",\"xcustomer\": {\"name\":\"cust2\", \"ctype\":\"industry\"}, \"famount\": 1500}", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest("deals").id("0") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"customer\": {\"name\":\"cust1\"}, \"zip\": \"12345\",\"secret\": \"tellnoone\",\"amount\": 10}", + XContentType.JSON + ) + ).actionGet(); + tc.index( + new IndexRequest("finance").id("1") + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source( + "{\"finfield2\":\"fff\",\"xcustomer\": {\"name\":\"cust2\", \"ctype\":\"industry\"}, \"famount\": 1500}", + XContentType.JSON + ) + ).actionGet(); } @Test @@ -40,7 +51,10 @@ public void testFlsMGetSearch() throws Exception { HttpResponse res; System.out.println("### normal search"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("deals,finance/_search?pretty", encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executeGetRequest("deals,finance/_search?pretty", encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode() + ); Assert.assertFalse(res.getBody().contains("_opendistro_security_")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); Assert.assertFalse(res.getBody().contains("xception")); @@ -50,16 +64,22 @@ public void testFlsMGetSearch() throws Exception { Assert.assertFalse(res.getBody().contains("amount")); Assert.assertFalse(res.getBody().contains("secret")); - //mget - //msearch - String msearchBody = - "{\"index\":\"deals\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator()+ - "{\"index\":\"finance\", \"ignore_unavailable\": true}"+System.lineSeparator()+ - "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}"+System.lineSeparator(); + // mget + // msearch + String msearchBody = "{\"index\":\"deals\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator() + + "{\"index\":\"finance\", \"ignore_unavailable\": true}" + + System.lineSeparator() + + "{\"size\":10, \"query\":{\"bool\":{\"must\":{\"match_all\":{}}}}}" + + System.lineSeparator(); System.out.println("### msearch"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("_msearch?pretty", msearchBody, encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode() + ); Assert.assertFalse(res.getBody().contains("_opendistro_security_")); Assert.assertTrue(res.getBody().contains("\"failed\" : 0")); Assert.assertFalse(res.getBody().contains("xception")); @@ -69,22 +89,24 @@ public void testFlsMGetSearch() throws Exception { Assert.assertFalse(res.getBody().contains("amount")); Assert.assertFalse(res.getBody().contains("secret")); - - String mgetBody = "{"+ - "\"docs\" : ["+ - "{"+ - "\"_index\" : \"deals\","+ - "\"_id\" : \"0\""+ - " },"+ - " {"+ - "\"_index\" : \"finance\","+ - " \"_id\" : \"1\""+ - "}"+ - "]"+ - "}"; + String mgetBody = "{" + + "\"docs\" : [" + + "{" + + "\"_index\" : \"deals\"," + + "\"_id\" : \"0\"" + + " }," + + " {" + + "\"_index\" : \"finance\"," + + " \"_id\" : \"1\"" + + "}" + + "]" + + "}"; System.out.println("### mget"); - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + (res = rh.executePostRequest("_mget?pretty", mgetBody, encodeBasicHeader("dept_manager_fls", "password"))).getStatusCode() + ); Assert.assertFalse(res.getBody().contains("_opendistro_security_")); Assert.assertTrue(res.getBody().contains("\"found\" : true")); Assert.assertFalse(res.getBody().contains("\"found\" : false")); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java index 7b590f1d46..e7752c21d1 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java @@ -44,8 +44,8 @@ public abstract class AbstractRestApiUnitTest extends SingleClusterTest { - protected RestHelper rh = null; - protected boolean init = true; + protected RestHelper rh = null; + protected boolean init = true; @Override protected String getResourceFolder() { @@ -53,247 +53,259 @@ protected String getResourceFolder() { } @Override - protected final void setup() throws Exception { - Settings.Builder builder = Settings.builder(); + protected final void setup() throws Exception { + Settings.Builder builder = Settings.builder(); - builder.put("plugins.security.ssl.http.enabled", true) - .put(SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, PasswordValidator.ScoreStrength.FAIR.name()) - .put("plugins.security.ssl.http.keystore_filepath", - FileHelper.getAbsoluteFilePathFromClassPath("restapi/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", - FileHelper.getAbsoluteFilePathFromClassPath("restapi/truststore.jks")); + builder.put("plugins.security.ssl.http.enabled", true) + .put(SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, PasswordValidator.ScoreStrength.FAIR.name()) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("restapi/node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("restapi/truststore.jks")); - setup(Settings.EMPTY, new DynamicSecurityConfig(), builder.build(), init); - rh = restHelper(); - rh.keystore = "restapi/kirk-keystore.jks"; - } + setup(Settings.EMPTY, new DynamicSecurityConfig(), builder.build(), init); + rh = restHelper(); + rh.keystore = "restapi/kirk-keystore.jks"; + } @Override - protected final void setup(Settings nodeOverride) throws Exception { - Settings.Builder builder = Settings.builder(); + protected final void setup(Settings nodeOverride) throws Exception { + Settings.Builder builder = Settings.builder(); - builder.put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.keystore_filepath", - FileHelper.getAbsoluteFilePathFromClassPath("restapi/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", - FileHelper.getAbsoluteFilePathFromClassPath("restapi/truststore.jks")) - .put(nodeOverride); + builder.put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("restapi/node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("restapi/truststore.jks")) + .put(nodeOverride); - System.out.println(builder.toString()); + System.out.println(builder.toString()); - setup(Settings.EMPTY, new DynamicSecurityConfig(), builder.build(), init); - rh = restHelper(); - rh.keystore = "restapi/kirk-keystore.jks"; - } + setup(Settings.EMPTY, new DynamicSecurityConfig(), builder.build(), init); + rh = restHelper(); + rh.keystore = "restapi/kirk-keystore.jks"; + } - protected final void setupWithRestRoles() throws Exception { + protected final void setupWithRestRoles() throws Exception { setupWithRestRoles(null); } - protected final void setupWithRestRoles(Settings nodeOverride) throws Exception { - Settings.Builder builder = Settings.builder(); - - builder.put("plugins.security.ssl.http.enabled", true) - .put(SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, PasswordValidator.ScoreStrength.FAIR.name()) - .put("plugins.security.ssl.http.keystore_filepath", - FileHelper.getAbsoluteFilePathFromClassPath("restapi/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", - FileHelper.getAbsoluteFilePathFromClassPath("restapi/truststore.jks")); - - builder.put(rolesSettings()); - - if (null != nodeOverride) { - builder.put(nodeOverride); - } - - setup(Settings.EMPTY, new DynamicSecurityConfig(), builder.build(), init); - rh = restHelper(); - rh.keystore = "restapi/kirk-keystore.jks"; - - AuditTestUtils.updateAuditConfig(rh, nodeOverride != null ? nodeOverride : Settings.EMPTY); - } - - protected Settings rolesSettings() { - return Settings.builder() - .put("plugins.security.restapi.roles_enabled.0", "opendistro_security_role_klingons") - .put("plugins.security.restapi.roles_enabled.1", "opendistro_security_role_vulcans") - .put("plugins.security.restapi.roles_enabled.2", "opendistro_security_test") - .put("plugins.security.restapi.endpoints_disabled.global.CACHE.0", "*") - .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.conFiGuration.0", "*") - .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.wRongType.0", "WRONGType") - .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.ROLESMAPPING.0", "PUT") - .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.ROLESMAPPING.1", "DELETE") - .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_vulcans.CONFIG.0", "*") - .build(); - } - - protected void deleteUser(String username) throws Exception { - boolean sendAdminCertificate = rh.sendAdminCertificate; - rh.sendAdminCertificate = true; - HttpResponse response = rh.executeDeleteRequest("/_opendistro/_security/api/internalusers/" + username, new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - rh.sendAdminCertificate = sendAdminCertificate; - } - - protected void addUserWithPassword(String username, String password, int status) throws Exception { - addUserWithPassword(username, password, status, null); - } - - protected void addUserWithPassword(String username, String password, int status, String message) throws Exception { - boolean sendAdminCertificate = rh.sendAdminCertificate; - rh.sendAdminCertificate = true; - HttpResponse response = rh.executePutRequest("/_opendistro/_security/api/internalusers/" + username, - "{\"password\": \"" + password + "\"}", new Header[0]); - Assert.assertEquals(status, response.getStatusCode()); - rh.sendAdminCertificate = sendAdminCertificate; - if (Objects.nonNull(message)) { - Assert.assertTrue(response.getBody().contains(message)); - } - } - - protected void addUserWithPassword(String username, String password, String[] roles, int status) throws Exception { - boolean sendAdminCertificate = rh.sendAdminCertificate; - rh.sendAdminCertificate = true; - String payload = "{" + "\"password\": \"" + password + "\"," + "\"backend_roles\": ["; - for (int i = 0; i < roles.length; i++) { - payload += "\"" + roles[i] + "\""; - if (i + 1 < roles.length) { - payload += ","; - } - } - payload += "]}"; - HttpResponse response = rh.executePutRequest("/_opendistro/_security/api/internalusers/" + username, payload, new Header[0]); - Assert.assertEquals(status, response.getStatusCode()); - rh.sendAdminCertificate = sendAdminCertificate; - } - - protected void addUserWithoutPasswordOrHash(String username, String[] roles, int status) throws Exception { - boolean sendAdminCertificate = rh.sendAdminCertificate; - rh.sendAdminCertificate = true; - String payload = "{ \"backend_roles\": ["; - for (int i = 0; i < roles.length; i++) { - payload += "\" " + roles[i] + " \""; - if (i + 1 < roles.length) { - payload += ","; - } - } - payload += "]}"; - HttpResponse response = rh.executePutRequest("/_opendistro/_security/api/internalusers/" + username, payload, new Header[0]); - Assert.assertEquals(status, response.getStatusCode()); - rh.sendAdminCertificate = sendAdminCertificate; - } - - protected void addUserWithHash(String username, String hash) throws Exception { - addUserWithHash(username, hash, HttpStatus.SC_OK); - } - - protected void addUserWithHash(String username, String hash, int status) throws Exception { - boolean sendAdminCertificate = rh.sendAdminCertificate; - rh.sendAdminCertificate = true; - HttpResponse response = rh.executePutRequest("/_opendistro/_security/api/internalusers/" + username, "{\"hash\": \"" + hash + "\"}", - new Header[0]); - Assert.assertEquals(status, response.getStatusCode()); - rh.sendAdminCertificate = sendAdminCertificate; - } - - protected void addUserWithPasswordAndHash(String username, String password, String hash, int status) throws Exception { - boolean sendAdminCertificate = rh.sendAdminCertificate; - rh.sendAdminCertificate = true; - HttpResponse response = rh.executePutRequest("/_opendistro/_security/api/internalusers/" + username, "{\"hash\": \"" + hash + "\", \"password\": \"" + password + "\"}", - new Header[0]); - Assert.assertEquals(status, response.getStatusCode()); - rh.sendAdminCertificate = sendAdminCertificate; - } - - protected void checkGeneralAccess(int status, String username, String password) throws Exception { - boolean sendAdminCertificate = rh.sendAdminCertificate; - rh.sendAdminCertificate = false; - Assert.assertEquals(status, - rh.executeGetRequest("", - encodeBasicHeader(username, password)) - .getStatusCode()); - rh.sendAdminCertificate = sendAdminCertificate; - } - - protected String checkReadAccess(int status, String username, String password, String indexName, String actionType, - int id) throws Exception { - rh.sendAdminCertificate = false; - String action = indexName + "/" + actionType + "/" + id; - HttpResponse response = rh.executeGetRequest(action, encodeBasicHeader(username, password)); - int returnedStatus = response.getStatusCode(); - Assert.assertEquals(status, returnedStatus); - return response.getBody(); - - } - - protected String checkWriteAccess(int status, String username, String password, String indexName, String actionType, - int id) throws Exception { - rh.sendAdminCertificate = false; - String action = indexName + "/" + actionType + "/" + id; - String payload = "{\"value\" : \"true\"}"; - HttpResponse response = rh.executePutRequest(action, payload, encodeBasicHeader(username, password)); - int returnedStatus = response.getStatusCode(); - Assert.assertEquals(response.getBody(), status, returnedStatus); - return response.getBody(); - } - - protected void setupStarfleetIndex() throws Exception { - boolean sendAdminCertificate = rh.sendAdminCertificate; - rh.sendAdminCertificate = true; - rh.executePutRequest("sf", null, new Header[0]); - rh.executePutRequest("sf/_doc/0", "{\"number\" : \"NCC-1701-D\"}", new Header[0]); - rh.executePutRequest("sf/_doc/0", "{\"some\" : \"value\"}", new Header[0]); - rh.sendAdminCertificate = sendAdminCertificate; - } - - protected void assertHealthy() throws Exception { - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_opendistro/_security/health?pretty").getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("admin", "admin")).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("admin", "admin")).getStatusCode()); - } - - protected Settings defaultNodeSettings(boolean enableRestSSL) { - Settings.Builder builder = Settings.builder(); - - if (enableRestSSL) { - builder.put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.keystore_filepath", - FileHelper.getAbsoluteFilePathFromClassPath("restapi/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", - FileHelper.getAbsoluteFilePathFromClassPath("restapi/truststore.jks")); - } - return builder.build(); - } - - protected Map jsonStringToMap(String json) throws JsonParseException, JsonMappingException, IOException { - TypeReference> typeRef = new TypeReference>() {}; - return DefaultObjectMapper.objectMapper.readValue(json, typeRef); - } - - protected static Collection> asCollection(Class... plugins) { - return Arrays.asList(plugins); - } - - String createRestAdminPermissionsPayload(String... additionPerms) throws JsonProcessingException { - final ObjectNode rootNode = (ObjectNode) DefaultObjectMapper.objectMapper.createObjectNode(); - rootNode.set("cluster_permissions", clusterPermissionsForRestAdmin(additionPerms)); - return DefaultObjectMapper.objectMapper.writeValueAsString(rootNode); - } - - ArrayNode clusterPermissionsForRestAdmin(String... additionPerms) { - final ArrayNode permissionsArray = (ArrayNode) DefaultObjectMapper.objectMapper.createArrayNode(); - for (final Map.Entry entry : RestApiAdminPrivilegesEvaluator.ENDPOINTS_WITH_PERMISSIONS.entrySet()) { - if (entry.getKey() == Endpoint.SSL) { - permissionsArray - .add(entry.getValue().build("certs")) - .add(entry.getValue().build("reloadcerts")); - } else { - permissionsArray.add(entry.getValue().build()); - } - } - if (additionPerms.length != 0) { - Stream.of(additionPerms).forEach(permissionsArray::add); - } - return permissionsArray; - } + protected final void setupWithRestRoles(Settings nodeOverride) throws Exception { + Settings.Builder builder = Settings.builder(); + + builder.put("plugins.security.ssl.http.enabled", true) + .put(SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, PasswordValidator.ScoreStrength.FAIR.name()) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("restapi/node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("restapi/truststore.jks")); + + builder.put(rolesSettings()); + + if (null != nodeOverride) { + builder.put(nodeOverride); + } + + setup(Settings.EMPTY, new DynamicSecurityConfig(), builder.build(), init); + rh = restHelper(); + rh.keystore = "restapi/kirk-keystore.jks"; + + AuditTestUtils.updateAuditConfig(rh, nodeOverride != null ? nodeOverride : Settings.EMPTY); + } + + protected Settings rolesSettings() { + return Settings.builder() + .put("plugins.security.restapi.roles_enabled.0", "opendistro_security_role_klingons") + .put("plugins.security.restapi.roles_enabled.1", "opendistro_security_role_vulcans") + .put("plugins.security.restapi.roles_enabled.2", "opendistro_security_test") + .put("plugins.security.restapi.endpoints_disabled.global.CACHE.0", "*") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.conFiGuration.0", "*") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.wRongType.0", "WRONGType") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.ROLESMAPPING.0", "PUT") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.ROLESMAPPING.1", "DELETE") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_vulcans.CONFIG.0", "*") + .build(); + } + + protected void deleteUser(String username) throws Exception { + boolean sendAdminCertificate = rh.sendAdminCertificate; + rh.sendAdminCertificate = true; + HttpResponse response = rh.executeDeleteRequest("/_opendistro/_security/api/internalusers/" + username, new Header[0]); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + rh.sendAdminCertificate = sendAdminCertificate; + } + + protected void addUserWithPassword(String username, String password, int status) throws Exception { + addUserWithPassword(username, password, status, null); + } + + protected void addUserWithPassword(String username, String password, int status, String message) throws Exception { + boolean sendAdminCertificate = rh.sendAdminCertificate; + rh.sendAdminCertificate = true; + HttpResponse response = rh.executePutRequest( + "/_opendistro/_security/api/internalusers/" + username, + "{\"password\": \"" + password + "\"}", + new Header[0] + ); + Assert.assertEquals(status, response.getStatusCode()); + rh.sendAdminCertificate = sendAdminCertificate; + if (Objects.nonNull(message)) { + Assert.assertTrue(response.getBody().contains(message)); + } + } + + protected void addUserWithPassword(String username, String password, String[] roles, int status) throws Exception { + boolean sendAdminCertificate = rh.sendAdminCertificate; + rh.sendAdminCertificate = true; + String payload = "{" + "\"password\": \"" + password + "\"," + "\"backend_roles\": ["; + for (int i = 0; i < roles.length; i++) { + payload += "\"" + roles[i] + "\""; + if (i + 1 < roles.length) { + payload += ","; + } + } + payload += "]}"; + HttpResponse response = rh.executePutRequest("/_opendistro/_security/api/internalusers/" + username, payload, new Header[0]); + Assert.assertEquals(status, response.getStatusCode()); + rh.sendAdminCertificate = sendAdminCertificate; + } + + protected void addUserWithoutPasswordOrHash(String username, String[] roles, int status) throws Exception { + boolean sendAdminCertificate = rh.sendAdminCertificate; + rh.sendAdminCertificate = true; + String payload = "{ \"backend_roles\": ["; + for (int i = 0; i < roles.length; i++) { + payload += "\" " + roles[i] + " \""; + if (i + 1 < roles.length) { + payload += ","; + } + } + payload += "]}"; + HttpResponse response = rh.executePutRequest("/_opendistro/_security/api/internalusers/" + username, payload, new Header[0]); + Assert.assertEquals(status, response.getStatusCode()); + rh.sendAdminCertificate = sendAdminCertificate; + } + + protected void addUserWithHash(String username, String hash) throws Exception { + addUserWithHash(username, hash, HttpStatus.SC_OK); + } + + protected void addUserWithHash(String username, String hash, int status) throws Exception { + boolean sendAdminCertificate = rh.sendAdminCertificate; + rh.sendAdminCertificate = true; + HttpResponse response = rh.executePutRequest( + "/_opendistro/_security/api/internalusers/" + username, + "{\"hash\": \"" + hash + "\"}", + new Header[0] + ); + Assert.assertEquals(status, response.getStatusCode()); + rh.sendAdminCertificate = sendAdminCertificate; + } + + protected void addUserWithPasswordAndHash(String username, String password, String hash, int status) throws Exception { + boolean sendAdminCertificate = rh.sendAdminCertificate; + rh.sendAdminCertificate = true; + HttpResponse response = rh.executePutRequest( + "/_opendistro/_security/api/internalusers/" + username, + "{\"hash\": \"" + hash + "\", \"password\": \"" + password + "\"}", + new Header[0] + ); + Assert.assertEquals(status, response.getStatusCode()); + rh.sendAdminCertificate = sendAdminCertificate; + } + + protected void checkGeneralAccess(int status, String username, String password) throws Exception { + boolean sendAdminCertificate = rh.sendAdminCertificate; + rh.sendAdminCertificate = false; + Assert.assertEquals(status, rh.executeGetRequest("", encodeBasicHeader(username, password)).getStatusCode()); + rh.sendAdminCertificate = sendAdminCertificate; + } + + protected String checkReadAccess(int status, String username, String password, String indexName, String actionType, int id) + throws Exception { + rh.sendAdminCertificate = false; + String action = indexName + "/" + actionType + "/" + id; + HttpResponse response = rh.executeGetRequest(action, encodeBasicHeader(username, password)); + int returnedStatus = response.getStatusCode(); + Assert.assertEquals(status, returnedStatus); + return response.getBody(); + + } + + protected String checkWriteAccess(int status, String username, String password, String indexName, String actionType, int id) + throws Exception { + rh.sendAdminCertificate = false; + String action = indexName + "/" + actionType + "/" + id; + String payload = "{\"value\" : \"true\"}"; + HttpResponse response = rh.executePutRequest(action, payload, encodeBasicHeader(username, password)); + int returnedStatus = response.getStatusCode(); + Assert.assertEquals(response.getBody(), status, returnedStatus); + return response.getBody(); + } + + protected void setupStarfleetIndex() throws Exception { + boolean sendAdminCertificate = rh.sendAdminCertificate; + rh.sendAdminCertificate = true; + rh.executePutRequest("sf", null, new Header[0]); + rh.executePutRequest("sf/_doc/0", "{\"number\" : \"NCC-1701-D\"}", new Header[0]); + rh.executePutRequest("sf/_doc/0", "{\"some\" : \"value\"}", new Header[0]); + rh.sendAdminCertificate = sendAdminCertificate; + } + + protected void assertHealthy() throws Exception { + Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_opendistro/_security/health?pretty").getStatusCode()); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("admin", "admin")).getStatusCode() + ); + Assert.assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("*/_search?pretty", encodeBasicHeader("admin", "admin")).getStatusCode() + ); + } + + protected Settings defaultNodeSettings(boolean enableRestSSL) { + Settings.Builder builder = Settings.builder(); + + if (enableRestSSL) { + builder.put("plugins.security.ssl.http.enabled", true) + .put( + "plugins.security.ssl.http.keystore_filepath", + FileHelper.getAbsoluteFilePathFromClassPath("restapi/node-0-keystore.jks") + ) + .put( + "plugins.security.ssl.http.truststore_filepath", + FileHelper.getAbsoluteFilePathFromClassPath("restapi/truststore.jks") + ); + } + return builder.build(); + } + + protected Map jsonStringToMap(String json) throws JsonParseException, JsonMappingException, IOException { + TypeReference> typeRef = new TypeReference>() { + }; + return DefaultObjectMapper.objectMapper.readValue(json, typeRef); + } + + protected static Collection> asCollection(Class... plugins) { + return Arrays.asList(plugins); + } + + String createRestAdminPermissionsPayload(String... additionPerms) throws JsonProcessingException { + final ObjectNode rootNode = (ObjectNode) DefaultObjectMapper.objectMapper.createObjectNode(); + rootNode.set("cluster_permissions", clusterPermissionsForRestAdmin(additionPerms)); + return DefaultObjectMapper.objectMapper.writeValueAsString(rootNode); + } + + ArrayNode clusterPermissionsForRestAdmin(String... additionPerms) { + final ArrayNode permissionsArray = (ArrayNode) DefaultObjectMapper.objectMapper.createArrayNode(); + for (final Map.Entry< + Endpoint, + RestApiAdminPrivilegesEvaluator.PermissionBuilder> entry : RestApiAdminPrivilegesEvaluator.ENDPOINTS_WITH_PERMISSIONS + .entrySet()) { + if (entry.getKey() == Endpoint.SSL) { + permissionsArray.add(entry.getValue().build("certs")).add(entry.getValue().build("reloadcerts")); + } else { + permissionsArray.add(entry.getValue().build()); + } + } + if (additionPerms.length != 0) { + Stream.of(additionPerms).forEach(permissionsArray::add); + } + return permissionsArray; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java index 25974e322f..c07b0f1333 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java @@ -31,11 +31,12 @@ public class AccountApiTest extends AbstractRestApiUnitTest { private final String BASE_ENDPOINT; private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public AccountApiTest(){ + public AccountApiTest() { BASE_ENDPOINT = getEndpointPrefix() + "/api/"; ENDPOINT = getEndpointPrefix() + "/api/account"; } @@ -189,14 +190,16 @@ public void testPutAccountRetainsAccountInformation() throws Exception { final String testUsername = "test"; final String testPassword = "test-password"; final String newPassword = "new-password"; - final String createInternalUserPayload = "{\n" + - " \"password\": \"" + testPassword + "\",\n" + - " \"backend_roles\": [\"test-backend-role-1\"],\n" + - " \"opendistro_security_roles\": [\"opendistro_security_all_access\"],\n" + - " \"attributes\": {\n" + - " \"attribute1\": \"value1\"\n" + - " }\n" + - "}"; + final String createInternalUserPayload = "{\n" + + " \"password\": \"" + + testPassword + + "\",\n" + + " \"backend_roles\": [\"test-backend-role-1\"],\n" + + " \"opendistro_security_roles\": [\"opendistro_security_all_access\"],\n" + + " \"attributes\": {\n" + + " \"attribute1\": \"value1\"\n" + + " }\n" + + "}"; final String changePasswordPayload = "{\"password\":\"" + newPassword + "\", \"current_password\":\"" + testPassword + "\"}"; final String internalUserEndpoint = BASE_ENDPOINT + "internalusers/" + testUsername; @@ -215,9 +218,9 @@ public void testPutAccountRetainsAccountInformation() throws Exception { response = rh.executeGetRequest(internalUserEndpoint); assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings responseBody = Settings.builder() - .loadFromSource(response.getBody(), XContentType.JSON) - .build() - .getAsSettings(testUsername); + .loadFromSource(response.getBody(), XContentType.JSON) + .build() + .getAsSettings(testUsername); assertTrue(responseBody.getAsList("backend_roles").contains("test-backend-role-1")); assertTrue(responseBody.getAsList("opendistro_security_roles").contains("opendistro_security_all_access")); assertEquals(responseBody.getAsSettings("attributes").get("attribute1"), "value1"); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java index 8030703197..c87698155f 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java @@ -33,11 +33,12 @@ public class ActionGroupsApiTest extends AbstractRestApiUnitTest { private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public ActionGroupsApiTest(){ + public ActionGroupsApiTest() { ENDPOINT = getEndpointPrefix() + "/api/actiongroups"; } @@ -69,7 +70,7 @@ public void testActionGroupsApi() throws Exception { void verifyGetForSuperAdmin(final Header[] header) throws Exception { // --- GET_UT // GET_UT, actiongroup exists - HttpResponse response = rh.executeGetRequest(ENDPOINT+"/CRUD_UT", header); + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/CRUD_UT", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); List permissions = settings.getAsList("CRUD_UT.allowed_actions"); @@ -79,7 +80,7 @@ void verifyGetForSuperAdmin(final Header[] header) throws Exception { Assert.assertTrue(permissions.contains("OPENDISTRO_SECURITY_WRITE")); // GET_UT, actiongroup does not exist - response = rh.executeGetRequest(ENDPOINT+"/nothinghthere", header); + response = rh.executeGetRequest(ENDPOINT + "/nothinghthere", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // GET_UT, old endpoint @@ -112,13 +113,13 @@ void verifyDeleteForSuperAdmin(final Header[] header, final boolean userAdminCer // Non-existing role rh.sendAdminCertificate = userAdminCert; - HttpResponse response = rh.executeDeleteRequest(ENDPOINT+"/idonotexist", header); + HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/idonotexist", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // remove action group READ_UT, read access not possible since // opendistro_security_role_starfleet // uses this action group. - response = rh.executeDeleteRequest(ENDPOINT+"/READ_UT", header); + response = rh.executeDeleteRequest(ENDPOINT + "/READ_UT", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); rh.sendAdminCertificate = false; @@ -133,7 +134,7 @@ void verifyDeleteForSuperAdmin(final Header[] header, final boolean userAdminCer // now remove also CRUD_UT groups, write also not possible anymore rh.sendAdminCertificate = true; - response = rh.executeDeleteRequest(ENDPOINT+"/CRUD_UT", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/CRUD_UT", new Header[0]); rh.sendAdminCertificate = false; checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 0); checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 0); @@ -143,19 +144,18 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean userAdminCert) // -- PUT // put with empty payload, must fail rh.sendAdminCertificate = userAdminCert; - HttpResponse response = rh.executePutRequest(ENDPOINT+"/SOMEGROUP", "", header); + HttpResponse response = rh.executePutRequest(ENDPOINT + "/SOMEGROUP", "", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason")); // put new configuration with invalid payload, must fail - response = rh.executePutRequest(ENDPOINT+"/SOMEGROUP", FileHelper.loadFile("restapi/actiongroup_not_parseable.json"), - header); + response = rh.executePutRequest(ENDPOINT + "/SOMEGROUP", FileHelper.loadFile("restapi/actiongroup_not_parseable.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason")); - response = rh.executePutRequest(ENDPOINT+"/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_crud.json"), header); + response = rh.executePutRequest(ENDPOINT + "/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_crud.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; @@ -166,7 +166,7 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean userAdminCert) // restore READ_UT action groups rh.sendAdminCertificate = userAdminCert; - response = rh.executePutRequest(ENDPOINT+"/READ_UT", FileHelper.loadFile("restapi/actiongroup_read.json"), header); + response = rh.executePutRequest(ENDPOINT + "/READ_UT", FileHelper.loadFile("restapi/actiongroup_read.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; @@ -176,49 +176,51 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean userAdminCert) // -- PUT, new JSON format including readonly flag, disallowed in REST API rh.sendAdminCertificate = userAdminCert; - response = rh.executePutRequest(ENDPOINT+"/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_readonly.json"), header); + response = rh.executePutRequest(ENDPOINT + "/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_readonly.json"), header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // -- DELETE read only resource, must be forbidden // superAdmin can delete read only resource rh.sendAdminCertificate = userAdminCert; - response = rh.executeDeleteRequest(ENDPOINT+"/GET_UT", header); + response = rh.executeDeleteRequest(ENDPOINT + "/GET_UT", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // -- PUT read only resource, must be forbidden // superAdmin can add/update read only resource rh.sendAdminCertificate = userAdminCert; - response = rh.executePutRequest(ENDPOINT+"/GET_UT", FileHelper.loadFile("restapi/actiongroup_read.json"), header); + response = rh.executePutRequest(ENDPOINT + "/GET_UT", FileHelper.loadFile("restapi/actiongroup_read.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); Assert.assertFalse(response.getBody().contains("Resource 'GET_UT' is read-only.")); // PUT with role name rh.sendAdminCertificate = userAdminCert; - response = rh.executePutRequest(ENDPOINT+"/kibana_user", FileHelper.loadFile("restapi/actiongroup_read.json"), header); + response = rh.executePutRequest(ENDPOINT + "/kibana_user", FileHelper.loadFile("restapi/actiongroup_read.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("kibana_user is an existing role. A action group cannot be named with an existing role name.")); + Assert.assertTrue( + response.getBody().contains("kibana_user is an existing role. A action group cannot be named with an existing role name.") + ); // PUT with self-referencing action groups rh.sendAdminCertificate = userAdminCert; - response = rh.executePutRequest(ENDPOINT+"/reference_itself", "{\"allowed_actions\": [\"reference_itself\"]}", header); + response = rh.executePutRequest(ENDPOINT + "/reference_itself", "{\"allowed_actions\": [\"reference_itself\"]}", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("reference_itself cannot be an allowed_action of itself")); // -- GET_UT hidden resource, must be 404 but super admin can find it rh.sendAdminCertificate = userAdminCert; - response = rh.executeGetRequest(ENDPOINT+"/INTERNAL", header); + response = rh.executeGetRequest(ENDPOINT + "/INTERNAL", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"hidden\":true")); // -- DELETE hidden resource, must be 404 rh.sendAdminCertificate = userAdminCert; - response = rh.executeDeleteRequest(ENDPOINT+"/INTERNAL", header); + response = rh.executeDeleteRequest(ENDPOINT + "/INTERNAL", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("'INTERNAL' deleted.")); // -- PUT hidden resource, must be forbidden rh.sendAdminCertificate = userAdminCert; - response = rh.executePutRequest(ENDPOINT+"/INTERNAL", FileHelper.loadFile("restapi/actiongroup_read.json"), header); + response = rh.executePutRequest(ENDPOINT + "/INTERNAL", FileHelper.loadFile("restapi/actiongroup_read.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); } @@ -226,52 +228,79 @@ void verifyPatchForSuperAdmin(final Header[] header, final boolean userAdminCert // -- PATCH // PATCH on non-existing resource rh.sendAdminCertificate = userAdminCert; - HttpResponse response = rh.executePatchRequest(ENDPOINT+"/imnothere", - "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", header); + HttpResponse response = rh.executePatchRequest( + ENDPOINT + "/imnothere", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // PATCH read only resource, must be forbidden // SuperAdmin can patch read only resource rh.sendAdminCertificate = userAdminCert; - response = rh.executePatchRequest(ENDPOINT+"/GET_UT", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/GET_UT", + "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH with self-referencing action groups rh.sendAdminCertificate = userAdminCert; - response = rh.executePatchRequest(ENDPOINT+"/GET_UT", "[{ \"op\": \"add\", \"path\": \"/allowed_actions/-\", \"value\": \"GET_UT\" }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/GET_UT", + "[{ \"op\": \"add\", \"path\": \"/allowed_actions/-\", \"value\": \"GET_UT\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("GET_UT cannot be an allowed_action of itself")); // bulk PATCH with self-referencing action groups - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/BULKNEW1\", \"value\": {\"allowed_actions\": [\"BULKNEW1\"] } }," + - "{ \"op\": \"add\", \"path\": \"/BULKNEW2\", \"value\": {\"allowed_actions\": [\"READ_UT\"] } }]", header); + response = rh.executePatchRequest( + ENDPOINT, + "[{ \"op\": \"add\", \"path\": \"/BULKNEW1\", \"value\": {\"allowed_actions\": [\"BULKNEW1\"] } }," + + "{ \"op\": \"add\", \"path\": \"/BULKNEW2\", \"value\": {\"allowed_actions\": [\"READ_UT\"] } }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("BULKNEW1 cannot be an allowed_action of itself")); // PATCH hidden resource, must be not found, can be found by superadmin, but fails with no path exist error rh.sendAdminCertificate = userAdminCert; - response = rh.executePatchRequest(ENDPOINT+"/INTERNAL", - "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/INTERNAL", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error rh.sendAdminCertificate = userAdminCert; - response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", header); + response = rh.executePatchRequest(ENDPOINT + "/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertTrue(response.getBody(), response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); + Assert.assertTrue( + response.getBody(), + response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*") + ); // PATCH with relative JSON pointer, must fail rh.sendAdminCertificate = userAdminCert; - response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"1/INTERNAL/allowed_actions/-\", " + - "\"value\": \"OPENDISTRO_SECURITY_DELETE\" }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/CRUD_UT", + "[{ \"op\": \"add\", \"path\": \"1/INTERNAL/allowed_actions/-\", " + "\"value\": \"OPENDISTRO_SECURITY_DELETE\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH new format rh.sendAdminCertificate = userAdminCert; - response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"/allowed_actions/-\", " + - "\"value\": \"OPENDISTRO_SECURITY_DELETE\" }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/CRUD_UT", + "[{ \"op\": \"add\", \"path\": \"/allowed_actions/-\", " + "\"value\": \"OPENDISTRO_SECURITY_DELETE\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT+"/CRUD_UT", header); + response = rh.executeGetRequest(ENDPOINT + "/CRUD_UT", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); List permissions = settings.getAsList("CRUD_UT.allowed_actions"); @@ -281,12 +310,15 @@ void verifyPatchForSuperAdmin(final Header[] header, final boolean userAdminCert Assert.assertTrue(permissions.contains("OPENDISTRO_SECURITY_WRITE")); Assert.assertTrue(permissions.contains("OPENDISTRO_SECURITY_DELETE")); - // -- PATCH on whole config resource // PATCH read only resource, must be forbidden // SuperAdmin can patch read only resource rh.sendAdminCertificate = userAdminCert; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/GET_UT/a\", \"value\": [ \"foo\", \"bar\" ] }]", header); + response = rh.executePatchRequest( + ENDPOINT, + "[{ \"op\": \"add\", \"path\": \"/GET_UT/a\", \"value\": [ \"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); rh.sendAdminCertificate = userAdminCert; @@ -295,7 +327,11 @@ void verifyPatchForSuperAdmin(final Header[] header, final boolean userAdminCert // PATCH hidden resource, must be bad request rh.sendAdminCertificate = userAdminCert; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/INTERNAL/a\", \"value\": [ \"foo\", \"bar\" ] }]", header); + response = rh.executePatchRequest( + ENDPOINT, + "[{ \"op\": \"add\", \"path\": \"/INTERNAL/a\", \"value\": [ \"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH delete read only resource, must be forbidden @@ -310,7 +346,6 @@ void verifyPatchForSuperAdmin(final Header[] header, final boolean userAdminCert Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"message\":\"Resource updated.")); - // PATCH value of hidden flag, must fail with validation error rh.sendAdminCertificate = userAdminCert; response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/CRUD_UT/hidden\", \"value\": true }]", header); @@ -319,16 +354,24 @@ void verifyPatchForSuperAdmin(final Header[] header, final boolean userAdminCert // add new resource with hidden flag, must fail with validation error rh.sendAdminCertificate = userAdminCert; - response = rh.executePatchRequest(ENDPOINT, - "[{ \"op\": \"add\", \"path\": \"/NEWNEWNEW\", \"value\": {\"allowed_actions\": [\"indices:data/write*\"], \"hidden\":true }}]", header); + response = rh.executePatchRequest( + ENDPOINT, + "[{ \"op\": \"add\", \"path\": \"/NEWNEWNEW\", \"value\": {\"allowed_actions\": [\"indices:data/write*\"], \"hidden\":true }}]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // add new valid resources rh.sendAdminCertificate = userAdminCert; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/BULKNEW1\", \"value\": {\"allowed_actions\": [\"indices:data/*\", \"cluster:monitor/*\"] } }," + "{ \"op\": \"add\", \"path\": \"/BULKNEW2\", \"value\": {\"allowed_actions\": [\"READ_UT\"] } }]", header); + response = rh.executePatchRequest( + ENDPOINT, + "[{ \"op\": \"add\", \"path\": \"/BULKNEW1\", \"value\": {\"allowed_actions\": [\"indices:data/*\", \"cluster:monitor/*\"] } }," + + "{ \"op\": \"add\", \"path\": \"/BULKNEW2\", \"value\": {\"allowed_actions\": [\"READ_UT\"] } }]", + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT+"/BULKNEW1", header); + response = rh.executeGetRequest(ENDPOINT + "/BULKNEW1", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); permissions = settings.getAsList("BULKNEW1.allowed_actions"); @@ -337,7 +380,7 @@ void verifyPatchForSuperAdmin(final Header[] header, final boolean userAdminCert Assert.assertTrue(permissions.contains("indices:data/*")); Assert.assertTrue(permissions.contains("cluster:monitor/*")); - response = rh.executeGetRequest(ENDPOINT+"/BULKNEW2", header); + response = rh.executeGetRequest(ENDPOINT + "/BULKNEW2", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); permissions = settings.getAsList("BULKNEW2.allowed_actions"); @@ -348,11 +391,11 @@ void verifyPatchForSuperAdmin(final Header[] header, final boolean userAdminCert // delete resource response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/BULKNEW1\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT+"/BULKNEW1", header); + response = rh.executeGetRequest(ENDPOINT + "/BULKNEW1", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // assert other resource is still there - response = rh.executeGetRequest(ENDPOINT+"/BULKNEW2", header); + response = rh.executeGetRequest(ENDPOINT + "/BULKNEW2", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); permissions = settings.getAsList("BULKNEW2.allowed_actions"); @@ -373,10 +416,10 @@ public void testActionGroupsApiForRestAdmin() throws Exception { addUserWithPassword("picard", "picardpicardpicard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 0); - verifyGetForSuperAdmin(new Header[] {restApiAdminHeader}); - verifyDeleteForSuperAdmin(new Header[]{restApiAdminHeader}, false); - verifyPutForSuperAdmin(new Header[]{restApiAdminHeader}, false); - verifyPatchForSuperAdmin(new Header[]{restApiAdminHeader}, false); + verifyGetForSuperAdmin(new Header[] { restApiAdminHeader }); + verifyDeleteForSuperAdmin(new Header[] { restApiAdminHeader }, false); + verifyPutForSuperAdmin(new Header[] { restApiAdminHeader }, false); + verifyPatchForSuperAdmin(new Header[] { restApiAdminHeader }, false); } @Test @@ -391,10 +434,10 @@ public void testActionGroupsApiForActionGroupsRestApiAdmin() throws Exception { addUserWithPassword("picard", "picardpicardpicard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 0); - verifyGetForSuperAdmin(new Header[] {restApiAdminActionGroupsHeader}); - verifyDeleteForSuperAdmin(new Header[]{restApiAdminActionGroupsHeader}, false); - verifyPutForSuperAdmin(new Header[]{restApiAdminActionGroupsHeader}, false); - verifyPatchForSuperAdmin(new Header[]{restApiAdminActionGroupsHeader}, false); + verifyGetForSuperAdmin(new Header[] { restApiAdminActionGroupsHeader }); + verifyDeleteForSuperAdmin(new Header[] { restApiAdminActionGroupsHeader }, false); + verifyPutForSuperAdmin(new Header[] { restApiAdminActionGroupsHeader }, false); + verifyPatchForSuperAdmin(new Header[] { restApiAdminActionGroupsHeader }, false); } @Test @@ -405,8 +448,7 @@ public void testCreateActionGroupWithRestAdminPermissionsForbidden() throws Exce final Header restApiAdminActionGroupsHeader = encodeBasicHeader("rest_api_admin_actiongroups", "rest_api_admin_actiongroups"); final Header restApiHeader = encodeBasicHeader("test", "test"); - HttpResponse response = rh.executePutRequest(ENDPOINT + "/rest_api_admin_group", restAdminAllowedActions(), - restApiAdminHeader); + HttpResponse response = rh.executePutRequest(ENDPOINT + "/rest_api_admin_group", restAdminAllowedActions(), restApiAdminHeader); Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); response = rh.executePutRequest(ENDPOINT + "/rest_api_admin_group", restAdminAllowedActions(), restApiAdminActionGroupsHeader); Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); @@ -432,10 +474,7 @@ String restAdminPatchBody() throws JsonProcessingException { final ObjectNode opAddRootNode = DefaultObjectMapper.objectMapper.createObjectNode(); final ObjectNode allowedActionsNode = DefaultObjectMapper.objectMapper.createObjectNode(); allowedActionsNode.set("allowed_actions", clusterPermissionsForRestAdmin("cluster/*")); - opAddRootNode - .put("op", "add") - .put("path", "/rest_api_admin_group") - .set("value", allowedActionsNode); + opAddRootNode.put("op", "add").put("path", "/rest_api_admin_group").set("value", allowedActionsNode); rootNode.add(opAddRootNode); return DefaultObjectMapper.objectMapper.writeValueAsString(rootNode); } @@ -452,44 +491,60 @@ public void testActionGroupsApiForNonSuperAdmin() throws Exception { HttpResponse response; // Delete read only actiongroups - response = rh.executeDeleteRequest(ENDPOINT+"/create_index" , new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/create_index", new Header[0]); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Put read only actiongroups - response = rh.executePutRequest(ENDPOINT+"/create_index", FileHelper.loadFile("restapi/actiongroup_crud.json"), new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/create_index", FileHelper.loadFile("restapi/actiongroup_crud.json"), new Header[0]); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch single read only actiongroups - response = rh.executePatchRequest(ENDPOINT+"/create_index", "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT + "/create_index", + "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch multiple read only actiongroups - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"replace\", \"path\": \"/create_index/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT, + "[{ \"op\": \"replace\", \"path\": \"/create_index/description\", \"value\": \"foo\" }]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT+"/INTERNAL" , new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/INTERNAL", new Header[0]); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Delete hidden actiongroups - response = rh.executeDeleteRequest(ENDPOINT+"/INTERNAL" , new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/INTERNAL", new Header[0]); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Put hidden actiongroups - response = rh.executePutRequest(ENDPOINT+"/INTERNAL", FileHelper.loadFile("restapi/actiongroup_crud.json"), new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/INTERNAL", FileHelper.loadFile("restapi/actiongroup_crud.json"), new Header[0]); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch hidden actiongroups - response = rh.executePatchRequest(ENDPOINT+"/INTERNAL", "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT + "/INTERNAL", + "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch multiple hidden actiongroups - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"replace\", \"path\": \"/INTERNAL/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT, + "[{ \"op\": \"replace\", \"path\": \"/INTERNAL/description\", \"value\": \"foo\" }]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); } @Test - public void checkNullElementsInArray() throws Exception{ + public void checkNullElementsInArray() throws Exception { setup(); rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java index 50090fcfcc..79ce591723 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java @@ -11,7 +11,6 @@ package org.opensearch.security.dlic.rest.api; - import java.util.Map; import java.util.stream.Collectors; @@ -60,24 +59,32 @@ public class AllowlistApiTest extends AbstractRestApiUnitTest { * * @throws Exception */ - private void checkGetAndPutAllowlistPermissions(final int expectedStatus, final boolean sendAdminCertificate, final Header... headers) throws Exception { + private void checkGetAndPutAllowlistPermissions(final int expectedStatus, final boolean sendAdminCertificate, final Header... headers) + throws Exception { final boolean prevSendAdminCertificate = rh.sendAdminCertificate; rh.sendAdminCertificate = sendAdminCertificate; - //CHECK GET REQUEST + // CHECK GET REQUEST response = rh.executeGetRequest(ENDPOINT, headers); assertThat(response.getBody(), response.getStatusCode(), equalTo(expectedStatus)); if (expectedStatus == HttpStatus.SC_OK) { - //Note: the response has no whitespaces, so the .json file does not have whitespaces - Assert.assertEquals(FileHelper.loadFile("restapi/whitelist_response_success.json"), FileHelper.loadFile("restapi/whitelist_response_success.json")); + // Note: the response has no whitespaces, so the .json file does not have whitespaces + Assert.assertEquals( + FileHelper.loadFile("restapi/whitelist_response_success.json"), + FileHelper.loadFile("restapi/whitelist_response_success.json") + ); } - //FORBIDDEN FOR NON SUPER ADMIN + // FORBIDDEN FOR NON SUPER ADMIN if (expectedStatus == HttpStatus.SC_FORBIDDEN) { assertTrue(response.getBody().contains("API allowed only for super admin.")); } - //CHECK PUT REQUEST - response = rh.executePutRequest(ENDPOINT, "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", headers); + // CHECK PUT REQUEST + response = rh.executePutRequest( + ENDPOINT, + "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", + headers + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(expectedStatus)); rh.sendAdminCertificate = prevSendAdminCertificate; @@ -100,7 +107,10 @@ public void testPutUnknownKey() throws Exception { setup(); rh.sendAdminCertificate = true; - RestHelper.HttpResponse response = rh.executePutRequest(ENDPOINT, "{ \"unknownkey\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}"); + RestHelper.HttpResponse response = rh.executePutRequest( + ENDPOINT, + "{ \"unknownkey\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}" + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); assertTrue(response.getBody().contains("invalid_keys")); assertHealthy(); @@ -111,7 +121,10 @@ public void testPutInvalidJson() throws Exception { setup(); rh.sendAdminCertificate = true; - RestHelper.HttpResponse response = rh.executePutRequest(ENDPOINT, "{ \"invalid\"::{{ [\"*\"], \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}"); + RestHelper.HttpResponse response = rh.executePutRequest( + ENDPOINT, + "{ \"invalid\"::{{ [\"*\"], \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}" + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); assertHealthy(); } @@ -147,7 +160,7 @@ public void testAllowlistApi() throws Exception { // No creds, no admin certificate - UNAUTHORIZED checkGetAndPutAllowlistPermissions(HttpStatus.SC_UNAUTHORIZED, false); - //non admin creds, no admin certificate - FORBIDDEN + // non admin creds, no admin certificate - FORBIDDEN checkGetAndPutAllowlistPermissions(HttpStatus.SC_FORBIDDEN, false, nonAdminCredsHeader); // admin creds, no admin certificate - FORBIDDEN @@ -183,37 +196,45 @@ public void testAllowlistApiWithAllowListPermissions() throws Exception { @Test public void testAllowlistAuditComplianceLogging() throws Exception { Settings settings = 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, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .build(); + .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, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) + .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .build(); setupWithRestRoles(settings); TestAuditlogImpl.clear(); // any creds, admin certificate - OK checkGetAndPutAllowlistPermissions(HttpStatus.SC_OK, true, nonAdminCredsHeader); - //TESTS THAT 1 READ AND 1 WRITE HAPPENS IN testGetAndPut() + // TESTS THAT 1 READ AND 1 WRITE HAPPENS IN testGetAndPut() final Map expectedCategoryCounts = ImmutableMap.of( - AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ, 1L, - AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE, 1L); - Map actualCategoryCounts = TestAuditlogImpl.messages.stream().collect(Collectors.groupingBy(AuditMessage::getCategory, Collectors.counting())); + AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ, + 1L, + AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE, + 1L + ); + Map actualCategoryCounts = TestAuditlogImpl.messages.stream() + .collect(Collectors.groupingBy(AuditMessage::getCategory, Collectors.counting())); assertThat(actualCategoryCounts, equalTo(expectedCategoryCounts)); } @Test - public void testAllowlistInvalidHttpRequestMethod() throws Exception{ + public void testAllowlistInvalidHttpRequestMethod() throws Exception { setup(); rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT, "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GE\"],\"/_cat/indices\": [\"PUT\"] }}", adminCredsHeader); + response = rh.executePutRequest( + ENDPOINT, + "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GE\"],\"/_cat/indices\": [\"PUT\"] }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_INTERNAL_SERVER_ERROR)); assertTrue(response.getBody().contains("\\\"GE\\\": not one of the values accepted for Enum class")); } @@ -226,37 +247,53 @@ public void testAllowlistInvalidHttpRequestMethod() throws Exception{ * @throws Exception */ @Test - public void testPatchApi() throws Exception{ + public void testPatchApi() throws Exception { setup(); rh.sendAdminCertificate = true; - //PATCH entire config entry - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"replace\", \"path\": \"/config\", \"value\": {\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"PUT\"] }}}]", new Header[0]); + // PATCH entire config entry + response = rh.executePatchRequest( + ENDPOINT, + "[{ \"op\": \"replace\", \"path\": \"/config\", \"value\": {\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"PUT\"] }}}]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT, adminCredsHeader); - assertEquals(response.getBody(),"{\"config\":{\"enabled\":true,\"requests\":{\"/_cat/nodes\":[\"GET\"],\"/_cat/indices\":[\"PUT\"]}}}"); - - //PATCH just requests - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"replace\", \"path\": \"/config/requests\", \"value\": {\"/_cat/nodes\": [\"GET\"]}}]", new Header[0]); + assertEquals( + response.getBody(), + "{\"config\":{\"enabled\":true,\"requests\":{\"/_cat/nodes\":[\"GET\"],\"/_cat/indices\":[\"PUT\"]}}}" + ); + + // PATCH just requests + response = rh.executePatchRequest( + ENDPOINT, + "[{ \"op\": \"replace\", \"path\": \"/config/requests\", \"value\": {\"/_cat/nodes\": [\"GET\"]}}]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT, adminCredsHeader); assertTrue(response.getBody().contains("\"requests\":{\"/_cat/nodes\":[\"GET\"]}")); - //PATCH just allowlisted_enabled using "replace" operation - works when enabled is already true - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"replace\", \"path\": \"/config/enabled\", \"value\": false}]", new Header[0]); + // PATCH just allowlisted_enabled using "replace" operation - works when enabled is already true + response = rh.executePatchRequest( + ENDPOINT, + "[{ \"op\": \"replace\", \"path\": \"/config/enabled\", \"value\": false}]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT, adminCredsHeader); assertTrue(response.getBody().contains("\"enabled\":false")); - //PATCH just enabled using "add" operation when it is currently false - works correctly + // PATCH just enabled using "add" operation when it is currently false - works correctly response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/config/enabled\", \"value\": true}]", new Header[0]); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT, adminCredsHeader); assertTrue(response.getBody().contains("\"enabled\":true")); - //PATCH just enabled using "add" operation when it is currently true - works correctly + // PATCH just enabled using "add" operation when it is currently true - works correctly response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/config/enabled\", \"value\": false}]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());response = rh.executeGetRequest(ENDPOINT, adminCredsHeader); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + response = rh.executeGetRequest(ENDPOINT, adminCredsHeader); response = rh.executeGetRequest(ENDPOINT, adminCredsHeader); assertTrue(response.getBody().contains("\"enabled\":false")); } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java index 018af18293..3f97bae693 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AuditApiActionTest.java @@ -53,11 +53,12 @@ public class AuditApiActionTest extends AbstractRestApiUnitTest { private final String ENDPOINT; private final String CONFIG_ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public AuditApiActionTest(){ + public AuditApiActionTest() { ENDPOINT = getEndpointPrefix() + "/api/audit"; CONFIG_ENDPOINT = ENDPOINT + "/config"; } @@ -106,16 +107,18 @@ public void testDisabledCategoryOrder() throws Exception { setup(); final List testCategories = ImmutableList.of("SSL_EXCEPTION", "AUTHENTICATED", "BAD_HEADERS"); - final AuditConfig auditConfig = new AuditConfig(true, AuditConfig.Filter.from( - ImmutableMap.of("disabled_rest_categories", testCategories) - ), ComplianceConfig.DEFAULT); + final AuditConfig auditConfig = new AuditConfig( + true, + AuditConfig.Filter.from(ImmutableMap.of("disabled_rest_categories", testCategories)), + ComplianceConfig.DEFAULT + ); final ObjectNode json = DefaultObjectMapper.objectMapper.valueToTree(auditConfig); testPutRequest(json, HttpStatus.SC_OK, true); RestHelper.HttpResponse response = rh.executeGetRequest(ENDPOINT, adminCredsHeader); List actual = Streams.stream(readTree(response.getBody()).at("/config/audit/disabled_rest_categories").iterator()) - .map(JsonNode::textValue) - .collect(Collectors.toList()); + .map(JsonNode::textValue) + .collect(Collectors.toList()); assertEquals(testCategories, actual); } @@ -125,36 +128,69 @@ public void testInvalidDisabledCategories() throws Exception { rh.sendAdminCertificate = true; // test bad request for REST disabled categories - AuditConfig auditConfig = new AuditConfig(true, AuditConfig.Filter.from( - ImmutableMap.of("disabled_rest_categories", ImmutableList.of("INDEX_EVENT", "COMPLIANCE_DOC_READ")) - ), ComplianceConfig.DEFAULT); + AuditConfig auditConfig = new AuditConfig( + true, + AuditConfig.Filter.from(ImmutableMap.of("disabled_rest_categories", ImmutableList.of("INDEX_EVENT", "COMPLIANCE_DOC_READ"))), + ComplianceConfig.DEFAULT + ); ObjectNode json = DefaultObjectMapper.objectMapper.valueToTree(auditConfig); RestHelper.HttpResponse response = rh.executePutRequest(CONFIG_ENDPOINT, writeValueAsString(json, false)); assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // test success for REST disabled categories - auditConfig = new AuditConfig(true, AuditConfig.Filter.from( - ImmutableMap.of("disabled_rest_categories", - ImmutableList.of("BAD_HEADERS", "SSL_EXCEPTION", "AUTHENTICATED", "FAILED_LOGIN", "GRANTED_PRIVILEGES", "MISSING_PRIVILEGES")) - ), ComplianceConfig.DEFAULT); + auditConfig = new AuditConfig( + true, + AuditConfig.Filter.from( + ImmutableMap.of( + "disabled_rest_categories", + ImmutableList.of( + "BAD_HEADERS", + "SSL_EXCEPTION", + "AUTHENTICATED", + "FAILED_LOGIN", + "GRANTED_PRIVILEGES", + "MISSING_PRIVILEGES" + ) + ) + ), + ComplianceConfig.DEFAULT + ); json = DefaultObjectMapper.objectMapper.valueToTree(auditConfig); response = rh.executePutRequest(CONFIG_ENDPOINT, writeValueAsString(json, false)); assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // test bad request for transport disabled categories - auditConfig = new AuditConfig(true, AuditConfig.Filter.from( - ImmutableMap.of("disabled_transport_categories", - ImmutableList.of("COMPLIANCE_DOC_READ", "COMPLIANCE_DOC_WRITE")) - ), ComplianceConfig.DEFAULT); + auditConfig = new AuditConfig( + true, + AuditConfig.Filter.from( + ImmutableMap.of("disabled_transport_categories", ImmutableList.of("COMPLIANCE_DOC_READ", "COMPLIANCE_DOC_WRITE")) + ), + ComplianceConfig.DEFAULT + ); json = DefaultObjectMapper.objectMapper.valueToTree(auditConfig); response = rh.executePutRequest(CONFIG_ENDPOINT, writeValueAsString(json, false)); assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // test success for transport disabled categories - auditConfig = new AuditConfig(true, AuditConfig.Filter.from( - ImmutableMap.of("disabled_transport_categories", - ImmutableList.of("BAD_HEADERS", "SSL_EXCEPTION", "AUTHENTICATED", "FAILED_LOGIN", "GRANTED_PRIVILEGES", "MISSING_PRIVILEGES", "INDEX_EVENT", "OPENDISTRO_SECURITY_INDEX_ATTEMPT")) - ), ComplianceConfig.DEFAULT); + auditConfig = new AuditConfig( + true, + AuditConfig.Filter.from( + ImmutableMap.of( + "disabled_transport_categories", + ImmutableList.of( + "BAD_HEADERS", + "SSL_EXCEPTION", + "AUTHENTICATED", + "FAILED_LOGIN", + "GRANTED_PRIVILEGES", + "MISSING_PRIVILEGES", + "INDEX_EVENT", + "OPENDISTRO_SECURITY_INDEX_ATTEMPT" + ) + ) + ), + ComplianceConfig.DEFAULT + ); json = DefaultObjectMapper.objectMapper.valueToTree(auditConfig); response = rh.executePutRequest(CONFIG_ENDPOINT, writeValueAsString(json, false)); assertEquals(HttpStatus.SC_OK, response.getStatusCode()); @@ -162,7 +198,12 @@ public void testInvalidDisabledCategories() throws Exception { @Test public void testReadonlyApi() throws Exception { - final List readonlyFields = ImmutableList.of("/audit/enable_rest", "/audit/disabled_rest_categories", "/audit/ignore_requests", "/compliance/read_watched_fields"); + final List readonlyFields = ImmutableList.of( + "/audit/enable_rest", + "/audit/disabled_rest_categories", + "/audit/ignore_requests", + "/compliance/read_watched_fields" + ); updateStaticResourceReadonly(readonlyFields); setupWithRestRoles(null); @@ -172,8 +213,8 @@ public void testReadonlyApi() throws Exception { RestHelper.HttpResponse response = rh.executeGetRequest(ENDPOINT, adminCredsHeader); assertEquals(HttpStatus.SC_OK, response.getStatusCode()); List actual = Streams.stream(readTree(response.getBody()).get("_readonly").iterator()) - .map(JsonNode::textValue) - .collect(Collectors.toList()); + .map(JsonNode::textValue) + .collect(Collectors.toList()); assertEquals(readonlyFields, actual); // test config @@ -209,13 +250,15 @@ public void testReadonlyApi() throws Exception { private void updateStaticResourceReadonly(List readonly) throws IOException { // create audit config - final Map result = ImmutableMap.of( - AuditApiAction.READONLY_FIELD, readonly + final Map result = ImmutableMap.of(AuditApiAction.READONLY_FIELD, readonly); + DefaultObjectMapper.YAML_MAPPER.writeValue( + FileHelper.getAbsoluteFilePathFromClassPath(AuditApiAction.STATIC_RESOURCE.substring(1)).toFile(), + result ); - DefaultObjectMapper.YAML_MAPPER.writeValue(FileHelper.getAbsoluteFilePathFromClassPath(AuditApiAction.STATIC_RESOURCE.substring(1)).toFile(), result); } - private void testPutRequest(final JsonNode json, final int expectedStatus, final boolean sendAdminCertificate, final Header... header) throws Exception { + private void testPutRequest(final JsonNode json, final int expectedStatus, final boolean sendAdminCertificate, final Header... header) + throws Exception { rh.sendAdminCertificate = sendAdminCertificate; RestHelper.HttpResponse response = rh.executePutRequest(CONFIG_ENDPOINT, writeValueAsString(json, false), header); assertEquals(expectedStatus, response.getStatusCode()); @@ -223,12 +266,12 @@ private void testPutRequest(final JsonNode json, final int expectedStatus, final private void testReadonlyBoolean(final ObjectNode json, final String config, final String resource) throws Exception { final String resourcePath = "/config" + config + "/" + resource; - ((ObjectNode)json.at(config)).put(resource, true); + ((ObjectNode) json.at(config)).put(resource, true); testPutRequest(json, HttpStatus.SC_OK, true); - ((ObjectNode)json.at(config)).put(resource, false); + ((ObjectNode) json.at(config)).put(resource, false); testPutRequest(json, HttpStatus.SC_CONFLICT, false, adminCredsHeader); testBooleanPatch(resourcePath, false, HttpStatus.SC_CONFLICT, adminCredsHeader); - ((ObjectNode)json.at(config)).put(resource, true); + ((ObjectNode) json.at(config)).put(resource, true); testPutRequest(json, HttpStatus.SC_OK, true); testBooleanPatch(resourcePath, true, HttpStatus.SC_OK, adminCredsHeader); testBooleanPatch(resourcePath, true, HttpStatus.SC_OK, adminCredsHeader); @@ -236,79 +279,96 @@ private void testReadonlyBoolean(final ObjectNode json, final String config, fin private void testReadonlyList(final ObjectNode json, final String config, final String resource) throws Exception { final String resourcePath = "/config" + config + "/" + resource; - ((ObjectNode)json.at(config)).putPOJO(resource, ImmutableList.of("test-resource-1", "test-resource-2")); + ((ObjectNode) json.at(config)).putPOJO(resource, ImmutableList.of("test-resource-1", "test-resource-2")); testPutRequest(json, HttpStatus.SC_OK, true); // change order List testList = ImmutableList.of("test-resource-2", "test-resource-1"); - ((ObjectNode)json.at(config)).putPOJO(resource, testList); + ((ObjectNode) json.at(config)).putPOJO(resource, testList); testPutRequest(json, HttpStatus.SC_CONFLICT, false, adminCredsHeader); testList(resourcePath, testList, HttpStatus.SC_CONFLICT, adminCredsHeader); // change values testList = ImmutableList.of("test-resource-3", "test-resource-4"); - ((ObjectNode)json.at(config)).putPOJO(resource, testList); + ((ObjectNode) json.at(config)).putPOJO(resource, testList); testPutRequest(json, HttpStatus.SC_CONFLICT, false, adminCredsHeader); testList(resourcePath, testList, HttpStatus.SC_CONFLICT, adminCredsHeader); // change values testList = Collections.emptyList(); - ((ObjectNode)json.at(config)).putPOJO(resource, testList); + ((ObjectNode) json.at(config)).putPOJO(resource, testList); testPutRequest(json, HttpStatus.SC_CONFLICT, false, adminCredsHeader); testList(resourcePath, testList, HttpStatus.SC_CONFLICT, adminCredsHeader); } private void testReadonlyMap(final ObjectNode json, final String config, final String resource) throws Exception { final String resourcePath = "/config" + config + "/" + resource; - ((ObjectNode)json.at(config)).putPOJO(resource, ImmutableMap.of("test-read-index-1", Collections.singletonList("test-field-1"), "test-read-index-2", Collections.singletonList("test-field-2"))); + ((ObjectNode) json.at(config)).putPOJO( + resource, + ImmutableMap.of( + "test-read-index-1", + Collections.singletonList("test-field-1"), + "test-read-index-2", + Collections.singletonList("test-field-2") + ) + ); testPutRequest(json, HttpStatus.SC_OK, true); // change values - Map> testMap = ImmutableMap.of("test-read-index-1", Collections.singletonList("test-field-1")); - ((ObjectNode)json.at(config)).putPOJO(resource, testMap); + Map> testMap = ImmutableMap.of("test-read-index-1", Collections.singletonList("test-field-1")); + ((ObjectNode) json.at(config)).putPOJO(resource, testMap); testPutRequest(json, HttpStatus.SC_CONFLICT, false, adminCredsHeader); testMap(resourcePath, testMap, HttpStatus.SC_CONFLICT, adminCredsHeader); // change values - testMap = ImmutableMap.of("test-read-index-1", ImmutableList.of("test-field-1", "test-field-2")); - ((ObjectNode)json.at(config)).putPOJO(resource, testMap); + testMap = ImmutableMap.of("test-read-index-1", ImmutableList.of("test-field-1", "test-field-2")); + ((ObjectNode) json.at(config)).putPOJO(resource, testMap); testPutRequest(json, HttpStatus.SC_CONFLICT, false, adminCredsHeader); testMap(resourcePath, testMap, HttpStatus.SC_CONFLICT, adminCredsHeader); // change values testMap = ImmutableMap.of("test-read-index", ImmutableList.of("test-field")); - ((ObjectNode)json.at(config)).putPOJO(resource, testMap); + ((ObjectNode) json.at(config)).putPOJO(resource, testMap); testPutRequest(json, HttpStatus.SC_CONFLICT, false, adminCredsHeader); testMap(resourcePath, testMap, HttpStatus.SC_CONFLICT, adminCredsHeader); // same object different order is valid - testMap = ImmutableMap.of("test-read-index-2", Collections.singletonList("test-field-2"), "test-read-index-1", Collections.singletonList("test-field-1")); - ((ObjectNode)json.at(config)).putPOJO(resource, testMap); + testMap = ImmutableMap.of( + "test-read-index-2", + Collections.singletonList("test-field-2"), + "test-read-index-1", + Collections.singletonList("test-field-1") + ); + ((ObjectNode) json.at(config)).putPOJO(resource, testMap); testPutRequest(json, HttpStatus.SC_OK, false, adminCredsHeader); - RestHelper.HttpResponse response = rh.executePatchRequest(ENDPOINT, "[{\"op\": \"add\",\"path\": \"" + resourcePath + "\",\"value\": " + writeValueAsString(testMap, false) + "}]", adminCredsHeader); + RestHelper.HttpResponse response = rh.executePatchRequest( + ENDPOINT, + "[{\"op\": \"add\",\"path\": \"" + resourcePath + "\",\"value\": " + writeValueAsString(testMap, false) + "}]", + adminCredsHeader + ); assertEquals(HttpStatus.SC_OK, response.getStatusCode()); } private void testReadonlyCategories(final ObjectNode json, final String config, final String resource) throws Exception { final String resourcePath = "/config" + config + "/" + resource; // change disabled_rest_categories readonly property - ((ObjectNode)json.at(config)).putPOJO(resource, ImmutableList.of("SSL_EXCEPTION", "AUTHENTICATED")); + ((ObjectNode) json.at(config)).putPOJO(resource, ImmutableList.of("SSL_EXCEPTION", "AUTHENTICATED")); testPutRequest(json, HttpStatus.SC_OK, true); // change order List testList = ImmutableList.of("AUTHENTICATED", "SSL_EXCEPTION"); - ((ObjectNode)json.at(config)).putPOJO(resource, testList); + ((ObjectNode) json.at(config)).putPOJO(resource, testList); testPutRequest(json, HttpStatus.SC_CONFLICT, false, adminCredsHeader); testList(resourcePath, testList, HttpStatus.SC_CONFLICT, adminCredsHeader); // change values testList = ImmutableList.of("AUTHENTICATED", "SSL_EXCEPTION", "FAILED_LOGIN"); - ((ObjectNode)json.at(config)).putPOJO(resource, testList); + ((ObjectNode) json.at(config)).putPOJO(resource, testList); testPutRequest(json, HttpStatus.SC_CONFLICT, false, adminCredsHeader); testList(resourcePath, testList, HttpStatus.SC_CONFLICT, adminCredsHeader); // change values testList = null; - ((ObjectNode)json.at(config)).putPOJO(resource, testList); + ((ObjectNode) json.at(config)).putPOJO(resource, testList); testPutRequest(json, HttpStatus.SC_CONFLICT, false, adminCredsHeader); testList(resourcePath, testList, HttpStatus.SC_CONFLICT, adminCredsHeader); } @@ -332,9 +392,15 @@ public void testBadRequest() throws Exception { // incorrect category final String jsonValue = DefaultObjectMapper.writeValueAsString(ImmutableList.of("RANDOM", "Test"), true); RestHelper.HttpResponse response; - response = rh.executePatchRequest(ENDPOINT, "[{\"op\": \"add\",\"path\": \"" + "/config/audit/disabled_rest_categories" + "\",\"value\": " + jsonValue + "}]"); + response = rh.executePatchRequest( + ENDPOINT, + "[{\"op\": \"add\",\"path\": \"" + "/config/audit/disabled_rest_categories" + "\",\"value\": " + jsonValue + "}]" + ); assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - response = rh.executePatchRequest(ENDPOINT, "[{\"op\": \"add\",\"path\": \"" + "/config/audit/disabled_transport_categories" + "\",\"value\": " + jsonValue + "}]"); + response = rh.executePatchRequest( + ENDPOINT, + "[{\"op\": \"add\",\"path\": \"" + "/config/audit/disabled_transport_categories" + "\",\"value\": " + jsonValue + "}]" + ); assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); } @@ -432,15 +498,25 @@ private void testPatchAction(final int expectedStatus, final Header... headers) testBoolean("/config/compliance/external_config", expectedStatus, headers); testBoolean("/config/compliance/read_metadata_only", expectedStatus, headers); testList("/config/compliance/read_ignore_users", ImmutableList.of("test-user-1"), expectedStatus, headers); - testMap("/config/compliance/read_watched_fields", ImmutableMap.of("test-index-1", Collections.singletonList("test-field")), expectedStatus, headers); + testMap( + "/config/compliance/read_watched_fields", + ImmutableMap.of("test-index-1", Collections.singletonList("test-field")), + expectedStatus, + headers + ); testBoolean("/config/compliance/write_metadata_only", expectedStatus, headers); testBoolean("/config/compliance/write_log_diffs", expectedStatus, headers); testList("/config/compliance/write_ignore_users", ImmutableList.of("test-user-1"), expectedStatus, headers); testList("/config/compliance/write_watched_indices", ImmutableList.of("test-index-1"), expectedStatus, headers); } - private void testBooleanPatch(final String patchResource, final boolean value, final int expected, final Header... headers) throws Exception { - RestHelper.HttpResponse response = rh.executePatchRequest(ENDPOINT, "[{\"op\": \"add\",\"path\": \"" + patchResource + "\",\"value\": " + value + "}]", headers); + private void testBooleanPatch(final String patchResource, final boolean value, final int expected, final Header... headers) + throws Exception { + RestHelper.HttpResponse response = rh.executePatchRequest( + ENDPOINT, + "[{\"op\": \"add\",\"path\": \"" + patchResource + "\",\"value\": " + value + "}]", + headers + ); assertEquals(expected, response.getStatusCode()); if (expected == HttpStatus.SC_OK) { assertEquals(value, readTree(rh.executeGetRequest(ENDPOINT, headers).getBody()).at(patchResource).asBoolean()); @@ -458,22 +534,35 @@ private void testBoolean(final String patchResource, final int expected, final H testBooleanPatch(patchResource, true, expected, headers); } - private void testList(final String patchResource, final List expectedList, final int expectedStatus, final Header... headers) throws Exception { + private void testList(final String patchResource, final List expectedList, final int expectedStatus, final Header... headers) + throws Exception { final String jsonValue = DefaultObjectMapper.writeValueAsString(expectedList, true); // make empty - RestHelper.HttpResponse response = rh.executePatchRequest(ENDPOINT, "[{\"op\": \"add\",\"path\": \"" + patchResource + "\",\"value\": []}]", headers); + RestHelper.HttpResponse response = rh.executePatchRequest( + ENDPOINT, + "[{\"op\": \"add\",\"path\": \"" + patchResource + "\",\"value\": []}]", + headers + ); assertEquals(expectedStatus, response.getStatusCode()); if (expectedStatus == HttpStatus.SC_OK) { assertEquals(0, readTree(rh.executeGetRequest(ENDPOINT, headers).getBody()).at(patchResource).size()); } // add value - response = rh.executePatchRequest(ENDPOINT, "[{\"op\": \"add\",\"path\": \"" + patchResource + "\",\"value\": " + jsonValue + "}]", headers); + response = rh.executePatchRequest( + ENDPOINT, + "[{\"op\": \"add\",\"path\": \"" + patchResource + "\",\"value\": " + jsonValue + "}]", + headers + ); assertEquals(expectedStatus, response.getStatusCode()); if (expectedStatus == HttpStatus.SC_OK) { final JsonNode responseJson = readTree(rh.executeGetRequest(ENDPOINT, headers).getBody()); - final List actualList = DefaultObjectMapper.readValue(responseJson.at(patchResource).toString(), new TypeReference>(){}); + final List actualList = DefaultObjectMapper.readValue( + responseJson.at(patchResource).toString(), + new TypeReference>() { + } + ); assertEquals(expectedList.size(), actualList.size()); assertTrue(actualList.containsAll(expectedList)); } @@ -486,22 +575,39 @@ private void testList(final String patchResource, final List expectedLis } } - private void testMap(final String patchResource, final Map> expectedMap, final int expectedStatus, final Header... headers) throws Exception { + private void testMap( + final String patchResource, + final Map> expectedMap, + final int expectedStatus, + final Header... headers + ) throws Exception { final String jsonValue = DefaultObjectMapper.writeValueAsString(expectedMap, true); // make empty - RestHelper.HttpResponse response = rh.executePatchRequest(ENDPOINT, "[{\"op\": \"add\",\"path\": \"" + patchResource + "\",\"value\": {}}]", headers); + RestHelper.HttpResponse response = rh.executePatchRequest( + ENDPOINT, + "[{\"op\": \"add\",\"path\": \"" + patchResource + "\",\"value\": {}}]", + headers + ); assertEquals(expectedStatus, response.getStatusCode()); if (expectedStatus == HttpStatus.SC_OK) { assertEquals(0, readTree(rh.executeGetRequest(ENDPOINT, headers).getBody()).at(patchResource).size()); } // add value - response = rh.executePatchRequest(ENDPOINT, "[{\"op\": \"add\",\"path\": \"" + patchResource + "\",\"value\": " + jsonValue + "}]", headers); + response = rh.executePatchRequest( + ENDPOINT, + "[{\"op\": \"add\",\"path\": \"" + patchResource + "\",\"value\": " + jsonValue + "}]", + headers + ); assertEquals(expectedStatus, response.getStatusCode()); if (expectedStatus == HttpStatus.SC_OK) { final JsonNode responseJson = readTree(rh.executeGetRequest(ENDPOINT, headers).getBody()); - final Map> actualMap = DefaultObjectMapper.readValue(responseJson.at(patchResource).toString(), new TypeReference>>(){}); + final Map> actualMap = DefaultObjectMapper.readValue( + responseJson.at(patchResource).toString(), + new TypeReference>>() { + } + ); assertEquals(actualMap, expectedMap); } @@ -520,32 +626,38 @@ public void testPatchRequest() throws Exception { rh.sendAdminCertificate = true; // update with non-default configuration - AuditConfig auditConfig = new AuditConfig(true, AuditConfig.Filter.from( + AuditConfig auditConfig = new AuditConfig( + true, + AuditConfig.Filter.from( ImmutableMap.builder() - .put("enable_rest", false) - .put("disabled_rest_categories", Collections.emptyList()) - .put("enable_transport", false) - .put("disabled_transport_categories", Collections.emptyList()) - .put("resolve_bulk_requests", false) - .put("resolve_indices", false) - .put("log_request_body", false) - .put("exclude_sensitive_headers", false) - .put("ignore_users", Collections.emptyList()) - .put("ignore_requests", Collections.emptyList()) - .build()) - , ComplianceConfig.from( + .put("enable_rest", false) + .put("disabled_rest_categories", Collections.emptyList()) + .put("enable_transport", false) + .put("disabled_transport_categories", Collections.emptyList()) + .put("resolve_bulk_requests", false) + .put("resolve_indices", false) + .put("log_request_body", false) + .put("exclude_sensitive_headers", false) + .put("ignore_users", Collections.emptyList()) + .put("ignore_requests", Collections.emptyList()) + .build() + ), + ComplianceConfig.from( ImmutableMap.builder() - .put("enabled", true) - .put("external_config", false) - .put("internal_config", false) - .put("read_metadata_only", false) - .put("read_watched_fields", Collections.emptyMap()) - .put("read_ignore_users", Collections.emptyList()) - .put("write_metadata_only", true) - .put("write_log_diffs", true) - .put("write_watched_indices", Collections.emptyList()) - .put("write_ignore_users", Collections.emptyList()) - .build(), Settings.EMPTY)); + .put("enabled", true) + .put("external_config", false) + .put("internal_config", false) + .put("read_metadata_only", false) + .put("read_watched_fields", Collections.emptyMap()) + .put("read_ignore_users", Collections.emptyList()) + .put("write_metadata_only", true) + .put("write_log_diffs", true) + .put("write_watched_indices", Collections.emptyList()) + .put("write_ignore_users", Collections.emptyList()) + .build(), + Settings.EMPTY + ) + ); final String payload = DefaultObjectMapper.writeValueAsString(auditConfig, false); // update config @@ -566,18 +678,18 @@ public void testPatchRequest() throws Exception { } private String getTestPayload() { - return "{" + - "\"enabled\":true," + - "\"audit\":{" + - "\"enable_rest\":true,\"disabled_rest_categories\":[\"AUTHENTICATED\"]," + - "\"enable_transport\":true,\"disabled_transport_categories\":[\"SSL_EXCEPTION\"]," + - "\"resolve_bulk_requests\":true,\"log_request_body\":true,\"resolve_indices\":true,\"exclude_sensitive_headers\":true," + - "\"ignore_users\":[\"test-user-1\"],\"ignore_requests\":[\"test-request\"]}," + - "\"compliance\":{" + - "\"enabled\":true," + - "\"internal_config\":true,\"external_config\":true," + - "\"read_metadata_only\":true,\"read_watched_fields\":{\"test-read-watch-field\":[]},\"read_ignore_users\":[\"test-user-2\"]," + - "\"write_metadata_only\":true,\"write_log_diffs\":true,\"write_watched_indices\":[\"test-write-watch-index\"],\"write_ignore_users\":[\"test-user-3\"]}" + - "}"; + return "{" + + "\"enabled\":true," + + "\"audit\":{" + + "\"enable_rest\":true,\"disabled_rest_categories\":[\"AUTHENTICATED\"]," + + "\"enable_transport\":true,\"disabled_transport_categories\":[\"SSL_EXCEPTION\"]," + + "\"resolve_bulk_requests\":true,\"log_request_body\":true,\"resolve_indices\":true,\"exclude_sensitive_headers\":true," + + "\"ignore_users\":[\"test-user-1\"],\"ignore_requests\":[\"test-request\"]}," + + "\"compliance\":{" + + "\"enabled\":true," + + "\"internal_config\":true,\"external_config\":true," + + "\"read_metadata_only\":true,\"read_watched_fields\":{\"test-read-watch-field\":[]},\"read_ignore_users\":[\"test-user-2\"]," + + "\"write_metadata_only\":true,\"write_log_diffs\":true,\"write_watched_indices\":[\"test-write-watch-index\"],\"write_ignore_users\":[\"test-user-3\"]}" + + "}"; } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java index 7d2396ecb0..46128f5a71 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/DashboardsInfoActionTest.java @@ -23,17 +23,20 @@ public class DashboardsInfoActionTest extends AbstractRestApiUnitTest { private final String ENDPOINT; + protected String getEndpoint() { return PLUGINS_PREFIX + "/dashboardsinfo"; } - public DashboardsInfoActionTest(){ + public DashboardsInfoActionTest() { ENDPOINT = getEndpoint(); } @Test public void testDashboardsInfo() throws Exception { - Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build(); + Settings settings = Settings.builder() + .put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true) + .build(); setup(settings); rh.keystore = "restapi/kirk-keystore.jks"; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java index 1d25b7dee2..120596f046 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/FlushCacheApiTest.java @@ -24,10 +24,12 @@ public class FlushCacheApiTest extends AbstractRestApiUnitTest { private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public FlushCacheApiTest(){ + + public FlushCacheApiTest() { ENDPOINT = getEndpointPrefix() + "/api/cache"; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java index 9f767fd7a6..09c4a762b5 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/GetConfigurationApiTest.java @@ -25,11 +25,12 @@ public class GetConfigurationApiTest extends AbstractRestApiUnitTest { private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public GetConfigurationApiTest(){ + public GetConfigurationApiTest() { ENDPOINT = getEndpointPrefix() + "/api"; } @@ -48,9 +49,7 @@ public void testGetConfiguration() throws Exception { response = rh.executeGetRequest(ENDPOINT + "/securityconfig"); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals( - settings.getAsBoolean("config.dynamic.authc.authentication_domain_basic_internal.http_enabled", false), - true); + Assert.assertEquals(settings.getAsBoolean("config.dynamic.authc.authentication_domain_basic_internal.http_enabled", false), true); Assert.assertNull(settings.get("_opendistro_security_meta.type")); // internalusers diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java index 1b403760f8..22035bba28 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/IndexMissingTest.java @@ -25,11 +25,12 @@ public class IndexMissingTest extends AbstractRestApiUnitTest { private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public IndexMissingTest(){ + public IndexMissingTest() { ENDPOINT = getEndpointPrefix() + "/api"; } @@ -81,7 +82,11 @@ protected void testHttpOperations() throws Exception { Assert.assertEquals("{\"status\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Security index not initialized\"}", errorString); // PUT request - response = rh.executePutRequest(ENDPOINT + "/actiongroups/READ", FileHelper.loadFile("restapi/actiongroup_read.json"), new Header[0]); + response = rh.executePutRequest( + ENDPOINT + "/actiongroups/READ", + FileHelper.loadFile("restapi/actiongroup_read.json"), + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); // DELETE request @@ -95,7 +100,10 @@ protected void testHttpOperations() throws Exception { response = rh.executeGetRequest(ENDPOINT + "/roles"); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); SecurityJsonNode securityJsonNode = new SecurityJsonNode(DefaultObjectMapper.readTree(response.getBody())); - Assert.assertEquals("OPENDISTRO_SECURITY_CLUSTER_ALL", securityJsonNode.get("opendistro_security_admin").get("cluster_permissions").get(0).asString()); + Assert.assertEquals( + "OPENDISTRO_SECURITY_CLUSTER_ALL", + securityJsonNode.get("opendistro_security_admin").get("cluster_permissions").get(0).asString() + ); } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiTest.java index 24aa8737c6..7132dcc491 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiTest.java @@ -31,27 +31,28 @@ private void verifyTenantUpdate(final Header... header) throws Exception { final HttpResponse getSettingResponse = rh.executeGetRequest("/_plugins/_security/api/tenancy/config", header); assertThat(getSettingResponse.getBody(), getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat( - getSettingResponse.getBody(), - getSettingResponse.findValueInJson("default_tenant"), - equalTo(ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME) + getSettingResponse.getBody(), + getSettingResponse.findValueInJson("default_tenant"), + equalTo(ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME) ); HttpResponse getDashboardsinfoResponse = rh.executeGetRequest("/_plugins/_security/dashboardsinfo", header); assertThat(getDashboardsinfoResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat( - getDashboardsinfoResponse.getBody(), - getDashboardsinfoResponse.findValueInJson("default_tenant"), - equalTo(ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME) + getDashboardsinfoResponse.getBody(), + getDashboardsinfoResponse.findValueInJson("default_tenant"), + equalTo(ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME) ); final HttpResponse setPrivateTenantAsDefaultResponse = rh.executePutRequest( - "/_plugins/_security/api/tenancy/config", - "{\"default_tenant\": \"Private\"}", header + "/_plugins/_security/api/tenancy/config", + "{\"default_tenant\": \"Private\"}", + header ); assertThat( - setPrivateTenantAsDefaultResponse.getBody(), - setPrivateTenantAsDefaultResponse.getStatusCode(), - equalTo(HttpStatus.SC_OK) + setPrivateTenantAsDefaultResponse.getBody(), + setPrivateTenantAsDefaultResponse.getStatusCode(), + equalTo(HttpStatus.SC_OK) ); getDashboardsinfoResponse = rh.executeGetRequest("/_plugins/_security/dashboardsinfo", ADMIN_FULL_ACCESS_USER); assertThat(getDashboardsinfoResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); @@ -73,67 +74,79 @@ public void testUpdateRestAPIAdmin() throws Exception { verifyTenantUpdate(ADMIN_FULL_ACCESS_USER); } - private void verifyTenantUpdateFailed(final Header... header) throws Exception { final HttpResponse disablePrivateTenantResponse = rh.executePutRequest( - "/_plugins/_security/api/tenancy/config", - "{\"private_tenant_enabled\":false}", header + "/_plugins/_security/api/tenancy/config", + "{\"private_tenant_enabled\":false}", + header ); assertThat(disablePrivateTenantResponse.getBody(), disablePrivateTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); final HttpResponse setPrivateTenantAsDefaultFailResponse = rh.executePutRequest( - "/_plugins/_security/api/tenancy/config", - "{\"default_tenant\": \"Private\"}", header + "/_plugins/_security/api/tenancy/config", + "{\"default_tenant\": \"Private\"}", + header + ); + assertThat( + setPrivateTenantAsDefaultFailResponse.getBody(), + setPrivateTenantAsDefaultFailResponse.getStatusCode(), + equalTo(HttpStatus.SC_BAD_REQUEST) ); - assertThat(setPrivateTenantAsDefaultFailResponse.getBody(), setPrivateTenantAsDefaultFailResponse.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); assertThat( - setPrivateTenantAsDefaultFailResponse.getBody(), - setPrivateTenantAsDefaultFailResponse.findValueInJson("error.reason"), - containsString("Private tenant can not be disabled if it is the default tenant.") + setPrivateTenantAsDefaultFailResponse.getBody(), + setPrivateTenantAsDefaultFailResponse.findValueInJson("error.reason"), + containsString("Private tenant can not be disabled if it is the default tenant.") ); final HttpResponse enablePrivateTenantResponse = rh.executePutRequest( - "/_plugins/_security/api/tenancy/config", - "{\"private_tenant_enabled\":true}", - header + "/_plugins/_security/api/tenancy/config", + "{\"private_tenant_enabled\":true}", + header ); assertThat(enablePrivateTenantResponse.getBody(), enablePrivateTenantResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); final HttpResponse setPrivateTenantAsDefaultResponse = rh.executePutRequest( - "/_plugins/_security/api/tenancy/config", - "{\"default_tenant\": \"Private\"}", - header + "/_plugins/_security/api/tenancy/config", + "{\"default_tenant\": \"Private\"}", + header + ); + assertThat( + setPrivateTenantAsDefaultResponse.getBody(), + setPrivateTenantAsDefaultResponse.getStatusCode(), + equalTo(HttpStatus.SC_OK) + ); + final HttpResponse updatePrivateSettingResponse = rh.executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"private_tenant_enabled\":false}", + header ); - assertThat(setPrivateTenantAsDefaultResponse.getBody(), setPrivateTenantAsDefaultResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); - final HttpResponse updatePrivateSettingResponse = - rh.executePutRequest("/_plugins/_security/api/tenancy/config", "{\"private_tenant_enabled\":false}", header); assertThat(updatePrivateSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); - assertThat(updatePrivateSettingResponse.findValueInJson("error.reason"), containsString("Private tenant can not be disabled if it is the default tenant.")); + assertThat( + updatePrivateSettingResponse.findValueInJson("error.reason"), + containsString("Private tenant can not be disabled if it is the default tenant.") + ); final HttpResponse getSettingResponseAfterUpdate = rh.executeGetRequest("/_plugins/_security/api/tenancy/config", header); assertThat(getSettingResponseAfterUpdate.getBody(), getSettingResponseAfterUpdate.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat( - getSettingResponseAfterUpdate.getBody(), - getSettingResponseAfterUpdate.findValueInJson("default_tenant"), - equalTo("Private") + getSettingResponseAfterUpdate.getBody(), + getSettingResponseAfterUpdate.findValueInJson("default_tenant"), + equalTo("Private") ); final HttpResponse getDashboardsinfoResponse = rh.executeGetRequest("/_plugins/_security/dashboardsinfo", header); - assertThat( - getDashboardsinfoResponse.getBody(), - getDashboardsinfoResponse.findValueInJson("default_tenant"), - equalTo("Private") - ); + assertThat(getDashboardsinfoResponse.getBody(), getDashboardsinfoResponse.findValueInJson("default_tenant"), equalTo("Private")); final HttpResponse setRandomStringAsDefaultTenant = rh.executePutRequest( - "/_plugins/_security/api/tenancy/config", - "{\"default_tenant\": \"NonExistentTenant\"}", - header + "/_plugins/_security/api/tenancy/config", + "{\"default_tenant\": \"NonExistentTenant\"}", + header ); assertThat(setRandomStringAsDefaultTenant.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); - assertThat(setPrivateTenantAsDefaultFailResponse.getBody(), - setRandomStringAsDefaultTenant.findValueInJson("error.reason"), - containsString("Default tenant should be selected from one of the available tenants.") + assertThat( + setPrivateTenantAsDefaultFailResponse.getBody(), + setRandomStringAsDefaultTenant.findValueInJson("error.reason"), + containsString("Default tenant should be selected from one of the available tenants.") ); } @@ -160,11 +173,11 @@ public void testForbiddenAccess() throws Exception { HttpResponse getSettingResponse = rh.executeGetRequest("/_plugins/_security/api/tenancy/config", USER_NO_REST_API_ACCESS); assertThat(getSettingResponse.getBody(), getSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); HttpResponse updateSettingResponse = rh.executePutRequest( - "/_plugins/_security/api/tenancy/config", - "{\"default_tenant\": \"Private\"}", USER_NO_REST_API_ACCESS + "/_plugins/_security/api/tenancy/config", + "{\"default_tenant\": \"Private\"}", + USER_NO_REST_API_ACCESS ); assertThat(getSettingResponse.getBody(), updateSettingResponse.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); } - } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java index 1f752bd0b2..a549ad4dd9 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java @@ -43,11 +43,12 @@ public class NodesDnApiTest extends AbstractRestApiUnitTest { private HttpResponse response; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public NodesDnApiTest(){ + public NodesDnApiTest() { ENDPOINT = getEndpointPrefix() + "/api"; } @@ -55,7 +56,7 @@ private JsonNode asJsonNode(T t) throws Exception { return OBJECT_MAPPER.readTree(OBJECT_MAPPER.writeValueAsString(t)); } - private Map> nodesDnEntry(String...nodesDn) { + private Map> nodesDnEntry(String... nodesDn) { return ImmutableMap.of("nodes_dn", Arrays.asList(nodesDn)); } @@ -63,9 +64,14 @@ private void testCrudScenarios(final int expectedStatus, final Header... headers response = rh.executeGetRequest(ENDPOINT + "/nodesdn?show_all=true", headers); assertThat(response.getStatusCode(), equalTo(expectedStatus)); if (expectedStatus == HttpStatus.SC_OK) { - JsonNode expected = asJsonNode(ImmutableMap.of( - "cluster1", nodesDnEntry("cn=popeye"), - NodesDnApiAction.STATIC_OPENSEARCH_YML_NODES_DN, nodesDnEntry("CN=example.com"))); + JsonNode expected = asJsonNode( + ImmutableMap.of( + "cluster1", + nodesDnEntry("cn=popeye"), + NodesDnApiAction.STATIC_OPENSEARCH_YML_NODES_DN, + nodesDnEntry("CN=example.com") + ) + ); JsonNode node = OBJECT_MAPPER.readTree(response.getBody()); assertThat(node, equalTo(asJsonNode(expected))); @@ -98,7 +104,11 @@ private void testCrudScenarios(final int expectedStatus, final Header... headers response = rh.executePutRequest(ENDPOINT + "/nodesdn/cluster1", "{\"nodes_dn\": [\"cn=popeye\"]}", headers); assertThat(response.getBody(), response.getStatusCode(), equalTo(expectedStatus)); - response = rh.executePatchRequest(ENDPOINT + "/nodesdn/cluster1", "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", headers); + response = rh.executePatchRequest( + ENDPOINT + "/nodesdn/cluster1", + "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", + headers + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(expectedStatus)); response = rh.executePatchRequest(ENDPOINT + "/nodesdn", "[{ \"op\": \"remove\", \"path\": \"/cluster1/nodes_dn/0\"}]", headers); @@ -108,7 +118,7 @@ private void testCrudScenarios(final int expectedStatus, final Header... headers assertThat(response.getBody(), response.getStatusCode(), equalTo(expectedStatus)); } - private void checkNullElementsInArray(final Header headers) throws Exception{ + private void checkNullElementsInArray(final Header headers) throws Exception { String body = FileHelper.loadFile("restapi/nodesdn_null_array_element.json"); HttpResponse response = rh.executePutRequest(ENDPOINT + "/nodesdn/cluster1", body, headers); @@ -128,7 +138,8 @@ public void testNodesDnApiWithDynamicConfigDisabled() throws Exception { @Test public void testNodesDnApi() throws Exception { - Settings settings = Settings.builder().put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) + Settings settings = Settings.builder() + .put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) .putList(ConfigConstants.SECURITY_NODES_DN, "CN=example.com") .build(); setupWithRestRoles(settings); @@ -170,67 +181,70 @@ public void testNodesDnApi() throws Exception { final int expectedStatus = HttpStatus.SC_FORBIDDEN; - response = rh.executePutRequest(ENDPOINT + "/nodesdn/" + NodesDnApiAction.STATIC_OPENSEARCH_YML_NODES_DN, "{\"nodes_dn\": [\"cn=popeye\"]}", nonAdminCredsHeader); + response = rh.executePutRequest( + ENDPOINT + "/nodesdn/" + NodesDnApiAction.STATIC_OPENSEARCH_YML_NODES_DN, + "{\"nodes_dn\": [\"cn=popeye\"]}", + nonAdminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(expectedStatus)); - response = rh.executePatchRequest(ENDPOINT + "/nodesdn/" + NodesDnApiAction.STATIC_OPENSEARCH_YML_NODES_DN, - "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]" , nonAdminCredsHeader); + response = rh.executePatchRequest( + ENDPOINT + "/nodesdn/" + NodesDnApiAction.STATIC_OPENSEARCH_YML_NODES_DN, + "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", + nonAdminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(expectedStatus)); - response = rh.executeDeleteRequest(ENDPOINT + "/nodesdn/" + NodesDnApiAction.STATIC_OPENSEARCH_YML_NODES_DN, nonAdminCredsHeader); + response = rh.executeDeleteRequest( + ENDPOINT + "/nodesdn/" + NodesDnApiAction.STATIC_OPENSEARCH_YML_NODES_DN, + nonAdminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(expectedStatus)); } } - @Test public void testNodesDnApiWithPermissions() throws Exception { - Settings settings = - Settings.builder() - .put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) - .put(SECURITY_RESTAPI_ADMIN_ENABLED, true) - .build(); + Settings settings = Settings.builder() + .put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) + .put(SECURITY_RESTAPI_ADMIN_ENABLED, true) + .build(); setupWithRestRoles(settings); final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); final Header restApiNodesDnHeader = encodeBasicHeader("rest_api_admin_nodesdn", "rest_api_admin_nodesdn"); final Header restApiUserHeader = encodeBasicHeader("test", "test"); - //full access admin + // full access admin { rh.sendAdminCertificate = false; - response = rh.executeGetRequest( - ENDPOINT + "/nodesdn", restApiAdminHeader); + response = rh.executeGetRequest(ENDPOINT + "/nodesdn", restApiAdminHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - response = rh.executePutRequest( - ENDPOINT + "/nodesdn/c1", "{\"nodes_dn\": [\"cn=popeye\"]}", - restApiAdminHeader); + response = rh.executePutRequest(ENDPOINT + "/nodesdn/c1", "{\"nodes_dn\": [\"cn=popeye\"]}", restApiAdminHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); response = rh.executePatchRequest( - ENDPOINT + "/nodesdn/c1", - "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", - restApiAdminHeader + ENDPOINT + "/nodesdn/c1", + "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", + restApiAdminHeader ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); response = rh.executeDeleteRequest(ENDPOINT + "/nodesdn/c1", restApiAdminHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); } - //NodesDN only + // NodesDN only { rh.sendAdminCertificate = false; response = rh.executeGetRequest(ENDPOINT + "/nodesdn", restApiNodesDnHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - response = rh.executePutRequest( - ENDPOINT + "/nodesdn/c1", "{\"nodes_dn\": [\"cn=popeye\"]}", - restApiNodesDnHeader); + response = rh.executePutRequest(ENDPOINT + "/nodesdn/c1", "{\"nodes_dn\": [\"cn=popeye\"]}", restApiNodesDnHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); response = rh.executePatchRequest( - ENDPOINT + "/nodesdn/c1", - "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", - restApiNodesDnHeader + ENDPOINT + "/nodesdn/c1", + "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", + restApiNodesDnHeader ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); @@ -240,21 +254,19 @@ public void testNodesDnApiWithPermissions() throws Exception { response = rh.executeGetRequest(ENDPOINT + "/actiongroups", restApiNodesDnHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); } - //rest api user + // rest api user { rh.sendAdminCertificate = false; response = rh.executeGetRequest(ENDPOINT + "/nodesdn", restApiUserHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - response = rh.executePutRequest( - ENDPOINT + "/nodesdn/c1", "{\"nodes_dn\": [\"cn=popeye\"]}", - restApiUserHeader); + response = rh.executePutRequest(ENDPOINT + "/nodesdn/c1", "{\"nodes_dn\": [\"cn=popeye\"]}", restApiUserHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); response = rh.executePatchRequest( - ENDPOINT + "/nodesdn/c1", - "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", - restApiUserHeader + ENDPOINT + "/nodesdn/c1", + "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", + restApiUserHeader ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); @@ -266,7 +278,8 @@ public void testNodesDnApiWithPermissions() throws Exception { @Test public void testNodesDnApiAuditComplianceLogging() throws Exception { - Settings settings = Settings.builder().put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) + Settings settings = Settings.builder() + .put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) .putList(ConfigConstants.SECURITY_NODES_DN, "CN=example.com") .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) @@ -293,9 +306,13 @@ public void testNodesDnApiAuditComplianceLogging() throws Exception { System.out.println(TestAuditlogImpl.sb.toString()); final Map expectedCategoryCounts = ImmutableMap.of( - AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ, 4L, - AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE, 4L); - Map actualCategoryCounts = TestAuditlogImpl.messages.stream().collect(Collectors.groupingBy(AuditMessage::getCategory, Collectors.counting())); + AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ, + 4L, + AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE, + 4L + ); + Map actualCategoryCounts = TestAuditlogImpl.messages.stream() + .collect(Collectors.groupingBy(AuditMessage::getCategory, Collectors.counting())); assertThat(actualCategoryCounts, equalTo(expectedCategoryCounts)); } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluatorTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluatorTest.java index 6573082e5b..bbe1bf90f8 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluatorTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluatorTest.java @@ -34,12 +34,14 @@ public class RestApiPrivilegesEvaluatorTest { @Before public void setUp() { - this.privilegesEvaluator = new RestApiPrivilegesEvaluator(Settings.EMPTY, - mock(AdminDNs.class), - mock(PrivilegesEvaluator.class), - mock(PrincipalExtractor.class), - mock(Path.class), - mock(ThreadPool.class)); + this.privilegesEvaluator = new RestApiPrivilegesEvaluator( + Settings.EMPTY, + mock(AdminDNs.class), + mock(PrivilegesEvaluator.class), + mock(PrincipalExtractor.class), + mock(Path.class), + mock(ThreadPool.class) + ); } @Test diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java index 9b5c7dc8c5..fc5c59d36f 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RoleBasedAccessTest.java @@ -26,11 +26,12 @@ public class RoleBasedAccessTest extends AbstractRestApiUnitTest { private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public RoleBasedAccessTest(){ + public RoleBasedAccessTest() { ENDPOINT = getEndpointPrefix() + "/api"; } @@ -100,7 +101,6 @@ public void testActionGroupsApi() throws Exception { Assert.assertEquals("", settings.getAsList("opendistro_security_role_starfleet_library.backend_roles").get(0), "starfleet*"); Assert.assertEquals("", settings.getAsList("opendistro_security_zdummy_all.users").get(0), "bug108"); - // Deprecated get configuration API, acessible for sarek // response = rh.executeGetRequest("_opendistro/_security/api/configuration/internalusers", encodeBasicHeader("sarek", "sarek")); // settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); @@ -119,9 +119,9 @@ public void testActionGroupsApi() throws Exception { // Assert.assertEquals("", settings.getAsList("CRUD.permissions").get(0), "READ_UT"); // configuration API, not accessible for worf -// response = rh.executeGetRequest("_opendistro/_security/api/configuration/actiongroups", encodeBasicHeader("worf", "worf")); -// Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); -// Assert.assertTrue(response.getBody().contains("does not have any access to endpoint CONFIGURATION")); + // response = rh.executeGetRequest("_opendistro/_security/api/configuration/actiongroups", encodeBasicHeader("worf", "worf")); + // Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + // Assert.assertTrue(response.getBody().contains("does not have any access to endpoint CONFIGURATION")); // cache API, not accessible for worf since it's disabled globally response = rh.executeDeleteRequest("_opendistro/_security/api/cache", encodeBasicHeader("worf", "worf")); @@ -173,10 +173,18 @@ public void testActionGroupsApi() throws Exception { // Worf, has access to roles API, get captains role response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("worf", "worf")); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertEquals(new SecurityJsonNode(DefaultObjectMapper.readTree(response.getBody())).getDotted("opendistro_security_role_starfleet_captains.cluster_permissions").get(0).asString(), "cluster:monitor*"); + Assert.assertEquals( + new SecurityJsonNode(DefaultObjectMapper.readTree(response.getBody())).getDotted( + "opendistro_security_role_starfleet_captains.cluster_permissions" + ).get(0).asString(), + "cluster:monitor*" + ); // Worf, has access to roles API, able to delete - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("worf", "worf")); + response = rh.executeDeleteRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + encodeBasicHeader("worf", "worf") + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("'opendistro_security_role_starfleet_captains' deleted")); @@ -196,20 +204,31 @@ public void testActionGroupsApi() throws Exception { // --- PUT --- // admin, no access - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_tenants.json"), encodeBasicHeader("admin", "admin")); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_captains_tenants.json"), + encodeBasicHeader("admin", "admin") + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // worf, restore role starfleet captains - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_different_content.json"), encodeBasicHeader("worf", "worf")); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_captains_different_content.json"), + encodeBasicHeader("worf", "worf") + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); // starfleet role present again response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("worf", "worf")); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertEquals(new SecurityJsonNode(DefaultObjectMapper.readTree(response.getBody())).getDotted("opendistro_security_role_starfleet_captains.index_permissions").get(0).get("allowed_actions").get(0).asString(), "blafasel"); + Assert.assertEquals( + new SecurityJsonNode(DefaultObjectMapper.readTree(response.getBody())).getDotted( + "opendistro_security_role_starfleet_captains.index_permissions" + ).get(0).get("allowed_actions").get(0).asString(), + "blafasel" + ); // Try the same, but now with admin certificate rh.sendAdminCertificate = true; @@ -245,8 +264,11 @@ public void testActionGroupsApi() throws Exception { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // PUT roles - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_different_content.json"), encodeBasicHeader("test", "test")); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_captains_different_content.json"), + encodeBasicHeader("test", "test") + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET captions role @@ -254,7 +276,10 @@ public void testActionGroupsApi() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // Delete captions role - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("test", "test")); + response = rh.executeDeleteRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + encodeBasicHeader("test", "test") + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("'opendistro_security_role_starfleet_captains' deleted")); @@ -262,6 +287,5 @@ public void testActionGroupsApi() throws Exception { response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", encodeBasicHeader("test", "test")); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java index a85566dc91..bf703fea35 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java @@ -81,7 +81,7 @@ public void testAllRolesForRestAdmin() throws Exception { setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); rh.sendAdminCertificate = false; - checkSuperAdminRoles(new Header[]{restApiAdminHeader}); + checkSuperAdminRoles(new Header[] { restApiAdminHeader }); } @Test @@ -89,7 +89,7 @@ public void testAllRolesForRolesRestAdmin() throws Exception { setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); final Header restApiAdminRolesHeader = encodeBasicHeader("rest_api_admin_roles", "rest_api_admin_roles"); rh.sendAdminCertificate = false; - checkSuperAdminRoles(new Header[]{restApiAdminRolesHeader}); + checkSuperAdminRoles(new Header[] { restApiAdminRolesHeader }); } void checkSuperAdminRoles(final Header[] header) { @@ -108,12 +108,14 @@ public void testPutDuplicateKeys() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - HttpResponse response = rh.executePutRequest(ENDPOINT + "/roles/dup", "{ \"cluster_permissions\": [\"*\"], \"cluster_permissions\": [\"*\"] }"); + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/roles/dup", + "{ \"cluster_permissions\": [\"*\"], \"cluster_permissions\": [\"*\"] }" + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); assertHealthy(); } - @Test public void testPutUnknownKey() throws Exception { @@ -121,7 +123,10 @@ public void testPutUnknownKey() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - HttpResponse response = rh.executePutRequest(ENDPOINT + "/roles/dup", "{ \"unknownkey\": [\"*\"], \"cluster_permissions\": [\"*\"] }"); + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/roles/dup", + "{ \"unknownkey\": [\"*\"], \"cluster_permissions\": [\"*\"] }" + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("invalid_keys")); assertHealthy(); @@ -134,7 +139,10 @@ public void testPutInvalidJson() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - HttpResponse response = rh.executePutRequest(ENDPOINT + "/roles/dup", "{ \"invalid\"::{{ [\"*\"], \"cluster_permissions\": [\"*\"] }"); + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/roles/dup", + "{ \"invalid\"::{{ [\"*\"], \"cluster_permissions\": [\"*\"] }" + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); assertHealthy(); } @@ -151,7 +159,7 @@ public void testRolesApi() throws Exception { setupStarfleetIndex(); // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picardpicardpicardpicard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); + addUserWithPassword("picard", "picardpicardpicardpicard", new String[] { "starfleet", "captains" }, HttpStatus.SC_CREATED); checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); @@ -242,25 +250,37 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason").asText()); // put new configuration with invalid payload, must fail - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", - FileHelper.loadFile("restapi/roles_not_parseable.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet", + FileHelper.loadFile("restapi/roles_not_parseable.json"), + header + ); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason").asText()); // put new configuration with invalid keys, must fail - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", - FileHelper.loadFile("restapi/roles_invalid_keys.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet", + FileHelper.loadFile("restapi/roles_invalid_keys.json"), + header + ); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage(), settings.get("reason").asText()); - Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY).get("keys").asText().contains("indexx_permissions")); Assert.assertTrue( - settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY).get("keys").asText().contains("kluster_permissions")); + settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY).get("keys").asText().contains("indexx_permissions") + ); + Assert.assertTrue( + settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY).get("keys").asText().contains("kluster_permissions") + ); // put new configuration with wrong datatypes, must fail - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", - FileHelper.loadFile("restapi/roles_wrong_datatype.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet", + FileHelper.loadFile("restapi/roles_wrong_datatype.json"), + header + ); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason").asText()); @@ -268,18 +288,27 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) // put read only role, must be forbidden // But SuperAdmin can still create it - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_transport_client", - FileHelper.loadFile("restapi/roles_captains.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_transport_client", + FileHelper.loadFile("restapi/roles_captains.json"), + header + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // put hidden role, must be forbidden, but allowed for super admin - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_internal", - FileHelper.loadFile("restapi/roles_captains.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_internal", + FileHelper.loadFile("restapi/roles_captains.json"), + header + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // restore starfleet role - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", - FileHelper.loadFile("restapi/roles_starfleet.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet", + FileHelper.loadFile("restapi/roles_starfleet.json"), + header + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); @@ -287,53 +316,94 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) // now picard is only in opendistro_security_role_starfleet, which has write access to // all indices. We collapse all document types in ODFE7 so this permission in the // starfleet role grants all permissions: - // _doc: - // - 'indices:*' + // _doc: + // - 'indices:*' checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); rh.sendAdminCertificate = sendAdminCert; // restore captains role - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_captains.json"), + header + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); rh.sendAdminCertificate = sendAdminCert; - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_complete_invalid.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_complete_invalid.json"), + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_multiple_2.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_multiple_2.json"), + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // check tenants rh.sendAdminCertificate = sendAdminCert; - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_tenants.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_captains_tenants.json"), + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(2, settings.size()); Assert.assertEquals(settings.get("status").asText(), "OK"); - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); System.out.println(response.getBody()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(1, settings.size()); - Assert.assertEquals(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(1).get("tenant_patterns").get(0).asString(), "tenant1"); - Assert.assertEquals(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(1).get("allowed_actions").get(0).asString(), "kibana_all_read"); + Assert.assertEquals( + new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions") + .get(1) + .get("tenant_patterns") + .get(0) + .asString(), + "tenant1" + ); + Assert.assertEquals( + new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions") + .get(1) + .get("allowed_actions") + .get(0) + .asString(), + "kibana_all_read" + ); + + Assert.assertEquals( + new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions") + .get(0) + .get("tenant_patterns") + .get(0) + .asString(), + "tenant2" + ); + Assert.assertEquals( + new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions") + .get(0) + .get("allowed_actions") + .get(0) + .asString(), + "kibana_all_write" + ); - Assert.assertEquals(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(0).get("tenant_patterns").get(0).asString(), "tenant2"); - Assert.assertEquals(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(0).get("allowed_actions").get(0).asString(), "kibana_all_write"); - - - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_tenants2.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_captains_tenants2.json"), + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(2, settings.size()); @@ -344,18 +414,63 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(1, settings.size()); - Assert.assertEquals(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(0).get("tenant_patterns").get(0).asString(), "tenant2"); - Assert.assertEquals(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(0).get("tenant_patterns").get(1).asString(), "tenant4"); - - Assert.assertEquals(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(0).get("allowed_actions").get(0).asString(), "kibana_all_write"); - - Assert.assertEquals(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(1).get("tenant_patterns").get(0).asString(), "tenant1"); - Assert.assertEquals(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(1).get("tenant_patterns").get(1).asString(), "tenant3"); - Assert.assertEquals(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(1).get("allowed_actions").get(0).asString(), "kibana_all_read"); + Assert.assertEquals( + new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions") + .get(0) + .get("tenant_patterns") + .get(0) + .asString(), + "tenant2" + ); + Assert.assertEquals( + new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions") + .get(0) + .get("tenant_patterns") + .get(1) + .asString(), + "tenant4" + ); + + Assert.assertEquals( + new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions") + .get(0) + .get("allowed_actions") + .get(0) + .asString(), + "kibana_all_write" + ); + + Assert.assertEquals( + new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions") + .get(1) + .get("tenant_patterns") + .get(0) + .asString(), + "tenant1" + ); + Assert.assertEquals( + new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions") + .get(1) + .get("tenant_patterns") + .get(1) + .asString(), + "tenant3" + ); + Assert.assertEquals( + new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions") + .get(1) + .get("allowed_actions") + .get(0) + .asString(), + "kibana_all_read" + ); // remove tenants from role - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_no_tenants.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_captains_no_tenants.json"), + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(2, settings.size()); @@ -365,11 +480,18 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(1, settings.size()); - Assert.assertFalse(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.cluster_permissions").get(0).isNull()); - Assert.assertTrue(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(0).isNull()); + Assert.assertFalse( + new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.cluster_permissions").get(0).isNull() + ); + Assert.assertTrue( + new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(0).isNull() + ); - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_tenants_malformed.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/roles_captains_tenants_malformed.json"), + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(settings.get("status").asText(), "error"); @@ -381,36 +503,43 @@ void verifyPatchForSuperAdmin(final Header[] header, final boolean sendAdminCert // PATCH on non-existing resource rh.sendAdminCertificate = sendAdminCert; HttpResponse response = rh.executePatchRequest( - ENDPOINT + "/roles/imnothere", - "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", - header); + ENDPOINT + "/roles/imnothere", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // PATCH read only resource, must be forbidden // SuperAdmin can patch it rh.sendAdminCertificate = sendAdminCert; response = rh.executePatchRequest( - ENDPOINT + "/roles/opendistro_security_transport_client", - "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", - header); + ENDPOINT + "/roles/opendistro_security_transport_client", + "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be not found, can be found for superadmin, but will fail with no path present exception rh.sendAdminCertificate = sendAdminCert; response = rh.executePatchRequest( - ENDPOINT + "/roles/opendistro_security_internal", - "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", - header); + ENDPOINT + "/roles/opendistro_security_internal", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error rh.sendAdminCertificate = sendAdminCert; response = rh.executePatchRequest( - ENDPOINT + "/roles/opendistro_security_role_starfleet", - "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", - header); + ENDPOINT + "/roles/opendistro_security_role_starfleet", + "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertTrue(response.getBody(), response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); + Assert.assertTrue( + response.getBody(), + response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*") + ); List permissions = null; @@ -433,61 +562,69 @@ void verifyPatchForSuperAdmin(final Header[] header, final boolean sendAdminCert // PATCH on non-existing resource rh.sendAdminCertificate = sendAdminCert; response = rh.executePatchRequest( - ENDPOINT + "/roles", - "[{ \"op\": \"add\", \"path\": \"/imnothere/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", - header); + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/imnothere/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH read only resource, must be forbidden rh.sendAdminCertificate = sendAdminCert; response = rh.executePatchRequest( - ENDPOINT + "/roles", - "[{ \"op\": \"add\", \"path\": \"/opendistro_security_transport_client/a\", \"value\": [ \"foo\", \"bar\" ] }]", - header); + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_transport_client/a\", \"value\": [ \"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH hidden resource, must be bad request rh.sendAdminCertificate = sendAdminCert; response = rh.executePatchRequest( - ENDPOINT + "/roles", - "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", - header); + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH delete read only resource, must be forbidden // SuperAdmin can delete read only user rh.sendAdminCertificate = sendAdminCert; response = rh.executePatchRequest( - ENDPOINT + "/roles", "[{ \"op\": \"remove\", \"path\": \"/opendistro_security_transport_client\" }]", - header); + ENDPOINT + "/roles", + "[{ \"op\": \"remove\", \"path\": \"/opendistro_security_transport_client\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be bad request, but allowed for superadmin rh.sendAdminCertificate = sendAdminCert; response = rh.executePatchRequest( - ENDPOINT + "/roles", - "[{ \"op\": \"remove\", \"path\": \"/opendistro_security_internal\"}]", - header); + ENDPOINT + "/roles", + "[{ \"op\": \"remove\", \"path\": \"/opendistro_security_internal\"}]", + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"message\":\"Resource updated.")); // PATCH value of hidden flag, must fail with validation error rh.sendAdminCertificate = sendAdminCert; response = rh.executePatchRequest( - ENDPOINT + "/roles", - "[{ \"op\": \"add\", \"path\": \"/newnewnew\", \"value\": { \"hidden\": true, \"index_permissions\" : " + - "[ {\"index_patterns\" : [ \"sf\" ],\"allowed_actions\" : [ \"OPENDISTRO_SECURITY_READ\" ]}] }}]", - header); + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/newnewnew\", \"value\": { \"hidden\": true, \"index_permissions\" : " + + "[ {\"index_patterns\" : [ \"sf\" ],\"allowed_actions\" : [ \"OPENDISTRO_SECURITY_READ\" ]}] }}]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH rh.sendAdminCertificate = sendAdminCert; response = rh.executePatchRequest( - ENDPOINT + "/roles", - "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"index_permissions\" : " + - "[ {\"index_patterns\" : [ \"sf\" ],\"allowed_actions\" : [ \"OPENDISTRO_SECURITY_READ\" ]}] }}]", - header); + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"index_permissions\" : " + + "[ {\"index_patterns\" : [ \"sf\" ],\"allowed_actions\" : [ \"OPENDISTRO_SECURITY_READ\" ]}] }}]", + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/roles/bulknew1", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); @@ -505,13 +642,19 @@ void verifyPatchForSuperAdmin(final Header[] header, final boolean sendAdminCert Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // put valid field masks - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_field_mask_valid", - FileHelper.loadFile("restapi/roles_field_masks_valid.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_field_mask_valid", + FileHelper.loadFile("restapi/roles_field_masks_valid.json"), + header + ); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); // put invalid field masks - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_field_mask_invalid", - FileHelper.loadFile("restapi/roles_field_masks_invalid.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_field_mask_invalid", + FileHelper.loadFile("restapi/roles_field_masks_invalid.json"), + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); } @@ -525,14 +668,14 @@ public void testRolesApiWithAllRestApiPermissions() throws Exception { setupStarfleetIndex(); // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picardpicardpicardpicard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); + addUserWithPassword("picard", "picardpicardpicardpicard", new String[] { "starfleet", "captains" }, HttpStatus.SC_CREATED); checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); - verifyGetForSuperAdmin(new Header[]{restApiAdminHeader}); - verifyDeleteForSuperAdmin(new Header[]{restApiAdminHeader}, false); - verifyPutForSuperAdmin(new Header[]{restApiAdminHeader}, false); - verifyPatchForSuperAdmin(new Header[]{restApiAdminHeader}, false); + verifyGetForSuperAdmin(new Header[] { restApiAdminHeader }); + verifyDeleteForSuperAdmin(new Header[] { restApiAdminHeader }, false); + verifyPutForSuperAdmin(new Header[] { restApiAdminHeader }, false); + verifyPatchForSuperAdmin(new Header[] { restApiAdminHeader }, false); } @Test @@ -545,15 +688,14 @@ public void testRolesApiWithRestApiRolePermission() throws Exception { setupStarfleetIndex(); // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picardpicardpicardpicard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); + addUserWithPassword("picard", "picardpicardpicardpicard", new String[] { "starfleet", "captains" }, HttpStatus.SC_CREATED); checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicard", "sf", "_doc", 0); - - verifyGetForSuperAdmin(new Header[]{restApiRolesHeader}); - verifyDeleteForSuperAdmin(new Header[]{restApiRolesHeader}, false); - verifyPutForSuperAdmin(new Header[]{restApiRolesHeader}, false); - verifyPatchForSuperAdmin(new Header[]{restApiRolesHeader}, false); + verifyGetForSuperAdmin(new Header[] { restApiRolesHeader }); + verifyDeleteForSuperAdmin(new Header[] { restApiRolesHeader }, false); + verifyPutForSuperAdmin(new Header[] { restApiRolesHeader }, false); + verifyPatchForSuperAdmin(new Header[] { restApiRolesHeader }, false); } @Test @@ -567,63 +709,45 @@ public void testCreateOrUpdateRestApiAdminRoleForbiddenForNonSuperAdmin() throws final String restAdminPermissionsPayload = createRestAdminPermissionsPayload("cluster/*"); HttpResponse response = rh.executePutRequest( - ENDPOINT + "/roles/new_rest_admin_role", restAdminPermissionsPayload, restApiAdminHeader); + ENDPOINT + "/roles/new_rest_admin_role", + restAdminPermissionsPayload, + restApiAdminHeader + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - response = rh.executePutRequest( - ENDPOINT + "/roles/rest_admin_role_to_delete", restAdminPermissionsPayload, restApiAdminHeader); + response = rh.executePutRequest(ENDPOINT + "/roles/rest_admin_role_to_delete", restAdminPermissionsPayload, restApiAdminHeader); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // attempt to create a new rest admin role by admin - response = rh.executePutRequest( - ENDPOINT + "/roles/some_rest_admin_role", - restAdminPermissionsPayload, - adminHeader); + response = rh.executePutRequest(ENDPOINT + "/roles/some_rest_admin_role", restAdminPermissionsPayload, adminHeader); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // attempt to update exiting admin role - response = rh.executePutRequest( - ENDPOINT + "/roles/new_rest_admin_role", - restAdminPermissionsPayload, - adminHeader); + response = rh.executePutRequest(ENDPOINT + "/roles/new_rest_admin_role", restAdminPermissionsPayload, adminHeader); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // attempt to patch exiting admin role response = rh.executePatchRequest( - ENDPOINT + "/roles/new_rest_admin_role", - createPatchRestAdminPermissionsPayload("replace"), - adminHeader); + ENDPOINT + "/roles/new_rest_admin_role", + createPatchRestAdminPermissionsPayload("replace"), + adminHeader + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // attempt to update exiting admin role - response = rh.executePutRequest( - ENDPOINT + "/roles/new_rest_admin_role", - restAdminPermissionsPayload, - restApiHeader); + response = rh.executePutRequest(ENDPOINT + "/roles/new_rest_admin_role", restAdminPermissionsPayload, restApiHeader); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // attempt to create a new rest admin role by admin - response = rh.executePutRequest( - ENDPOINT + "/roles/some_rest_admin_role", - restAdminPermissionsPayload, - restApiHeader); + response = rh.executePutRequest(ENDPOINT + "/roles/some_rest_admin_role", restAdminPermissionsPayload, restApiHeader); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // attempt to patch exiting admin role and crate a new one - response = rh.executePatchRequest( - ENDPOINT + "/roles", - createPatchRestAdminPermissionsPayload("replace"), - restApiHeader); + response = rh.executePatchRequest(ENDPOINT + "/roles", createPatchRestAdminPermissionsPayload("replace"), restApiHeader); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - response = rh.executePatchRequest( - ENDPOINT + "/roles", - createPatchRestAdminPermissionsPayload("add"), - restApiHeader); + response = rh.executePatchRequest(ENDPOINT + "/roles", createPatchRestAdminPermissionsPayload("add"), restApiHeader); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - response = rh.executePatchRequest( - ENDPOINT + "/roles", - createPatchRestAdminPermissionsPayload("remove"), - restApiHeader); + response = rh.executePatchRequest(ENDPOINT + "/roles", createPatchRestAdminPermissionsPayload("remove"), restApiHeader); System.out.println("RESPONSE: " + response.getBody()); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); } @@ -640,51 +764,42 @@ public void testDeleteRestApiAdminRoleForbiddenForNonSuperAdmin() throws Excepti final String allRestAdminPermissionsPayload = createRestAdminPermissionsPayload("cluster/*"); HttpResponse response = rh.executePutRequest( - ENDPOINT + "/roles/new_rest_admin_role", allRestAdminPermissionsPayload, restApiAdminHeader); + ENDPOINT + "/roles/new_rest_admin_role", + allRestAdminPermissionsPayload, + restApiAdminHeader + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // attempt to update exiting admin role - response = rh.executeDeleteRequest( - ENDPOINT + "/roles/new_rest_admin_role", - adminHeader); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/new_rest_admin_role", adminHeader); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - //true to change - response = rh.executeDeleteRequest( - ENDPOINT + "/roles/new_rest_admin_role", - allRestAdminPermissionsPayload, - restApiHeader); + // true to change + response = rh.executeDeleteRequest(ENDPOINT + "/roles/new_rest_admin_role", allRestAdminPermissionsPayload, restApiHeader); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); } - private String createPatchRestAdminPermissionsPayload(final String op) throws JsonProcessingException { final ArrayNode rootNode = (ArrayNode) DefaultObjectMapper.objectMapper.createArrayNode(); final ObjectNode opAddObjectNode = DefaultObjectMapper.objectMapper.createObjectNode(); final ObjectNode clusterPermissionsNode = DefaultObjectMapper.objectMapper.createObjectNode(); clusterPermissionsNode.set("cluster_permissions", clusterPermissionsForRestAdmin("cluster/*")); if ("add".equals(op)) { - opAddObjectNode - .put("op", "add") - .put("path", "/some_rest_admin_role") - .set("value", clusterPermissionsNode); + opAddObjectNode.put("op", "add").put("path", "/some_rest_admin_role").set("value", clusterPermissionsNode); rootNode.add(opAddObjectNode); } if ("remove".equals(op)) { final ObjectNode opRemoveObjectNode = DefaultObjectMapper.objectMapper.createObjectNode(); - opRemoveObjectNode - .put("op", "remove") - .put("path", "/rest_admin_role_to_delete"); + opRemoveObjectNode.put("op", "remove").put("path", "/rest_admin_role_to_delete"); rootNode.add(opRemoveObjectNode); } if ("replace".equals(op)) { final ObjectNode replaceRemoveObjectNode = DefaultObjectMapper.objectMapper.createObjectNode(); - replaceRemoveObjectNode - .put("op", "replace") - .put("path", "/new_rest_admin_role/cluster_permissions") - .set("value", clusterPermissionsForRestAdmin("*")); + replaceRemoveObjectNode.put("op", "replace") + .put("path", "/new_rest_admin_role/cluster_permissions") + .set("value", clusterPermissionsForRestAdmin("*")); rootNode.add(replaceRemoveObjectNode); } @@ -709,21 +824,27 @@ void checkNonSuperAdminRoles(final Header[] header) throws Exception { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Put read only roles - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_transport_client", - FileHelper.loadFile("restapi/roles_captains.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/roles/opendistro_security_transport_client", + FileHelper.loadFile("restapi/roles_captains.json"), + header + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch single read only roles response = rh.executePatchRequest( - ENDPOINT + "/roles/opendistro_security_transport_client", - "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", - header); + ENDPOINT + "/roles/opendistro_security_transport_client", + "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch multiple read only roles - response = rh.executePatchRequest(ENDPOINT + "/roles/", - "[{ \"op\": \"add\", \"path\": \"/opendistro_security_transport_client/description\", \"value\": \"foo\" }]", - header); + response = rh.executePatchRequest( + ENDPOINT + "/roles/", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_transport_client/description\", \"value\": \"foo\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // get hidden role @@ -740,14 +861,19 @@ void checkNonSuperAdminRoles(final Header[] header) throws Exception { Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch single hidden roles - response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_internal", - "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/roles/opendistro_security_internal", + "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch multiple hidden roles - response = rh.executePatchRequest(ENDPOINT + "/roles/", - "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", - header); + response = rh.executePatchRequest( + ENDPOINT + "/roles/", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java index c15651fcc8..42fb111281 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java @@ -33,11 +33,12 @@ public class RolesMappingApiTest extends AbstractRestApiUnitTest { private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public RolesMappingApiTest(){ + public RolesMappingApiTest() { ENDPOINT = getEndpointPrefix() + "/api"; } @@ -56,7 +57,7 @@ public void testRolesMappingApi() throws Exception { addUserWithPassword("picard", "picardpicardpicard", new String[] { "captains" }, HttpStatus.SC_CREATED); checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picardpicardpicard", "sf", "_doc", 1); // TODO: only one doctype allowed for ES6 - //checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + // checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); rh.sendAdminCertificate = true; verifyGetForSuperAdmin(new Header[0]); rh.sendAdminCertificate = true; @@ -65,35 +66,35 @@ public void testRolesMappingApi() throws Exception { verifyPutForSuperAdmin(new Header[0]); verifyPatchForSuperAdmin(new Header[0]); // mapping with several backend roles, one of the is captain - deleteAndPutNewMapping(new Header[0],"rolesmapping_backendroles_captains_list.json", true); + deleteAndPutNewMapping(new Header[0], "rolesmapping_backendroles_captains_list.json", true); checkAllSfAllowed(); // mapping with one backend role, captain - deleteAndPutNewMapping(new Header[0],"rolesmapping_backendroles_captains_single.json", true); + deleteAndPutNewMapping(new Header[0], "rolesmapping_backendroles_captains_single.json", true); checkAllSfAllowed(); // mapping with several users, one is picard - deleteAndPutNewMapping(new Header[0],"rolesmapping_users_picard_list.json", true); + deleteAndPutNewMapping(new Header[0], "rolesmapping_users_picard_list.json", true); checkAllSfAllowed(); // just user picard - deleteAndPutNewMapping(new Header[0],"rolesmapping_users_picard_single.json", true); + deleteAndPutNewMapping(new Header[0], "rolesmapping_users_picard_single.json", true); checkAllSfAllowed(); // hosts - deleteAndPutNewMapping(new Header[0],"rolesmapping_hosts_list.json", true); + deleteAndPutNewMapping(new Header[0], "rolesmapping_hosts_list.json", true); checkAllSfAllowed(); // hosts - deleteAndPutNewMapping(new Header[0],"rolesmapping_hosts_single.json", true); + deleteAndPutNewMapping(new Header[0], "rolesmapping_hosts_single.json", true); checkAllSfAllowed(); // full settings, access - deleteAndPutNewMapping(new Header[0],"rolesmapping_all_access.json", true); + deleteAndPutNewMapping(new Header[0], "rolesmapping_all_access.json", true); checkAllSfAllowed(); // full settings, no access - deleteAndPutNewMapping(new Header[0],"rolesmapping_all_noaccess.json", true); + deleteAndPutNewMapping(new Header[0], "rolesmapping_all_noaccess.json", true); checkAllSfForbidden(); } @@ -110,42 +111,42 @@ public void testRolesMappingApiWithFullPermissions() throws Exception { addUserWithPassword("picard", "picardpicardpicard", new String[] { "captains" }, HttpStatus.SC_CREATED); checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picardpicardpicard", "sf", "_doc", 1); // TODO: only one doctype allowed for ES6 - //checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + // checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); - verifyGetForSuperAdmin(new Header[]{restApiAdminHeader}); - verifyDeleteForSuperAdmin(new Header[]{restApiAdminHeader}, false); - verifyPutForSuperAdmin(new Header[]{restApiAdminHeader}); - verifyPatchForSuperAdmin(new Header[]{restApiAdminHeader}); + verifyGetForSuperAdmin(new Header[] { restApiAdminHeader }); + verifyDeleteForSuperAdmin(new Header[] { restApiAdminHeader }, false); + verifyPutForSuperAdmin(new Header[] { restApiAdminHeader }); + verifyPatchForSuperAdmin(new Header[] { restApiAdminHeader }); // mapping with several backend roles, one of the is captain - deleteAndPutNewMapping(new Header[]{restApiAdminHeader}, "rolesmapping_backendroles_captains_list.json", false); + deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_backendroles_captains_list.json", false); checkAllSfAllowed(); // mapping with one backend role, captain - deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_backendroles_captains_single.json", true); + deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_backendroles_captains_single.json", true); checkAllSfAllowed(); // mapping with several users, one is picard - deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_users_picard_list.json", true); + deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_users_picard_list.json", true); checkAllSfAllowed(); // just user picard - deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_users_picard_single.json", true); + deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_users_picard_single.json", true); checkAllSfAllowed(); // hosts - deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_hosts_list.json", true); + deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_hosts_list.json", true); checkAllSfAllowed(); // hosts - deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_hosts_single.json", true); + deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_hosts_single.json", true); checkAllSfAllowed(); // full settings, access - deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_all_access.json", true); + deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_all_access.json", true); checkAllSfAllowed(); // full settings, no access - deleteAndPutNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_all_noaccess.json", true); + deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_all_noaccess.json", true); checkAllSfForbidden(); } @@ -166,7 +167,6 @@ void verifyGetForSuperAdmin(final Header[] header) throws Exception { // Superadmin should be able to see reserved rolesmapping Assert.assertTrue(response.getBody().contains("opendistro_security_reserved")); - // -- GET // GET opendistro_security_role_starfleet, exists response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", header); @@ -242,26 +242,34 @@ void verifyPutForSuperAdmin(final Header[] header) throws Exception { Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason")); // put new configuration with invalid payload, must fail - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_not_parseable.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/rolesmapping_not_parseable.json"), + header + ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason")); // put new configuration with invalid keys, must fail - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_invalid_keys.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/rolesmapping_invalid_keys.json"), + header + ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage(), settings.get("reason")); Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("theusers")); - Assert.assertTrue( - settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("thebackendroles")); + Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("thebackendroles")); Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("thehosts")); // wrong datatypes - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_backendroles_captains_single_wrong_datatype.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/rolesmapping_backendroles_captains_single_wrong_datatype.json"), + header + ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -269,8 +277,11 @@ void verifyPutForSuperAdmin(final Header[] header) throws Exception { Assert.assertTrue(settings.get("hosts") == null); Assert.assertTrue(settings.get("users") == null); - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_hosts_single_wrong_datatype.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/rolesmapping_hosts_single_wrong_datatype.json"), + header + ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -278,8 +289,11 @@ void verifyPutForSuperAdmin(final Header[] header) throws Exception { Assert.assertTrue(settings.get("backend_roles") == null); Assert.assertTrue(settings.get("users") == null); - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_users_picard_single_wrong_datatype.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/rolesmapping_users_picard_single_wrong_datatype.json"), + header + ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -289,46 +303,70 @@ void verifyPutForSuperAdmin(final Header[] header) throws Exception { // Read only role mapping // SuperAdmin can add read only roles - mappings - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), + header + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // hidden role, allowed for super admin - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/opendistro_security_internal", + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), + header + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), + header + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); } void verifyPatchForSuperAdmin(final Header[] header) throws Exception { // PATCH on non-existing resource - HttpResponse response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", header); + HttpResponse response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping/imnothere", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // PATCH read only resource, must be forbidden // SuperAdmin can patch read-only resource - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\"] }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", + "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\"] }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH hidden resource, must be not found, can be found by super admin - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", - "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ " + - "\"foo\", \"bar\" ] }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping/opendistro_security_internal", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ " + "\"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", - "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", + "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", - "[{ \"op\": \"add\", \"path\": \"/backend_roles/-\", \"value\": \"spring\" }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", + "[{ \"op\": \"add\", \"path\": \"/backend_roles/-\", \"value\": \"spring\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); @@ -339,32 +377,48 @@ void verifyPatchForSuperAdmin(final Header[] header) throws Exception { // -- PATCH on whole config resource // PATCH on non-existing resource - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", - "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH read only resource, must be forbidden // SuperAdmin can patch read only resource rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", - "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be bad request rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", - "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_vulcans/hidden\", \"value\": true }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_vulcans/hidden\", \"value\": true }]", + header + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", - "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"backend_roles\":[\"vulcanadmin\"]} }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"backend_roles\":[\"vulcanadmin\"]} }]", + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); @@ -394,11 +448,13 @@ private void checkAllSfForbidden() throws Exception { private HttpResponse deleteAndPutNewMapping(final Header[] header, final String fileName, final boolean useAdminCert) throws Exception { rh.sendAdminCertificate = useAdminCert; - HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - header); + HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/"+fileName), header); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + FileHelper.loadFile("restapi/" + fileName), + header + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; return response; @@ -421,27 +477,38 @@ public void testRolesMappingApiForNonSuperAdminRestApiUser() throws Exception { setupWithRestRoles(); rh.sendAdminCertificate = false; final Header restApiHeader = encodeBasicHeader("test", "test"); - verifyNonSuperAdminUser(new Header[] {restApiHeader}); + verifyNonSuperAdminUser(new Header[] { restApiHeader }); } void verifyNonSuperAdminUser(final Header[] header) throws Exception { HttpResponse response; // Delete read only roles mapping - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library" , header); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Put read only roles mapping - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), + header + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch single read only roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", + "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch multiple read only roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // GET, rolesmapping is hidden, allowed for super admin @@ -449,21 +516,33 @@ void verifyNonSuperAdminUser(final Header[] header) throws Exception { Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Delete hidden roles mapping - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal" , header); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Put hidden roles mapping - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/opendistro_security_internal", + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), + header + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch hidden roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", header); + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping/opendistro_security_internal", + "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", + header + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch multiple hidden roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", header); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); } + response = rh.executePatchRequest( + ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", + header + ); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + } @Test public void testChangeRestApiAdminRoleMappingForbiddenForNonSuperAdmin() throws Exception { @@ -475,16 +554,22 @@ public void testChangeRestApiAdminRoleMappingForbiddenForNonSuperAdmin() throws final Header restApiHeader = encodeBasicHeader("test", "test"); HttpResponse response = rh.executePutRequest( - ENDPOINT + "/roles/new_rest_api_role", - createRestAdminPermissionsPayload(), restApiAdminHeader); + ENDPOINT + "/roles/new_rest_api_role", + createRestAdminPermissionsPayload(), + restApiAdminHeader + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); response = rh.executePutRequest( - ENDPOINT + "/roles/new_rest_api_role_without_mapping", - createRestAdminPermissionsPayload(), restApiAdminHeader); + ENDPOINT + "/roles/new_rest_api_role_without_mapping", + createRestAdminPermissionsPayload(), + restApiAdminHeader + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/new_rest_api_role", - createUsersPayload("a", "b", "c"), restApiAdminHeader); + ENDPOINT + "/rolesmapping/new_rest_api_role", + createUsersPayload("a", "b", "c"), + restApiAdminHeader + ); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); verifyRestApiPutAndDeleteForNonRestApiAdmin(adminHeader); @@ -497,11 +582,13 @@ public void testChangeRestApiAdminRoleMappingForbiddenForNonSuperAdmin() throws private void verifyRestApiPutAndDeleteForNonRestApiAdmin(final Header header) throws Exception { HttpResponse response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/new_rest_api_role", createUsersPayload("a", "b", "c"), header); + ENDPOINT + "/rolesmapping/new_rest_api_role", + createUsersPayload("a", "b", "c"), + header + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - response = rh.executeDeleteRequest( - ENDPOINT + "/rolesmapping/new_rest_api_role", "", header); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/new_rest_api_role", "", header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); } @@ -552,28 +639,29 @@ private String createPathPayload(final String op) throws JsonProcessingException } @Test - public void checkNullElementsInArray() throws Exception{ + public void checkNullElementsInArray() throws Exception { setup(); rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; String body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_users.json"); - HttpResponse response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - body, new Header[0]); + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", + body, + new Header[0] + ); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_backend_roles.json"); - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - body, new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", body, new Header[0]); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_hosts.json"); - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - body, new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", body, new Header[0]); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java index 53c6ff2e96..81fad7d4ff 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityApiAccessTest.java @@ -19,11 +19,12 @@ public class SecurityApiAccessTest extends AbstractRestApiUnitTest { private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public SecurityApiAccessTest(){ + public SecurityApiAccessTest() { ENDPOINT = getEndpointPrefix() + "/api/internalusers"; } @@ -33,22 +34,14 @@ public void testRestApi() throws Exception { setup(); // test with no cert, must fail - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, - rh.executeGetRequest(ENDPOINT).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, - rh.executeGetRequest(ENDPOINT, - encodeBasicHeader("admin", "admin")) - .getStatusCode()); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest(ENDPOINT).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest(ENDPOINT, encodeBasicHeader("admin", "admin")).getStatusCode()); // test with non-admin cert, must fail rh.keystore = "restapi/node-0-keystore.jks"; rh.sendAdminCertificate = true; - Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, - rh.executeGetRequest(ENDPOINT).getStatusCode()); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, - rh.executeGetRequest(ENDPOINT, - encodeBasicHeader("admin", "admin")) - .getStatusCode()); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executeGetRequest(ENDPOINT).getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest(ENDPOINT, encodeBasicHeader("admin", "admin")).getStatusCode()); } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java index 50014993c1..8fc6ae1dd8 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiTest.java @@ -26,11 +26,12 @@ public class SecurityConfigApiTest extends AbstractRestApiUnitTest { private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public SecurityConfigApiTest(){ + public SecurityConfigApiTest() { ENDPOINT = getEndpointPrefix() + "/api"; } @@ -61,7 +62,9 @@ public void testSecurityConfigApiRead() throws Exception { @Test public void testSecurityConfigApiWrite() throws Exception { - Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build(); + Settings settings = Settings.builder() + .put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true) + .build(); setup(settings); rh.keystore = "restapi/kirk-keystore.jks"; @@ -70,13 +73,25 @@ public void testSecurityConfigApiWrite() throws Exception { HttpResponse response = rh.executeGetRequest(ENDPOINT + "/securityconfig", new Header[0]); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executePutRequest(ENDPOINT + "/securityconfig/xxx", FileHelper.loadFile("restapi/securityconfig.json"), new Header[0]); + response = rh.executePutRequest( + ENDPOINT + "/securityconfig/xxx", + FileHelper.loadFile("restapi/securityconfig.json"), + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - response = rh.executePutRequest(ENDPOINT + "/securityconfig/config", FileHelper.loadFile("restapi/securityconfig.json"), new Header[0]); + response = rh.executePutRequest( + ENDPOINT + "/securityconfig/config", + FileHelper.loadFile("restapi/securityconfig.json"), + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executePutRequest(ENDPOINT + "/securityconfig/config", FileHelper.loadFile("restapi/invalid_config.json"), new Header[0]); + response = rh.executePutRequest( + ENDPOINT + "/securityconfig/config", + FileHelper.loadFile("restapi/invalid_config.json"), + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusCode()); Assert.assertTrue(response.getContentType(), response.isJsonContentType()); Assert.assertTrue(response.getBody().contains("Unrecognized field")); @@ -87,7 +102,11 @@ public void testSecurityConfigApiWrite() throws Exception { response = rh.executePostRequest(ENDPOINT + "/securityconfig", "{\"xxx\": 1}", new Header[0]); Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode()); - response = rh.executePatchRequest(ENDPOINT + "/securityconfig", "[{\"op\": \"replace\",\"path\": \"/config/dynamic/hosts_resolver_mode\",\"value\": \"other\"}]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT + "/securityconfig", + "[{\"op\": \"replace\",\"path\": \"/config/dynamic/hosts_resolver_mode\",\"value\": \"other\"}]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeDeleteRequest(ENDPOINT + "/securityconfig", new Header[0]); @@ -98,30 +117,35 @@ public void testSecurityConfigApiWrite() throws Exception { @Test public void testSecurityConfigForHTTPPatch() throws Exception { - Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build(); + Settings settings = Settings.builder() + .put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true) + .build(); setup(settings); rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - //non-default config + // non-default config String updatedConfig = FileHelper.loadFile("restapi/securityconfig_nondefault.json"); - //update config + // update config HttpResponse response = rh.executePutRequest(ENDPOINT + "/securityconfig/config", updatedConfig, new Header[0]); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - //make patch request - response = rh.executePatchRequest(ENDPOINT + "/securityconfig", "[{\"op\": \"add\",\"path\": \"/config/dynamic/do_not_fail_on_forbidden\",\"value\": \"false\"}]", new Header[0]); + // make patch request + response = rh.executePatchRequest( + ENDPOINT + "/securityconfig", + "[{\"op\": \"add\",\"path\": \"/config/dynamic/do_not_fail_on_forbidden\",\"value\": \"false\"}]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - //get config + // get config response = rh.executeGetRequest(ENDPOINT + "/securityconfig", new Header[0]); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // verify configs are same Assert.assertEquals(DefaultObjectMapper.readTree(updatedConfig), DefaultObjectMapper.readTree(response.getBody()).get("config")); - } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java index 93371b548a..d7a6edfea9 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityHealthActionTest.java @@ -23,17 +23,20 @@ public class SecurityHealthActionTest extends AbstractRestApiUnitTest { private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public SecurityHealthActionTest(){ + public SecurityHealthActionTest() { ENDPOINT = getEndpointPrefix(); } @Test public void testSecurityHealthAPI() throws Exception { - Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build(); + Settings settings = Settings.builder() + .put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true) + .build(); setup(settings); rh.keystore = "restapi/kirk-keystore.jks"; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java index 654e0c6230..db27be85ee 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SecurityInfoActionTest.java @@ -23,17 +23,20 @@ public class SecurityInfoActionTest extends AbstractRestApiUnitTest { private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public SecurityInfoActionTest(){ + public SecurityInfoActionTest() { ENDPOINT = getEndpointPrefix() + "/authinfo"; } @Test public void testSecurityInfoAPI() throws Exception { - Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build(); + Settings settings = Settings.builder() + .put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true) + .build(); setup(settings); rh.keystore = "restapi/kirk-keystore.jks"; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java index 425c2dca50..8797d196f5 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java @@ -36,32 +36,38 @@ public class SslCertsApiTest extends AbstractRestApiUnitTest { static final String TRANSPORT_CERTS = "transport"; - private final static List> EXPECTED_CERTIFICATES = - ImmutableList.of( - ImmutableMap.of( - "issuer_dn", "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", - "subject_dn", "CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE", - "san", "[[2, node-0.example.com], [2, localhost], [7, 127.0.0.1], [8, 1.2.3.4.5.5]]", - "not_before", "2018-05-05T14:37:09Z", - "not_after", "2028-05-02T14:37:09Z" - ), - ImmutableMap.of( - "issuer_dn", "CN=Example Com Inc. Root CA,OU=Example Com Inc. Root CA,O=Example Com Inc.,DC=example,DC=com", - "subject_dn", "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", - "san", "", - "not_before", "2018-05-05T14:37:08Z", - "not_after", "2028-05-04T14:37:08Z" - ) - ); + private final static List> EXPECTED_CERTIFICATES = ImmutableList.of( + ImmutableMap.of( + "issuer_dn", + "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", + "subject_dn", + "CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE", + "san", + "[[2, node-0.example.com], [2, localhost], [7, 127.0.0.1], [8, 1.2.3.4.5.5]]", + "not_before", + "2018-05-05T14:37:09Z", + "not_after", + "2028-05-02T14:37:09Z" + ), + ImmutableMap.of( + "issuer_dn", + "CN=Example Com Inc. Root CA,OU=Example Com Inc. Root CA,O=Example Com Inc.,DC=example,DC=com", + "subject_dn", + "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", + "san", + "", + "not_before", + "2018-05-05T14:37:08Z", + "not_after", + "2028-05-04T14:37:08Z" + ) + ); private final static String EXPECTED_CERTIFICATES_BY_TYPE; static { try { EXPECTED_CERTIFICATES_BY_TYPE = DefaultObjectMapper.objectMapper.writeValueAsString( - ImmutableMap.of( - "http_certificates_list", EXPECTED_CERTIFICATES, - "transport_certificates_list", EXPECTED_CERTIFICATES - ) + ImmutableMap.of("http_certificates_list", EXPECTED_CERTIFICATES, "transport_certificates_list", EXPECTED_CERTIFICATES) ); } catch (JsonProcessingException e) { throw new RuntimeException(e); @@ -71,11 +77,13 @@ public class SslCertsApiTest extends AbstractRestApiUnitTest { private final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); private final Header restApiCertsInfoAdminHeader = encodeBasicHeader("rest_api_admin_ssl_info", "rest_api_admin_ssl_info"); - private final Header restApiReloadCertsAdminHeader = encodeBasicHeader("rest_api_admin_ssl_reloadcerts", "rest_api_admin_ssl_reloadcerts"); + private final Header restApiReloadCertsAdminHeader = encodeBasicHeader( + "rest_api_admin_ssl_reloadcerts", + "rest_api_admin_ssl_reloadcerts" + ); private final Header restApiHeader = encodeBasicHeader("test", "test"); - public String certsInfoEndpoint() { return PLUGINS_PREFIX + "/api/ssl/certs"; } @@ -165,9 +173,7 @@ private void sendAdminCert() { } Settings reloadEnabled() { - return Settings.builder() - .put(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, true) - .build(); + return Settings.builder().put(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, true).build(); } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java index 00e983cc4f..2c6a45faf7 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/TenantInfoActionTest.java @@ -23,23 +23,26 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class TenantInfoActionTest extends AbstractRestApiUnitTest { - private String payload = "{\"hosts\":[],\"users\":[\"sarek\"]," + - "\"backend_roles\":[\"starfleet*\",\"ambassador\"],\"and_backend_roles\":[],\"description\":\"Migrated " + - "from v6\"}"; + private String payload = "{\"hosts\":[],\"users\":[\"sarek\"]," + + "\"backend_roles\":[\"starfleet*\",\"ambassador\"],\"and_backend_roles\":[],\"description\":\"Migrated " + + "from v6\"}"; private final String BASE_ENDPOINT; private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public TenantInfoActionTest(){ + public TenantInfoActionTest() { BASE_ENDPOINT = getEndpointPrefix(); ENDPOINT = getEndpointPrefix() + "/tenantinfo"; } @Test public void testTenantInfoAPIAccess() throws Exception { - Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build(); + Settings settings = Settings.builder() + .put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true) + .build(); setup(settings); rh.keystore = "restapi/kirk-keystore.jks"; @@ -58,16 +61,21 @@ public void testTenantInfoAPIAccess() throws Exception { @Test public void testTenantInfoAPIUpdate() throws Exception { - Settings settings = Settings.builder().put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build(); + Settings settings = Settings.builder() + .put(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true) + .build(); setup(settings); rh.keystore = "restapi/kirk-keystore.jks"; rh.sendHTTPClientCredentials = true; rh.sendAdminCertificate = true; - //update security config - RestHelper.HttpResponse response = rh.executePatchRequest(BASE_ENDPOINT + "/api/securityconfig", "[{\"op\": \"add\",\"path\": \"/config/dynamic/kibana/opendistro_role\"," + - "\"value\": \"opendistro_security_internal\"}]", new Header[0]); + // update security config + RestHelper.HttpResponse response = rh.executePatchRequest( + BASE_ENDPOINT + "/api/securityconfig", + "[{\"op\": \"add\",\"path\": \"/config/dynamic/kibana/opendistro_role\"," + "\"value\": \"opendistro_security_internal\"}]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executePutRequest(BASE_ENDPOINT + "/api/rolesmapping/opendistro_security_internal", payload, new Header[0]); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index abcda9a69c..895f65bc81 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -37,43 +37,42 @@ import static org.opensearch.security.dlic.rest.api.InternalUsersApiAction.RESTRICTED_FROM_USERNAME; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; - public class UserApiTest extends AbstractRestApiUnitTest { private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - final int USER_SETTING_SIZE = 7 * 19; // Lines per account entry * number of accounts - private static final String ENABLED_SERVICE_ACCOUNT_BODY = "{" - + " \"attributes\": { \"service\": \"true\", " - + "\"enabled\": \"true\"}" - + " }\n"; + private static final String ENABLED_SERVICE_ACCOUNT_BODY = "{" + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"true\"}" + + " }\n"; private static final String DISABLED_SERVICE_ACCOUNT_BODY = "{" - + " \"attributes\": { \"service\": \"true\", " - + "\"enabled\": \"false\"}" - + " }\n"; + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"false\"}" + + " }\n"; private static final String ENABLED_NOT_SERVICE_ACCOUNT_BODY = "{" - + " \"attributes\": { \"service\": \"false\", " - + "\"enabled\": \"true\"}" - + " }\n"; + + " \"attributes\": { \"service\": \"false\", " + + "\"enabled\": \"true\"}" + + " }\n"; private static final String PASSWORD_SERVICE = "{ \"password\" : \"test\"," - + " \"attributes\": { \"service\": \"true\", " - + "\"enabled\": \"true\"}" - + " }\n"; + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"true\"}" + + " }\n"; private static final String HASH_SERVICE = "{ \"owner\" : \"test_owner\"," - + " \"attributes\": { \"service\": \"true\", " - + "\"enabled\": \"true\"}" - + " }\n"; + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"true\"}" + + " }\n"; private static final String PASSWORD_HASH_SERVICE = "{ \"password\" : \"test\", \"hash\" : \"123\"," - + " \"attributes\": { \"service\": \"true\", " - + "\"enabled\": \"true\"}" - + " }\n"; + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"true\"}" + + " }\n"; - public UserApiTest(){ + public UserApiTest() { ENDPOINT = getEndpointPrefix() + "/api"; } @@ -86,17 +85,16 @@ public void testSecurityRoles() throws Exception { rh.sendAdminCertificate = true; // initial configuration, 6 users - HttpResponse response = rh - .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(133, settings.size()); response = rh.executePatchRequest( - ENDPOINT + "/internalusers", - "[{ \"op\": \"add\", \"path\": \"/newuser\", " + - "\"value\": {\"password\": \"fair password for the user\", " + - "\"opendistro_security_roles\": [\"opendistro_security_all_access\"] } }]", - new Header[0] + ENDPOINT + "/internalusers", + "[{ \"op\": \"add\", \"path\": \"/newuser\", " + + "\"value\": {\"password\": \"fair password for the user\", " + + "\"opendistro_security_roles\": [\"opendistro_security_all_access\"] } }]", + new Header[0] ); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); @@ -116,8 +114,11 @@ public void testParallelPutRequests() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - HttpResponse[] responses = rh.executeMultipleAsyncPutRequest(10, - ENDPOINT + "/internalusers/test1", "{\"password\":\"test1test1test1test1test1test1\"}"); + HttpResponse[] responses = rh.executeMultipleAsyncPutRequest( + 10, + ENDPOINT + "/internalusers/test1", + "{\"password\":\"test1test1test1test1test1test1\"}" + ); boolean created = false; for (HttpResponse response : responses) { int sc = response.getStatusCode(); @@ -188,8 +189,7 @@ private void verifyPut(final Header... header) throws Exception { Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode()); // Faulty JSON payload - response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{some: \"thing\" asd other: \"thing\"}", - header); + response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{some: \"thing\" asd other: \"thing\"}", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage()); @@ -198,37 +198,44 @@ private void verifyPut(final Header... header) throws Exception { response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{some: \"thing\", other: \"thing\"}", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - //JK: this should be "Could not parse content of request." because JSON is truly invalid - //Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage()); - //Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("some")); - //Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("other")); + // JK: this should be "Could not parse content of request." because JSON is truly invalid + // Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage()); + // Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("some")); + // Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("other")); // Get hidden role - response = rh.executeGetRequest(ENDPOINT + "/internalusers/hide" , header); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/hide", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"hidden\":true")); // Associating with hidden role is allowed (for superadmin) - response = rh.executePutRequest(ENDPOINT + "/internalusers/test", "{ \"opendistro_security_roles\": " + - "[\"opendistro_security_hidden\"]}", header); + response = rh.executePutRequest( + ENDPOINT + "/internalusers/test", + "{ \"opendistro_security_roles\": " + "[\"opendistro_security_hidden\"]}", + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // Associating with reserved role is allowed (for superadmin) - response = rh.executePutRequest(ENDPOINT + "/internalusers/test", "{ \"opendistro_security_roles\": [\"opendistro_security_reserved\"], " + - "\"hash\": \"123\"}", - header); + response = rh.executePutRequest( + ENDPOINT + "/internalusers/test", + "{ \"opendistro_security_roles\": [\"opendistro_security_reserved\"], " + "\"hash\": \"123\"}", + header + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // Associating with non-existent role is not allowed - response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{ \"opendistro_security_roles\": [\"non_existent\"]}", - header); + response = rh.executePutRequest( + ENDPOINT + "/internalusers/nagilum", + "{ \"opendistro_security_roles\": [\"non_existent\"]}", + header + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(settings.get("message"), "Role 'non_existent' is not available for role-mapping."); // Wrong config keys - response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{\"some\": \"thing\", \"other\": \"thing\"}", - header); + response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{\"some\": \"thing\", \"other\": \"thing\"}", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage()); @@ -241,29 +248,49 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) // -- PATCH // PATCH on non-existing resource rh.sendAdminCertificate = sendAdminCert; - HttpResponse response = rh.executePatchRequest(ENDPOINT + "/internalusers/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", restAdminHeader); + HttpResponse response = rh.executePatchRequest( + ENDPOINT + "/internalusers/imnothere", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + restAdminHeader + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // PATCH read only resource, must be forbidden, // but SuperAdmin can PATCH read-only resource rh.sendAdminCertificate = sendAdminCert; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/sarek", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", restAdminHeader); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers/sarek", + "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", + restAdminHeader + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be not found, can be found for super admin rh.sendAdminCertificate = sendAdminCert; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/q", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", restAdminHeader); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers/q", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + restAdminHeader + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error rh.sendAdminCertificate = sendAdminCert; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/test", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", restAdminHeader); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers/test", + "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", + restAdminHeader + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH password rh.sendAdminCertificate = sendAdminCert; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/test", "[{ \"op\": \"add\", \"path\": \"/password\", \"value\": \"neu password 42\" }]", restAdminHeader); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers/test", + "[{ \"op\": \"add\", \"path\": \"/password\", \"value\": \"neu password 42\" }]", + restAdminHeader + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/internalusers/test", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); @@ -274,34 +301,56 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) // -- PATCH on whole config resource // PATCH on non-existing resource rh.sendAdminCertificate = sendAdminCert; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", restAdminHeader); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers", + "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", + restAdminHeader + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH read only resource, must be forbidden, // but SuperAdmin can PATCH read only resouce rh.sendAdminCertificate = sendAdminCert; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/sarek/description\", \"value\": \"foo\" }]", restAdminHeader); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers", + "[{ \"op\": \"add\", \"path\": \"/sarek/description\", \"value\": \"foo\" }]", + restAdminHeader + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); rh.sendAdminCertificate = false; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/sarek/a\", \"value\": [ \"foo\", \"bar\" ] }]"); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers", + "[{ \"op\": \"add\", \"path\": \"/sarek/a\", \"value\": [ \"foo\", \"bar\" ] }]" + ); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusCode()); // PATCH hidden resource, must be bad request rh.sendAdminCertificate = sendAdminCert; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/q/a\", \"value\": [ \"foo\", \"bar\" ] }]", restAdminHeader); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers", + "[{ \"op\": \"add\", \"path\": \"/q/a\", \"value\": [ \"foo\", \"bar\" ] }]", + restAdminHeader + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error rh.sendAdminCertificate = sendAdminCert; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/test/hidden\", \"value\": true }]", restAdminHeader); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers", + "[{ \"op\": \"add\", \"path\": \"/test/hidden\", \"value\": true }]", + restAdminHeader + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH rh.sendAdminCertificate = sendAdminCert; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", - "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": {\"password\": \"bla bla bla password 42\", \"backend_roles\": [\"vulcan\"] } }]", restAdminHeader); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers", + "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": {\"password\": \"bla bla bla password 42\", \"backend_roles\": [\"vulcan\"] } }]", + restAdminHeader + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/internalusers/bulknew1", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); @@ -320,46 +369,36 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) // add/update user, user is read only, forbidden // SuperAdmin can add read only users rh.sendAdminCertificate = sendAdminCert; - addUserWithHash("sarek", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", - HttpStatus.SC_OK); + addUserWithHash("sarek", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_OK); // add/update user, user is hidden, forbidden, allowed for super admin rh.sendAdminCertificate = sendAdminCert; - addUserWithHash("q", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", - HttpStatus.SC_OK); + addUserWithHash("q", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_OK); // add users rh.sendAdminCertificate = sendAdminCert; - addUserWithHash("nagilum", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", - HttpStatus.SC_CREATED); + addUserWithHash("nagilum", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); // Add enabled service account then get it - response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceLive", - ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceLive", ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/internalusers/happyServiceLive", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - // Add disabled service account - response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceDead", - DISABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceDead", DISABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); - // Add service account with password -- Should Fail - response = rh.executePutRequest(ENDPOINT + "/internalusers/passwordService", - PASSWORD_SERVICE, restAdminHeader); + response = rh.executePutRequest(ENDPOINT + "/internalusers/passwordService", PASSWORD_SERVICE, restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - //Add service with hash -- should fail - response = rh.executePutRequest(ENDPOINT + "/internalusers/hashService", - HASH_SERVICE, restAdminHeader); + // Add service with hash -- should fail + response = rh.executePutRequest(ENDPOINT + "/internalusers/hashService", HASH_SERVICE, restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // Add Service account with password & Hash -- should fail - response = rh.executePutRequest(ENDPOINT + "/internalusers/passwordHashService", - PASSWORD_HASH_SERVICE, restAdminHeader); + response = rh.executePutRequest(ENDPOINT + "/internalusers/passwordHashService", PASSWORD_HASH_SERVICE, restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // access must be allowed now @@ -403,12 +442,11 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) rh.sendAdminCertificate = sendAdminCert; // new user, password or hash is mandatory - addUserWithoutPasswordOrHash("nagilum", new String[]{"starfleet"}, HttpStatus.SC_BAD_REQUEST); + addUserWithoutPasswordOrHash("nagilum", new String[] { "starfleet" }, HttpStatus.SC_BAD_REQUEST); // new user, add hash - addUserWithHash("nagilum", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", - HttpStatus.SC_CREATED); + addUserWithHash("nagilum", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); // update user, do not specify hash or password, hash must remain the same - addUserWithoutPasswordOrHash("nagilum", new String[]{"starfleet"}, HttpStatus.SC_OK); + addUserWithoutPasswordOrHash("nagilum", new String[] { "starfleet" }, HttpStatus.SC_OK); // get user, check hash, must be untouched response = rh.executeGetRequest(ENDPOINT + "/internalusers/nagilum", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); @@ -421,42 +459,51 @@ private void verifyAuthToken(final boolean sendAdminCert, Header... restAdminHea // Add enabled service account then generate auth token rh.sendAdminCertificate = sendAdminCert; - HttpResponse response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceLive", - ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/internalusers/happyServiceLive", + ENABLED_SERVICE_ACCOUNT_BODY, + restAdminHeader + ); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = sendAdminCert; response = rh.executeGetRequest(ENDPOINT + "/internalusers/happyServiceLive", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executePostRequest(ENDPOINT + "/internalusers/happyServiceLive/authtoken", - ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + response = rh.executePostRequest( + ENDPOINT + "/internalusers/happyServiceLive/authtoken", + ENABLED_SERVICE_ACCOUNT_BODY, + restAdminHeader + ); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); String tokenFromResponse = response.getBody(); byte[] decodedResponse = Base64.getUrlDecoder().decode(tokenFromResponse); String[] decodedResponseString = new String(decodedResponse).split(":", 2); String username = decodedResponseString[0]; String password = decodedResponseString[1]; - Assert.assertEquals("Username is: " + username,username, "happyServiceLive"); + Assert.assertEquals("Username is: " + username, username, "happyServiceLive"); // Add disabled service account then try to get its auth token rh.sendAdminCertificate = sendAdminCert; - response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceDead", - DISABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceDead", DISABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); - response = rh.executePostRequest(ENDPOINT + "/internalusers/happyServiceDead/authtoken", - ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + response = rh.executePostRequest( + ENDPOINT + "/internalusers/happyServiceDead/authtoken", + ENABLED_SERVICE_ACCOUNT_BODY, + restAdminHeader + ); Assert.assertEquals(response.getBody(), HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - // Add enabled non-service account rh.sendAdminCertificate = sendAdminCert; - response = rh.executePutRequest(ENDPOINT + "/internalusers/user_is_owner_1", - ENABLED_NOT_SERVICE_ACCOUNT_BODY, restAdminHeader); + response = rh.executePutRequest(ENDPOINT + "/internalusers/user_is_owner_1", ENABLED_NOT_SERVICE_ACCOUNT_BODY, restAdminHeader); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - response = rh.executePostRequest(ENDPOINT + "/internalusers/user_is_owner_1/authtoken", - ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + response = rh.executePostRequest( + ENDPOINT + "/internalusers/user_is_owner_1/authtoken", + ENABLED_SERVICE_ACCOUNT_BODY, + restAdminHeader + ); Assert.assertEquals(response.getBody(), HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); } @@ -464,7 +511,11 @@ private void verifyAuthToken(final boolean sendAdminCert, Header... restAdminHea private void verifyRoles(final boolean sendAdminCert, Header... header) throws Exception { // wrong datatypes in roles file rh.sendAdminCertificate = sendAdminCert; - HttpResponse response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes.json"), header); + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/internalusers/picard", + FileHelper.loadFile("restapi/users_wrong_datatypes.json"), + header + ); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -472,7 +523,11 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E rh.sendAdminCertificate = false; rh.sendAdminCertificate = sendAdminCert; - response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/internalusers/picard", + FileHelper.loadFile("restapi/users_wrong_datatypes.json"), + header + ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -480,7 +535,11 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E rh.sendAdminCertificate = false; rh.sendAdminCertificate = sendAdminCert; - response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes2.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/internalusers/picard", + FileHelper.loadFile("restapi/users_wrong_datatypes2.json"), + header + ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -489,7 +548,11 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E rh.sendAdminCertificate = false; rh.sendAdminCertificate = sendAdminCert; - response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes3.json"), header); + response = rh.executePutRequest( + ENDPOINT + "/internalusers/picard", + FileHelper.loadFile("restapi/users_wrong_datatypes3.json"), + header + ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -507,14 +570,14 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicardpicardpicard", "sf", "_doc", 0); // overwrite user picard, and give him role "starfleet". - addUserWithPassword("picard", "picardpicardpicardpicardpicard", new String[]{"starfleet"}, HttpStatus.SC_OK); + addUserWithPassword("picard", "picardpicardpicardpicardpicard", new String[] { "starfleet" }, HttpStatus.SC_OK); checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicardpicard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicardpicardpicard", "sf", "_doc", 1); // overwrite user picard, and give him role "starfleet" plus "captains. Now // document can be created. - addUserWithPassword("picard", "picardpicardpicardpicardpicard", new String[]{"starfleet", "captains"}, HttpStatus.SC_OK); + addUserWithPassword("picard", "picardpicardpicardpicardpicard", new String[] { "starfleet", "captains" }, HttpStatus.SC_OK); checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicardpicardpicard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picardpicardpicardpicardpicard", "sf", "_doc", 1); @@ -576,14 +639,11 @@ public void testUserApiWithRestInternalUsersAdminPermissions() throws Exception @Test public void testRegExpPasswordRules() throws Exception { - Settings nodeSettings = - Settings.builder() - .put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, "xxx") - .put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, - "(?=.*[A-Z])(?=.*[^a-zA-Z\\\\d])(?=.*[0-9])(?=.*[a-z]).{8,}") - .put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, - PasswordValidator.ScoreStrength.FAIR.name()) - .build(); + Settings nodeSettings = Settings.builder() + .put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, "xxx") + .put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, "(?=.*[A-Z])(?=.*[^a-zA-Z\\\\d])(?=.*[0-9])(?=.*[a-z]).{8,}") + .put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, PasswordValidator.ScoreStrength.FAIR.name()) + .build(); setup(nodeSettings); @@ -591,8 +651,7 @@ public void testRegExpPasswordRules() throws Exception { rh.sendAdminCertificate = true; // initial configuration, 6 users - HttpResponse response = rh - .executeGetRequest("_plugins/_security/api/" + CType.INTERNALUSERS.toLCString()); + HttpResponse response = rh.executeGetRequest("_plugins/_security/api/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(USER_SETTING_SIZE, settings.size()); @@ -611,7 +670,7 @@ public void testRegExpPasswordRules() throws Exception { private void verifyCouldNotCreatePasswords(final int expectedStatus) throws Exception { addUserWithPassword("tooshoort", "", expectedStatus); - addUserWithPassword("tooshoort", "123",expectedStatus); + addUserWithPassword("tooshoort", "123", expectedStatus); addUserWithPassword("tooshoort", "1234567", expectedStatus); addUserWithPassword("tooshoort", "1Aa%", expectedStatus); addUserWithPassword("no-nonnumeric", "123456789", expectedStatus); @@ -619,39 +678,65 @@ private void verifyCouldNotCreatePasswords(final int expectedStatus) throws Exce addUserWithPassword("no-lowercase", "A123456789", expectedStatus); addUserWithPassword("empty_password_no_hash", "", expectedStatus); HttpResponse response = rh.executePatchRequest( - PLUGINS_PREFIX + "/api/internalusers", - "[{ \"op\": \"add\", \"path\": \"/ok4\", \"value\": {\"password\": \"bla\", \"backend_roles\": [\"vulcan\"] } }]", - new Header[0] + PLUGINS_PREFIX + "/api/internalusers", + "[{ \"op\": \"add\", \"path\": \"/ok4\", \"value\": {\"password\": \"bla\", \"backend_roles\": [\"vulcan\"] } }]", + new Header[0] ); Assert.assertEquals(response.getBody(), expectedStatus, response.getStatusCode()); - response = rh.executePatchRequest(PLUGINS_PREFIX + "/api/internalusers", "[{ \"op\": \"replace\", \"path\": \"/ok4\", \"value\": {\"password\": \"bla\", \"backend_roles\": [\"vulcan\"] } }]", new Header[0]); + response = rh.executePatchRequest( + PLUGINS_PREFIX + "/api/internalusers", + "[{ \"op\": \"replace\", \"path\": \"/ok4\", \"value\": {\"password\": \"bla\", \"backend_roles\": [\"vulcan\"] } }]", + new Header[0] + ); Assert.assertEquals(response.getBody(), expectedStatus, response.getStatusCode()); addUserWithPassword("ok4", "123", expectedStatus); - //its not allowed to use the username as password (case insensitive) - response = rh.executePatchRequest(PLUGINS_PREFIX + "/api/internalusers", "[{ \"op\": \"add\", \"path\": \"/$1aAAAAAAAAB\", \"value\": {\"password\": \"$1aAAAAAAAAB\", \"backend_roles\": [\"vulcan\"] } }]", new Header[0]); + // its not allowed to use the username as password (case insensitive) + response = rh.executePatchRequest( + PLUGINS_PREFIX + "/api/internalusers", + "[{ \"op\": \"add\", \"path\": \"/$1aAAAAAAAAB\", \"value\": {\"password\": \"$1aAAAAAAAAB\", \"backend_roles\": [\"vulcan\"] } }]", + new Header[0] + ); Assert.assertEquals(response.getBody(), expectedStatus, response.getStatusCode()); addUserWithPassword("$1aAAAAAAAAC", "$1aAAAAAAAAC", expectedStatus); addUserWithPassword("$1aAAAAAAAac", "$1aAAAAAAAAC", expectedStatus); addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%", "UTF-8"), "$1aAAAAAAAAC%", expectedStatus); - addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%!=\"/\\;:test&~@^", "UTF-8").replace("+", "%2B"), "$1aAAAAAAAac%!=\\\"/\\\\;:test&~@^", expectedStatus); - addUserWithPassword(URLEncoder.encode("$1aAAAAAAAac%!=\"/\\;: test&", "UTF-8"), "$1aAAAAAAAac%!=\\\"/\\\\;: test&123", expectedStatus); - String patchPayload = "[ " + - "{ \"op\": \"add\", \"path\": \"/testuser1\", \"value\": { \"password\": \"$aA123456789\", \"backend_roles\": [\"testrole1\"] } }," + - "{ \"op\": \"add\", \"path\": \"/testuser2\", \"value\": { \"password\": \"testpassword2\", \"backend_roles\": [\"testrole2\"] } }" + - "]"; - - response = rh.executePatchRequest(PLUGINS_PREFIX + "/api/internalusers", patchPayload, new BasicHeader("Content-Type", "application/json")); + addUserWithPassword( + URLEncoder.encode("$1aAAAAAAAac%!=\"/\\;:test&~@^", "UTF-8").replace("+", "%2B"), + "$1aAAAAAAAac%!=\\\"/\\\\;:test&~@^", + expectedStatus + ); + addUserWithPassword( + URLEncoder.encode("$1aAAAAAAAac%!=\"/\\;: test&", "UTF-8"), + "$1aAAAAAAAac%!=\\\"/\\\\;: test&123", + expectedStatus + ); + String patchPayload = "[ " + + "{ \"op\": \"add\", \"path\": \"/testuser1\", \"value\": { \"password\": \"$aA123456789\", \"backend_roles\": [\"testrole1\"] } }," + + "{ \"op\": \"add\", \"path\": \"/testuser2\", \"value\": { \"password\": \"testpassword2\", \"backend_roles\": [\"testrole2\"] } }" + + "]"; + + response = rh.executePatchRequest( + PLUGINS_PREFIX + "/api/internalusers", + patchPayload, + new BasicHeader("Content-Type", "application/json") + ); Assert.assertEquals(expectedStatus, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("error")); Assert.assertTrue(response.getBody().contains("xxx")); - response = rh.executePutRequest(PLUGINS_PREFIX + "/api/internalusers/ok1", "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{},\"password\":\"\"}", new Header[0]); + response = rh.executePutRequest( + PLUGINS_PREFIX + "/api/internalusers/ok1", + "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{},\"password\":\"\"}", + new Header[0] + ); Assert.assertEquals(expectedStatus, response.getStatusCode()); - response = rh.executePutRequest(PLUGINS_PREFIX + "/api/internalusers/ok1", - "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{},\"password\":\"bla\"}", - new Header[0]); + response = rh.executePutRequest( + PLUGINS_PREFIX + "/api/internalusers/ok1", + "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{},\"password\":\"bla\"}", + new Header[0] + ); Assert.assertEquals(expectedStatus, response.getStatusCode()); } @@ -662,35 +747,35 @@ private void verifyCanCreatePasswords() throws Exception { addUserWithPassword("ok4", "$1aAAAAAAAAA", HttpStatus.SC_CREATED); addUserWithPassword("ok4", "$1aAAAAAAAAC", HttpStatus.SC_OK); HttpResponse response = rh.executePatchRequest( - PLUGINS_PREFIX + "/api/internalusers", - "[{ \"op\": \"add\", \"path\": \"/ok4\", \"value\": {\"password\": \"$1aAAAAAAAAB\", \"backend_roles\": [\"vulcan\"] } }]", - new Header[0] + PLUGINS_PREFIX + "/api/internalusers", + "[{ \"op\": \"add\", \"path\": \"/ok4\", \"value\": {\"password\": \"$1aAAAAAAAAB\", \"backend_roles\": [\"vulcan\"] } }]", + new Header[0] ); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executePutRequest(PLUGINS_PREFIX + "/api/internalusers/ok1", "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{},\"password\":\"Admin_123\"}", new Header[0]); + response = rh.executePutRequest( + PLUGINS_PREFIX + "/api/internalusers/ok1", + "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{},\"password\":\"Admin_123\"}", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executePutRequest(PLUGINS_PREFIX + "/api/internalusers/ok1", "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{}}", new Header[0]); + response = rh.executePutRequest( + PLUGINS_PREFIX + "/api/internalusers/ok1", + "{\"backend_roles\":[\"my-backend-role\"],\"attributes\":{}}", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); } - private void verifySimilarity(final String expectedMessage) throws Exception { - addUserWithPassword( - "some_user_name", "H3235,cc,some_User_Name", - HttpStatus.SC_BAD_REQUEST, - expectedMessage - ); + addUserWithPassword("some_user_name", "H3235,cc,some_User_Name", HttpStatus.SC_BAD_REQUEST, expectedMessage); } @Test public void testScoreBasedPasswordRules() throws Exception { - Settings nodeSettings = - Settings.builder() - .put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, 9) - .build(); + Settings nodeSettings = Settings.builder().put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, 9).build(); setup(nodeSettings); @@ -698,28 +783,25 @@ public void testScoreBasedPasswordRules() throws Exception { rh.sendAdminCertificate = true; // initial configuration, 6 users - HttpResponse response = rh - .executeGetRequest("_plugins/_security/api/" + CType.INTERNALUSERS.toLCString()); + HttpResponse response = rh.executeGetRequest("_plugins/_security/api/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(133, settings.size()); addUserWithPassword( - "admin", "password89", - HttpStatus.SC_BAD_REQUEST, - AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD.getMessage() + "admin", + "password89", + HttpStatus.SC_BAD_REQUEST, + AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD.getMessage() ); addUserWithPassword( - "admin", "A123456789", - HttpStatus.SC_BAD_REQUEST, - AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD.getMessage() + "admin", + "A123456789", + HttpStatus.SC_BAD_REQUEST, + AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD.getMessage() ); - addUserWithPassword( - "admin", "pas", - HttpStatus.SC_BAD_REQUEST, - "Password does not match minimum criteria" - ); + addUserWithPassword("admin", "pas", HttpStatus.SC_BAD_REQUEST, "Password does not match minimum criteria"); verifySimilarity(AbstractConfigurationValidator.ErrorType.SIMILAR_PASSWORD.getMessage()); @@ -735,23 +817,18 @@ public void testUserApiWithDots() throws Exception { rh.sendAdminCertificate = true; // initial configuration, 6 users - HttpResponse response = rh - .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(USER_SETTING_SIZE, settings.size()); - addUserWithPassword(".my.dotuser0", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", - HttpStatus.SC_CREATED); + addUserWithPassword(".my.dotuser0", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); - addUserWithPassword(".my.dot.user0", "12345678Sd", - HttpStatus.SC_CREATED); + addUserWithPassword(".my.dot.user0", "12345678Sd", HttpStatus.SC_CREATED); - addUserWithHash(".my.dotuser1", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", - HttpStatus.SC_CREATED); + addUserWithHash(".my.dotuser1", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); - addUserWithPassword(".my.dot.user2", "12345678Sd", - HttpStatus.SC_CREATED); + addUserWithPassword(".my.dot.user2", "12345678Sd", HttpStatus.SC_CREATED); } @@ -766,25 +843,32 @@ public void testUserApiNoPasswordChange() throws Exception { // initial configuration, 5 users HttpResponse response; - addUserWithHash("user1", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", - HttpStatus.SC_CREATED); + addUserWithHash("user1", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); - response = rh.executePutRequest(ENDPOINT + "/internalusers/user1", "{\"hash\":\"$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m\",\"password\":\"\",\"backend_roles\":[\"admin\",\"rolea\"]}"); + response = rh.executePutRequest( + ENDPOINT + "/internalusers/user1", + "{\"hash\":\"$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m\",\"password\":\"\",\"backend_roles\":[\"admin\",\"rolea\"]}" + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - response = rh.executePutRequest(ENDPOINT + "/internalusers/user1", "{\"hash\":\"$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m\",\"password\":\"Admin_123345Yq\",\"backend_roles\":[\"admin\",\"rolea\"]}"); + response = rh.executePutRequest( + ENDPOINT + "/internalusers/user1", + "{\"hash\":\"$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m\",\"password\":\"Admin_123345Yq\",\"backend_roles\":[\"admin\",\"rolea\"]}" + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/internalusers/user1"); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - addUserWithHash("user2", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", - HttpStatus.SC_CREATED); + addUserWithHash("user2", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); response = rh.executePutRequest(ENDPOINT + "/internalusers/user2", "{\"password\":\"\",\"backend_roles\":[\"admin\",\"rolex\"]}"); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - response = rh.executePutRequest(ENDPOINT + "/internalusers/user2", "{\"password\":\"Admin_123Qerty\",\"backend_roles\":[\"admin\",\"rolex\"]}"); + response = rh.executePutRequest( + ENDPOINT + "/internalusers/user2", + "{\"password\":\"Admin_123Qerty\",\"backend_roles\":[\"admin\",\"rolex\"]}" + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/internalusers/user2"); @@ -803,54 +887,89 @@ public void testUserApiForNonSuperAdmin() throws Exception { HttpResponse response; // Delete read only user - response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/sarek" , new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/sarek", new Header[0]); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch read only users - response = rh.executePatchRequest(ENDPOINT + "/internalusers/sarek", "[{ \"op\": \"add\", \"path\": \"/sarek/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers/sarek", + "[{ \"op\": \"add\", \"path\": \"/sarek/description\", \"value\": \"foo\" }]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Put read only users - response = rh.executePutRequest(ENDPOINT + "/internalusers/sarek", "{ \"opendistro_security_roles\": [\"opendistro_security_reserved\"]}", new Header[0]); + response = rh.executePutRequest( + ENDPOINT + "/internalusers/sarek", + "{ \"opendistro_security_roles\": [\"opendistro_security_reserved\"]}", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch single read only user - response = rh.executePatchRequest(ENDPOINT + "/internalusers/sarek", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers/sarek", + "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch multiple read only users - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/sarek/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers", + "[{ \"op\": \"add\", \"path\": \"/sarek/description\", \"value\": \"foo\" }]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Get hidden role - response = rh.executeGetRequest(ENDPOINT + "/internalusers/hide" , new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/hide", new Header[0]); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Delete hidden user - response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/hide" , new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/hide", new Header[0]); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch hidden users - response = rh.executePatchRequest(ENDPOINT + "/internalusers/hide", "[{ \"op\": \"add\", \"path\": \"/sarek/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers/hide", + "[{ \"op\": \"add\", \"path\": \"/sarek/description\", \"value\": \"foo\" }]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Put hidden users - response = rh.executePutRequest(ENDPOINT + "/internalusers/hide", "{ \"opendistro_security_roles\": [\"opendistro_security_reserved\"]}", new Header[0]); + response = rh.executePutRequest( + ENDPOINT + "/internalusers/hide", + "{ \"opendistro_security_roles\": [\"opendistro_security_reserved\"]}", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Put reserved role is forbidden for non-superadmin - response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{ \"opendistro_security_roles\": [\"opendistro_security_reserved\"]}", - new Header[0]); + response = rh.executePutRequest( + ENDPOINT + "/internalusers/nagilum", + "{ \"opendistro_security_roles\": [\"opendistro_security_reserved\"]}", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(settings.get("message"), "Resource 'opendistro_security_reserved' is read-only."); // Patch single hidden user - response = rh.executePatchRequest(ENDPOINT + "/internalusers/hide", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers/hide", + "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch multiple hidden users - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/hide/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT + "/internalusers", + "[{ \"op\": \"add\", \"path\": \"/hide/description\", \"value\": \"foo\" }]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); } @@ -873,7 +992,7 @@ public void restrictedUsernameContents() throws Exception { } @Test - public void checkNullElementsInArray() throws Exception{ + public void checkNullElementsInArray() throws Exception { setup(); rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java index cc148393c1..371341147e 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java @@ -52,11 +52,12 @@ public class WhitelistApiTest extends AbstractRestApiUnitTest { private final Header adminCredsHeader = encodeBasicHeader("admin_all_access", "admin_all_access"); private final Header nonAdminCredsHeader = encodeBasicHeader("sarek", "sarek"); private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public WhitelistApiTest(){ + public WhitelistApiTest() { ENDPOINT = getEndpointPrefix() + "/api"; } @@ -65,24 +66,32 @@ public WhitelistApiTest(){ * * @throws Exception */ - private void checkGetAndPutWhitelistPermissions(final int expectedStatus, final boolean sendAdminCertificate, final Header... headers) throws Exception { + private void checkGetAndPutWhitelistPermissions(final int expectedStatus, final boolean sendAdminCertificate, final Header... headers) + throws Exception { final boolean prevSendAdminCertificate = rh.sendAdminCertificate; rh.sendAdminCertificate = sendAdminCertificate; - //CHECK GET REQUEST + // CHECK GET REQUEST response = rh.executeGetRequest(ENDPOINT + "/whitelist", headers); assertThat(response.getBody(), response.getStatusCode(), equalTo(expectedStatus)); if (expectedStatus == HttpStatus.SC_OK) { - //Note: the response has no whitespaces, so the .json file does not have whitespaces - Assert.assertEquals(FileHelper.loadFile("restapi/whitelist_response_success.json"), FileHelper.loadFile("restapi/whitelist_response_success.json")); + // Note: the response has no whitespaces, so the .json file does not have whitespaces + Assert.assertEquals( + FileHelper.loadFile("restapi/whitelist_response_success.json"), + FileHelper.loadFile("restapi/whitelist_response_success.json") + ); } - //FORBIDDEN FOR NON SUPER ADMIN + // FORBIDDEN FOR NON SUPER ADMIN if (expectedStatus == HttpStatus.SC_FORBIDDEN) { assertTrue(response.getBody().contains("API allowed only for super admin.")); } - //CHECK PUT REQUEST - response = rh.executePutRequest(ENDPOINT + "/whitelist", "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", headers); + // CHECK PUT REQUEST + response = rh.executePutRequest( + ENDPOINT + "/whitelist", + "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", + headers + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(expectedStatus)); rh.sendAdminCertificate = prevSendAdminCertificate; @@ -105,7 +114,10 @@ public void testPutUnknownKey() throws Exception { setup(); rh.sendAdminCertificate = true; - RestHelper.HttpResponse response = rh.executePutRequest(ENDPOINT + "/whitelist", "{ \"unknownkey\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}"); + RestHelper.HttpResponse response = rh.executePutRequest( + ENDPOINT + "/whitelist", + "{ \"unknownkey\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}" + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); assertTrue(response.getBody().contains("invalid_keys")); assertHealthy(); @@ -116,7 +128,10 @@ public void testPutInvalidJson() throws Exception { setup(); rh.sendAdminCertificate = true; - RestHelper.HttpResponse response = rh.executePutRequest(ENDPOINT + "/whitelist", "{ \"invalid\"::{{ [\"*\"], \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}"); + RestHelper.HttpResponse response = rh.executePutRequest( + ENDPOINT + "/whitelist", + "{ \"invalid\"::{{ [\"*\"], \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}" + ); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); assertHealthy(); } @@ -152,7 +167,7 @@ public void testWhitelistApi() throws Exception { // No creds, no admin certificate - UNAUTHORIZED checkGetAndPutWhitelistPermissions(HttpStatus.SC_UNAUTHORIZED, false); - //non admin creds, no admin certificate - FORBIDDEN + // non admin creds, no admin certificate - FORBIDDEN checkGetAndPutWhitelistPermissions(HttpStatus.SC_FORBIDDEN, false, nonAdminCredsHeader); // admin creds, no admin certificate - FORBIDDEN @@ -165,37 +180,45 @@ public void testWhitelistApi() throws Exception { @Test public void testWhitelistAuditComplianceLogging() throws Exception { Settings settings = 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, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") - .build(); + .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, false) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) + .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, true) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") + .build(); setupWithRestRoles(settings); TestAuditlogImpl.clear(); // any creds, admin certificate - OK checkGetAndPutWhitelistPermissions(HttpStatus.SC_OK, true, nonAdminCredsHeader); - //TESTS THAT 1 READ AND 1 WRITE HAPPENS IN testGetAndPut() + // TESTS THAT 1 READ AND 1 WRITE HAPPENS IN testGetAndPut() final Map expectedCategoryCounts = ImmutableMap.of( - AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ, 1L, - AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE, 1L); - Map actualCategoryCounts = TestAuditlogImpl.messages.stream().collect(Collectors.groupingBy(AuditMessage::getCategory, Collectors.counting())); + AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ, + 1L, + AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE, + 1L + ); + Map actualCategoryCounts = TestAuditlogImpl.messages.stream() + .collect(Collectors.groupingBy(AuditMessage::getCategory, Collectors.counting())); assertThat(actualCategoryCounts, equalTo(expectedCategoryCounts)); } @Test - public void testWhitelistInvalidHttpRequestMethod() throws Exception{ + public void testWhitelistInvalidHttpRequestMethod() throws Exception { setup(); rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT + "/whitelist", "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GE\"],\"/_cat/indices\": [\"PUT\"] }}", adminCredsHeader); + response = rh.executePutRequest( + ENDPOINT + "/whitelist", + "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GE\"],\"/_cat/indices\": [\"PUT\"] }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_INTERNAL_SERVER_ERROR)); assertTrue(response.getBody().contains("\\\"GE\\\": not one of the values accepted for Enum class")); } @@ -208,37 +231,61 @@ public void testWhitelistInvalidHttpRequestMethod() throws Exception{ * @throws Exception */ @Test - public void testPatchApi() throws Exception{ + public void testPatchApi() throws Exception { setup(); rh.sendAdminCertificate = true; - //PATCH entire config entry - response = rh.executePatchRequest(ENDPOINT + "/whitelist", "[{ \"op\": \"replace\", \"path\": \"/config\", \"value\": {\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"PUT\"] }}}]", new Header[0]); + // PATCH entire config entry + response = rh.executePatchRequest( + ENDPOINT + "/whitelist", + "[{ \"op\": \"replace\", \"path\": \"/config\", \"value\": {\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"PUT\"] }}}]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/whitelist", adminCredsHeader); - assertEquals(response.getBody(),"{\"config\":{\"enabled\":true,\"requests\":{\"/_cat/nodes\":[\"GET\"],\"/_cat/indices\":[\"PUT\"]}}}"); - - //PATCH just requests - response = rh.executePatchRequest(ENDPOINT + "/whitelist", "[{ \"op\": \"replace\", \"path\": \"/config/requests\", \"value\": {\"/_cat/nodes\": [\"GET\"]}}]", new Header[0]); + assertEquals( + response.getBody(), + "{\"config\":{\"enabled\":true,\"requests\":{\"/_cat/nodes\":[\"GET\"],\"/_cat/indices\":[\"PUT\"]}}}" + ); + + // PATCH just requests + response = rh.executePatchRequest( + ENDPOINT + "/whitelist", + "[{ \"op\": \"replace\", \"path\": \"/config/requests\", \"value\": {\"/_cat/nodes\": [\"GET\"]}}]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/whitelist", adminCredsHeader); assertTrue(response.getBody().contains("\"requests\":{\"/_cat/nodes\":[\"GET\"]}")); - //PATCH just whitelisted_enabled using "replace" operation - works when enabled is already true - response = rh.executePatchRequest(ENDPOINT + "/whitelist", "[{ \"op\": \"replace\", \"path\": \"/config/enabled\", \"value\": false}]", new Header[0]); + // PATCH just whitelisted_enabled using "replace" operation - works when enabled is already true + response = rh.executePatchRequest( + ENDPOINT + "/whitelist", + "[{ \"op\": \"replace\", \"path\": \"/config/enabled\", \"value\": false}]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/whitelist", adminCredsHeader); assertTrue(response.getBody().contains("\"enabled\":false")); - //PATCH just enabled using "add" operation when it is currently false - works correctly - response = rh.executePatchRequest(ENDPOINT + "/whitelist", "[{ \"op\": \"add\", \"path\": \"/config/enabled\", \"value\": true}]", new Header[0]); + // PATCH just enabled using "add" operation when it is currently false - works correctly + response = rh.executePatchRequest( + ENDPOINT + "/whitelist", + "[{ \"op\": \"add\", \"path\": \"/config/enabled\", \"value\": true}]", + new Header[0] + ); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/whitelist", adminCredsHeader); assertTrue(response.getBody().contains("\"enabled\":true")); - //PATCH just enabled using "add" operation when it is currently true - works correctly - response = rh.executePatchRequest(ENDPOINT + "/whitelist", "[{ \"op\": \"add\", \"path\": \"/config/enabled\", \"value\": false}]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());response = rh.executeGetRequest(ENDPOINT + "/whitelist", adminCredsHeader); + // PATCH just enabled using "add" operation when it is currently true - works correctly + response = rh.executePatchRequest( + ENDPOINT + "/whitelist", + "[{ \"op\": \"add\", \"path\": \"/config/enabled\", \"value\": false}]", + new Header[0] + ); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + response = rh.executeGetRequest(ENDPOINT + "/whitelist", adminCredsHeader); response = rh.executeGetRequest(ENDPOINT + "/whitelist", adminCredsHeader); assertTrue(response.getBody().contains("\"enabled\":false")); } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAccountApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAccountApiTests.java index a48a7d2e3a..925d90ccba 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAccountApiTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAccountApiTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyAccountApiTests extends AccountApiTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyActionGroupsApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyActionGroupsApiTests.java index 9aa4b70c77..e92f046f65 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyActionGroupsApiTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyActionGroupsApiTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyActionGroupsApiTests extends ActionGroupsApiTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAuditApiActionTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAuditApiActionTests.java index 4d97da8bbb..fbde68e911 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAuditApiActionTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyAuditApiActionTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyAuditApiActionTests extends AuditApiActionTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyDashboardsInfoActionTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyDashboardsInfoActionTests.java index a9baec37bd..ee39f93ee0 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyDashboardsInfoActionTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyDashboardsInfoActionTests.java @@ -16,7 +16,7 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyDashboardsInfoActionTests extends DashboardsInfoActionTest { - @Override + @Override protected String getEndpoint() { return LEGACY_OPENDISTRO_PREFIX + "/kibanainfo"; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyFlushCacheApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyFlushCacheApiTests.java index ab09a6e2f2..df9cc3d59d 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyFlushCacheApiTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyFlushCacheApiTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyFlushCacheApiTests extends FlushCacheApiTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyGetConfigurationApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyGetConfigurationApiTests.java index cca6739733..07983bad0d 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyGetConfigurationApiTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyGetConfigurationApiTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyGetConfigurationApiTests extends GetConfigurationApiTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyIndexMissingTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyIndexMissingTests.java index 0680aa2c2e..fef436f4d7 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyIndexMissingTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyIndexMissingTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyIndexMissingTests extends IndexMissingTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyNodesDnApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyNodesDnApiTests.java index 22237ece3f..a316785f02 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyNodesDnApiTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyNodesDnApiTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyNodesDnApiTests extends NodesDnApiTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRoleBasedAccessTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRoleBasedAccessTests.java index c9f421058c..329404dfe7 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRoleBasedAccessTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRoleBasedAccessTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyRoleBasedAccessTests extends RoleBasedAccessTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesApiTests.java index b4ec33a2d5..118f8e1ebe 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesApiTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesApiTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyRolesApiTests extends RolesApiTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesMappingApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesMappingApiTests.java index c659fb57bc..dd29b524c1 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesMappingApiTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesMappingApiTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyRolesMappingApiTests extends RolesMappingApiTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityApiAccessTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityApiAccessTests.java index 72b6086c1e..85428d645d 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityApiAccessTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityApiAccessTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacySecurityApiAccessTests extends SecurityApiAccessTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityConfigApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityConfigApiTests.java index fd03e7248a..6175809b4a 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityConfigApiTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityConfigApiTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacySecurityConfigApiTests extends SecurityConfigApiTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityHealthActionTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityHealthActionTests.java index 470db0a526..99fa4a99ae 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityHealthActionTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityHealthActionTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacySecurityHealthActionTests extends SecurityHealthActionTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityInfoActionTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityInfoActionTests.java index 8480787423..75c5238f7f 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityInfoActionTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacySecurityInfoActionTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacySecurityInfoActionTests extends SecurityInfoActionTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyTenantInfoActionTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyTenantInfoActionTests.java index 1f2ac9a77d..49963d7d55 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyTenantInfoActionTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyTenantInfoActionTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyTenantInfoActionTests extends TenantInfoActionTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyUserApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyUserApiTests.java index 5753688097..449bce270d 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyUserApiTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyUserApiTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyUserApiTests extends UserApiTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyWhitelistApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyWhitelistApiTests.java index 3ae501f9a4..689981aa2a 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyWhitelistApiTests.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyWhitelistApiTests.java @@ -16,8 +16,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; public class LegacyWhitelistApiTests extends WhitelistApiTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } + @Override + protected String getEndpointPrefix() { + return LEGACY_OPENDISTRO_PREFIX; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/validation/PasswordValidatorTest.java b/src/test/java/org/opensearch/security/dlic/rest/validation/PasswordValidatorTest.java index b5d27827b8..7ea6f23898 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/validation/PasswordValidatorTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/validation/PasswordValidatorTest.java @@ -25,126 +25,120 @@ public class PasswordValidatorTest { - static final List WEAK_PASSWORDS = ImmutableList.of( - "q", "5", "&", "admin", "123456", "password" - ); + static final List WEAK_PASSWORDS = ImmutableList.of("q", "5", "&", "admin", "123456", "password"); static final List FAIR_PASSWORDS = ImmutableList.of( - "p@$$word@dmin", "qwertyuiop@[", - "zxcvbnm,./_", "asdfghjkl;:]", "20300101", - "pandapandapandapandapandapandapandapandapandaa", - "appleappleappleappleappleappleappleappleapplea", - "aelppaaelppaaelppaaelppaaelppaaelppaaelppaaelppa" + "p@$$word@dmin", + "qwertyuiop@[", + "zxcvbnm,./_", + "asdfghjkl;:]", + "20300101", + "pandapandapandapandapandapandapandapandapandaa", + "appleappleappleappleappleappleappleappleapplea", + "aelppaaelppaaelppaaelppaaelppaaelppaaelppaaelppa" ); static final List GOOD_PASSWORDS = ImmutableList.of( - "xsw234rfvb", "yaq123edc", "cde345tgbn", "yaqwedcvb", - "Tr0ub4dour&3", "qwER43@!" + "xsw234rfvb", + "yaq123edc", + "cde345tgbn", + "yaqwedcvb", + "Tr0ub4dour&3", + "qwER43@!" ); - static final List STRONG_PASSWORDS = ImmutableList.of( - "YWert,H90", "Admincc,H90", "Hadmin,120" - ); + static final List STRONG_PASSWORDS = ImmutableList.of("YWert,H90", "Admincc,H90", "Hadmin,120"); static final List VERY_STRONG_PASSWORDS = ImmutableList.of( - "AeTq($%u-44c_j9NJB45a#2#JP7sH", "IB7~EOw!51gug+7s#+%A9P1O/w8f", - "1v_f%7JvS8w!_t398+ON-CObI#v0", "8lFmfc0!w)&iU9DM6~4_w)D)Y44J" + "AeTq($%u-44c_j9NJB45a#2#JP7sH", + "IB7~EOw!51gug+7s#+%A9P1O/w8f", + "1v_f%7JvS8w!_t398+ON-CObI#v0", + "8lFmfc0!w)&iU9DM6~4_w)D)Y44J" ); static final List SIMILAR_PASSWORDS = ImmutableList.of( - "some_user_name,H2344cc", "H3235,Some_User_Name,cc", - "H3235,cc,some_User_Name", "H3235,SOME_User_Name,cc", - "H3235,eman_resu_emos,cc" + "some_user_name,H2344cc", + "H3235,Some_User_Name,cc", + "H3235,cc,some_User_Name", + "H3235,SOME_User_Name,cc", + "H3235,eman_resu_emos,cc" ); - public void verifyWeakPasswords(final PasswordValidator passwordValidator, - final AbstractConfigurationValidator.ErrorType expectedValidationResult) { + public void verifyWeakPasswords( + final PasswordValidator passwordValidator, + final AbstractConfigurationValidator.ErrorType expectedValidationResult + ) { for (final String password : WEAK_PASSWORDS) - assertEquals( - password, - expectedValidationResult, - passwordValidator.validate("some_user_name", password) - ); + assertEquals(password, expectedValidationResult, passwordValidator.validate("some_user_name", password)); } - public void verifyFairPasswords(final PasswordValidator passwordValidator, - final AbstractConfigurationValidator.ErrorType expectedValidationResult) { + public void verifyFairPasswords( + final PasswordValidator passwordValidator, + final AbstractConfigurationValidator.ErrorType expectedValidationResult + ) { for (final String password : FAIR_PASSWORDS) - assertEquals( - password, - expectedValidationResult, - passwordValidator.validate("some_user_name", password) - ); + assertEquals(password, expectedValidationResult, passwordValidator.validate("some_user_name", password)); } - public void verifyGoodPasswords(final PasswordValidator passwordValidator, - final AbstractConfigurationValidator.ErrorType expectedValidationResult) { + public void verifyGoodPasswords( + final PasswordValidator passwordValidator, + final AbstractConfigurationValidator.ErrorType expectedValidationResult + ) { for (final String password : GOOD_PASSWORDS) - assertEquals( - password, - expectedValidationResult, - passwordValidator.validate("some_user_name", password) - ); + assertEquals(password, expectedValidationResult, passwordValidator.validate("some_user_name", password)); } - public void verifyStrongPasswords(final PasswordValidator passwordValidator, - final AbstractConfigurationValidator.ErrorType expectedValidationResult) { + public void verifyStrongPasswords( + final PasswordValidator passwordValidator, + final AbstractConfigurationValidator.ErrorType expectedValidationResult + ) { for (final String password : STRONG_PASSWORDS) - assertEquals( - password, - expectedValidationResult, - passwordValidator.validate("some_user_name", password) - ); + assertEquals(password, expectedValidationResult, passwordValidator.validate("some_user_name", password)); } - public void verifyVeryStrongPasswords(final PasswordValidator passwordValidator, - final AbstractConfigurationValidator.ErrorType expectedValidationResult) { + public void verifyVeryStrongPasswords( + final PasswordValidator passwordValidator, + final AbstractConfigurationValidator.ErrorType expectedValidationResult + ) { for (final String password : VERY_STRONG_PASSWORDS) - assertEquals( - password, - expectedValidationResult, - passwordValidator.validate("some_user_name", password) - ); + assertEquals(password, expectedValidationResult, passwordValidator.validate("some_user_name", password)); } public void verifySimilarPasswords(final PasswordValidator passwordValidator) { for (final String password : SIMILAR_PASSWORDS) assertEquals( - password, - AbstractConfigurationValidator.ErrorType.SIMILAR_PASSWORD, - passwordValidator.validate("some_user_name", password) + password, + AbstractConfigurationValidator.ErrorType.SIMILAR_PASSWORD, + passwordValidator.validate("some_user_name", password) ); } @Test public void testRegExpBasedValidation() { - final PasswordValidator passwordValidator = - PasswordValidator.of( - Settings.builder() - .put( - SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, - "(?=.*[A-Z])(?=.*[^a-zA-Z\\\\d])(?=.*[0-9])(?=.*[a-z]).{8,}") - .build() - ); + final PasswordValidator passwordValidator = PasswordValidator.of( + Settings.builder() + .put(SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, "(?=.*[A-Z])(?=.*[^a-zA-Z\\\\d])(?=.*[0-9])(?=.*[a-z]).{8,}") + .build() + ); verifyWeakPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD); verifyFairPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD); for (final String password : GOOD_PASSWORDS.subList(0, GOOD_PASSWORDS.size() - 2)) assertEquals( - password, - AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD, - passwordValidator.validate("some_user_name", password) + password, + AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD, + passwordValidator.validate("some_user_name", password) ); - for (final String password: GOOD_PASSWORDS.subList(GOOD_PASSWORDS.size() - 2, GOOD_PASSWORDS.size())) + for (final String password : GOOD_PASSWORDS.subList(GOOD_PASSWORDS.size() - 2, GOOD_PASSWORDS.size())) assertEquals( - password, - AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD, - passwordValidator.validate("some_user_name", password) + password, + AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD, + passwordValidator.validate("some_user_name", password) ); verifyStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); verifyVeryStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); @@ -153,17 +147,11 @@ public void testRegExpBasedValidation() { @Test public void testMinLength() { - final PasswordValidator passwordValidator = - PasswordValidator.of( - Settings.builder() - .put(SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, 15) - .build() - ); - for (final String password: STRONG_PASSWORDS) { - assertEquals( - AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD, - passwordValidator.validate(password, "some_user_name") - ); + final PasswordValidator passwordValidator = PasswordValidator.of( + Settings.builder().put(SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, 15).build() + ); + for (final String password : STRONG_PASSWORDS) { + assertEquals(AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD, passwordValidator.validate(password, "some_user_name")); } } @@ -178,13 +166,11 @@ public void testScoreBasedValidation() { verifyVeryStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); verifySimilarPasswords(passwordValidator); - passwordValidator = - PasswordValidator.of( - Settings.builder() - .put( - SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, - PasswordValidator.ScoreStrength.FAIR.name() - ).build()); + passwordValidator = PasswordValidator.of( + Settings.builder() + .put(SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, PasswordValidator.ScoreStrength.FAIR.name()) + .build() + ); verifyWeakPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD); verifyFairPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); diff --git a/src/test/java/org/opensearch/security/filter/SecurityFilterTest.java b/src/test/java/org/opensearch/security/filter/SecurityFilterTest.java index 161e8aab72..3e9cfe4b5e 100644 --- a/src/test/java/org/opensearch/security/filter/SecurityFilterTest.java +++ b/src/test/java/org/opensearch/security/filter/SecurityFilterTest.java @@ -59,32 +59,33 @@ public SecurityFilterTest(Settings settings, WildcardMatcher expected) { @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[][]{ - {Settings.EMPTY, WildcardMatcher.NONE}, - {Settings.builder() - .putList(ConfigConstants.SECURITY_COMPLIANCE_IMMUTABLE_INDICES, "immutable1", "immutable2") - .build(), - WildcardMatcher.from(ImmutableSet.of("immutable1", "immutable2"))}, - {Settings.builder() + return Arrays.asList( + new Object[][] { + { Settings.EMPTY, WildcardMatcher.NONE }, + { + Settings.builder().putList(ConfigConstants.SECURITY_COMPLIANCE_IMMUTABLE_INDICES, "immutable1", "immutable2").build(), + WildcardMatcher.from(ImmutableSet.of("immutable1", "immutable2")) }, + { + Settings.builder() .putList(ConfigConstants.SECURITY_COMPLIANCE_IMMUTABLE_INDICES, "immutable1", "immutable2", "immutable2") .build(), - WildcardMatcher.from(ImmutableSet.of("immutable1", "immutable2"))}, - }); + WildcardMatcher.from(ImmutableSet.of("immutable1", "immutable2")) }, } + ); } @Test public void testImmutableIndicesWildcardMatcher() { final SecurityFilter filter = new SecurityFilter( - settings, - mock(PrivilegesEvaluator.class), - mock(AdminDNs.class), - mock(DlsFlsRequestValve.class), - mock(AuditLog.class), - mock(ThreadPool.class), - mock(ClusterService.class), - mock(CompatConfig.class), - mock(IndexResolverReplacer.class), - mock(XFFResolver.class) + settings, + mock(PrivilegesEvaluator.class), + mock(AdminDNs.class), + mock(DlsFlsRequestValve.class), + mock(AuditLog.class), + mock(ThreadPool.class), + mock(ClusterService.class), + mock(CompatConfig.class), + mock(IndexResolverReplacer.class), + mock(XFFResolver.class) ); assertThat(expected, equalTo(filter.getImmutableIndicesMatcher())); } @@ -103,7 +104,7 @@ public void testUnexepectedCausesAreNotSendToCallers() { mock(AdminDNs.class), mock(DlsFlsRequestValve.class), auditLog, - new ThreadPool(Settings.builder().put("node.name", "mock").build()), + new ThreadPool(Settings.builder().put("node.name", "mock").build()), mock(ClusterService.class), mock(CompatConfig.class), mock(IndexResolverReplacer.class), @@ -120,7 +121,11 @@ public void testUnexepectedCausesAreNotSendToCallers() { verify(listener).onFailure(cap.capture()); assertThat("The cause should never be included as it will leak to callers", cap.getValue().getCause(), nullValue()); - assertThat("Make sure the cause exception wasn't toStringed in the method", cap.getValue().getMessage(), not(containsString("ABC!"))); + assertThat( + "Make sure the cause exception wasn't toStringed in the method", + cap.getValue().getMessage(), + not(containsString("ABC!")) + ); verifyNoMoreInteractions(auditLog, listener); } diff --git a/src/test/java/org/opensearch/security/filter/SecurityRestFilterTest.java b/src/test/java/org/opensearch/security/filter/SecurityRestFilterTest.java index 1a087887d8..692a950fb1 100644 --- a/src/test/java/org/opensearch/security/filter/SecurityRestFilterTest.java +++ b/src/test/java/org/opensearch/security/filter/SecurityRestFilterTest.java @@ -48,25 +48,29 @@ public void checkWhitelistedApisAreAccessible() throws Exception { setup(); - //ADD SOME WHITELISTED APIs + // ADD SOME WHITELISTED APIs rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - response = rh.executePutRequest("_opendistro/_security/api/whitelist", "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", adminCredsHeader); + response = rh.executePutRequest( + "_opendistro/_security/api/whitelist", + "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", + adminCredsHeader + ); log.warn("the response is:" + rh.executeGetRequest("_opendistro/_security/api/whitelist", adminCredsHeader)); - //NON ADMIN TRIES ACCESSING A WHITELISTED API - OK + // NON ADMIN TRIES ACCESSING A WHITELISTED API - OK rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cat/nodes", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //ADMIN TRIES ACCESSING A WHITELISTED API - OK + // ADMIN TRIES ACCESSING A WHITELISTED API - OK rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cat/nodes", adminCredsHeader); log.warn("the second response is:{}", response); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //SUPERADMIN TRIES ACCESSING A WHITELISTED API - OK + // SUPERADMIN TRIES ACCESSING A WHITELISTED API - OK rh.sendAdminCertificate = true; response = rh.executeGetRequest("_cat/nodes", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); @@ -82,25 +86,29 @@ public void checkAllowlistedApisAreAccessible() throws Exception { setup(); - //ADD SOME ALLOWLISTED APIs + // ADD SOME ALLOWLISTED APIs rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - response = rh.executePutRequest("_plugins/_security/api/allowlist", "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", adminCredsHeader); + response = rh.executePutRequest( + "_plugins/_security/api/allowlist", + "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", + adminCredsHeader + ); log.warn("the response is:" + rh.executeGetRequest("_plugins/_security/api/allowlist", adminCredsHeader)); - //NON ADMIN TRIES ACCESSING A ALLOWLISTED API - OK + // NON ADMIN TRIES ACCESSING A ALLOWLISTED API - OK rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cat/nodes", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //ADMIN TRIES ACCESSING A ALLOWLISTED API - OK + // ADMIN TRIES ACCESSING A ALLOWLISTED API - OK rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cat/nodes", adminCredsHeader); log.warn("the second response is:{}", response); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //SUPERADMIN TRIES ACCESSING A ALLOWLISTED API - OK + // SUPERADMIN TRIES ACCESSING A ALLOWLISTED API - OK rh.sendAdminCertificate = true; response = rh.executeGetRequest("_cat/nodes", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); @@ -115,22 +123,26 @@ public void checkAllowlistedApisAreAccessible() throws Exception { public void checkNonWhitelistedApisAccessibleOnlyBySuperAdmin() throws Exception { setup(); - //ADD SOME WHITELISTED APIs - /_cat/nodes and /_cat/indices + // ADD SOME WHITELISTED APIs - /_cat/nodes and /_cat/indices rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - response = rh.executePutRequest("_opendistro/_security/api/whitelist", "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", nonAdminCredsHeader); + response = rh.executePutRequest( + "_opendistro/_security/api/whitelist", + "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", + nonAdminCredsHeader + ); - //NON ADMIN TRIES ACCESSING A NON-WHITELISTED API - FORBIDDEN + // NON ADMIN TRIES ACCESSING A NON-WHITELISTED API - FORBIDDEN rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cat/plugins", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - //ADMIN TRIES ACCESSING A NON-WHITELISTED API - FORBIDDEN + // ADMIN TRIES ACCESSING A NON-WHITELISTED API - FORBIDDEN rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cat/plugins", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - //SUPERADMIN TRIES ACCESSING A NON-WHITELISTED API - OK + // SUPERADMIN TRIES ACCESSING A NON-WHITELISTED API - OK rh.sendAdminCertificate = true; response = rh.executeGetRequest("_cat/plugins", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); @@ -145,22 +157,26 @@ public void checkNonWhitelistedApisAccessibleOnlyBySuperAdmin() throws Exception public void checkNonAllowlistedApisAccessibleOnlyBySuperAdmin() throws Exception { setup(); - //ADD SOME ALLOWLISTED APIs - /_cat/nodes and /_cat/indices + // ADD SOME ALLOWLISTED APIs - /_cat/nodes and /_cat/indices rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - response = rh.executePutRequest("_plugins/_security/api/allowlist", "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", nonAdminCredsHeader); + response = rh.executePutRequest( + "_plugins/_security/api/allowlist", + "{\"enabled\": true, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", + nonAdminCredsHeader + ); - //NON ADMIN TRIES ACCESSING A NON-ALLOWLISTED API - FORBIDDEN + // NON ADMIN TRIES ACCESSING A NON-ALLOWLISTED API - FORBIDDEN rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cat/plugins", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - //ADMIN TRIES ACCESSING A NON-ALLOWLISTED API - FORBIDDEN + // ADMIN TRIES ACCESSING A NON-ALLOWLISTED API - FORBIDDEN rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cat/plugins", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - //SUPERADMIN TRIES ACCESSING A NON-ALLOWLISTED API - OK + // SUPERADMIN TRIES ACCESSING A NON-ALLOWLISTED API - OK rh.sendAdminCertificate = true; response = rh.executeGetRequest("_cat/plugins", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); @@ -173,26 +189,30 @@ public void checkNonAllowlistedApisAccessibleOnlyBySuperAdmin() throws Exception public void checkAllApisWhenWhitelistingNotEnabled() throws Exception { setup(); - //DISABLE WHITELISTING BUT ADD SOME WHITELISTED APIs - /_cat/nodes and /_cat/plugins + // DISABLE WHITELISTING BUT ADD SOME WHITELISTED APIs - /_cat/nodes and /_cat/plugins rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - response = rh.executePutRequest("_opendistro/_security/api/whitelist", "{\"enabled\": false, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", nonAdminCredsHeader); + response = rh.executePutRequest( + "_opendistro/_security/api/whitelist", + "{\"enabled\": false, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", + nonAdminCredsHeader + ); - //NON-ADMIN TRIES ACCESSING 2 APIs: One in the list and one outside - OK for both (Because whitelisting is off) + // NON-ADMIN TRIES ACCESSING 2 APIs: One in the list and one outside - OK for both (Because whitelisting is off) rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cat/plugins", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); response = rh.executeGetRequest("_cat/nodes", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //ADMIN USER TRIES ACCESSING 2 APIs: One in the list and one outside - OK for both (Because whitelisting is off) + // ADMIN USER TRIES ACCESSING 2 APIs: One in the list and one outside - OK for both (Because whitelisting is off) rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cat/plugins", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); response = rh.executeGetRequest("_cat/nodes", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //SUPERADMIN TRIES ACCESSING 2 APIS - OK (would work even if whitelisting was on) + // SUPERADMIN TRIES ACCESSING 2 APIS - OK (would work even if whitelisting was on) rh.sendAdminCertificate = true; response = rh.executeGetRequest("_cat/plugins", adminCredsHeader); @@ -208,26 +228,30 @@ public void checkAllApisWhenWhitelistingNotEnabled() throws Exception { public void checkAllApisWhenAllowlistingNotEnabled() throws Exception { setup(); - //DISABLE ALLOWLISTED BUT ADD SOME ALLOWLISTED APIs - /_cat/nodes and /_cat/plugins + // DISABLE ALLOWLISTED BUT ADD SOME ALLOWLISTED APIs - /_cat/nodes and /_cat/plugins rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - response = rh.executePutRequest("_plugins/_security/api/allowlist", "{\"enabled\": false, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", nonAdminCredsHeader); + response = rh.executePutRequest( + "_plugins/_security/api/allowlist", + "{\"enabled\": false, \"requests\": {\"/_cat/nodes\": [\"GET\"],\"/_cat/indices\": [\"GET\"] }}", + nonAdminCredsHeader + ); - //NON-ADMIN TRIES ACCESSING 2 APIs: One in the list and one outside - OK for both (Because allowlisting is off) + // NON-ADMIN TRIES ACCESSING 2 APIs: One in the list and one outside - OK for both (Because allowlisting is off) rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cat/plugins", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); response = rh.executeGetRequest("_cat/nodes", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //ADMIN USER TRIES ACCESSING 2 APIs: One in the list and one outside - OK for both (Because allowlisting is off) + // ADMIN USER TRIES ACCESSING 2 APIs: One in the list and one outside - OK for both (Because allowlisting is off) rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cat/plugins", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); response = rh.executeGetRequest("_cat/nodes", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //SUPERADMIN TRIES ACCESSING 2 APIS - OK (would work even if allowlisting was on) + // SUPERADMIN TRIES ACCESSING 2 APIS - OK (would work even if allowlisting was on) rh.sendAdminCertificate = true; response = rh.executeGetRequest("_cat/plugins", adminCredsHeader); @@ -245,34 +269,50 @@ public void checkAllApisWhenAllowlistingNotEnabled() throws Exception { * */ @Test - public void checkSpecificRequestMethodWhitelisting() throws Exception{ + public void checkSpecificRequestMethodWhitelisting() throws Exception { setup(); - //WHITELIST GET /_cluster/settings + // WHITELIST GET /_cluster/settings rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - response = rh.executePutRequest("_opendistro/_security/api/whitelist", "{\"enabled\": true, \"requests\": {\"/_cluster/settings\": [\"GET\"]}}", nonAdminCredsHeader); + response = rh.executePutRequest( + "_opendistro/_security/api/whitelist", + "{\"enabled\": true, \"requests\": {\"/_cluster/settings\": [\"GET\"]}}", + nonAdminCredsHeader + ); - //NON-ADMIN TRIES ACCESSING GET - OK, PUT - FORBIDDEN + // NON-ADMIN TRIES ACCESSING GET - OK, PUT - FORBIDDEN rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cluster/settings", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - response = rh.executePutRequest("_cluster/settings","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", nonAdminCredsHeader); + response = rh.executePutRequest( + "_cluster/settings", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + nonAdminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - //ADMIN USER TRIES ACCESSING GET - OK, PUT - FORBIDDEN + // ADMIN USER TRIES ACCESSING GET - OK, PUT - FORBIDDEN rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cluster/settings", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - response = rh.executePutRequest("_cluster/settings","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", adminCredsHeader); + response = rh.executePutRequest( + "_cluster/settings", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - //SUPERADMIN TRIES ACCESSING GET - OK, PUT - OK + // SUPERADMIN TRIES ACCESSING GET - OK, PUT - OK rh.sendAdminCertificate = true; response = rh.executeGetRequest("_cluster/settings", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - response = rh.executePutRequest("_cluster/settings","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", adminCredsHeader); + response = rh.executePutRequest( + "_cluster/settings", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); } @@ -285,38 +325,53 @@ public void checkSpecificRequestMethodWhitelisting() throws Exception{ * */ @Test - public void checkSpecificRequestMethodAllowlisting() throws Exception{ + public void checkSpecificRequestMethodAllowlisting() throws Exception { setup(); - //WHITELIST GET /_cluster/settings + // WHITELIST GET /_cluster/settings rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - response = rh.executePutRequest("_plugins/_security/api/allowlist", "{\"enabled\": true, \"requests\": {\"/_cluster/settings\": [\"GET\"]}}", nonAdminCredsHeader); + response = rh.executePutRequest( + "_plugins/_security/api/allowlist", + "{\"enabled\": true, \"requests\": {\"/_cluster/settings\": [\"GET\"]}}", + nonAdminCredsHeader + ); - //NON-ADMIN TRIES ACCESSING GET - OK, PUT - FORBIDDEN + // NON-ADMIN TRIES ACCESSING GET - OK, PUT - FORBIDDEN rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cluster/settings", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - response = rh.executePutRequest("_cluster/settings","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", nonAdminCredsHeader); + response = rh.executePutRequest( + "_cluster/settings", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + nonAdminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - //ADMIN USER TRIES ACCESSING GET - OK, PUT - FORBIDDEN + // ADMIN USER TRIES ACCESSING GET - OK, PUT - FORBIDDEN rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cluster/settings", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - response = rh.executePutRequest("_cluster/settings","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", adminCredsHeader); + response = rh.executePutRequest( + "_cluster/settings", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - //SUPERADMIN TRIES ACCESSING GET - OK, PUT - OK + // SUPERADMIN TRIES ACCESSING GET - OK, PUT - OK rh.sendAdminCertificate = true; response = rh.executeGetRequest("_cluster/settings", adminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - response = rh.executePutRequest("_cluster/settings","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", adminCredsHeader); + response = rh.executePutRequest( + "_cluster/settings", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); } - /** * Tests that a whitelisted API with an extra '/' does not cause an issue * i.e if only GET /_cluster/settings/ is whitelisted, then: @@ -327,29 +382,41 @@ public void checkSpecificRequestMethodAllowlisting() throws Exception{ * @throws Exception */ @Test - public void testWhitelistedApiWithExtraSlash() throws Exception{ + public void testWhitelistedApiWithExtraSlash() throws Exception { setup(); - //WHITELIST GET /_cluster/settings/ - extra / in the request + // WHITELIST GET /_cluster/settings/ - extra / in the request rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - response = rh.executePutRequest("_opendistro/_security/api/whitelist", "{\"enabled\": true, \"requests\": {\"/_cluster/settings/\": [\"GET\"]}}", nonAdminCredsHeader); + response = rh.executePutRequest( + "_opendistro/_security/api/whitelist", + "{\"enabled\": true, \"requests\": {\"/_cluster/settings/\": [\"GET\"]}}", + nonAdminCredsHeader + ); - //NON ADMIN ACCESS GET /_cluster/settings/ - OK + // NON ADMIN ACCESS GET /_cluster/settings/ - OK rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cluster/settings/", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //NON ADMIN ACCESS GET /_cluster/settings - OK + // NON ADMIN ACCESS GET /_cluster/settings - OK response = rh.executeGetRequest("_cluster/settings", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //NON ADMIN ACCESS PUT /_cluster/settings/ - FORBIDDEN - response = rh.executePutRequest("_cluster/settings/","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", adminCredsHeader); + // NON ADMIN ACCESS PUT /_cluster/settings/ - FORBIDDEN + response = rh.executePutRequest( + "_cluster/settings/", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - //NON ADMIN ACCESS PUT /_cluster/settings - FORBIDDEN - response = rh.executePutRequest("_cluster/settings","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", adminCredsHeader); + // NON ADMIN ACCESS PUT /_cluster/settings - FORBIDDEN + response = rh.executePutRequest( + "_cluster/settings", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); } @@ -364,29 +431,41 @@ public void testWhitelistedApiWithExtraSlash() throws Exception{ * @throws Exception */ @Test - public void testAllowlistedApiWithExtraSlash() throws Exception{ + public void testAllowlistedApiWithExtraSlash() throws Exception { setup(); - //WHITELIST GET /_cluster/settings/ - extra / in the request + // WHITELIST GET /_cluster/settings/ - extra / in the request rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - response = rh.executePutRequest("_plugins/_security/api/allowlist", "{\"enabled\": true, \"requests\": {\"/_cluster/settings/\": [\"GET\"]}}", nonAdminCredsHeader); + response = rh.executePutRequest( + "_plugins/_security/api/allowlist", + "{\"enabled\": true, \"requests\": {\"/_cluster/settings/\": [\"GET\"]}}", + nonAdminCredsHeader + ); - //NON ADMIN ACCESS GET /_cluster/settings/ - OK + // NON ADMIN ACCESS GET /_cluster/settings/ - OK rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cluster/settings/", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //NON ADMIN ACCESS GET /_cluster/settings - OK + // NON ADMIN ACCESS GET /_cluster/settings - OK response = rh.executeGetRequest("_cluster/settings", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //NON ADMIN ACCESS PUT /_cluster/settings/ - FORBIDDEN - response = rh.executePutRequest("_cluster/settings/","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", adminCredsHeader); + // NON ADMIN ACCESS PUT /_cluster/settings/ - FORBIDDEN + response = rh.executePutRequest( + "_cluster/settings/", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - //NON ADMIN ACCESS PUT /_cluster/settings - FORBIDDEN - response = rh.executePutRequest("_cluster/settings","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", adminCredsHeader); + // NON ADMIN ACCESS PUT /_cluster/settings - FORBIDDEN + response = rh.executePutRequest( + "_cluster/settings", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); } @@ -401,29 +480,41 @@ public void testAllowlistedApiWithExtraSlash() throws Exception{ * @throws Exception */ @Test - public void testWhitelistedApiWithoutExtraSlash() throws Exception{ + public void testWhitelistedApiWithoutExtraSlash() throws Exception { setup(); - //WHITELIST GET /_cluster/settings (no extra / in request) + // WHITELIST GET /_cluster/settings (no extra / in request) rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - response = rh.executePutRequest("_opendistro/_security/api/whitelist", "{\"enabled\": true, \"requests\": {\"/_cluster/settings\": [\"GET\"]}}", nonAdminCredsHeader); + response = rh.executePutRequest( + "_opendistro/_security/api/whitelist", + "{\"enabled\": true, \"requests\": {\"/_cluster/settings\": [\"GET\"]}}", + nonAdminCredsHeader + ); - //NON ADMIN ACCESS GET /_cluster/settings/ - OK + // NON ADMIN ACCESS GET /_cluster/settings/ - OK rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cluster/settings/", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //NON ADMIN ACCESS GET /_cluster/settings - OK + // NON ADMIN ACCESS GET /_cluster/settings - OK response = rh.executeGetRequest("_cluster/settings", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //NON ADMIN ACCESS PUT /_cluster/settings/ - FORBIDDEN - response = rh.executePutRequest("_cluster/settings/","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", adminCredsHeader); + // NON ADMIN ACCESS PUT /_cluster/settings/ - FORBIDDEN + response = rh.executePutRequest( + "_cluster/settings/", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - //NON ADMIN ACCESS PUT /_cluster/settings - FORBIDDEN - response = rh.executePutRequest("_cluster/settings","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", adminCredsHeader); + // NON ADMIN ACCESS PUT /_cluster/settings - FORBIDDEN + response = rh.executePutRequest( + "_cluster/settings", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); } @@ -437,29 +528,41 @@ public void testWhitelistedApiWithoutExtraSlash() throws Exception{ * @throws Exception */ @Test - public void testAllowlistedApiWithoutExtraSlash() throws Exception{ + public void testAllowlistedApiWithoutExtraSlash() throws Exception { setup(); - //WHITELIST GET /_cluster/settings (no extra / in request) + // WHITELIST GET /_cluster/settings (no extra / in request) rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - response = rh.executePutRequest("_plugins/_security/api/allowlist", "{\"enabled\": true, \"requests\": {\"/_cluster/settings\": [\"GET\"]}}", nonAdminCredsHeader); + response = rh.executePutRequest( + "_plugins/_security/api/allowlist", + "{\"enabled\": true, \"requests\": {\"/_cluster/settings\": [\"GET\"]}}", + nonAdminCredsHeader + ); - //NON ADMIN ACCESS GET /_cluster/settings/ - OK + // NON ADMIN ACCESS GET /_cluster/settings/ - OK rh.sendAdminCertificate = false; response = rh.executeGetRequest("_cluster/settings/", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //NON ADMIN ACCESS GET /_cluster/settings - OK + // NON ADMIN ACCESS GET /_cluster/settings - OK response = rh.executeGetRequest("_cluster/settings", nonAdminCredsHeader); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - //NON ADMIN ACCESS PUT /_cluster/settings/ - FORBIDDEN - response = rh.executePutRequest("_cluster/settings/","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", adminCredsHeader); + // NON ADMIN ACCESS PUT /_cluster/settings/ - FORBIDDEN + response = rh.executePutRequest( + "_cluster/settings/", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - //NON ADMIN ACCESS PUT /_cluster/settings - FORBIDDEN - response = rh.executePutRequest("_cluster/settings","{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", adminCredsHeader); + // NON ADMIN ACCESS PUT /_cluster/settings - FORBIDDEN + response = rh.executePutRequest( + "_cluster/settings", + "{\"persistent\": { }, \"transient\": {\"indices.recovery.max_bytes_per_sec\": \"15mb\" }}", + adminCredsHeader + ); assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); } } diff --git a/src/test/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticatorTest.java b/src/test/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticatorTest.java index 487da55767..47d779cd12 100644 --- a/src/test/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticatorTest.java +++ b/src/test/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticatorTest.java @@ -65,9 +65,7 @@ public class HTTPExtendedProxyAuthenticatorTest { @Before public void setup() { context.putTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE, Boolean.TRUE); - settings = Settings.builder() - .put("user_header","user") - .build(); + settings = Settings.builder().put("user_header", "user").build(); authenticator = new HTTPExtendedProxyAuthenticator(settings, null); } @@ -79,7 +77,7 @@ public void testGetType() { @Test(expected = OpenSearchSecurityException.class) public void testThrowsExceptionWhenMissingXFFDone() { authenticator = new HTTPExtendedProxyAuthenticator(Settings.EMPTY, null); - authenticator.extractCredentials(new TestRestRequest(), new ThreadContext(Settings.EMPTY)); + authenticator.extractCredentials(new TestRestRequest(), new ThreadContext(Settings.EMPTY)); } @Test @@ -93,6 +91,7 @@ public void testReturnsNullWhenUserHeaderIsMissing() { assertNull(authenticator.extractCredentials(new TestRestRequest(), context)); } + @Test public void testReturnsCredentials() { @@ -104,8 +103,8 @@ public void testReturnsCredentials() { headers.get("proxy_uid").add("456"); headers.get("proxy_other").add("someothervalue"); - settings = Settings.builder().put(settings).put("attr_header_prefix","proxy_").build(); - authenticator = new HTTPExtendedProxyAuthenticator(settings,null); + settings = Settings.builder().put(settings).put("attr_header_prefix", "proxy_").build(); + authenticator = new HTTPExtendedProxyAuthenticator(settings, null); AuthCredentials creds = authenticator.extractCredentials(new TestRestRequest(headers), context); assertNotNull(creds); assertEquals("aValidUser", creds.getUsername()); @@ -116,16 +115,13 @@ public void testReturnsCredentials() { @Test public void testTrimOnRoles() { - headers.put("user", new ArrayList<>()); + headers.put("user", new ArrayList<>()); headers.put("roles", new ArrayList<>()); headers.get("user").add("aValidUser"); headers.get("roles").add("role1, role2,\t"); - settings = Settings.builder().put(settings) - .put("roles_header","roles") - .put("roles_separator", ",") - .build(); - authenticator = new HTTPExtendedProxyAuthenticator(settings,null); + settings = Settings.builder().put(settings).put("roles_header", "roles").put("roles_separator", ",").build(); + authenticator = new HTTPExtendedProxyAuthenticator(settings, null); AuthCredentials creds = authenticator.extractCredentials(new TestRestRequest(headers), context); assertNotNull(creds); assertEquals("aValidUser", creds.getUsername()); @@ -136,14 +132,20 @@ public void testTrimOnRoles() { static class TestRestRequest extends RestRequest { public TestRestRequest() { - super(NamedXContentRegistry.EMPTY, new HashMap<>(), "", new HashMap<>(),new HttpRequestImpl(),new HttpChannelImpl()); + super(NamedXContentRegistry.EMPTY, new HashMap<>(), "", new HashMap<>(), new HttpRequestImpl(), new HttpChannelImpl()); } + public TestRestRequest(Map> headers) { - super(NamedXContentRegistry.EMPTY, new HashMap<>(), "", headers, new HttpRequestImpl(),new HttpChannelImpl()); + super(NamedXContentRegistry.EMPTY, new HashMap<>(), "", headers, new HttpRequestImpl(), new HttpChannelImpl()); } - public TestRestRequest(NamedXContentRegistry xContentRegistry, Map params, String path, - Map> headers) { - super(xContentRegistry, params, path, headers, new HttpRequestImpl(),new HttpChannelImpl()); + + public TestRestRequest( + NamedXContentRegistry xContentRegistry, + Map params, + String path, + Map> headers + ) { + super(xContentRegistry, params, path, headers, new HttpRequestImpl(), new HttpChannelImpl()); } @Override diff --git a/src/test/java/org/opensearch/security/httpclient/HttpClientTest.java b/src/test/java/org/opensearch/security/httpclient/HttpClientTest.java index 96d41b6735..3da6ad3d7f 100644 --- a/src/test/java/org/opensearch/security/httpclient/HttpClientTest.java +++ b/src/test/java/org/opensearch/security/httpclient/HttpClientTest.java @@ -30,38 +30,44 @@ protected String getResourceFolder() { @Test public void testPlainConnection() throws Exception { - final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled", false) - .build(); + final Settings settings = Settings.builder().put("plugins.security.ssl.http.enabled", false).build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings); Thread.sleep(1000); - try(final HttpClient httpClient = HttpClient.builder(clusterInfo.httpHost+":"+clusterInfo.httpPort) - .setBasicCredentials("admin", "admin").build()) { + try ( + final HttpClient httpClient = HttpClient.builder(clusterInfo.httpHost + ":" + clusterInfo.httpPort) + .setBasicCredentials("admin", "admin") + .build() + ) { Assert.assertTrue(httpClient.index("{\"a\":5}", "index", "type", false)); Assert.assertTrue(httpClient.index("{\"a\":5}", "index", "type", true)); Assert.assertTrue(httpClient.index("{\"a\":5}", "index", "type", true)); } - try(final HttpClient httpClient = HttpClient.builder("unknownhost:6654") - .setBasicCredentials("admin", "admin").build()) { + try (final HttpClient httpClient = HttpClient.builder("unknownhost:6654").setBasicCredentials("admin", "admin").build()) { Assert.assertFalse(httpClient.index("{\"a\":5}", "index", "type", false)); Assert.assertFalse(httpClient.index("{\"a\":5}", "index", "type", true)); Assert.assertFalse(httpClient.index("{\"a\":5}", "index", "type", true)); } - try(final HttpClient httpClient = HttpClient.builder("unknownhost:6654", clusterInfo.httpHost+":"+clusterInfo.httpPort) - .enableSsl(FileHelper.getKeystoreFromClassPath("auditlog/truststore.jks","changeit"), false) - .setBasicCredentials("admin", "admin").build()) { + try ( + final HttpClient httpClient = HttpClient.builder("unknownhost:6654", clusterInfo.httpHost + ":" + clusterInfo.httpPort) + .enableSsl(FileHelper.getKeystoreFromClassPath("auditlog/truststore.jks", "changeit"), false) + .setBasicCredentials("admin", "admin") + .build() + ) { Assert.assertFalse(httpClient.index("{\"a\":5}", "index", "type", false)); Assert.assertFalse(httpClient.index("{\"a\":5}", "index", "type", true)); Assert.assertFalse(httpClient.index("{\"a\":5}", "index", "type", true)); } - try(final HttpClient httpClient = HttpClient.builder("unknownhost:6654", clusterInfo.httpHost+":"+clusterInfo.httpPort) - .setBasicCredentials("admin", "admin").build()) { + try ( + final HttpClient httpClient = HttpClient.builder("unknownhost:6654", clusterInfo.httpHost + ":" + clusterInfo.httpPort) + .setBasicCredentials("admin", "admin") + .build() + ) { Assert.assertTrue(httpClient.index("{\"a\":5}", "index", "type", false)); Assert.assertTrue(httpClient.index("{\"a\":5}", "index", "type", true)); Assert.assertTrue(httpClient.index("{\"a\":5}", "index", "type", true)); @@ -73,27 +79,33 @@ public void testPlainConnection() throws Exception { public void testSslConnection() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled", true) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, false) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, false) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings); Thread.sleep(1000); - try(final HttpClient httpClient = HttpClient.builder(clusterInfo.httpHost+":"+clusterInfo.httpPort) - .enableSsl(FileHelper.getKeystoreFromClassPath("auditlog/truststore.jks","changeit"), false) - .setBasicCredentials("admin", "admin").build()) { + try ( + final HttpClient httpClient = HttpClient.builder(clusterInfo.httpHost + ":" + clusterInfo.httpPort) + .enableSsl(FileHelper.getKeystoreFromClassPath("auditlog/truststore.jks", "changeit"), false) + .setBasicCredentials("admin", "admin") + .build() + ) { Assert.assertTrue(httpClient.index("{\"a\":5}", "index", "type", false)); Assert.assertTrue(httpClient.index("{\"a\":5}", "index", "type", true)); Assert.assertTrue(httpClient.index("{\"a\":5}", "index", "type", true)); } - try(final HttpClient httpClient = HttpClient.builder(clusterInfo.httpHost+":"+clusterInfo.httpPort) - .setBasicCredentials("admin", "admin").build()) { + try ( + final HttpClient httpClient = HttpClient.builder(clusterInfo.httpHost + ":" + clusterInfo.httpPort) + .setBasicCredentials("admin", "admin") + .build() + ) { Assert.assertFalse(httpClient.index("{\"a\":5}", "index", "type", false)); Assert.assertFalse(httpClient.index("{\"a\":5}", "index", "type", true)); Assert.assertFalse(httpClient.index("{\"a\":5}", "index", "type", true)); @@ -105,22 +117,28 @@ public void testSslConnection() throws Exception { public void testSslConnectionPKIAuth() throws Exception { final Settings settings = Settings.builder() - .put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, false) - .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) - .build(); + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.clientauth_mode", "REQUIRE") + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, false) + .put(SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_ALIAS, "node-0") + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/truststore.jks")) + .build(); setup(Settings.EMPTY, new DynamicSecurityConfig(), settings); Thread.sleep(1000); - try(final HttpClient httpClient = HttpClient.builder(clusterInfo.httpHost+":"+clusterInfo.httpPort) - .enableSsl(FileHelper.getKeystoreFromClassPath("auditlog/truststore.jks","changeit"), false) - .setPkiCredentials(FileHelper.getKeystoreFromClassPath("auditlog/spock-keystore.jks", "changeit"), "changeit".toCharArray(), null) - .build()) { + try ( + final HttpClient httpClient = HttpClient.builder(clusterInfo.httpHost + ":" + clusterInfo.httpPort) + .enableSsl(FileHelper.getKeystoreFromClassPath("auditlog/truststore.jks", "changeit"), false) + .setPkiCredentials( + FileHelper.getKeystoreFromClassPath("auditlog/spock-keystore.jks", "changeit"), + "changeit".toCharArray(), + null + ) + .build() + ) { Assert.assertTrue(httpClient.index("{\"a\":5}", "index", "type", false)); Assert.assertTrue(httpClient.index("{\"a\":5}", "index", "type", true)); Assert.assertTrue(httpClient.index("{\"a\":5}", "index", "type", true)); diff --git a/src/test/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java b/src/test/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java index ba265bcf2e..b953ac8ddb 100644 --- a/src/test/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java +++ b/src/test/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java @@ -26,15 +26,15 @@ public class PrivilegesEvaluatorTest extends SingleClusterTest { private static final Header NegatedRegexUserHeader = encodeBasicHeader("negated_regex_user", "negated_regex_user"); public void setupSettingsIndexPattern() throws Exception { - Settings settings = Settings.builder() - .build(); - setup(Settings.EMPTY, - new DynamicSecurityConfig() - .setSecurityRoles("roles_index_patterns.yml") - .setSecurityInternalUsers("internal_users_index_patterns.yml") - .setSecurityRolesMapping("roles_mapping_index_patterns.yml"), - settings, - true); + Settings settings = Settings.builder().build(); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setSecurityRoles("roles_index_patterns.yml") + .setSecurityInternalUsers("internal_users_index_patterns.yml") + .setSecurityRolesMapping("roles_mapping_index_patterns.yml"), + settings, + true + ); } @Test @@ -42,9 +42,9 @@ public void testNegativeLookaheadPattern() throws Exception { setupSettingsIndexPattern(); RestHelper rh = nonSslRestHelper(); - RestHelper.HttpResponse response = rh.executeGetRequest( "*/_search", NegativeLookaheadUserHeader); + RestHelper.HttpResponse response = rh.executeGetRequest("*/_search", NegativeLookaheadUserHeader); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - response = rh.executeGetRequest( "r*/_search", NegativeLookaheadUserHeader); + response = rh.executeGetRequest("r*/_search", NegativeLookaheadUserHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); } @@ -53,9 +53,9 @@ public void testRegexPattern() throws Exception { setupSettingsIndexPattern(); RestHelper rh = nonSslRestHelper(); - RestHelper.HttpResponse response = rh.executeGetRequest( "*/_search", NegatedRegexUserHeader); + RestHelper.HttpResponse response = rh.executeGetRequest("*/_search", NegatedRegexUserHeader); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - response = rh.executeGetRequest( "r*/_search", NegatedRegexUserHeader); + response = rh.executeGetRequest("r*/_search", NegatedRegexUserHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); } } diff --git a/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java b/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java index d84d62c6b4..14c5eabb73 100644 --- a/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java +++ b/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java @@ -55,7 +55,6 @@ public class SecurityIndexAccessEvaluatorTest { @Mock private Logger log; - private SecurityIndexAccessEvaluator evaluator; private static final String UNPROTECTED_ACTION = "indices:data/read"; @@ -69,7 +68,8 @@ public void before() { .put("plugins.security.system_indices.enabled", true) .build(), auditLog, - irr); + irr + ); evaluator.log = log; when(log.isDebugEnabled()).thenReturn(true); @@ -140,13 +140,16 @@ public void protectedActionSystemIndex() { verify(presponse).markComplete(); verify(log).isDebugEnabled(); - verify(log).warn( - "{} for '{}' index is not allowed for a regular user", - "indices:data/write", - ".opendistro_security, .test"); + verify(log).warn("{} for '{}' index is not allowed for a regular user", "indices:data/write", ".opendistro_security, .test"); } private Resolved createResolved(final String... indexes) { - return new Resolved(ImmutableSet.of(), ImmutableSet.copyOf(indexes), ImmutableSet.copyOf(indexes), ImmutableSet.of(), IndicesOptions.STRICT_EXPAND_OPEN); + return new Resolved( + ImmutableSet.of(), + ImmutableSet.copyOf(indexes), + ImmutableSet.copyOf(indexes), + ImmutableSet.of(), + IndicesOptions.STRICT_EXPAND_OPEN + ); } } diff --git a/src/test/java/org/opensearch/security/protected_indices/ProtectedIndicesTests.java b/src/test/java/org/opensearch/security/protected_indices/ProtectedIndicesTests.java index 52f66f3462..0ff7d64de9 100644 --- a/src/test/java/org/opensearch/security/protected_indices/ProtectedIndicesTests.java +++ b/src/test/java/org/opensearch/security/protected_indices/ProtectedIndicesTests.java @@ -66,7 +66,10 @@ public class ProtectedIndicesTests extends SingleClusterTest { // This user is mapped to all_access, but is not mapped to any protectedIndexRoles private static final String indexAccessNoRoleUser = "indexAccessNoRoleUser"; private static final Header indexAccessNoRoleUserHeader = encodeBasicHeader(indexAccessNoRoleUser, indexAccessNoRoleUser); - private static final String generalErrorMessage = String.format("no permissions for [] and User [name=%s, backend_roles=[], requestedTenant=null]", indexAccessNoRoleUser); + private static final String generalErrorMessage = String.format( + "no permissions for [] and User [name=%s, backend_roles=[], requestedTenant=null]", + indexAccessNoRoleUser + ); // This user is mapped to all_access and protected_index_role1 private static final String protectedIndexUser = "protectedIndexUser"; private static final Header protectedIndexUserHeader = encodeBasicHeader(protectedIndexUser, protectedIndexUser); @@ -81,35 +84,37 @@ public class ProtectedIndicesTests extends SingleClusterTest { public void setupSettingsEnabled() throws Exception { // Setup settings Settings protectedIndexSettings = Settings.builder() - .put(ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, true) - .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, listOfIndexesToTest) - .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, protectedIndexRoles) - .build(); - setup(Settings.EMPTY, - new DynamicSecurityConfig() - .setConfig("config_protected_indices.yml") - .setSecurityRoles("roles_protected_indices.yml") - .setSecurityInternalUsers("internal_users_protected_indices.yml") - .setSecurityRolesMapping("roles_mapping_protected_indices.yml"), - protectedIndexSettings, - true); + .put(ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, true) + .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, listOfIndexesToTest) + .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, protectedIndexRoles) + .build(); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setConfig("config_protected_indices.yml") + .setSecurityRoles("roles_protected_indices.yml") + .setSecurityInternalUsers("internal_users_protected_indices.yml") + .setSecurityRolesMapping("roles_mapping_protected_indices.yml"), + protectedIndexSettings, + true + ); } public void setupSettingsIndexPatterns() throws Exception { // Setup settings Settings protectedIndexSettings = Settings.builder() - .put(ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, true) - .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, listOfIndexPatternsToTest) - .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, protectedIndexRoles) - .build(); - setup(Settings.EMPTY, - new DynamicSecurityConfig() - .setConfig("config_protected_indices.yml") - .setSecurityRoles("roles_protected_indices.yml") - .setSecurityInternalUsers("internal_users_protected_indices.yml") - .setSecurityRolesMapping("roles_mapping_protected_indices.yml"), - protectedIndexSettings, - true); + .put(ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, true) + .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, listOfIndexPatternsToTest) + .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, protectedIndexRoles) + .build(); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setConfig("config_protected_indices.yml") + .setSecurityRoles("roles_protected_indices.yml") + .setSecurityInternalUsers("internal_users_protected_indices.yml") + .setSecurityRolesMapping("roles_mapping_protected_indices.yml"), + protectedIndexSettings, + true + ); } /** @@ -122,35 +127,38 @@ public void setupSettingsIndexPatterns() throws Exception { public void setupSettingsDisabled() throws Exception { // Setup settings Settings protectedIndexSettings = Settings.builder() - .put(ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, false) - .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, listOfIndexesToTest) - .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, protectedIndexRoles) - .build(); - setup(Settings.EMPTY, - new DynamicSecurityConfig() - .setConfig("config_protected_indices.yml") - .setSecurityRoles("roles_protected_indices.yml") - .setSecurityInternalUsers("internal_users_protected_indices.yml") - .setSecurityRolesMapping("roles_mapping_protected_indices.yml"), - protectedIndexSettings, - true); + .put(ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, false) + .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, listOfIndexesToTest) + .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, protectedIndexRoles) + .build(); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setConfig("config_protected_indices.yml") + .setSecurityRoles("roles_protected_indices.yml") + .setSecurityInternalUsers("internal_users_protected_indices.yml") + .setSecurityRolesMapping("roles_mapping_protected_indices.yml"), + protectedIndexSettings, + true + ); } public void setupSettingsEnabledSnapshot() throws Exception { final Settings settings = Settings.builder() - .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) - .put(ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, true) - .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, listOfIndexesToTest) - .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, protectedIndexRoles) - .build(); - - setup(Settings.EMPTY, new DynamicSecurityConfig() - .setConfig("config_protected_indices.yml") + .putList("path.repo", repositoryPath.getRoot().getAbsolutePath()) + .put(ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, true) + .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, listOfIndexesToTest) + .putList(ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, protectedIndexRoles) + .build(); + + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setConfig("config_protected_indices.yml") .setSecurityRoles("roles_protected_indices.yml") .setSecurityInternalUsers("internal_users_protected_indices.yml") .setSecurityRolesMapping("roles_mapping_protected_indices.yml"), - settings, - true); + settings, + true + ); } /** @@ -162,7 +170,11 @@ public void createTestIndicesAndDocs() { try (Client tc = getClient()) { for (String index : listOfIndexesToTest) { tc.admin().indices().create(new CreateIndexRequest(index)).actionGet(); - tc.index(new IndexRequest(index).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).id("document1").source("{ \"foo\": \"bar\" }", XContentType.JSON)).actionGet(); + tc.index( + new IndexRequest(index).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .id("document1") + .source("{ \"foo\": \"bar\" }", XContentType.JSON) + ).actionGet(); } } } @@ -170,8 +182,19 @@ public void createTestIndicesAndDocs() { public void createSnapshots() { try (Client tc = getClient()) { for (String index : listOfIndexesToTest) { - tc.admin().cluster().putRepository(new PutRepositoryRequest(index).type("fs").settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/" + index))).actionGet(); - tc.admin().cluster().createSnapshot(new CreateSnapshotRequest(index, index + "_1").indices(index).includeGlobalState(true).waitForCompletion(true)).actionGet(); + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest(index).type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/" + index)) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot( + new CreateSnapshotRequest(index, index + "_1").indices(index).includeGlobalState(true).waitForCompletion(true) + ) + .actionGet(); } } } @@ -192,7 +215,8 @@ public void testNoSearchResults() throws Exception { // Test direct index query. for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = rh.executePostRequest(index + "/_search", matchAllQuery, indexAccessNoRoleUserHeader); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); SearchResponse searchResponse = SearchResponse.fromXContent(xcp); // confirm good response. assertTrue(searchResponse.status() == RestStatus.OK); @@ -207,7 +231,8 @@ public void testNoSearchResults() throws Exception { // Test index pattern for (String indexPattern : listOfIndexPatternsToTest) { RestHelper.HttpResponse response = rh.executePostRequest(indexPattern + "/_search", matchAllQuery, indexAccessNoRoleUserHeader); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); SearchResponse searchResponse = SearchResponse.fromXContent(xcp); // confirm good response. assertTrue(searchResponse.status() == RestStatus.OK); @@ -231,7 +256,8 @@ public void testSearchWithSettingDisabled() throws Exception { // Test direct index query. for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = rh.executePostRequest(index + "/_search", matchAllQuery, protectedIndexUserHeader); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); SearchResponse searchResponse = SearchResponse.fromXContent(xcp); // confirm good response. assertTrue(searchResponse.status() == RestStatus.OK); @@ -246,7 +272,8 @@ public void testSearchWithSettingDisabled() throws Exception { // Test index pattern for (String indexPattern : listOfIndexPatternsToTest) { RestHelper.HttpResponse response = rh.executePostRequest(indexPattern + "/_search", matchAllQuery, protectedIndexUserHeader); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); SearchResponse searchResponse = SearchResponse.fromXContent(xcp); // confirm good response. assertTrue(searchResponse.status() == RestStatus.OK); @@ -268,10 +295,9 @@ public void testNoResultsAlias() throws Exception { try (Client tc = getClient()) { for (String index : listOfIndexesToTest) { IndicesAliasesRequest request = new IndicesAliasesRequest(); - IndicesAliasesRequest.AliasActions aliasAction = - new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.ADD) - .index(index) - .alias("alias" + i); + IndicesAliasesRequest.AliasActions aliasAction = new IndicesAliasesRequest.AliasActions( + IndicesAliasesRequest.AliasActions.Type.ADD + ).index(index).alias("alias" + i); request.addAliasAction(aliasAction); tc.admin().indices().aliases(request).actionGet(); i++; @@ -282,8 +308,13 @@ public void testNoResultsAlias() throws Exception { RestHelper rh = nonSslRestHelper(); for (int aliasNumber = 0; aliasNumber < i; aliasNumber++) { - RestHelper.HttpResponse response = rh.executePostRequest("alias" + aliasNumber + "/_search", matchAllQuery, indexAccessNoRoleUserHeader); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + RestHelper.HttpResponse response = rh.executePostRequest( + "alias" + aliasNumber + "/_search", + matchAllQuery, + indexAccessNoRoleUserHeader + ); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); SearchResponse searchResponse = SearchResponse.fromXContent(xcp); // confirm good response. assertTrue(searchResponse.status() == RestStatus.OK); @@ -303,14 +334,14 @@ public void testNoAccessCreateIndexDisabled() throws Exception { // Create rest client RestHelper rh = nonSslRestHelper(); - String indexSettings = "{\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : 3, \n" + - " \"number_of_replicas\" : 2 \n" + - " }\n" + - " }\n" + - "}"; + String indexSettings = "{\n" + + " \"settings\" : {\n" + + " \"index\" : {\n" + + " \"number_of_shards\" : 3, \n" + + " \"number_of_replicas\" : 2 \n" + + " }\n" + + " }\n" + + "}"; for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = rh.executePutRequest(index, indexSettings, indexAccessNoRoleUserHeader); assertTrue(response.getStatusCode() == RestStatus.OK.getStatus()); @@ -446,10 +477,7 @@ public void testNonAccessUpdateMappings() throws Exception { setupSettingsEnabled(); createTestIndicesAndDocs(); - String newMappings = "{\"properties\": {" + - "\"user_name\": {" + - "\"type\": \"text\"" + - "}}}"; + String newMappings = "{\"properties\": {" + "\"user_name\": {" + "\"type\": \"text\"" + "}}}"; // Create rest client RestHelper rh = nonSslRestHelper(); @@ -467,10 +495,7 @@ public void testNonAccessUpdateMappingsDisabled() throws Exception { setupSettingsDisabled(); createTestIndicesAndDocs(); - String newMappings = "{\"properties\": {" + - "\"user_name\": {" + - "\"type\": \"text\"" + - "}}}"; + String newMappings = "{\"properties\": {" + "\"user_name\": {" + "\"type\": \"text\"" + "}}}"; // Create rest client RestHelper rh = nonSslRestHelper(); @@ -510,7 +535,11 @@ public void testNonAccessAliasOperations() throws Exception { String aliasTemplate = "{\"actions\" : [{ \"add\" : { \"index\" : \"%s\", \"alias\" : \"foobar\" } }]}"; for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = rh.executePostRequest("_aliases", String.format(aliasTemplate, index), indexAccessNoRoleUserHeader); + RestHelper.HttpResponse response = rh.executePostRequest( + "_aliases", + String.format(aliasTemplate, index), + indexAccessNoRoleUserHeader + ); assertTrue(response.getStatusCode() == RestStatus.FORBIDDEN.getStatus()); assertTrue(response.getBody().contains(generalErrorMessage)); } @@ -519,7 +548,11 @@ public void testNonAccessAliasOperations() throws Exception { aliasTemplate = "{\"actions\" : [{ \"remove\" : { \"index\" : \"%s\", \"alias\" : \"foobar\" } }]}"; for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = rh.executePostRequest("_aliases", String.format(aliasTemplate, index), indexAccessNoRoleUserHeader); + RestHelper.HttpResponse response = rh.executePostRequest( + "_aliases", + String.format(aliasTemplate, index), + indexAccessNoRoleUserHeader + ); assertTrue(response.getStatusCode() == RestStatus.FORBIDDEN.getStatus()); assertTrue(response.getBody().contains(generalErrorMessage)); } @@ -528,7 +561,11 @@ public void testNonAccessAliasOperations() throws Exception { aliasTemplate = "{\"actions\" : [{ \"remove_index\" : { \"index\" : \"%s\"} }]}"; for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = rh.executePostRequest("_aliases", String.format(aliasTemplate, index), indexAccessNoRoleUserHeader); + RestHelper.HttpResponse response = rh.executePostRequest( + "_aliases", + String.format(aliasTemplate, index), + indexAccessNoRoleUserHeader + ); assertTrue(response.getStatusCode() == RestStatus.FORBIDDEN.getStatus()); assertTrue(response.getBody().contains(generalErrorMessage)); } @@ -543,14 +580,14 @@ public void testNonAccessUpdateIndexSettings() throws Exception { // Create rest client RestHelper rh = nonSslRestHelper(); - String indexSettings = "{\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : 30, \n" + - " \"number_of_replicas\" : 20 \n" + - " }\n" + - " }\n" + - "}"; + String indexSettings = "{\n" + + " \"settings\" : {\n" + + " \"index\" : {\n" + + " \"number_of_shards\" : 30, \n" + + " \"number_of_replicas\" : 20 \n" + + " }\n" + + " }\n" + + "}"; for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = rh.executePutRequest(index + "/_settings", indexSettings, indexAccessNoRoleUserHeader); @@ -575,7 +612,8 @@ public void testSearchResults() throws Exception { // Test direct index query. for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = rh.executePostRequest(index + "/_search", matchAllQuery, protectedIndexUserHeader); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); SearchResponse searchResponse = SearchResponse.fromXContent(xcp); // confirm good response. assertTrue(searchResponse.status() == RestStatus.OK); @@ -590,7 +628,8 @@ public void testSearchResults() throws Exception { // Test index pattern for (String indexPattern : listOfIndexPatternsToTest) { RestHelper.HttpResponse response = rh.executePostRequest(indexPattern + "/_search", matchAllQuery, protectedIndexUserHeader); - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); SearchResponse searchResponse = SearchResponse.fromXContent(xcp); // confirm good response. assertTrue(searchResponse.status() == RestStatus.OK); @@ -612,10 +651,9 @@ public void testResultsAlias() throws Exception { try (Client tc = getClient()) { for (String index : listOfIndexesToTest) { IndicesAliasesRequest request = new IndicesAliasesRequest(); - IndicesAliasesRequest.AliasActions aliasAction = - new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.ADD) - .index(index) - .alias("alias" + i); + IndicesAliasesRequest.AliasActions aliasAction = new IndicesAliasesRequest.AliasActions( + IndicesAliasesRequest.AliasActions.Type.ADD + ).index(index).alias("alias" + i); request.addAliasAction(aliasAction); tc.admin().indices().aliases(request).actionGet(); i++; @@ -626,9 +664,14 @@ public void testResultsAlias() throws Exception { RestHelper rh = nonSslRestHelper(); for (int aliasNumber = 0; aliasNumber < i; aliasNumber++) { - RestHelper.HttpResponse response = rh.executePostRequest("alias" + aliasNumber + "/_search", matchAllQuery, protectedIndexUserHeader); - - XContentParser xcp = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + RestHelper.HttpResponse response = rh.executePostRequest( + "alias" + aliasNumber + "/_search", + matchAllQuery, + protectedIndexUserHeader + ); + + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); SearchResponse searchResponse = SearchResponse.fromXContent(xcp); // confirm good response. assertTrue(searchResponse.status() == RestStatus.OK); @@ -648,14 +691,14 @@ public void testCreateIndex() throws Exception { // Create rest client RestHelper rh = nonSslRestHelper(); - String indexSettings = "{\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : 3, \n" + - " \"number_of_replicas\" : 2 \n" + - " }\n" + - " }\n" + - "}"; + String indexSettings = "{\n" + + " \"settings\" : {\n" + + " \"index\" : {\n" + + " \"number_of_shards\" : 3, \n" + + " \"number_of_replicas\" : 2 \n" + + " }\n" + + " }\n" + + "}"; for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = rh.executePutRequest(index, indexSettings, protectedIndexUserHeader); assertTrue(response.getStatusCode() == RestStatus.OK.getStatus()); @@ -716,10 +759,7 @@ public void testUpdateMappings() throws Exception { setupSettingsEnabled(); createTestIndicesAndDocs(); - String newMappings = "{\"properties\": {" + - "\"user_name\": {" + - "\"type\": \"text\"" + - "}}}"; + String newMappings = "{\"properties\": {" + "\"user_name\": {" + "\"type\": \"text\"" + "}}}"; // Create rest client RestHelper rh = nonSslRestHelper(); @@ -775,7 +815,11 @@ public void testAliasOperations() throws Exception { String aliasTemplate = "{\"actions\" : [{ \"add\" : { \"index\" : \"%s\", \"alias\" : \"foobar\" } }]}"; for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = rh.executePostRequest("_aliases", String.format(aliasTemplate, index), protectedIndexUserHeader); + RestHelper.HttpResponse response = rh.executePostRequest( + "_aliases", + String.format(aliasTemplate, index), + protectedIndexUserHeader + ); assertTrue(response.getStatusCode() == RestStatus.OK.getStatus()); } @@ -783,7 +827,11 @@ public void testAliasOperations() throws Exception { aliasTemplate = "{\"actions\" : [{ \"remove\" : { \"index\" : \"%s\", \"alias\" : \"foobar\" } }]}"; for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = rh.executePostRequest("_aliases", String.format(aliasTemplate, index), protectedIndexUserHeader); + RestHelper.HttpResponse response = rh.executePostRequest( + "_aliases", + String.format(aliasTemplate, index), + protectedIndexUserHeader + ); assertTrue(response.getStatusCode() == RestStatus.OK.getStatus()); } @@ -791,7 +839,11 @@ public void testAliasOperations() throws Exception { aliasTemplate = "{\"actions\" : [{ \"remove_index\" : { \"index\" : \"%s\"} }]}"; for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = rh.executePostRequest("_aliases", String.format(aliasTemplate, index), protectedIndexUserHeader); + RestHelper.HttpResponse response = rh.executePostRequest( + "_aliases", + String.format(aliasTemplate, index), + protectedIndexUserHeader + ); assertTrue(response.getStatusCode() == RestStatus.OK.getStatus()); } } @@ -805,11 +857,7 @@ public void testUpdateIndexSettings() throws Exception { // Create rest client RestHelper rh = nonSslRestHelper(); - String indexSettings = "{\n" + - " \"index\" : {\n" + - " \"refresh_interval\" : null\n" + - " }\n" + - "}"; + String indexSettings = "{\n" + " \"index\" : {\n" + " \"refresh_interval\" : null\n" + " }\n" + "}"; for (String index : listOfIndexesToTest) { RestHelper.HttpResponse response = rh.executePutRequest(index + "/_settings", indexSettings, protectedIndexUserHeader); @@ -833,21 +881,48 @@ public void testAccessSnapshot() throws Exception { } } - String putSnapshot = "{"+ - "\"indices\": \"%s\"," + - "\"ignore_unavailable\": false," + - "\"include_global_state\": false" + - "}"; + String putSnapshot = "{" + "\"indices\": \"%s\"," + "\"ignore_unavailable\": false," + "\"include_global_state\": false" + "}"; // Create rest client RestHelper rh = nonSslRestHelper(); for (String index : listOfIndexesToTest) { - assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_snapshot/" + index + "/" + index + "_1", protectedIndexUserHeader).getStatusCode()); - assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true","{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", protectedIndexUserHeader).getStatusCode()); - assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", "", protectedIndexUserHeader).getStatusCode()); - assertEquals(HttpStatus.SC_OK, rh.executePostRequest("_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true","{ \"indices\": \"" + index + "\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"" + index + "_1\" }", protectedIndexUserHeader).getStatusCode()); - assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_snapshot/" + index + "/" + index + "_2?wait_for_completion=true", String.format(putSnapshot, index), protectedIndexUserHeader).getStatusCode()); + assertEquals( + HttpStatus.SC_OK, + rh.executeGetRequest("_snapshot/" + index + "/" + index + "_1", protectedIndexUserHeader).getStatusCode() + ); + assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + protectedIndexUserHeader + ).getStatusCode() + ); + assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "", + protectedIndexUserHeader + ).getStatusCode() + ); + assertEquals( + HttpStatus.SC_OK, + rh.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "{ \"indices\": \"" + index + "\", \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"" + index + "_1\" }", + protectedIndexUserHeader + ).getStatusCode() + ); + assertEquals( + HttpStatus.SC_OK, + rh.executePutRequest( + "_snapshot/" + index + "/" + index + "_2?wait_for_completion=true", + String.format(putSnapshot, index), + protectedIndexUserHeader + ).getStatusCode() + ); } } } diff --git a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java index 950409a3e3..975db57701 100644 --- a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java +++ b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java @@ -109,26 +109,32 @@ public abstract class AbstractSecurityUnitTest extends RandomizedTest { static { - System.out.println("OS: " + System.getProperty("os.name") + " " + System.getProperty("os.arch") + " " - + System.getProperty("os.version")); System.out.println( - "Java Version: " + System.getProperty("java.version") + " " + System.getProperty("java.vendor")); - System.out.println("JVM Impl.: " + System.getProperty("java.vm.version") + " " - + System.getProperty("java.vm.vendor") + " " + System.getProperty("java.vm.name")); + "OS: " + System.getProperty("os.name") + " " + System.getProperty("os.arch") + " " + System.getProperty("os.version") + ); + System.out.println("Java Version: " + System.getProperty("java.version") + " " + System.getProperty("java.vendor")); + System.out.println( + "JVM Impl.: " + + System.getProperty("java.vm.version") + + " " + + System.getProperty("java.vm.vendor") + + " " + + System.getProperty("java.vm.name") + ); System.out.println("Open SSL available: " + OpenSsl.isAvailable()); System.out.println("Open SSL version: " + OpenSsl.versionString()); withRemoteCluster = Boolean.parseBoolean(System.getenv("TESTARG_unittests_with_remote_cluster")); System.out.println("With remote cluster: " + withRemoteCluster); - //System.setProperty("security.display_lic_none","true"); + // System.setProperty("security.display_lic_none","true"); } protected final Logger log = LogManager.getLogger(this.getClass()); - public static final ThreadPool MOCK_POOL = new ThreadPool(Settings.builder().put("node.name", "mock").build()); + public static final ThreadPool MOCK_POOL = new ThreadPool(Settings.builder().put("node.name", "mock").build()); - //TODO Test Matrix - protected boolean allowOpenSSL = false; //disabled, we test this already in SSL Plugin - //enable//disable enterprise modules - //1node and 3 node + // TODO Test Matrix + protected boolean allowOpenSSL = false; // disabled, we test this already in SSL Plugin + // enable//disable enterprise modules + // 1node and 3 node @Rule public TestName name = new TestName(); @@ -136,63 +142,68 @@ public abstract class AbstractSecurityUnitTest extends RandomizedTest { @Rule public final TemporaryFolder repositoryPath = new TemporaryFolder(); - @Rule - public final TestWatcher testWatcher = new SecurityTestWatcher(); + @Rule + public final TestWatcher testWatcher = new SecurityTestWatcher(); public static Header encodeBasicHeader(final String username, final String password) { - return new BasicHeader("Authorization", "Basic "+Base64.getEncoder().encodeToString( - (username + ":" + Objects.requireNonNull(password)).getBytes(StandardCharsets.UTF_8))); + return new BasicHeader( + "Authorization", + "Basic " + + Base64.getEncoder().encodeToString((username + ":" + Objects.requireNonNull(password)).getBytes(StandardCharsets.UTF_8)) + ); } protected RestHighLevelClient getRestClient(ClusterInfo info, String keyStoreName, String trustStoreName) { return getRestClient(info, keyStoreName, trustStoreName, null); } - protected RestHighLevelClient getRestClient(ClusterInfo info, String keyStoreName, String trustStoreName, HttpVersionPolicy httpVersionPolicy) { - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + protected RestHighLevelClient getRestClient( + ClusterInfo info, + String keyStoreName, + String trustStoreName, + HttpVersionPolicy httpVersionPolicy + ) { + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; try { SSLContextBuilder sslContextBuilder = SSLContexts.custom(); File keyStoreFile = FileHelper.getAbsoluteFilePathFromClassPath(prefix + keyStoreName).toFile(); - KeyStore keyStore = KeyStore.getInstance(keyStoreName.endsWith(".jks")?"JKS":"PKCS12"); + KeyStore keyStore = KeyStore.getInstance(keyStoreName.endsWith(".jks") ? "JKS" : "PKCS12"); keyStore.load(new FileInputStream(keyStoreFile), null); sslContextBuilder.loadKeyMaterial(keyStore, "changeit".toCharArray()); - KeyStore trustStore = KeyStore.getInstance(trustStoreName.endsWith(".jks")?"JKS":"PKCS12"); + KeyStore trustStore = KeyStore.getInstance(trustStoreName.endsWith(".jks") ? "JKS" : "PKCS12"); File trustStoreFile = FileHelper.getAbsoluteFilePathFromClassPath(prefix + trustStoreName).toFile(); - trustStore.load(new FileInputStream(trustStoreFile), - "changeit".toCharArray()); + trustStore.load(new FileInputStream(trustStoreFile), "changeit".toCharArray()); sslContextBuilder.loadTrustMaterial(trustStore, null); SSLContext sslContext = sslContextBuilder.build(); HttpHost httpHost = new HttpHost("https", info.httpHost, info.httpPort); - RestClientBuilder restClientBuilder = RestClient.builder(httpHost) - .setHttpClientConfigCallback( - builder -> { - TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() - .setSslContext(sslContext) - .setTlsVersions(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2", "SSLv3"}) - .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) - // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 - .setTlsDetailsFactory(new Factory() { - @Override - public TlsDetails create(final SSLEngine sslEngine) { - return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); - } - }) - .build(); - - final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() - .setTlsStrategy(tlsStrategy) - .build(); - builder.setConnectionManager(cm); - if (httpVersionPolicy != null) { - builder.setVersionPolicy(httpVersionPolicy); - } - return builder; - }); + RestClientBuilder restClientBuilder = RestClient.builder(httpHost).setHttpClientConfigCallback(builder -> { + TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() + .setSslContext(sslContext) + .setTlsVersions(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2", "SSLv3" }) + .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) + // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 + .setTlsDetailsFactory(new Factory() { + @Override + public TlsDetails create(final SSLEngine sslEngine) { + return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); + } + }) + .build(); + + final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(tlsStrategy) + .build(); + builder.setConnectionManager(cm); + if (httpVersionPolicy != null) { + builder.setVersionPolicy(httpVersionPolicy); + } + return builder; + }); return new RestHighLevelClient(restClientBuilder); } catch (Exception e) { log.error("Cannot create client", e); @@ -210,7 +221,7 @@ public void waitForInit(Client client) { retainedException = Optional.empty(); return; } catch (OpenSearchSecurityException ex) { - if(ex.getMessage().contains("OpenSearch Security not initialized")) { + if (ex.getMessage().contains("OpenSearch Security not initialized")) { retainedException = Optional.of(ex); try { Thread.sleep(500); @@ -227,7 +238,11 @@ public void waitForInit(Client client) { } } - public static Settings.Builder nodeRolesSettings(final Settings.Builder settingsBuilder, final boolean isClusterManager, final boolean isDataNode) { + public static Settings.Builder nodeRolesSettings( + final Settings.Builder settingsBuilder, + final boolean isClusterManager, + final boolean isDataNode + ) { final ImmutableList.Builder nodeRolesBuilder = ImmutableList.builder(); if (isDataNode) { nodeRolesBuilder.add(DiscoveryNodeRole.DATA_ROLE.roleName()); @@ -245,29 +260,29 @@ public static Settings.Builder mergeNodeRolesAndSettings(final Settings.Builder .addAll(settingsBuilder.build().getAsList(NODE_ROLE_KEY, ImmutableList.of())) .addAll(otherSettings.getAsList(NODE_ROLE_KEY, ImmutableList.of())); - return settingsBuilder.put(otherSettings) - .putList(NODE_ROLE_KEY, originalRoles.build().asList()); + return settingsBuilder.put(otherSettings).putList(NODE_ROLE_KEY, originalRoles.build().asList()); } - protected void initialize(ClusterHelper clusterHelper, ClusterInfo clusterInfo, DynamicSecurityConfig securityConfig) throws IOException { + protected void initialize(ClusterHelper clusterHelper, ClusterInfo clusterInfo, DynamicSecurityConfig securityConfig) + throws IOException { try (Client tc = clusterHelper.nodeClient()) { - Assert.assertEquals(clusterInfo.numNodes, - tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); + Assert.assertEquals(clusterInfo.numNodes, tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet().getNodes().size()); try { tc.admin().indices().create(new CreateIndexRequest(".opendistro_security")).actionGet(); } catch (Exception e) { - //ignore + // ignore } List indexRequests = securityConfig.getDynamicConfig(getResourceFolder()); - for(IndexRequest ir: indexRequests) { + for (IndexRequest ir : indexRequests) { tc.index(ir).actionGet(); } - ConfigUpdateResponse cur = tc - .execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0]))) - .actionGet(); + ConfigUpdateResponse cur = tc.execute( + ConfigUpdateAction.INSTANCE, + new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0])) + ).actionGet(); Assert.assertFalse(cur.failures().toString(), cur.hasFailures()); Assert.assertEquals(clusterInfo.numNodes, cur.getNodes().size()); @@ -278,22 +293,27 @@ protected void initialize(ClusterHelper clusterHelper, ClusterInfo clusterInfo, protected Settings.Builder minimumSecuritySettingsBuilder(int node, boolean sslOnly, Settings other) { - final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + final String prefix = getResourceFolder() == null ? "" : getResourceFolder() + "/"; Settings.Builder builder = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL); + .put(SSLConfigConstants.SECURITY_SSL_HTTP_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL) + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, allowOpenSSL); // If custom transport settings are not defined use defaults if (!hasCustomTransportSettings(other)) { builder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, "node-0") - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, - FileHelper.getAbsoluteFilePathFromClassPath(prefix+"node-0-keystore.jks")) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks")) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath(prefix + "node-0-keystore.jks") + ) + .put( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + FileHelper.getAbsoluteFilePathFromClassPath(prefix + "truststore.jks") + ) .put("plugins.security.ssl.transport.enforce_hostname_verification", false); } - if(!sslOnly) { + if (!sslOnly) { builder.putList("plugins.security.authcz.admin_dn", "CN=kirk,OU=client,O=client,l=tEst, C=De"); builder.put(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, false); } @@ -340,8 +360,8 @@ protected NodeSettingsSupplier genericMinimumSecuritySettings(List oth assert i > 0; // i is 1-indexed // Set to default if input does not have value at (i-1) index - boolean sslOnlyFlag = i > sslOnly.size() ? false : sslOnly.get(i-1); - Settings settings = i > others.size() ? Settings.EMPTY : others.get(i-1); + boolean sslOnlyFlag = i > sslOnly.size() ? false : sslOnly.get(i - 1); + Settings settings = i > others.size() ? Settings.EMPTY : others.get(i - 1); return minimumSecuritySettingsBuilder(i, sslOnlyFlag, settings).build(); }; @@ -363,7 +383,6 @@ protected String getResourceFolder() { return null; } - /** * Check if transport certs are is mentioned in the custom settings * @param customSettings custom settings from the test class @@ -371,7 +390,7 @@ protected String getResourceFolder() { */ protected boolean hasCustomTransportSettings(Settings customSettings) { // If Transport key extended usage is enabled this is true - return Boolean.parseBoolean(customSettings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED)) || - customSettings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH) != null; + return Boolean.parseBoolean(customSettings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED)) + || customSettings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH) != null; } } diff --git a/src/test/java/org/opensearch/security/test/DynamicSecurityConfig.java b/src/test/java/org/opensearch/security/test/DynamicSecurityConfig.java index 3573c7c274..9213d36070 100644 --- a/src/test/java/org/opensearch/security/test/DynamicSecurityConfig.java +++ b/src/test/java/org/opensearch/security/test/DynamicSecurityConfig.java @@ -45,8 +45,8 @@ public class DynamicSecurityConfig { private String securityInternalUsers = "internal_users.yml"; private String securityActionGroups = "action_groups.yml"; private String securityNodesDn = "nodes_dn.yml"; - private String securityWhitelist= "whitelist.yml"; - private String securityAllowlist= "allowlist.yml"; + private String securityWhitelist = "whitelist.yml"; + private String securityAllowlist = "allowlist.yml"; private String securityAudit = "audit.yml"; private String securityConfigAsYamlString = null; private String legacyConfigFolder = ""; @@ -95,12 +95,12 @@ public DynamicSecurityConfig setSecurityNodesDn(String nodesDn) { return this; } - public DynamicSecurityConfig setSecurityWhitelist(String whitelist){ + public DynamicSecurityConfig setSecurityWhitelist(String whitelist) { this.securityWhitelist = whitelist; return this; } - public DynamicSecurityConfig setSecurityAllowlist(String allowlist){ + public DynamicSecurityConfig setSecurityAllowlist(String allowlist) { this.securityAllowlist = allowlist; return this; } @@ -117,71 +117,86 @@ public DynamicSecurityConfig setLegacy() { public List getDynamicConfig(String folder) { - final String prefix = legacyConfigFolder+(folder == null?"":folder+"/"); + final String prefix = legacyConfigFolder + (folder == null ? "" : folder + "/"); List ret = new ArrayList(); - ret.add(new IndexRequest(securityIndexName) - .id(CType.CONFIG.toLCString()) + ret.add( + new IndexRequest(securityIndexName).id(CType.CONFIG.toLCString()) .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(CType.CONFIG.toLCString(), securityConfigAsYamlString==null? FileHelper.readYamlContent(prefix+securityConfig):FileHelper.readYamlContentFromString(securityConfigAsYamlString))); - - ret.add(new IndexRequest(securityIndexName) - .id(CType.ACTIONGROUPS.toLCString()) + .source( + CType.CONFIG.toLCString(), + securityConfigAsYamlString == null + ? FileHelper.readYamlContent(prefix + securityConfig) + : FileHelper.readYamlContentFromString(securityConfigAsYamlString) + ) + ); + + ret.add( + new IndexRequest(securityIndexName).id(CType.ACTIONGROUPS.toLCString()) .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(CType.ACTIONGROUPS.toLCString(), FileHelper.readYamlContent(prefix+securityActionGroups))); + .source(CType.ACTIONGROUPS.toLCString(), FileHelper.readYamlContent(prefix + securityActionGroups)) + ); - ret.add(new IndexRequest(securityIndexName) - .id(CType.INTERNALUSERS.toLCString()) + ret.add( + new IndexRequest(securityIndexName).id(CType.INTERNALUSERS.toLCString()) .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(CType.INTERNALUSERS.toLCString(), FileHelper.readYamlContent(prefix+securityInternalUsers))); + .source(CType.INTERNALUSERS.toLCString(), FileHelper.readYamlContent(prefix + securityInternalUsers)) + ); - ret.add(new IndexRequest(securityIndexName) - .id(CType.ROLES.toLCString()) + ret.add( + new IndexRequest(securityIndexName).id(CType.ROLES.toLCString()) .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(CType.ROLES.toLCString(), FileHelper.readYamlContent(prefix+securityRoles))); + .source(CType.ROLES.toLCString(), FileHelper.readYamlContent(prefix + securityRoles)) + ); - ret.add(new IndexRequest(securityIndexName) - .id(CType.ROLESMAPPING.toLCString()) + ret.add( + new IndexRequest(securityIndexName).id(CType.ROLESMAPPING.toLCString()) .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(CType.ROLESMAPPING.toLCString(), FileHelper.readYamlContent(prefix+securityRolesMapping))); - if("".equals(legacyConfigFolder)) { - ret.add(new IndexRequest(securityIndexName) - .id(CType.TENANTS.toLCString()) + .source(CType.ROLESMAPPING.toLCString(), FileHelper.readYamlContent(prefix + securityRolesMapping)) + ); + if ("".equals(legacyConfigFolder)) { + ret.add( + new IndexRequest(securityIndexName).id(CType.TENANTS.toLCString()) .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(CType.TENANTS.toLCString(), FileHelper.readYamlContent(prefix+securityTenants))); + .source(CType.TENANTS.toLCString(), FileHelper.readYamlContent(prefix + securityTenants)) + ); } if (null != FileHelper.getAbsoluteFilePathFromClassPath(prefix + securityNodesDn)) { - ret.add(new IndexRequest(securityIndexName) - .id(CType.NODESDN.toLCString()) + ret.add( + new IndexRequest(securityIndexName).id(CType.NODESDN.toLCString()) .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(CType.NODESDN.toLCString(), FileHelper.readYamlContent(prefix + securityNodesDn))); + .source(CType.NODESDN.toLCString(), FileHelper.readYamlContent(prefix + securityNodesDn)) + ); } final String whitelistYmlFile = prefix + securityWhitelist; if (null != FileHelper.getAbsoluteFilePathFromClassPath(whitelistYmlFile)) { - ret.add(new IndexRequest(securityIndexName) - .id(CType.WHITELIST.toLCString()) + ret.add( + new IndexRequest(securityIndexName).id(CType.WHITELIST.toLCString()) .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(CType.WHITELIST.toLCString(), FileHelper.readYamlContent(whitelistYmlFile))); + .source(CType.WHITELIST.toLCString(), FileHelper.readYamlContent(whitelistYmlFile)) + ); } final String allowlistYmlFile = prefix + securityAllowlist; if (null != FileHelper.getAbsoluteFilePathFromClassPath(allowlistYmlFile)) { - ret.add(new IndexRequest(securityIndexName) - .id(CType.ALLOWLIST.toLCString()) + ret.add( + new IndexRequest(securityIndexName).id(CType.ALLOWLIST.toLCString()) .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(CType.ALLOWLIST.toLCString(), FileHelper.readYamlContent(allowlistYmlFile))); + .source(CType.ALLOWLIST.toLCString(), FileHelper.readYamlContent(allowlistYmlFile)) + ); } final String auditYmlFile = prefix + securityAudit; if (null != FileHelper.getAbsoluteFilePathFromClassPath(auditYmlFile)) { - ret.add(new IndexRequest(securityIndexName) - .id(CType.AUDIT.toLCString()) + ret.add( + new IndexRequest(securityIndexName).id(CType.AUDIT.toLCString()) .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(CType.AUDIT.toLCString(), FileHelper.readYamlContent(auditYmlFile))); + .source(CType.AUDIT.toLCString(), FileHelper.readYamlContent(auditYmlFile)) + ); } return Collections.unmodifiableList(ret); diff --git a/src/test/java/org/opensearch/security/test/SingleClusterTest.java b/src/test/java/org/opensearch/security/test/SingleClusterTest.java index 0c2f3bfc07..2839e1e283 100644 --- a/src/test/java/org/opensearch/security/test/SingleClusterTest.java +++ b/src/test/java/org/opensearch/security/test/SingleClusterTest.java @@ -48,10 +48,13 @@ public abstract class SingleClusterTest extends AbstractSecurityUnitTest { private static final int DEFAULT_CLUSTER_MANAGER_NODE_NUM = 3; private static final int DEFAULT_FIRST_DATA_NODE_NUM = 2; - protected ClusterHelper clusterHelper = new ClusterHelper("utest_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()); + protected ClusterHelper clusterHelper = new ClusterHelper( + "utest_n" + num.incrementAndGet() + "_f" + System.getProperty("forkno") + "_t" + System.nanoTime() + ); protected ClusterInfo clusterInfo; - private ClusterHelper remoteClusterHelper = withRemoteCluster ? - new ClusterHelper("crl2_n"+num.incrementAndGet()+"_f"+System.getProperty("forkno")+"_t"+System.nanoTime()) : null; + private ClusterHelper remoteClusterHelper = withRemoteCluster + ? new ClusterHelper("crl2_n" + num.incrementAndGet() + "_f" + System.getProperty("forkno") + "_t" + System.nanoTime()) + : null; private ClusterInfo remoteClusterInfo; protected void setup(Settings nodeOverride) throws Exception { @@ -66,48 +69,71 @@ protected void setup() throws Exception { setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY, true); } - protected void setup(Settings initTransportClientSettings, DynamicSecurityConfig dynamicSecuritySettings, Settings nodeOverride) throws Exception { + protected void setup(Settings initTransportClientSettings, DynamicSecurityConfig dynamicSecuritySettings, Settings nodeOverride) + throws Exception { setup(initTransportClientSettings, dynamicSecuritySettings, nodeOverride, true); } - protected void setup(Settings initTransportClientSettings, DynamicSecurityConfig dynamicSecuritySettings, Settings nodeOverride, boolean initSecurityIndex) throws Exception { + protected void setup( + Settings initTransportClientSettings, + DynamicSecurityConfig dynamicSecuritySettings, + Settings nodeOverride, + boolean initSecurityIndex + ) throws Exception { setup(initTransportClientSettings, dynamicSecuritySettings, nodeOverride, initSecurityIndex, ClusterConfiguration.DEFAULT); } - protected void restart(Settings initTransportClientSettings, DynamicSecurityConfig dynamicSecuritySettings, Settings nodeOverride, boolean initOpendistroSecurityIndex) throws Exception { + protected void restart( + Settings initTransportClientSettings, + DynamicSecurityConfig dynamicSecuritySettings, + Settings nodeOverride, + boolean initOpendistroSecurityIndex + ) throws Exception { clusterInfo = clusterHelper.startCluster(minimumSecuritySettings(ccs(nodeOverride)), ClusterConfiguration.DEFAULT); - if(initOpendistroSecurityIndex && dynamicSecuritySettings != null) { + if (initOpendistroSecurityIndex && dynamicSecuritySettings != null) { initialize(clusterHelper, clusterInfo, dynamicSecuritySettings); } } private Settings ccs(Settings nodeOverride) throws Exception { - if(remoteClusterHelper != null) { + if (remoteClusterHelper != null) { Assert.assertNull("No remote clusters", remoteClusterInfo); remoteClusterInfo = remoteClusterHelper.startCluster(minimumSecuritySettings(Settings.EMPTY), ClusterConfiguration.SINGLENODE); Settings.Builder builder = Settings.builder() - .put(nodeOverride) - .putList("cluster.remote.cross_cluster_two.seeds", remoteClusterInfo.nodeHost+":"+remoteClusterInfo.nodePort); + .put(nodeOverride) + .putList("cluster.remote.cross_cluster_two.seeds", remoteClusterInfo.nodeHost + ":" + remoteClusterInfo.nodePort); return builder.build(); } else { return nodeOverride; } } - - protected void setup(Settings initTransportClientSettings, DynamicSecurityConfig dynamicSecuritySettings, Settings nodeOverride, boolean initSecurityIndex, ClusterConfiguration clusterConfiguration) throws Exception { + protected void setup( + Settings initTransportClientSettings, + DynamicSecurityConfig dynamicSecuritySettings, + Settings nodeOverride, + boolean initSecurityIndex, + ClusterConfiguration clusterConfiguration + ) throws Exception { Assert.assertNull("No cluster", clusterInfo); clusterInfo = clusterHelper.startCluster(minimumSecuritySettings(ccs(nodeOverride)), clusterConfiguration); - if(initSecurityIndex && dynamicSecuritySettings != null) { + if (initSecurityIndex && dynamicSecuritySettings != null) { initialize(clusterHelper, clusterInfo, dynamicSecuritySettings); } } - protected void setup(Settings initTransportClientSettings, DynamicSecurityConfig dynamicSecuritySettings, Settings nodeOverride - , boolean initSecurityIndex, ClusterConfiguration clusterConfiguration, int timeout, Integer nodes) throws Exception { + protected void setup( + Settings initTransportClientSettings, + DynamicSecurityConfig dynamicSecuritySettings, + Settings nodeOverride, + boolean initSecurityIndex, + ClusterConfiguration clusterConfiguration, + int timeout, + Integer nodes + ) throws Exception { Assert.assertNull("No cluster", clusterInfo); clusterInfo = clusterHelper.startCluster(minimumSecuritySettings(ccs(nodeOverride)), clusterConfiguration, timeout, nodes); - if(initSecurityIndex) { + if (initSecurityIndex) { initialize(clusterHelper, clusterInfo, dynamicSecuritySettings); } } @@ -119,20 +145,24 @@ protected void setupSslOnlyMode(Settings nodeOverride) throws Exception { protected void setupSslOnlyModeWithClusterManagerNodeWithoutSSL(Settings nodeOverride) throws Exception { Assert.assertNull("No cluster", clusterInfo); - clusterInfo = clusterHelper.startCluster(minimumSecuritySettingsSslOnlyWithOneNodeNonSSL(nodeOverride, - DEFAULT_CLUSTER_MANAGER_NODE_NUM), ClusterConfiguration.DEFAULT_CLUSTER_MANAGER_WITHOUT_SECURITY_PLUGIN); + clusterInfo = clusterHelper.startCluster( + minimumSecuritySettingsSslOnlyWithOneNodeNonSSL(nodeOverride, DEFAULT_CLUSTER_MANAGER_NODE_NUM), + ClusterConfiguration.DEFAULT_CLUSTER_MANAGER_WITHOUT_SECURITY_PLUGIN + ); } protected void setupSslOnlyModeWithDataNodeWithoutSSL(Settings nodeOverride) throws Exception { Assert.assertNull("No cluster", clusterInfo); - clusterInfo = clusterHelper.startCluster(minimumSecuritySettingsSslOnlyWithOneNodeNonSSL(nodeOverride, - DEFAULT_FIRST_DATA_NODE_NUM), ClusterConfiguration.DEFAULT_ONE_DATA_NODE_WITHOUT_SECURITY_PLUGIN); + clusterInfo = clusterHelper.startCluster( + minimumSecuritySettingsSslOnlyWithOneNodeNonSSL(nodeOverride, DEFAULT_FIRST_DATA_NODE_NUM), + ClusterConfiguration.DEFAULT_ONE_DATA_NODE_WITHOUT_SECURITY_PLUGIN + ); } - protected void setupGenericNodes(List nodeOverride, List sslOnly, ClusterConfiguration clusterConfiguration) throws Exception { + protected void setupGenericNodes(List nodeOverride, List sslOnly, ClusterConfiguration clusterConfiguration) + throws Exception { Assert.assertNull("No cluster", clusterInfo); - clusterInfo = clusterHelper.startCluster(genericMinimumSecuritySettings(nodeOverride, sslOnly), - clusterConfiguration); + clusterInfo = clusterHelper.startCluster(genericMinimumSecuritySettings(nodeOverride, sslOnly), clusterConfiguration); } protected RestHelper restHelper() { @@ -147,11 +177,10 @@ protected Client getClient() { return clusterHelper.nodeClient(); } - @After public void tearDown() { - if(remoteClusterInfo != null) { + if (remoteClusterInfo != null) { try { remoteClusterHelper.stopCluster(); } catch (Exception e) { @@ -161,7 +190,7 @@ public void tearDown() { remoteClusterInfo = null; } - if(clusterInfo != null) { + if (clusterInfo != null) { try { clusterHelper.stopCluster(); } catch (Exception e) { diff --git a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterConfiguration.java b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterConfiguration.java index 5a0c41fd33..e9b503e669 100644 --- a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterConfiguration.java +++ b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterConfiguration.java @@ -45,90 +45,111 @@ import org.opensearch.transport.Netty4ModulePlugin; public enum ClusterConfiguration { - //first one needs to be a cluster manager - //HUGE(new NodeSettings(true, false, false), new NodeSettings(true, false, false), new NodeSettings(true, false, false), new NodeSettings(false, true,false), new NodeSettings(false, true, false)), + // first one needs to be a cluster manager + // HUGE(new NodeSettings(true, false, false), new NodeSettings(true, false, false), new NodeSettings(true, false, false), new + // NodeSettings(false, true,false), new NodeSettings(false, true, false)), - //3 nodes (1m, 2d) + // 3 nodes (1m, 2d) DEFAULT(new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true)), - //2 nodes (1m, 3d) - ONE_CLUSTER_MANAGER_THREE_DATA(new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true), new NodeSettings(false, true)), - - DEFAULT_CLUSTER_MANAGER_WITHOUT_SECURITY_PLUGIN(new NodeSettings(true, false) - .removePluginIfPresent(OpenSearchSecurityPlugin.class) - , new NodeSettings(false, true) - , new NodeSettings(false, true)), - - DEFAULT_ONE_DATA_NODE_WITHOUT_SECURITY_PLUGIN(new NodeSettings(true, false) - , new NodeSettings(false, true).removePluginIfPresent(OpenSearchSecurityPlugin.class) - , new NodeSettings(false, true)), - - //1 node (1md) - SINGLENODE(new NodeSettings(true, true)), - - //4 node (1m, 2d, 1c) - CLIENTNODE(new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true), new NodeSettings(false, false)), - - //3 nodes (1m, 2d) plus additional UserInjectorPlugin - USERINJECTOR(new NodeSettings(true, false, Lists.newArrayList(UserInjectorPlugin.class)), new NodeSettings(false, true, Lists.newArrayList(UserInjectorPlugin.class)), new NodeSettings(false, true, Lists.newArrayList(UserInjectorPlugin.class))); - - private List nodeSettings = new LinkedList<>(); - - private ClusterConfiguration(NodeSettings ... settings) { - nodeSettings.addAll(Arrays.asList(settings)); - } + // 2 nodes (1m, 3d) + ONE_CLUSTER_MANAGER_THREE_DATA( + new NodeSettings(true, false), + new NodeSettings(false, true), + new NodeSettings(false, true), + new NodeSettings(false, true) + ), + + DEFAULT_CLUSTER_MANAGER_WITHOUT_SECURITY_PLUGIN( + new NodeSettings(true, false).removePluginIfPresent(OpenSearchSecurityPlugin.class), + new NodeSettings(false, true), + new NodeSettings(false, true) + ), + + DEFAULT_ONE_DATA_NODE_WITHOUT_SECURITY_PLUGIN( + new NodeSettings(true, false), + new NodeSettings(false, true).removePluginIfPresent(OpenSearchSecurityPlugin.class), + new NodeSettings(false, true) + ), + + // 1 node (1md) + SINGLENODE(new NodeSettings(true, true)), + + // 4 node (1m, 2d, 1c) + CLIENTNODE(new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true), new NodeSettings(false, false)), + + // 3 nodes (1m, 2d) plus additional UserInjectorPlugin + USERINJECTOR( + new NodeSettings(true, false, Lists.newArrayList(UserInjectorPlugin.class)), + new NodeSettings(false, true, Lists.newArrayList(UserInjectorPlugin.class)), + new NodeSettings(false, true, Lists.newArrayList(UserInjectorPlugin.class)) + ); + + private List nodeSettings = new LinkedList<>(); + + private ClusterConfiguration(NodeSettings... settings) { + nodeSettings.addAll(Arrays.asList(settings)); + } - public List getNodeSettings() { - return Collections.unmodifiableList(nodeSettings); - } + public List getNodeSettings() { + return Collections.unmodifiableList(nodeSettings); + } - public List getClusterManagerNodeSettings() { - return Collections.unmodifiableList(nodeSettings.stream().filter(a->a.clusterManagerNode).collect(Collectors.toList())); + public List getClusterManagerNodeSettings() { + return Collections.unmodifiableList(nodeSettings.stream().filter(a -> a.clusterManagerNode).collect(Collectors.toList())); } - public List getNonClusterManagerNodeSettings() { - return Collections.unmodifiableList(nodeSettings.stream().filter(a->!a.clusterManagerNode).collect(Collectors.toList())); + public List getNonClusterManagerNodeSettings() { + return Collections.unmodifiableList(nodeSettings.stream().filter(a -> !a.clusterManagerNode).collect(Collectors.toList())); } - public int getNodes() { + public int getNodes() { return nodeSettings.size(); } - public int getClusterManagerNodes() { - return (int) nodeSettings.stream().filter(a->a.clusterManagerNode).count(); + public int getClusterManagerNodes() { + return (int) nodeSettings.stream().filter(a -> a.clusterManagerNode).count(); } - public int getDataNodes() { - return (int) nodeSettings.stream().filter(a->a.dataNode).count(); + public int getDataNodes() { + return (int) nodeSettings.stream().filter(a -> a.dataNode).count(); } - public int getClientNodes() { - return (int) nodeSettings.stream().filter(a->!a.clusterManagerNode && !a.dataNode).count(); + public int getClientNodes() { + return (int) nodeSettings.stream().filter(a -> !a.clusterManagerNode && !a.dataNode).count(); } - public static class NodeSettings { - public boolean clusterManagerNode; - public boolean dataNode; - public List> plugins = Lists.newArrayList(Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, MatrixAggregationModulePlugin.class, MustacheModulePlugin.class, ParentJoinModulePlugin.class, PercolatorModulePlugin.class, ReindexModulePlugin.class); - - public NodeSettings(boolean clusterManagerNode, boolean dataNode) { - super(); - this.clusterManagerNode = clusterManagerNode; - this.dataNode = dataNode; - } + public static class NodeSettings { + public boolean clusterManagerNode; + public boolean dataNode; + public List> plugins = Lists.newArrayList( + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + MatrixAggregationModulePlugin.class, + MustacheModulePlugin.class, + ParentJoinModulePlugin.class, + PercolatorModulePlugin.class, + ReindexModulePlugin.class + ); + + public NodeSettings(boolean clusterManagerNode, boolean dataNode) { + super(); + this.clusterManagerNode = clusterManagerNode; + this.dataNode = dataNode; + } - public NodeSettings(boolean clusterManagerNode, boolean dataNode, List> additionalPlugins) { + public NodeSettings(boolean clusterManagerNode, boolean dataNode, List> additionalPlugins) { this(clusterManagerNode, dataNode); this.plugins.addAll(additionalPlugins); } - public NodeSettings removePluginIfPresent(Class pluginToRemove){ - this.plugins.remove(pluginToRemove); - return this; - } + public NodeSettings removePluginIfPresent(Class pluginToRemove) { + this.plugins.remove(pluginToRemove); + return this; + } - public Class[] getPlugins() { - return plugins.toArray(new Class[0] ); - } - } + public Class[] getPlugins() { + return plugins.toArray(new Class[0]); + } + } } diff --git a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java index efa0a8f89d..e60fab8e48 100644 --- a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java +++ b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterHelper.java @@ -78,7 +78,7 @@ public final class ClusterHelper { /** Resets all system properties associated with a cluster */ public static void resetSystemProperties() { System.setProperty("opensearch.enforce.bootstrap.checks", "true"); - updateDefaultDirectory(new File( SingleClusterTest.PROJECT_ROOT_RELATIVE_PATH + "config").getAbsolutePath()); + updateDefaultDirectory(new File(SingleClusterTest.PROJECT_ROOT_RELATIVE_PATH + "config").getAbsolutePath()); } /** @@ -115,12 +115,17 @@ public String getClusterName() { * @throws Exception */ - public final ClusterInfo startCluster(final NodeSettingsSupplier nodeSettingsSupplier, ClusterConfiguration clusterConfiguration) throws Exception { + public final ClusterInfo startCluster(final NodeSettingsSupplier nodeSettingsSupplier, ClusterConfiguration clusterConfiguration) + throws Exception { return startCluster(nodeSettingsSupplier, clusterConfiguration, 10, null); } - public final synchronized ClusterInfo startCluster(final NodeSettingsSupplier nodeSettingsSupplier, ClusterConfiguration clusterConfiguration, int timeout, Integer nodes) - throws Exception { + public final synchronized ClusterInfo startCluster( + final NodeSettingsSupplier nodeSettingsSupplier, + ClusterConfiguration clusterConfiguration, + int timeout, + Integer nodes + ) throws Exception { switch (clusterState) { case UNINITIALIZED: @@ -140,27 +145,40 @@ public final synchronized ClusterInfo startCluster(final NodeSettingsSupplier no final String forkno = System.getProperty("forkno"); int forkNumber = 1; - if(forkno != null && forkno.length() > 0) { + if (forkno != null && forkno.length() > 0) { forkNumber = Integer.parseInt(forkno.split("_")[1]); } - final int min = SocketUtils.PORT_RANGE_MIN+(forkNumber*5000); - final int max = SocketUtils.PORT_RANGE_MIN+((forkNumber+1)*5000)-1; + final int min = SocketUtils.PORT_RANGE_MIN + (forkNumber * 5000); + final int max = SocketUtils.PORT_RANGE_MIN + ((forkNumber + 1) * 5000) - 1; - final SortedSet freePorts = SocketUtils.findAvailableTcpPorts(internalNodeSettings.size()*2, min, max); - assert freePorts.size() == internalNodeSettings.size()*2; + final SortedSet freePorts = SocketUtils.findAvailableTcpPorts(internalNodeSettings.size() * 2, min, max); + assert freePorts.size() == internalNodeSettings.size() * 2; final SortedSet tcpClusterManagerPortsOnly = new TreeSet(); final SortedSet tcpAllPorts = new TreeSet(); - freePorts.stream().limit(clusterConfiguration.getClusterManagerNodes()).forEach(el->tcpClusterManagerPortsOnly.add(el)); - freePorts.stream().limit(internalNodeSettings.size()).forEach(el->tcpAllPorts.add(el)); + freePorts.stream().limit(clusterConfiguration.getClusterManagerNodes()).forEach(el -> tcpClusterManagerPortsOnly.add(el)); + freePorts.stream().limit(internalNodeSettings.size()).forEach(el -> tcpAllPorts.add(el)); final Iterator tcpPortsAllIt = tcpAllPorts.iterator(); final SortedSet httpPorts = new TreeSet(); - freePorts.stream().skip(internalNodeSettings.size()).limit(internalNodeSettings.size()).forEach(el->httpPorts.add(el)); + freePorts.stream().skip(internalNodeSettings.size()).limit(internalNodeSettings.size()).forEach(el -> httpPorts.add(el)); final Iterator httpPortsIt = httpPorts.iterator(); - System.out.println("tcpClusterManagerPorts: "+tcpClusterManagerPortsOnly+"/tcpAllPorts: "+tcpAllPorts+"/httpPorts: "+httpPorts+" for ("+min+"-"+max+") fork "+forkNumber); + System.out.println( + "tcpClusterManagerPorts: " + + tcpClusterManagerPortsOnly + + "/tcpAllPorts: " + + tcpAllPorts + + "/httpPorts: " + + httpPorts + + " for (" + + min + + "-" + + max + + ") fork " + + forkNumber + ); final CountDownLatch latch = new CountDownLatch(internalNodeSettings.size()); @@ -174,7 +192,15 @@ public final synchronized ClusterInfo startCluster(final NodeSettingsSupplier no for (int i = 0; i < internalClusterManagerNodeSettings.size(); i++) { NodeSettings setting = internalClusterManagerNodeSettings.get(i); int nodeNum = nodeNumCounter--; - final Settings.Builder nodeSettingsBuilder = getMinimumNonSecurityNodeSettingsBuilder(nodeNum, setting.clusterManagerNode, setting.dataNode, internalNodeSettings.size(), tcpClusterManagerPortsOnly, tcpPortsAllIt.next(), httpPortsIt.next()); + final Settings.Builder nodeSettingsBuilder = getMinimumNonSecurityNodeSettingsBuilder( + nodeNum, + setting.clusterManagerNode, + setting.dataNode, + internalNodeSettings.size(), + tcpClusterManagerPortsOnly, + tcpPortsAllIt.next(), + httpPortsIt.next() + ); final Settings settingsForNode; if (nodeSettingsSupplier != null) { final Settings suppliedSettings = nodeSettingsSupplier.get(nodeNum); @@ -206,7 +232,15 @@ public void run() { for (int i = 0; i < internalNonClusterManagerNodeSettings.size(); i++) { NodeSettings setting = internalNonClusterManagerNodeSettings.get(i); int nodeNum = nodeNumCounter--; - final Settings.Builder nodeSettingsBuilder = getMinimumNonSecurityNodeSettingsBuilder(nodeNum, setting.clusterManagerNode, setting.dataNode, internalNodeSettings.size(), tcpClusterManagerPortsOnly, tcpPortsAllIt.next(), httpPortsIt.next()); + final Settings.Builder nodeSettingsBuilder = getMinimumNonSecurityNodeSettingsBuilder( + nodeNum, + setting.clusterManagerNode, + setting.dataNode, + internalNodeSettings.size(), + tcpClusterManagerPortsOnly, + tcpPortsAllIt.next(), + httpPortsIt.next() + ); final Settings settingsForNode; if (nodeSettingsSupplier != null) { final Settings suppliedSettings = nodeSettingsSupplier.get(nodeNum); @@ -239,27 +273,34 @@ public void run() { latch.await(); - if(err.get() != null) { - throw new RuntimeException("Could not start all nodes "+err.get(),err.get()); + if (err.get() != null) { + throw new RuntimeException("Could not start all nodes " + err.get(), err.get()); } - ClusterInfo cInfo = waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(timeout), nodes == null?opensearchNodes.size():nodes.intValue()); + ClusterInfo cInfo = waitForCluster( + ClusterHealthStatus.GREEN, + TimeValue.timeValueSeconds(timeout), + nodes == null ? opensearchNodes.size() : nodes.intValue() + ); cInfo.numNodes = internalNodeSettings.size(); cInfo.clustername = clustername; - cInfo.tcpClusterManagerPortsOnly = tcpClusterManagerPortsOnly.stream().map(s->"127.0.0.1:"+s).collect(Collectors.toList()); - - final String defaultTemplate = "{\n" + - " \"index_patterns\": [\"*\"],\n" + - " \"order\": -1,\n" + - " \"settings\": {\n" + - " \"number_of_shards\": \"5\",\n" + - " \"number_of_replicas\": \"1\"\n" + - " }\n" + - " }"; - - final AcknowledgedResponse templateAck = nodeClient().admin().indices().putTemplate(new PutIndexTemplateRequest("default").source(defaultTemplate, XContentType.JSON)).actionGet(); - - if(!templateAck.isAcknowledged()) { + cInfo.tcpClusterManagerPortsOnly = tcpClusterManagerPortsOnly.stream().map(s -> "127.0.0.1:" + s).collect(Collectors.toList()); + + final String defaultTemplate = "{\n" + + " \"index_patterns\": [\"*\"],\n" + + " \"order\": -1,\n" + + " \"settings\": {\n" + + " \"number_of_shards\": \"5\",\n" + + " \"number_of_replicas\": \"1\"\n" + + " }\n" + + " }"; + + final AcknowledgedResponse templateAck = nodeClient().admin() + .indices() + .putTemplate(new PutIndexTemplateRequest("default").source(defaultTemplate, XContentType.JSON)) + .actionGet(); + + if (!templateAck.isAcknowledged()) { throw new RuntimeException("Default template could not be created"); } @@ -269,15 +310,15 @@ public void run() { public final void stopCluster() throws Exception { closeAllNodes(); - FileUtils.deleteDirectory(new File("./target/data/"+clustername)); + FileUtils.deleteDirectory(new File("./target/data/" + clustername)); } - private void closeAllNodes() throws Exception { - //close non cluster manager nodes - opensearchNodes.stream().filter(n->!n.isClusterManagerEligible()).forEach(ClusterHelper::closeNode); + private void closeAllNodes() throws Exception { + // close non cluster manager nodes + opensearchNodes.stream().filter(n -> !n.isClusterManagerEligible()).forEach(ClusterHelper::closeNode); - //close cluster manager nodes - opensearchNodes.stream().filter(n->n.isClusterManagerEligible()).forEach(ClusterHelper::closeNode); + // close cluster manager nodes + opensearchNodes.stream().filter(n -> n.isClusterManagerEligible()).forEach(ClusterHelper::closeNode); opensearchNodes.clear(); clusterState = ClusterState.STOPPED; } @@ -287,16 +328,16 @@ private static void closeNode(Node node) { node.close(); node.awaitClose(250, TimeUnit.MILLISECONDS); } catch (Throwable e) { - //ignore + // ignore } } - public Client nodeClient() { return opensearchNodes.get(0).client(); } - public ClusterInfo waitForCluster(final ClusterHealthStatus status, final TimeValue timeout, final int expectedNodeCount) throws IOException { + public ClusterInfo waitForCluster(final ClusterHealthStatus status, final TimeValue timeout, final int expectedNodeCount) + throws IOException { if (opensearchNodes.isEmpty()) { throw new RuntimeException("List of nodes was empty."); } @@ -307,12 +348,19 @@ public ClusterInfo waitForCluster(final ClusterHealthStatus status, final TimeVa Client client = node.client(); try { log.debug("waiting for cluster state {} and {} nodes", status.name(), expectedNodeCount); - final ClusterHealthResponse healthResponse = client.admin().cluster().prepareHealth() - .setWaitForStatus(status).setTimeout(timeout).setClusterManagerNodeTimeout(timeout).setWaitForNodes("" + expectedNodeCount).execute() - .actionGet(); + final ClusterHealthResponse healthResponse = client.admin() + .cluster() + .prepareHealth() + .setWaitForStatus(status) + .setTimeout(timeout) + .setClusterManagerNodeTimeout(timeout) + .setWaitForNodes("" + expectedNodeCount) + .execute() + .actionGet(); if (healthResponse.isTimedOut()) { - throw new IOException("cluster state is " + healthResponse.getStatus().name() + " with " - + healthResponse.getNumberOfNodes() + " nodes"); + throw new IOException( + "cluster state is " + healthResponse.getStatus().name() + " with " + healthResponse.getNumberOfNodes() + " nodes" + ); } else { log.debug("... cluster state ok {} with {} nodes", healthResponse.getStatus().name(), healthResponse.getNumberOfNodes()); } @@ -323,21 +371,32 @@ public ClusterInfo waitForCluster(final ClusterHealthStatus status, final TimeVa final List nodes = res.getNodes(); - final List clusterManagerNodes = nodes.stream().filter(n->n.getNode().getRoles().contains(DiscoveryNodeRole.CLUSTER_MANAGER_ROLE)).collect(Collectors.toList()); - final List dataNodes = nodes.stream().filter(n->n.getNode().getRoles().contains(DiscoveryNodeRole.DATA_ROLE) && !n.getNode().getRoles().contains(DiscoveryNodeRole.CLUSTER_MANAGER_ROLE)).collect(Collectors.toList()); + final List clusterManagerNodes = nodes.stream() + .filter(n -> n.getNode().getRoles().contains(DiscoveryNodeRole.CLUSTER_MANAGER_ROLE)) + .collect(Collectors.toList()); + final List dataNodes = nodes.stream() + .filter( + n -> n.getNode().getRoles().contains(DiscoveryNodeRole.DATA_ROLE) + && !n.getNode().getRoles().contains(DiscoveryNodeRole.CLUSTER_MANAGER_ROLE) + ) + .collect(Collectors.toList()); // Sorting the nodes so that the node receiving the http requests is always deterministic dataNodes.sort(Comparator.comparing(nodeInfo -> nodeInfo.getNode().getName())); - final List clientNodes = nodes.stream().filter(n->!n.getNode().getRoles().contains(DiscoveryNodeRole.CLUSTER_MANAGER_ROLE) && !n.getNode().getRoles().contains(DiscoveryNodeRole.DATA_ROLE)).collect(Collectors.toList()); - - - for (NodeInfo nodeInfo: clusterManagerNodes) { + final List clientNodes = nodes.stream() + .filter( + n -> !n.getNode().getRoles().contains(DiscoveryNodeRole.CLUSTER_MANAGER_ROLE) + && !n.getNode().getRoles().contains(DiscoveryNodeRole.DATA_ROLE) + ) + .collect(Collectors.toList()); + + for (NodeInfo nodeInfo : clusterManagerNodes) { final TransportInfo transportInfo = nodeInfo.getInfo(TransportInfo.class); final TransportAddress transportAddress = transportInfo.getAddress().publishAddress(); clusterInfo.nodePort = transportAddress.getPort(); clusterInfo.nodeHost = transportAddress.getAddress(); } - if(!clientNodes.isEmpty()) { + if (!clientNodes.isEmpty()) { NodeInfo nodeInfo = clientNodes.get(0); final HttpInfo httpInfo = nodeInfo.getInfo(HttpInfo.class); if (httpInfo != null && httpInfo.address() != null) { @@ -348,9 +407,9 @@ public ClusterInfo waitForCluster(final ClusterHealthStatus status, final TimeVa } else { throw new RuntimeException("no http host/port for client node"); } - } else if(!dataNodes.isEmpty()) { + } else if (!dataNodes.isEmpty()) { - for (NodeInfo nodeInfo: dataNodes) { + for (NodeInfo nodeInfo : dataNodes) { final HttpInfo httpInfo = nodeInfo.getInfo(HttpInfo.class); if (httpInfo != null && httpInfo.address() != null) { final TransportAddress transportAddress = httpInfo.address().publishAddress(); @@ -360,9 +419,9 @@ public ClusterInfo waitForCluster(final ClusterHealthStatus status, final TimeVa break; } } - } else { + } else { - for (NodeInfo nodeInfo: nodes) { + for (NodeInfo nodeInfo : nodes) { final HttpInfo httpInfo = nodeInfo.getInfo(HttpInfo.class); if (httpInfo != null && httpInfo.address() != null) { final TransportAddress transportAddress = httpInfo.address().publishAddress(); @@ -374,32 +433,41 @@ public ClusterInfo waitForCluster(final ClusterHealthStatus status, final TimeVa } } } catch (final OpenSearchTimeoutException e) { - throw new IOException( - "timeout, cluster does not respond to health request, cowardly refusing to continue with operations"); + throw new IOException("timeout, cluster does not respond to health request, cowardly refusing to continue with operations"); } return clusterInfo; } // @formatter:off - private Settings.Builder getMinimumNonSecurityNodeSettingsBuilder(final int nodenum, final boolean isClusterManagerNode, - final boolean isDataNode, int nodeCount, SortedSet clusterManagerTcpPorts, int tcpPort, int httpPort) { + private Settings.Builder getMinimumNonSecurityNodeSettingsBuilder( + final int nodenum, + final boolean isClusterManagerNode, + final boolean isDataNode, + int nodeCount, + SortedSet clusterManagerTcpPorts, + int tcpPort, + int httpPort + ) { return AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), isClusterManagerNode, isDataNode) - .put("node.name", "node_"+clustername+ "_num" + nodenum) - .put("cluster.name", clustername) - .put("path.data", "./target/data/"+clustername+"/data") - .put("path.logs", "./target/data/"+clustername+"/logs") - .put("node.max_local_storage_nodes", nodeCount) - .putList("cluster.initial_cluster_manager_nodes", clusterManagerTcpPorts.stream().map(s->"127.0.0.1:"+s).collect(Collectors.toList())) - .put("discovery.initial_state_timeout","8s") - .putList("discovery.seed_hosts", clusterManagerTcpPorts.stream().map(s->"127.0.0.1:"+s).collect(Collectors.toList())) - .put("transport.tcp.port", tcpPort) - .put("http.port", httpPort) - .put("http.cors.enabled", true) - .put("path.home", "./target"); + .put("node.name", "node_" + clustername + "_num" + nodenum) + .put("cluster.name", clustername) + .put("path.data", "./target/data/" + clustername + "/data") + .put("path.logs", "./target/data/" + clustername + "/logs") + .put("node.max_local_storage_nodes", nodeCount) + .putList( + "cluster.initial_cluster_manager_nodes", + clusterManagerTcpPorts.stream().map(s -> "127.0.0.1:" + s).collect(Collectors.toList()) + ) + .put("discovery.initial_state_timeout", "8s") + .putList("discovery.seed_hosts", clusterManagerTcpPorts.stream().map(s -> "127.0.0.1:" + s).collect(Collectors.toList())) + .put("transport.tcp.port", tcpPort) + .put("http.port", httpPort) + .put("http.cors.enabled", true) + .put("path.home", "./target"); } - private enum ClusterState{ + private enum ClusterState { UNINITIALIZED, STARTED, STOPPED diff --git a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterInfo.java b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterInfo.java index bb59450752..d50274e0e0 100644 --- a/src/test/java/org/opensearch/security/test/helper/cluster/ClusterInfo.java +++ b/src/test/java/org/opensearch/security/test/helper/cluster/ClusterInfo.java @@ -33,12 +33,12 @@ import org.opensearch.common.transport.TransportAddress; public class ClusterInfo { - public int numNodes; - public String httpHost = null; - public int httpPort = -1; - public Set httpAdresses = new HashSet(); - public String nodeHost; - public int nodePort; - public String clustername; + public int numNodes; + public String httpHost = null; + public int httpPort = -1; + public Set httpAdresses = new HashSet(); + public String nodeHost; + public int nodePort; + public String clustername; public List tcpClusterManagerPortsOnly; } diff --git a/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java b/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java index 803088771a..acb4a7d217 100644 --- a/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java +++ b/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java @@ -57,90 +57,90 @@ public class FileHelper { - protected final static Logger log = LogManager.getLogger(FileHelper.class); - - public static KeyStore getKeystoreFromClassPath(final String fileNameFromClasspath, String password) throws Exception { - Path path = getAbsoluteFilePathFromClassPath(fileNameFromClasspath); - if(path==null) { - return null; - } - - KeyStore ks = KeyStore.getInstance("JKS"); - try (FileInputStream fin = new FileInputStream(path.toFile())) { - ks.load(fin, password==null||password.isEmpty()?null:password.toCharArray()); - } - return ks; - } - - public static Path getAbsoluteFilePathFromClassPath(final String fileNameFromClasspath) { - File file = null; - final URL fileUrl = FileHelper.class.getClassLoader().getResource(fileNameFromClasspath); - if (fileUrl != null) { - try { - file = new File(URLDecoder.decode(fileUrl.getFile(), "UTF-8")); - } catch (final UnsupportedEncodingException e) { - return null; - } - - if (file.exists() && file.canRead()) { - return Paths.get(file.getAbsolutePath()); - } else { - log.error("Cannot read from {}, maybe the file does not exists? ", file.getAbsolutePath()); - } - - } else { - log.error("Failed to load {}", fileNameFromClasspath); - } - return null; - } - - public static final String loadFile(final String file) throws IOException { - final StringWriter sw = new StringWriter(); - IOUtils.copy(FileHelper.class.getResourceAsStream("/" + file), sw, StandardCharsets.UTF_8); - return sw.toString(); - } + protected final static Logger log = LogManager.getLogger(FileHelper.class); + + public static KeyStore getKeystoreFromClassPath(final String fileNameFromClasspath, String password) throws Exception { + Path path = getAbsoluteFilePathFromClassPath(fileNameFromClasspath); + if (path == null) { + return null; + } + + KeyStore ks = KeyStore.getInstance("JKS"); + try (FileInputStream fin = new FileInputStream(path.toFile())) { + ks.load(fin, password == null || password.isEmpty() ? null : password.toCharArray()); + } + return ks; + } + + public static Path getAbsoluteFilePathFromClassPath(final String fileNameFromClasspath) { + File file = null; + final URL fileUrl = FileHelper.class.getClassLoader().getResource(fileNameFromClasspath); + if (fileUrl != null) { + try { + file = new File(URLDecoder.decode(fileUrl.getFile(), "UTF-8")); + } catch (final UnsupportedEncodingException e) { + return null; + } + + if (file.exists() && file.canRead()) { + return Paths.get(file.getAbsolutePath()); + } else { + log.error("Cannot read from {}, maybe the file does not exists? ", file.getAbsolutePath()); + } + + } else { + log.error("Failed to load {}", fileNameFromClasspath); + } + return null; + } + + public static final String loadFile(final String file) throws IOException { + final StringWriter sw = new StringWriter(); + IOUtils.copy(FileHelper.class.getResourceAsStream("/" + file), sw, StandardCharsets.UTF_8); + return sw.toString(); + } public static BytesReference readYamlContent(final String file) { XContentParser parser = null; try { - parser = XContentFactory.xContent(XContentType.YAML).createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, new StringReader(loadFile(file))); + parser = XContentFactory.xContent(XContentType.YAML) + .createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, new StringReader(loadFile(file))); parser.nextToken(); final XContentBuilder builder = XContentFactory.jsonBuilder(); builder.copyCurrentStructure(parser); return BytesReference.bytes(builder); } catch (Exception e) { throw new RuntimeException(e); - } - finally { + } finally { if (parser != null) { try { parser.close(); } catch (IOException e) { - //ignore + // ignore } } } - } + } public static BytesReference readYamlContentFromString(final String yaml) { XContentParser parser = null; try { - parser = XContentFactory.xContent(XContentType.YAML).createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, new StringReader(yaml)); + parser = XContentFactory.xContent(XContentType.YAML) + .createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, new StringReader(yaml)); parser.nextToken(); final XContentBuilder builder = XContentFactory.jsonBuilder(); builder.copyCurrentStructure(parser); return BytesReference.bytes(builder); } catch (Exception e) { throw new RuntimeException(e); - } - finally { + } finally { if (parser != null) { try { parser.close(); } catch (IOException e) { - //ignore + // ignore } } } diff --git a/src/test/java/org/opensearch/security/test/helper/network/SocketUtils.java b/src/test/java/org/opensearch/security/test/helper/network/SocketUtils.java index 1d264025e0..b9f014960c 100644 --- a/src/test/java/org/opensearch/security/test/helper/network/SocketUtils.java +++ b/src/test/java/org/opensearch/security/test/helper/network/SocketUtils.java @@ -79,10 +79,8 @@ public class SocketUtils { */ public static final int PORT_RANGE_MAX = 65535; - private static final Random random = new Random(System.currentTimeMillis()); - /** * Although {@code SocketUtils} consists solely of static utility methods, * this constructor is intentionally {@code public}. @@ -103,7 +101,6 @@ public SocketUtils() { /* no-op */ } - /** * Find an available TCP port randomly selected from the range * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. @@ -218,19 +215,17 @@ public static SortedSet findAvailableUdpPorts(int numRequested, int min return SocketType.UDP.findAvailablePorts(numRequested, minPort, maxPort); } - private enum SocketType { TCP { @Override protected boolean isPortAvailable(int port) { try { - ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket( - port, 1, InetAddress.getByName("localhost")); + ServerSocket serverSocket = ServerSocketFactory.getDefault() + .createServerSocket(port, 1, InetAddress.getByName("localhost")); serverSocket.close(); return true; - } - catch (Exception ex) { + } catch (Exception ex) { return false; } } @@ -243,8 +238,7 @@ protected boolean isPortAvailable(int port) { DatagramSocket socket = new DatagramSocket(port, InetAddress.getByName("localhost")); socket.close(); return true; - } - catch (Exception ex) { + } catch (Exception ex) { return false; } } @@ -277,23 +271,28 @@ private int findRandomPort(int minPort, int maxPort) { * @throws IllegalStateException if no available port could be found */ int findAvailablePort(int minPort, int maxPort) { - //Assert.assertTrue(minPort > 0, "'minPort' must be greater than 0"); - //Assert.isTrue(maxPort >= minPort, "'maxPort' must be greater than or equal to 'minPort'"); - //Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX); + // Assert.assertTrue(minPort > 0, "'minPort' must be greater than 0"); + // Assert.isTrue(maxPort >= minPort, "'maxPort' must be greater than or equal to 'minPort'"); + // Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX); int portRange = maxPort - minPort; int candidatePort; int searchCounter = 0; do { if (searchCounter > portRange) { - throw new IllegalStateException(String.format( + throw new IllegalStateException( + String.format( "Could not find an available %s port in the range [%d, %d] after %d attempts", - name(), minPort, maxPort, searchCounter)); + name(), + minPort, + maxPort, + searchCounter + ) + ); } candidatePort = findRandomPort(minPort, maxPort); searchCounter++; - } - while (!isPortAvailable(candidatePort)); + } while (!isPortAvailable(candidatePort)); return candidatePort; } @@ -308,12 +307,12 @@ int findAvailablePort(int minPort, int maxPort) { * @throws IllegalStateException if the requested number of available ports could not be found */ SortedSet findAvailablePorts(int numRequested, int minPort, int maxPort) { - //Assert.isTrue(minPort > 0, "'minPort' must be greater than 0"); - //Assert.isTrue(maxPort > minPort, "'maxPort' must be greater than 'minPort'"); - //Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX); - //Assert.isTrue(numRequested > 0, "'numRequested' must be greater than 0"); - //Assert.isTrue((maxPort - minPort) >= numRequested, - // "'numRequested' must not be greater than 'maxPort' - 'minPort'"); + // Assert.isTrue(minPort > 0, "'minPort' must be greater than 0"); + // Assert.isTrue(maxPort > minPort, "'maxPort' must be greater than 'minPort'"); + // Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX); + // Assert.isTrue(numRequested > 0, "'numRequested' must be greater than 0"); + // Assert.isTrue((maxPort - minPort) >= numRequested, + // "'numRequested' must not be greater than 'maxPort' - 'minPort'"); SortedSet availablePorts = new TreeSet<>(); int attemptCount = 0; @@ -322,9 +321,9 @@ SortedSet findAvailablePorts(int numRequested, int minPort, int maxPort } if (availablePorts.size() != numRequested) { - throw new IllegalStateException(String.format( - "Could not find %d available %s ports in the range [%d, %d]", - numRequested, name(), minPort, maxPort)); + throw new IllegalStateException( + String.format("Could not find %d available %s ports in the range [%d, %d]", numRequested, name(), minPort, maxPort) + ); } return availablePorts; 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 7ed23b9c73..730a22f18f 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 @@ -94,450 +94,479 @@ public class RestHelper { - protected final Logger log = LogManager.getLogger(RestHelper.class); - - public boolean enableHTTPClientSSL = true; - public boolean enableHTTPClientSSLv3Only = false; - public boolean sendAdminCertificate = false; - public boolean trustHTTPServerCertificate = true; - public boolean sendHTTPClientCredentials = false; - public String keystore = "node-0-keystore.jks"; - public final String prefix; - //public String truststore = "truststore.jks"; - private ClusterInfo clusterInfo; - - public RestHelper(ClusterInfo clusterInfo, String prefix) { - this.clusterInfo = clusterInfo; - this.prefix = prefix; - } - - public RestHelper(ClusterInfo clusterInfo, boolean enableHTTPClientSSL, boolean trustHTTPServerCertificate, String prefix) { - this.clusterInfo = clusterInfo; - this.enableHTTPClientSSL = enableHTTPClientSSL; - this.trustHTTPServerCertificate = trustHTTPServerCertificate; - this.prefix = prefix; - } - public String executeSimpleRequest(final String request) throws Exception { - - CloseableHttpAsyncClient httpClient = null; - - try { - httpClient = getHTTPClient(); - httpClient.start(); - - final CompletableFuture future = new CompletableFuture<>(); - final SimpleHttpRequest simpleRequest = SimpleRequestBuilder.copy(new HttpGet(getRequestUri(request))).build(); - httpClient.execute(simpleRequest, new FutureCallback() { - @Override - public void completed(SimpleHttpResponse result) { - future.complete(result); - } - - @Override - public void failed(Exception ex) { - future.completeExceptionally(ex); - } - - @Override - public void cancelled() { - future.cancel(true); - } - }); - - final SimpleHttpResponse response = future.join(); - if (response.getCode() >= 300) { - throw new Exception("Statuscode " + response.getCode()); - } - - if (enableHTTPClientSSL && !response.getVersion().equals(HttpVersion.HTTP_2)) { - throw new IllegalStateException("HTTP/2 expected for HTTPS communication but " + response.getVersion() + " was used"); - } - - return response.getBodyText(); - } catch (final CompletionException e) { - final Throwable cause = e.getCause(); - // Make it compatible with DefaultHttpResponseParser::createConnectionClosedException() - if (cause instanceof ConnectionClosedException) { - throw new NoHttpResponseException(cause.getMessage(), cause); - } else { - throw (Exception)cause; - } - } finally { - if (httpClient != null) { - httpClient.close(); - } - } - } - - public HttpResponse[] executeMultipleAsyncPutRequest(final int numOfRequests, final String request, String body) throws Exception { - final ExecutorService executorService = Executors.newFixedThreadPool(numOfRequests); - Future[] futures = new Future[numOfRequests]; - for (int i = 0; i < numOfRequests; i++) { - futures[i] = executorService.submit(() -> executePutRequest(request, body, new Header[0])); - } - executorService.shutdown(); - return Arrays.stream(futures) - .map(HttpResponse::from) - .toArray(s -> new HttpResponse[s]); - } - - public HttpResponse executeGetRequest(final String request, Header... header) { - return executeRequest(new HttpGet(getRequestUri(request)), header); - } - - public HttpResponse executeGetRequest(final String request, String body, Header... header) { - HttpGet getRequest = new HttpGet(getRequestUri(request)); - getRequest.setEntity(createStringEntity(body)); - getRequest.addHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - return executeRequest(getRequest, header); - } - - public HttpResponse executeHeadRequest(final String request, Header... header) { - return executeRequest(new HttpHead(getRequestUri(request)), header); - } - - public HttpResponse executeOptionsRequest(final String request) { - return executeRequest(new HttpOptions(getRequestUri(request))); - } - - public HttpResponse executePutRequest(final String request, String body, Header... header) { - HttpPut uriRequest = new HttpPut(getRequestUri(request)); - if (body != null && !body.isEmpty()) { - uriRequest.setEntity(createStringEntity(body)); - } - return executeRequest(uriRequest, header); - } - - public HttpResponse executeDeleteRequest(final String request, Header... header) { - return executeRequest(new HttpDelete(getRequestUri(request)), header); - } - - public HttpResponse executeDeleteRequest(final String request, String body, Header... header) { - HttpDelete delRequest = new HttpDelete(getRequestUri(request)); - delRequest.setEntity(createStringEntity(body)); - delRequest.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - return executeRequest(delRequest, header); - } - - - public HttpResponse executePostRequest(final String request, String body, Header... header) { - HttpPost uriRequest = new HttpPost(getRequestUri(request)); - if (body != null && !body.isEmpty()) { - uriRequest.setEntity(createStringEntity(body)); - } - - return executeRequest(uriRequest, header); - } - - public HttpResponse executePatchRequest(final String request, String body, Header... header) { - HttpPatch uriRequest = new HttpPatch(getRequestUri(request)); - if (body != null && !body.isEmpty()) { - uriRequest.setEntity(createStringEntity(body)); - } - return executeRequest(uriRequest, header); - } - - public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... header) { - - CloseableHttpAsyncClient httpClient = null; - try { - - httpClient = getHTTPClient(); - httpClient.start(); - - if (header != null && header.length > 0) { - for (int i = 0; i < header.length; i++) { - Header h = header[i]; - uriRequest.addHeader(h); - } - } - - if (!uriRequest.containsHeader("Content-Type")) { - uriRequest.addHeader("Content-Type","application/json"); - } - - final CompletableFuture future = new CompletableFuture<>(); - final SimpleHttpRequest simpleRequest = SimpleRequestBuilder.copy(uriRequest).build(); - if (uriRequest.getEntity() != null) { - simpleRequest.setBody(EntityUtils.toByteArray(uriRequest.getEntity()), - ContentType.parse(uriRequest.getEntity().getContentType())); - } - httpClient.execute(simpleRequest, new FutureCallback() { - @Override - public void completed(SimpleHttpResponse result) { - future.complete(result); - } - - @Override - public void failed(Exception ex) { - future.completeExceptionally(ex); - } - - @Override - public void cancelled() { - future.cancel(true); - } - }); - - final HttpResponse res = new HttpResponse(future.join()); - if (enableHTTPClientSSL && !res.getProtocolVersion().equals(HttpVersion.HTTP_2)) { - throw new IllegalStateException("HTTP/2 expected for HTTPS communication but " + res.getProtocolVersion() + " was used"); - } - - log.debug(res.getBody()); - return res; - } catch (final CompletionException e) { - final Throwable cause = e.getCause(); - // Make it compatible with DefaultHttpResponseParser::createConnectionClosedException() - if (cause instanceof ConnectionClosedException) { - throw new RuntimeException(new NoHttpResponseException(cause.getMessage(), cause)); - } else if (cause instanceof RuntimeException) { - throw (RuntimeException)cause; - } else { - throw new RuntimeException(cause); - } - } catch (final Exception e) { - throw new RuntimeException(e); - } finally { - - if (httpClient != null) { - try { - httpClient.close(); - } catch (final Exception e) { - throw new RuntimeException(e); - } - } - } - } - - private HttpEntity createStringEntity(String body) { - return new StringEntity(body); - } - - protected final String getHttpServerUri() { - final String address = "http" + (enableHTTPClientSSL ? "s" : "") + "://" + clusterInfo.httpHost + ":" + clusterInfo.httpPort; - log.debug("Connect to {}", address); - return address; - } - - protected final String getRequestUri(String request) { - return getHttpServerUri() + "/" + StringUtils.strip(request, "/"); - } - - protected final CloseableHttpAsyncClient getHTTPClient() throws Exception { - - final HttpAsyncClientBuilder hcb = HttpAsyncClients.custom(); - - if (sendHTTPClientCredentials) { - UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("sarek", "sarek".toCharArray()); - BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials(new AuthScope(null, -1), credentials); - hcb.setDefaultCredentialsProvider(credentialsProvider); - } - - if (enableHTTPClientSSL) { - - log.debug("Configure HTTP client with SSL"); - - if(prefix != null && !keystore.contains("/")) { - keystore = prefix+"/"+keystore; - } - - final String keyStorePath = FileHelper.getAbsoluteFilePathFromClassPath(keystore).toFile().getParent(); - - final KeyStore myTrustStore = KeyStore.getInstance("JKS"); - myTrustStore.load(new FileInputStream(keyStorePath+"/truststore.jks"), - "changeit".toCharArray()); - - final KeyStore keyStore = KeyStore.getInstance("JKS"); - keyStore.load(new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath(keystore).toFile()), "changeit".toCharArray()); - - final SSLContextBuilder sslContextbBuilder = SSLContexts.custom(); - - if (trustHTTPServerCertificate) { - sslContextbBuilder.loadTrustMaterial(myTrustStore, null); - } - - if (sendAdminCertificate) { - sslContextbBuilder.loadKeyMaterial(keyStore, "changeit".toCharArray()); - } - - final SSLContext sslContext = sslContextbBuilder.build(); - - String[] protocols = null; - - if (enableHTTPClientSSLv3Only) { - protocols = new String[] { "SSLv3" }; - } else { - protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" }; - } - - final TlsStrategy tlsStrategy = ClientTlsStrategyBuilder - .create() - .setSslContext(sslContext) - .setTlsVersions(protocols) - .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) - // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 - .setTlsDetailsFactory(new Factory() { - @Override - public TlsDetails create(final SSLEngine sslEngine) { - return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); - } - }) - .build(); - - final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() - .setTlsStrategy(tlsStrategy) - .build(); - - hcb.setConnectionManager(cm); - } - - final RequestConfig.Builder requestConfigBuilder = RequestConfig.custom() - .setResponseTimeout(Timeout.ofSeconds(60)); - - return hcb.setDefaultRequestConfig(requestConfigBuilder.build()).disableAutomaticRetries().build(); - } - - - public static class HttpResponse { - private final SimpleHttpResponse inner; - private final String body; - private final Header[] header; - private final int statusCode; - private final String statusReason; - private final ProtocolVersion protocolVersion; - - public HttpResponse(SimpleHttpResponse inner) throws IllegalStateException, IOException { - super(); - this.inner = inner; - if(inner.getBody() == null) { //head request does not have a entity - this.body = ""; - } else { - this.body = inner.getBodyText(); - } - this.header = inner.getHeaders(); - this.statusCode = inner.getCode(); - this.statusReason = inner.getReasonPhrase(); - this.protocolVersion = inner.getVersion(); - } - - public String getContentType() { - Header h = getInner().getFirstHeader("content-type"); - if(h!= null) { - return h.getValue(); - } - return null; - } - - public boolean isJsonContentType() { - String ct = getContentType(); - if(ct == null) { - return false; - } - return ct.contains("application/json"); - } - - public SimpleHttpResponse getInner() { - return inner; - } - - public String getBody() { - return body; - } - - public Header[] getHeader() { - return header; - } - - public int getStatusCode() { - return statusCode; - } - - public String getStatusReason() { - return statusReason; - } - - public List
getHeaders() { - return header==null?Collections.emptyList():Arrays.asList(header); - } - - public ProtocolVersion getProtocolVersion() { - return protocolVersion; - } - - @Override - public String toString() { - return "HttpResponse [inner=" + inner + ", body=" + body + ", header=" + Arrays.toString(header) + ", statusCode=" + statusCode - + ", statusReason=" + statusReason + "]"; - } - - /** - * Given a json path with dots delimiated returns the object at the leaf - */ - public String findValueInJson(final String jsonDotPath) { - // Make sure its json / then parse it - if (!isJsonContentType()) { - throw new RuntimeException("Response was expected to be JSON, body was: \n" + body); - } - JsonNode currentNode = null; - try { - currentNode = DefaultObjectMapper.readTree(body); - } catch (final Exception e) { - throw new RuntimeException(e); - } - - // Break the path into parts, and scan into the json object - try (final Scanner jsonPathScanner = new Scanner(jsonDotPath).useDelimiter("\\.")) { - if (!jsonPathScanner.hasNext()) { - throw new RuntimeException("Invalid json dot path '" + jsonDotPath + "', rewrite with '.' characters between path elements."); - } - do { - String pathEntry = jsonPathScanner.next(); - // if pathEntry is an array lookup - int arrayEntryIdx = -1; - - // Looks for an array-lookup pattern in the path - // e.g. root_cause[1] -> will match - // e.g. root_cause[2aasd] -> won't match - final Pattern r = Pattern.compile("(.+?)\\[(\\d+)\\]"); - final Matcher m = r.matcher(pathEntry); - if(m.find()) { - pathEntry = m.group(1); - arrayEntryIdx = Integer.parseInt(m.group(2)); - } - - if (!currentNode.has(pathEntry)) { - throw new RuntimeException("Unable to resolve '" + jsonDotPath + "', on path entry '" + pathEntry + "' from available fields " + currentNode.toPrettyString()); - } - currentNode = currentNode.get(pathEntry); - - // if it's an Array lookup we get the requested index item - if (arrayEntryIdx > -1) { - if(!currentNode.isArray()) { - throw new RuntimeException("Unable to resolve '" + jsonDotPath + "', the '" + pathEntry + "' field is not an array " + currentNode.toPrettyString()); - } else if (!currentNode.has(arrayEntryIdx)) { - throw new RuntimeException("Unable to resolve '" + jsonDotPath + "', index '" + arrayEntryIdx + "' is out of bounds for array '" + pathEntry + "' \n" + currentNode.toPrettyString()); - } - currentNode = currentNode.get(arrayEntryIdx); - } - } while (jsonPathScanner.hasNext()); - - if (!currentNode.isValueNode()) { - throw new RuntimeException("Unexpected value note, index directly to the object to reference, object\n" + currentNode.toPrettyString()); - } - return currentNode.asText(); - } - } - - private static HttpResponse from(Future future) { - try { - return future.get(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - + protected final Logger log = LogManager.getLogger(RestHelper.class); + + public boolean enableHTTPClientSSL = true; + public boolean enableHTTPClientSSLv3Only = false; + public boolean sendAdminCertificate = false; + public boolean trustHTTPServerCertificate = true; + public boolean sendHTTPClientCredentials = false; + public String keystore = "node-0-keystore.jks"; + public final String prefix; + // public String truststore = "truststore.jks"; + private ClusterInfo clusterInfo; + + public RestHelper(ClusterInfo clusterInfo, String prefix) { + this.clusterInfo = clusterInfo; + this.prefix = prefix; + } + + public RestHelper(ClusterInfo clusterInfo, boolean enableHTTPClientSSL, boolean trustHTTPServerCertificate, String prefix) { + this.clusterInfo = clusterInfo; + this.enableHTTPClientSSL = enableHTTPClientSSL; + this.trustHTTPServerCertificate = trustHTTPServerCertificate; + this.prefix = prefix; + } + + public String executeSimpleRequest(final String request) throws Exception { + + CloseableHttpAsyncClient httpClient = null; + + try { + httpClient = getHTTPClient(); + httpClient.start(); + + final CompletableFuture future = new CompletableFuture<>(); + final SimpleHttpRequest simpleRequest = SimpleRequestBuilder.copy(new HttpGet(getRequestUri(request))).build(); + httpClient.execute(simpleRequest, new FutureCallback() { + @Override + public void completed(SimpleHttpResponse result) { + future.complete(result); + } + + @Override + public void failed(Exception ex) { + future.completeExceptionally(ex); + } + + @Override + public void cancelled() { + future.cancel(true); + } + }); + + final SimpleHttpResponse response = future.join(); + if (response.getCode() >= 300) { + throw new Exception("Statuscode " + response.getCode()); + } + + if (enableHTTPClientSSL && !response.getVersion().equals(HttpVersion.HTTP_2)) { + throw new IllegalStateException("HTTP/2 expected for HTTPS communication but " + response.getVersion() + " was used"); + } + + return response.getBodyText(); + } catch (final CompletionException e) { + final Throwable cause = e.getCause(); + // Make it compatible with DefaultHttpResponseParser::createConnectionClosedException() + if (cause instanceof ConnectionClosedException) { + throw new NoHttpResponseException(cause.getMessage(), cause); + } else { + throw (Exception) cause; + } + } finally { + if (httpClient != null) { + httpClient.close(); + } + } + } + + public HttpResponse[] executeMultipleAsyncPutRequest(final int numOfRequests, final String request, String body) throws Exception { + final ExecutorService executorService = Executors.newFixedThreadPool(numOfRequests); + Future[] futures = new Future[numOfRequests]; + for (int i = 0; i < numOfRequests; i++) { + futures[i] = executorService.submit(() -> executePutRequest(request, body, new Header[0])); + } + executorService.shutdown(); + return Arrays.stream(futures).map(HttpResponse::from).toArray(s -> new HttpResponse[s]); + } + + public HttpResponse executeGetRequest(final String request, Header... header) { + return executeRequest(new HttpGet(getRequestUri(request)), header); + } + + public HttpResponse executeGetRequest(final String request, String body, Header... header) { + HttpGet getRequest = new HttpGet(getRequestUri(request)); + getRequest.setEntity(createStringEntity(body)); + getRequest.addHeader(HttpHeaders.CONTENT_TYPE, "application/json"); + return executeRequest(getRequest, header); + } + + public HttpResponse executeHeadRequest(final String request, Header... header) { + return executeRequest(new HttpHead(getRequestUri(request)), header); + } + + public HttpResponse executeOptionsRequest(final String request) { + return executeRequest(new HttpOptions(getRequestUri(request))); + } + + public HttpResponse executePutRequest(final String request, String body, Header... header) { + HttpPut uriRequest = new HttpPut(getRequestUri(request)); + if (body != null && !body.isEmpty()) { + uriRequest.setEntity(createStringEntity(body)); + } + return executeRequest(uriRequest, header); + } + + public HttpResponse executeDeleteRequest(final String request, Header... header) { + return executeRequest(new HttpDelete(getRequestUri(request)), header); + } + + public HttpResponse executeDeleteRequest(final String request, String body, Header... header) { + HttpDelete delRequest = new HttpDelete(getRequestUri(request)); + delRequest.setEntity(createStringEntity(body)); + delRequest.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); + return executeRequest(delRequest, header); + } + + public HttpResponse executePostRequest(final String request, String body, Header... header) { + HttpPost uriRequest = new HttpPost(getRequestUri(request)); + if (body != null && !body.isEmpty()) { + uriRequest.setEntity(createStringEntity(body)); + } + + return executeRequest(uriRequest, header); + } + + public HttpResponse executePatchRequest(final String request, String body, Header... header) { + HttpPatch uriRequest = new HttpPatch(getRequestUri(request)); + if (body != null && !body.isEmpty()) { + uriRequest.setEntity(createStringEntity(body)); + } + return executeRequest(uriRequest, header); + } + + public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... header) { + + CloseableHttpAsyncClient httpClient = null; + try { + + httpClient = getHTTPClient(); + httpClient.start(); + + if (header != null && header.length > 0) { + for (int i = 0; i < header.length; i++) { + Header h = header[i]; + uriRequest.addHeader(h); + } + } + + if (!uriRequest.containsHeader("Content-Type")) { + uriRequest.addHeader("Content-Type", "application/json"); + } + + final CompletableFuture future = new CompletableFuture<>(); + final SimpleHttpRequest simpleRequest = SimpleRequestBuilder.copy(uriRequest).build(); + if (uriRequest.getEntity() != null) { + simpleRequest.setBody( + EntityUtils.toByteArray(uriRequest.getEntity()), + ContentType.parse(uriRequest.getEntity().getContentType()) + ); + } + httpClient.execute(simpleRequest, new FutureCallback() { + @Override + public void completed(SimpleHttpResponse result) { + future.complete(result); + } + + @Override + public void failed(Exception ex) { + future.completeExceptionally(ex); + } + + @Override + public void cancelled() { + future.cancel(true); + } + }); + + final HttpResponse res = new HttpResponse(future.join()); + if (enableHTTPClientSSL && !res.getProtocolVersion().equals(HttpVersion.HTTP_2)) { + throw new IllegalStateException("HTTP/2 expected for HTTPS communication but " + res.getProtocolVersion() + " was used"); + } + + log.debug(res.getBody()); + return res; + } catch (final CompletionException e) { + final Throwable cause = e.getCause(); + // Make it compatible with DefaultHttpResponseParser::createConnectionClosedException() + if (cause instanceof ConnectionClosedException) { + throw new RuntimeException(new NoHttpResponseException(cause.getMessage(), cause)); + } else if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else { + throw new RuntimeException(cause); + } + } catch (final Exception e) { + throw new RuntimeException(e); + } finally { + + if (httpClient != null) { + try { + httpClient.close(); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + } + } + + private HttpEntity createStringEntity(String body) { + return new StringEntity(body); + } + + protected final String getHttpServerUri() { + final String address = "http" + (enableHTTPClientSSL ? "s" : "") + "://" + clusterInfo.httpHost + ":" + clusterInfo.httpPort; + log.debug("Connect to {}", address); + return address; + } + + protected final String getRequestUri(String request) { + return getHttpServerUri() + "/" + StringUtils.strip(request, "/"); + } + + protected final CloseableHttpAsyncClient getHTTPClient() throws Exception { + + final HttpAsyncClientBuilder hcb = HttpAsyncClients.custom(); + + if (sendHTTPClientCredentials) { + UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("sarek", "sarek".toCharArray()); + BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(new AuthScope(null, -1), credentials); + hcb.setDefaultCredentialsProvider(credentialsProvider); + } + + if (enableHTTPClientSSL) { + + log.debug("Configure HTTP client with SSL"); + + if (prefix != null && !keystore.contains("/")) { + keystore = prefix + "/" + keystore; + } + + final String keyStorePath = FileHelper.getAbsoluteFilePathFromClassPath(keystore).toFile().getParent(); + + final KeyStore myTrustStore = KeyStore.getInstance("JKS"); + myTrustStore.load(new FileInputStream(keyStorePath + "/truststore.jks"), "changeit".toCharArray()); + + final KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath(keystore).toFile()), "changeit".toCharArray()); + + final SSLContextBuilder sslContextbBuilder = SSLContexts.custom(); + + if (trustHTTPServerCertificate) { + sslContextbBuilder.loadTrustMaterial(myTrustStore, null); + } + + if (sendAdminCertificate) { + sslContextbBuilder.loadKeyMaterial(keyStore, "changeit".toCharArray()); + } + + final SSLContext sslContext = sslContextbBuilder.build(); + + String[] protocols = null; + + if (enableHTTPClientSSLv3Only) { + protocols = new String[] { "SSLv3" }; + } else { + protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" }; + } + + final TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() + .setSslContext(sslContext) + .setTlsVersions(protocols) + .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) + // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 + .setTlsDetailsFactory(new Factory() { + @Override + public TlsDetails create(final SSLEngine sslEngine) { + return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); + } + }) + .build(); + + final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create().setTlsStrategy(tlsStrategy).build(); + + hcb.setConnectionManager(cm); + } + + final RequestConfig.Builder requestConfigBuilder = RequestConfig.custom().setResponseTimeout(Timeout.ofSeconds(60)); + + return hcb.setDefaultRequestConfig(requestConfigBuilder.build()).disableAutomaticRetries().build(); + } + + public static class HttpResponse { + private final SimpleHttpResponse inner; + private final String body; + private final Header[] header; + private final int statusCode; + private final String statusReason; + private final ProtocolVersion protocolVersion; + + public HttpResponse(SimpleHttpResponse inner) throws IllegalStateException, IOException { + super(); + this.inner = inner; + if (inner.getBody() == null) { // head request does not have a entity + this.body = ""; + } else { + this.body = inner.getBodyText(); + } + this.header = inner.getHeaders(); + this.statusCode = inner.getCode(); + this.statusReason = inner.getReasonPhrase(); + this.protocolVersion = inner.getVersion(); + } + + public String getContentType() { + Header h = getInner().getFirstHeader("content-type"); + if (h != null) { + return h.getValue(); + } + return null; + } + + public boolean isJsonContentType() { + String ct = getContentType(); + if (ct == null) { + return false; + } + return ct.contains("application/json"); + } + + public SimpleHttpResponse getInner() { + return inner; + } + + public String getBody() { + return body; + } + + public Header[] getHeader() { + return header; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatusReason() { + return statusReason; + } + + public List
getHeaders() { + return header == null ? Collections.emptyList() : Arrays.asList(header); + } + + public ProtocolVersion getProtocolVersion() { + return protocolVersion; + } + + @Override + public String toString() { + return "HttpResponse [inner=" + + inner + + ", body=" + + body + + ", header=" + + Arrays.toString(header) + + ", statusCode=" + + statusCode + + ", statusReason=" + + statusReason + + "]"; + } + + /** + * Given a json path with dots delimiated returns the object at the leaf + */ + public String findValueInJson(final String jsonDotPath) { + // Make sure its json / then parse it + if (!isJsonContentType()) { + throw new RuntimeException("Response was expected to be JSON, body was: \n" + body); + } + JsonNode currentNode = null; + try { + currentNode = DefaultObjectMapper.readTree(body); + } catch (final Exception e) { + throw new RuntimeException(e); + } + + // Break the path into parts, and scan into the json object + try (final Scanner jsonPathScanner = new Scanner(jsonDotPath).useDelimiter("\\.")) { + if (!jsonPathScanner.hasNext()) { + throw new RuntimeException( + "Invalid json dot path '" + jsonDotPath + "', rewrite with '.' characters between path elements." + ); + } + do { + String pathEntry = jsonPathScanner.next(); + // if pathEntry is an array lookup + int arrayEntryIdx = -1; + + // Looks for an array-lookup pattern in the path + // e.g. root_cause[1] -> will match + // e.g. root_cause[2aasd] -> won't match + final Pattern r = Pattern.compile("(.+?)\\[(\\d+)\\]"); + final Matcher m = r.matcher(pathEntry); + if (m.find()) { + pathEntry = m.group(1); + arrayEntryIdx = Integer.parseInt(m.group(2)); + } + + if (!currentNode.has(pathEntry)) { + throw new RuntimeException( + "Unable to resolve '" + + jsonDotPath + + "', on path entry '" + + pathEntry + + "' from available fields " + + currentNode.toPrettyString() + ); + } + currentNode = currentNode.get(pathEntry); + + // if it's an Array lookup we get the requested index item + if (arrayEntryIdx > -1) { + if (!currentNode.isArray()) { + throw new RuntimeException( + "Unable to resolve '" + + jsonDotPath + + "', the '" + + pathEntry + + "' field is not an array " + + currentNode.toPrettyString() + ); + } else if (!currentNode.has(arrayEntryIdx)) { + throw new RuntimeException( + "Unable to resolve '" + + jsonDotPath + + "', index '" + + arrayEntryIdx + + "' is out of bounds for array '" + + pathEntry + + "' \n" + + currentNode.toPrettyString() + ); + } + currentNode = currentNode.get(arrayEntryIdx); + } + } while (jsonPathScanner.hasNext()); + + if (!currentNode.isValueNode()) { + throw new RuntimeException( + "Unexpected value note, index directly to the object to reference, object\n" + currentNode.toPrettyString() + ); + } + return currentNode.asText(); + } + } + + private static HttpResponse from(Future future) { + try { + return future.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } } diff --git a/src/test/java/org/opensearch/security/test/helper/rules/SecurityTestWatcher.java b/src/test/java/org/opensearch/security/test/helper/rules/SecurityTestWatcher.java index c194d7e6de..b888bfbc35 100644 --- a/src/test/java/org/opensearch/security/test/helper/rules/SecurityTestWatcher.java +++ b/src/test/java/org/opensearch/security/test/helper/rules/SecurityTestWatcher.java @@ -29,27 +29,27 @@ import org.junit.rules.TestWatcher; import org.junit.runner.Description; -public class SecurityTestWatcher extends TestWatcher{ - - @Override - protected void starting(final Description description) { - final String methodName = description.getMethodName(); - String className = description.getClassName(); - className = className.substring(className.lastIndexOf('.') + 1); - System.out.println("---------------- Starting JUnit-test: " + className + " " + methodName + " ----------------"); - } - - @Override - protected void failed(final Throwable e, final Description description) { - final String methodName = description.getMethodName(); - String className = description.getClassName(); - className = className.substring(className.lastIndexOf('.') + 1); - System.out.println(">>>> " + className + " " + methodName + " FAILED due to " + e); - } - - @Override - protected void finished(final Description description) { - // System.out.println("-----------------------------------------------------------------------------------------"); - } +public class SecurityTestWatcher extends TestWatcher { + + @Override + protected void starting(final Description description) { + final String methodName = description.getMethodName(); + String className = description.getClassName(); + className = className.substring(className.lastIndexOf('.') + 1); + System.out.println("---------------- Starting JUnit-test: " + className + " " + methodName + " ----------------"); + } + + @Override + protected void failed(final Throwable e, final Description description) { + final String methodName = description.getMethodName(); + String className = description.getClassName(); + className = className.substring(className.lastIndexOf('.') + 1); + System.out.println(">>>> " + className + " " + methodName + " FAILED due to " + e); + } + + @Override + protected void finished(final Description description) { + // System.out.println("-----------------------------------------------------------------------------------------"); + } } diff --git a/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java b/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java index f0059d50fb..227dd6699d 100644 --- a/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java +++ b/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java @@ -69,19 +69,46 @@ public UserInjectorPlugin(final Settings settings, final Path configPath) { } @Override - public Map> getHttpTransports(Settings settings, ThreadPool threadPool, BigArrays bigArrays, - PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, - NetworkService networkService, Dispatcher dispatcher, ClusterSettings clusterSettings) { + public Map> getHttpTransports( + Settings settings, + ThreadPool threadPool, + BigArrays bigArrays, + PageCacheRecycler pageCacheRecycler, + CircuitBreakerService circuitBreakerService, + NamedXContentRegistry xContentRegistry, + NetworkService networkService, + Dispatcher dispatcher, + ClusterSettings clusterSettings + ) { final UserInjectingDispatcher validatingDispatcher = new UserInjectingDispatcher(dispatcher); - return ImmutableMap.of("org.opensearch.security.http.UserInjectingServerTransport", - () -> new UserInjectingServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, validatingDispatcher, clusterSettings, sharedGroupFactory)); + return ImmutableMap.of( + "org.opensearch.security.http.UserInjectingServerTransport", + () -> new UserInjectingServerTransport( + settings, + networkService, + bigArrays, + threadPool, + xContentRegistry, + validatingDispatcher, + clusterSettings, + sharedGroupFactory + ) + ); } class UserInjectingServerTransport extends Netty4HttpServerTransport { - public UserInjectingServerTransport(final Settings settings, final NetworkService networkService, final BigArrays bigArrays, - final ThreadPool threadPool, final NamedXContentRegistry namedXContentRegistry, final Dispatcher dispatcher, ClusterSettings clusterSettings, SharedGroupFactory sharedGroupFactory) { + public UserInjectingServerTransport( + final Settings settings, + final NetworkService networkService, + final BigArrays bigArrays, + final ThreadPool threadPool, + final NamedXContentRegistry namedXContentRegistry, + final Dispatcher dispatcher, + ClusterSettings clusterSettings, + SharedGroupFactory sharedGroupFactory + ) { super(settings, networkService, bigArrays, threadPool, namedXContentRegistry, dispatcher, clusterSettings, sharedGroupFactory); } } @@ -97,14 +124,20 @@ public UserInjectingDispatcher(final Dispatcher originalDispatcher) { @Override public void dispatchRequest(RestRequest request, RestChannel channel, ThreadContext threadContext) { - threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, request.header(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER)); + threadContext.putTransient( + ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, + request.header(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER) + ); originalDispatcher.dispatchRequest(request, channel, threadContext); } @Override public void dispatchBadRequest(RestChannel channel, ThreadContext threadContext, Throwable cause) { - threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, channel.request().header(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER)); + threadContext.putTransient( + ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, + channel.request().header(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER) + ); originalDispatcher.dispatchBadRequest(channel, threadContext, cause); } } diff --git a/src/test/java/org/opensearch/security/util/FakeRestRequest.java b/src/test/java/org/opensearch/security/util/FakeRestRequest.java index 989b775709..05e69ef614 100644 --- a/src/test/java/org/opensearch/security/util/FakeRestRequest.java +++ b/src/test/java/org/opensearch/security/util/FakeRestRequest.java @@ -21,11 +21,10 @@ public class FakeRestRequest extends RestRequest { - //private final Map headers; + // private final Map headers; private final BytesReference content; private final Method method; - public FakeRestRequest() { this(new HashMap<>(), new HashMap<>(), null, Method.GET, "/"); } @@ -35,10 +34,10 @@ public FakeRestRequest(Map headers, Map params) } private FakeRestRequest(Map headers, Map params, BytesReference content, Method method, String path) { - //NamedXContentRegistry xContentRegistry, Map params, String path, - //Map> headers, HttpRequest httpRequest, HttpChannel httpChannel + // NamedXContentRegistry xContentRegistry, Map params, String path, + // Map> headers, HttpRequest httpRequest, HttpChannel httpChannel super(null, params, path, convert(headers), null, null); - //this.headers = headers; + // this.headers = headers; this.content = content; this.method = method; } @@ -108,7 +107,7 @@ public FakeRestRequest build() { private static Map> convert(Map headers) { Map> ret = new HashMap>(); - for (String h:headers.keySet()) { + for (String h : headers.keySet()) { ret.put(h, Collections.singletonList(headers.get(h))); } return ret; diff --git a/src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorV4Test.java b/src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorV4Test.java index cc75ec6eb0..976f085ce4 100644 --- a/src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorV4Test.java +++ b/src/test/java/org/opensearch/security/util/SettingsBasedSSLConfiguratorV4Test.java @@ -84,25 +84,32 @@ public class SettingsBasedSSLConfiguratorV4Test { @Test public void testPemTrust() throws Exception { - try (TestServer testServer = new TestServer("sslConfigurator/pem/truststore.jks", - "sslConfigurator/pem/node1-keystore.jks", "secret", false)) { + try ( + TestServer testServer = new TestServer( + "sslConfigurator/pem/truststore.jks", + "sslConfigurator/pem/node1-keystore.jks", + "secret", + false + ) + ) { Path rootCaPemPath = FileHelper.getAbsoluteFilePathFromClassPath("sslConfigurator/pem/root-ca.pem"); Assert.assertTrue(rootCaPemPath.toFile().exists()); Settings settings = Settings.builder() - .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) - .put("prefix.enable_ssl", "true") - .put("path.home", rootCaPemPath.getParent().toString()) - .build(); + .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) + .put("prefix.enable_ssl", "true") + .put("path.home", rootCaPemPath.getParent().toString()) + .build(); Path configPath = rootCaPemPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); - try (CloseableHttpClient httpClient = HttpClients.custom() - .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build()) { + try ( + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build() + ) { try (CloseableHttpResponse response = httpClient.execute(new HttpGet(testServer.getUri()))) { // Success @@ -115,23 +122,30 @@ public void testPemTrust() throws Exception { @Test public void testPemWrongTrust() throws Exception { - try (TestServer testServer = new TestServer("sslConfigurator/pem/truststore.jks", - "sslConfigurator/pem/node1-keystore.jks", "secret", false)) { + try ( + TestServer testServer = new TestServer( + "sslConfigurator/pem/truststore.jks", + "sslConfigurator/pem/node1-keystore.jks", + "secret", + false + ) + ) { Path rootCaPemPath = FileHelper.getAbsoluteFilePathFromClassPath("sslConfigurator/pem/other-root-ca.pem"); Settings settings = Settings.builder() - .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) - .put("prefix.enable_ssl", "true") - .put("path.home", rootCaPemPath.getParent().toString()) - .build(); + .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) + .put("prefix.enable_ssl", "true") + .put("path.home", rootCaPemPath.getParent().toString()) + .build(); Path configPath = rootCaPemPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); - try (CloseableHttpClient httpClient = HttpClients.custom() - .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build()) { + try ( + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build() + ) { thrown.expect(SSLHandshakeException.class); @@ -146,27 +160,34 @@ public void testPemWrongTrust() throws Exception { @Test public void testPemClientAuth() throws Exception { - try (TestServer testServer = new TestServer("sslConfigurator/pem/truststore.jks", - "sslConfigurator/pem/node1-keystore.jks", "secret", true)) { + try ( + TestServer testServer = new TestServer( + "sslConfigurator/pem/truststore.jks", + "sslConfigurator/pem/node1-keystore.jks", + "secret", + true + ) + ) { Path rootCaPemPath = FileHelper.getAbsoluteFilePathFromClassPath("sslConfigurator/pem/root-ca.pem"); Settings settings = Settings.builder() - .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) - .put("prefix.enable_ssl", "true") - .put("path.home", rootCaPemPath.getParent().toString()) - .put("prefix.enable_ssl_client_auth", "true") - .put("prefix.pemcert_filepath", "kirk.pem") - .put("prefix.pemkey_filepath", "kirk.key") - .put("prefix.pemkey_password", "secret") - .build(); + .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) + .put("prefix.enable_ssl", "true") + .put("path.home", rootCaPemPath.getParent().toString()) + .put("prefix.enable_ssl_client_auth", "true") + .put("prefix.pemcert_filepath", "kirk.pem") + .put("prefix.pemkey_filepath", "kirk.key") + .put("prefix.pemkey_password", "secret") + .build(); Path configPath = rootCaPemPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); - try (CloseableHttpClient httpClient = HttpClients.custom() - .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build()) { + try ( + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build() + ) { try (CloseableHttpResponse response = httpClient.execute(new HttpGet(testServer.getUri()))) { // Success @@ -179,33 +200,43 @@ public void testPemClientAuth() throws Exception { @Test public void testPemClientAuthFailure() throws Exception { - try (TestServer testServer = new TestServer("sslConfigurator/pem/truststore.jks", - "sslConfigurator/pem/node1-keystore.jks", "secret", true)) { + try ( + TestServer testServer = new TestServer( + "sslConfigurator/pem/truststore.jks", + "sslConfigurator/pem/node1-keystore.jks", + "secret", + true + ) + ) { Path rootCaPemPath = FileHelper.getAbsoluteFilePathFromClassPath("sslConfigurator/pem/root-ca.pem"); Settings settings = Settings.builder() - .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) - .put("prefix.enable_ssl", "true") - .put("path.home", rootCaPemPath.getParent().toString()) - .put("prefix.enable_ssl_client_auth", "true") - .put("prefix.pemcert_filepath", "wrong-kirk.pem") - .put("prefix.pemkey_filepath", "wrong-kirk.key") - .put("prefix.pemkey_password", "G0CVtComen4a") - .build(); + .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) + .put("prefix.enable_ssl", "true") + .put("path.home", rootCaPemPath.getParent().toString()) + .put("prefix.enable_ssl_client_auth", "true") + .put("prefix.pemcert_filepath", "wrong-kirk.pem") + .put("prefix.pemkey_filepath", "wrong-kirk.key") + .put("prefix.pemkey_password", "G0CVtComen4a") + .build(); Path configPath = rootCaPemPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); - try (CloseableHttpClient httpClient = HttpClients.custom() - .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build()) { + try ( + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build() + ) { // Due to some race condition in Java's internal network stack, this can be one // of the following exceptions - thrown.expect(either(instanceOf(SocketException.class)).or(instanceOf(SSLHandshakeException.class)) - .or(instanceOf(SSLException.class)) // Java 11: javax.net.ssl.SSLException: readHandshakeRecord + thrown.expect( + either(instanceOf(SocketException.class)).or(instanceOf(SSLHandshakeException.class)).or(instanceOf(SSLException.class)) // Java + // 11: + // javax.net.ssl.SSLException: + // readHandshakeRecord ); try (CloseableHttpResponse response = httpClient.execute(new HttpGet(testServer.getUri()))) { @@ -218,24 +249,31 @@ public void testPemClientAuthFailure() throws Exception { @Test public void testPemHostnameVerificationFailure() throws Exception { - try (TestServer testServer = new TestServer("sslConfigurator/pem/truststore.jks", - "sslConfigurator/pem/node-wrong-hostname-keystore.jks", "secret", false)) { + try ( + TestServer testServer = new TestServer( + "sslConfigurator/pem/truststore.jks", + "sslConfigurator/pem/node-wrong-hostname-keystore.jks", + "secret", + false + ) + ) { Path rootCaPemPath = FileHelper.getAbsoluteFilePathFromClassPath("sslConfigurator/pem/root-ca.pem"); Settings settings = Settings.builder() - .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) - .put("prefix.enable_ssl", "true") - .put("prefix.verify_hostnames", "true") - .put("path.home", rootCaPemPath.getParent().toString()) - .build(); + .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) + .put("prefix.enable_ssl", "true") + .put("prefix.verify_hostnames", "true") + .put("path.home", rootCaPemPath.getParent().toString()) + .build(); Path configPath = rootCaPemPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); - try (CloseableHttpClient httpClient = HttpClients.custom() - .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build()) { + try ( + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build() + ) { thrown.expect(SSLPeerUnverifiedException.class); @@ -249,24 +287,31 @@ public void testPemHostnameVerificationFailure() throws Exception { @Test public void testPemHostnameVerificationOff() throws Exception { - try (TestServer testServer = new TestServer("sslConfigurator/pem/truststore.jks", - "sslConfigurator/pem/node-wrong-hostname-keystore.jks", "secret", false)) { + try ( + TestServer testServer = new TestServer( + "sslConfigurator/pem/truststore.jks", + "sslConfigurator/pem/node-wrong-hostname-keystore.jks", + "secret", + false + ) + ) { Path rootCaPemPath = FileHelper.getAbsoluteFilePathFromClassPath("sslConfigurator/pem/root-ca.pem"); Settings settings = Settings.builder() - .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) - .put("prefix.enable_ssl", "true") - .put("prefix.verify_hostnames", "false") - .put("path.home", rootCaPemPath.getParent().toString()) - .build(); + .put("prefix.pemtrustedcas_filepath", rootCaPemPath.getFileName().toString()) + .put("prefix.enable_ssl", "true") + .put("prefix.verify_hostnames", "false") + .put("path.home", rootCaPemPath.getParent().toString()) + .build(); Path configPath = rootCaPemPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); - try (CloseableHttpClient httpClient = HttpClients.custom() - .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build()) { + try ( + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build() + ) { try (CloseableHttpResponse response = httpClient.execute(new HttpGet(testServer.getUri()))) { // Success @@ -278,26 +323,33 @@ public void testPemHostnameVerificationOff() throws Exception { @Test public void testJksTrust() throws Exception { - try (TestServer testServer = new TestServer("sslConfigurator/jks/truststore.jks", - "sslConfigurator/jks/node1-keystore.jks", "secret", false)) { + try ( + TestServer testServer = new TestServer( + "sslConfigurator/jks/truststore.jks", + "sslConfigurator/jks/node1-keystore.jks", + "secret", + false + ) + ) { Path rootCaJksPath = FileHelper.getAbsoluteFilePathFromClassPath("sslConfigurator/jks/truststore.jks"); MockSecureSettings mockSecureSettings = new MockSecureSettings(); mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.propertyName, "secret"); Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, rootCaJksPath.getFileName().toString()) - .put("prefix.enable_ssl", "true") - .put("path.home", rootCaJksPath.getParent().toString()) - .setSecureSettings(mockSecureSettings) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, rootCaJksPath.getFileName().toString()) + .put("prefix.enable_ssl", "true") + .put("path.home", rootCaJksPath.getParent().toString()) + .setSecureSettings(mockSecureSettings) + .build(); Path configPath = rootCaJksPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); - try (CloseableHttpClient httpClient = HttpClients.custom() - .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build()) { + try ( + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build() + ) { try (CloseableHttpResponse response = httpClient.execute(new HttpGet(testServer.getUri()))) { // Success @@ -310,26 +362,33 @@ public void testJksTrust() throws Exception { @Test public void testJksWrongTrust() throws Exception { - try (TestServer testServer = new TestServer("sslConfigurator/jks/truststore.jks", - "sslConfigurator/jks/node1-keystore.jks", "secret", false)) { + try ( + TestServer testServer = new TestServer( + "sslConfigurator/jks/truststore.jks", + "sslConfigurator/jks/node1-keystore.jks", + "secret", + false + ) + ) { Path rootCaJksPath = FileHelper.getAbsoluteFilePathFromClassPath("sslConfigurator/jks/other-root-ca.jks"); MockSecureSettings mockSecureSettings = new MockSecureSettings(); mockSecureSettings.setString(SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.propertyName, "secret"); Settings settings = Settings.builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, rootCaJksPath.getFileName().toString()) - .put("prefix.enable_ssl", "true") - .put("path.home", rootCaJksPath.getParent().toString()) - .setSecureSettings(mockSecureSettings) - .build(); + .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, rootCaJksPath.getFileName().toString()) + .put("prefix.enable_ssl", "true") + .put("path.home", rootCaJksPath.getParent().toString()) + .setSecureSettings(mockSecureSettings) + .build(); Path configPath = rootCaJksPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); - try (CloseableHttpClient httpClient = HttpClients.custom() - .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build()) { + try ( + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build() + ) { thrown.expect(SSLHandshakeException.class); @@ -342,23 +401,30 @@ public void testJksWrongTrust() throws Exception { @Test public void testTrustAll() throws Exception { - try (TestServer testServer = new TestServer("sslConfigurator/jks/truststore.jks", - "sslConfigurator/jks/node1-keystore.jks", "secret", false)) { + try ( + TestServer testServer = new TestServer( + "sslConfigurator/jks/truststore.jks", + "sslConfigurator/jks/node1-keystore.jks", + "secret", + false + ) + ) { Path rootCaJksPath = FileHelper.getAbsoluteFilePathFromClassPath("sslConfigurator/jks/other-root-ca.jks"); Settings settings = Settings.builder() - .put("prefix.enable_ssl", "true") - .put("prefix.trust_all", "true") - .put("path.home", rootCaJksPath.getParent().toString()) - .build(); + .put("prefix.enable_ssl", "true") + .put("prefix.trust_all", "true") + .put("path.home", rootCaJksPath.getParent().toString()) + .build(); Path configPath = rootCaJksPath.getParent(); SettingsBasedSSLConfiguratorV4 sbsc = new SettingsBasedSSLConfiguratorV4(settings, configPath, "prefix"); SSLConfig sslConfig = sbsc.buildSSLConfig(); - try (CloseableHttpClient httpClient = HttpClients.custom() - .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build()) { + try ( + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()).build() + ) { try (CloseableHttpResponse response = httpClient.execute(new HttpGet(testServer.getUri()))) { // Success @@ -379,43 +445,50 @@ String getUri() { return "https://localhost:" + port + "/test"; } - private void createHttpServer(String trustStore, String keyStore, String password, boolean clientAuth) - throws IOException { + private void createHttpServer(String trustStore, String keyStore, String password, boolean clientAuth) throws IOException { this.port = SocketUtils.findAvailableTcpPort(); - ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap().setListenerPort(port).registerHandler("test", - new HttpRequestHandler() { + ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap() + .setListenerPort(port) + .registerHandler("test", new HttpRequestHandler() { - @Override - public void handle(HttpRequest request, HttpResponse response, HttpContext context) - throws HttpException, IOException { + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { - } - }); + } + }); serverBootstrap = serverBootstrap.setSslContext(createSSLContext(trustStore, keyStore, password)) - .setSslSetupHandler(new SSLServerSetupHandler() { + .setSslSetupHandler(new SSLServerSetupHandler() { - @Override - public void initialize(SSLServerSocket socket) throws SSLException { - if (clientAuth) { - socket.setNeedClientAuth(true); - } - } - }).setConnectionFactory(new HttpConnectionFactory() { - - private ConnectionConfig cconfig = ConnectionConfig.DEFAULT; - - @Override - public DefaultBHttpServerConnection createConnection(final Socket socket) throws IOException { - final SSLTestHttpServerConnection conn = new SSLTestHttpServerConnection( - this.cconfig.getBufferSize(), this.cconfig.getFragmentSizeHint(), - ConnSupport.createDecoder(this.cconfig), ConnSupport.createEncoder(this.cconfig), - this.cconfig.getMessageConstraints(), null, null, null, null); - conn.bind(socket); - return conn; + @Override + public void initialize(SSLServerSocket socket) throws SSLException { + if (clientAuth) { + socket.setNeedClientAuth(true); } - }); + } + }) + .setConnectionFactory(new HttpConnectionFactory() { + + private ConnectionConfig cconfig = ConnectionConfig.DEFAULT; + + @Override + public DefaultBHttpServerConnection createConnection(final Socket socket) throws IOException { + final SSLTestHttpServerConnection conn = new SSLTestHttpServerConnection( + this.cconfig.getBufferSize(), + this.cconfig.getFragmentSizeHint(), + ConnSupport.createDecoder(this.cconfig), + ConnSupport.createEncoder(this.cconfig), + this.cconfig.getMessageConstraints(), + null, + null, + null, + null + ); + conn.bind(socket); + return conn; + } + }); this.httpServer = serverBootstrap.create(); @@ -434,8 +507,7 @@ private SSLContext createSSLContext(String trustStorePath, String keyStorePath, try { TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore trustStore = KeyStore.getInstance("JKS"); - InputStream trustStream = new FileInputStream( - FileHelper.getAbsoluteFilePathFromClassPath(trustStorePath).toFile()); + InputStream trustStream = new FileInputStream(FileHelper.getAbsoluteFilePathFromClassPath(trustStorePath).toFile()); trustStore.load(trustStream, password.toCharArray()); tmf.init(trustStore); @@ -472,14 +544,28 @@ public String chooseAlias(Map aliases, Socket socket) } static class SSLTestHttpServerConnection extends DefaultBHttpServerConnection { - public SSLTestHttpServerConnection(final int buffersize, final int fragmentSizeHint, - final CharsetDecoder chardecoder, final CharsetEncoder charencoder, - final MessageConstraints constraints, final ContentLengthStrategy incomingContentStrategy, - final ContentLengthStrategy outgoingContentStrategy, - final HttpMessageParserFactory requestParserFactory, - final HttpMessageWriterFactory responseWriterFactory) { - super(buffersize, fragmentSizeHint, chardecoder, charencoder, constraints, incomingContentStrategy, - outgoingContentStrategy, requestParserFactory, responseWriterFactory); + public SSLTestHttpServerConnection( + final int buffersize, + final int fragmentSizeHint, + final CharsetDecoder chardecoder, + final CharsetEncoder charencoder, + final MessageConstraints constraints, + final ContentLengthStrategy incomingContentStrategy, + final ContentLengthStrategy outgoingContentStrategy, + final HttpMessageParserFactory requestParserFactory, + final HttpMessageWriterFactory responseWriterFactory + ) { + super( + buffersize, + fragmentSizeHint, + chardecoder, + charencoder, + constraints, + incomingContentStrategy, + outgoingContentStrategy, + requestParserFactory, + responseWriterFactory + ); } public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { From 0dbd84cb2b2ff301f67ad26b743254af8c7d4dba Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Tue, 13 Jun 2023 11:06:04 -0400 Subject: [PATCH 200/369] Add Andrey Pleskach (Willyborankin) to Maintainers (#2843) * Add Andrey to Maintainers Signed-off-by: Stephen Crawford * Update MAINTAINERS.md Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> * Update codeowners Signed-off-by: Stephen Crawford --------- Signed-off-by: Stephen Crawford Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- .github/CODEOWNERS | 2 +- MAINTAINERS.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5832378bff..5041f4b625 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @cliu123 @cwperks @DarshitChanpura @davidlago @peternied @RyanL1997 @scrawfor99 @reta +* @cliu123 @cwperks @DarshitChanpura @davidlago @peternied @RyanL1997 @scrawfor99 @reta @willyborankin diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 4605135a16..f27e192923 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -22,6 +22,7 @@ This document contains a list of maintainers in this repo. See [opensearch-proje | Ryan Liang | [RyanL1997](https://github.com/RyanL1997) | Amazon | | Stephen Crawford | [scrawfor99](https://github.com/scrawfor99) | Amazon | | Andriy Redko | [reta](https://github.com/reta) | Aiven | +| Andrey Pleskach | [willyborankin](https://github.com/willyborankin) | Aiven | ## Practices From 7e8acd4cab0cda02db432e3ebd24acd4ff7276ad Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 13 Jun 2023 13:06:31 -0400 Subject: [PATCH 201/369] Fix JwtAuthenticationTests assertion (#2844) Signed-off-by: Craig Perkins --- .../org/opensearch/security/http/JwtAuthenticationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java b/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java index 5875a120a4..65a4e32d7e 100644 --- a/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java @@ -141,7 +141,7 @@ public void shouldAuthenticateWithJwtToken_positive() { response.assertStatusCode(200); String username = response.getTextFromJsonBody(POINTER_USERNAME); - assertThat(username, equalTo(username)); + assertThat(username, equalTo(USER_SUPERHERO)); } } From 2e263b8182d644dbc3c595a4393c750c9e3d3e7a Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Tue, 13 Jun 2023 15:39:22 -0400 Subject: [PATCH 202/369] Update the formatting of all *.java file under **/test/ (#2840) * Update all tests Signed-off-by: Stephen Crawford * spotless Signed-off-by: Stephen Crawford --------- Signed-off-by: Stephen Crawford --- build.gradle | 10 +- .../SecurityBackwardsCompatibilityIT.java | 19 +- .../org/opensearch/node/PluginAwareNode.java | 4 +- .../security/SecurityRolesTests.java | 4 +- .../http/CommonProxyAuthenticationTests.java | 4 +- .../test/framework/AuditCompliance.java | 171 +-- .../test/framework/AuditConfiguration.java | 57 +- .../test/framework/AuditFilters.java | 201 +-- .../test/framework/AuthFailureListeners.java | 30 +- .../test/framework/AuthorizationBackend.java | 40 +- .../test/framework/AuthzDomain.java | 74 +- .../test/framework/JwtConfigBuilder.java | 84 +- .../LdapAuthenticationConfigBuilder.java | 192 +-- .../LdapAuthorizationConfigBuilder.java | 100 +- .../test/framework/RateLimiting.java | 110 +- .../test/framework/RolesMapping.java | 118 +- .../opensearch/test/framework/TestIndex.java | 91 +- .../test/framework/TestSecurityConfig.java | 1264 +++++++++-------- .../opensearch/test/framework/XffConfig.java | 68 +- .../test/framework/audit/AuditLogsRule.java | 184 +-- .../audit/AuditMessagePredicate.java | 315 ++-- .../framework/audit/TestRuleAuditLogSink.java | 62 +- .../framework/certificate/AlgorithmKit.java | 189 +-- .../certificate/CertificateData.java | 78 +- .../certificate/CertificateMetadata.java | 349 +++-- .../certificate/CertificatesIssuer.java | 293 ++-- .../CertificatesIssuerFactory.java | 68 +- .../framework/certificate/PemConverter.java | 115 +- .../framework/certificate/PublicKeyUsage.java | 71 +- .../certificate/TestCertificates.java | 323 +++-- .../cluster/CloseableHttpClientFactory.java | 66 +- .../framework/cluster/ClusterManager.java | 224 +-- .../cluster/ContextHeaderDecoratorClient.java | 44 +- .../cluster/LocalAddressRoutePlanner.java | 40 +- .../test/framework/cluster/LocalCluster.java | 859 +++++------ .../cluster/LocalOpenSearchCluster.java | 925 ++++++------ ...inimumSecuritySettingsSupplierFactory.java | 70 +- .../test/framework/cluster/NodeRole.java | 4 +- .../cluster/NodeSettingsSupplier.java | 2 +- .../test/framework/cluster/NodeType.java | 4 +- .../cluster/OpenSearchClientProvider.java | 390 ++--- .../test/framework/cluster/PortAllocator.java | 169 +-- .../cluster/RestClientException.java | 6 +- .../cluster/SearchRequestFactory.java | 136 +- .../test/framework/cluster/SocketUtils.java | 511 ++++--- .../framework/cluster/SocketUtilsTests.java | 301 ++-- .../test/framework/cluster/StartStage.java | 4 +- .../framework/cluster/TestRestClient.java | 690 ++++----- .../cluster/TestRestClientConfiguration.java | 260 ++-- .../framework/ldap/EmbeddedLDAPServer.java | 68 +- .../test/framework/ldap/LdapServer.java | 340 ++--- .../test/framework/ldap/LdifBuilder.java | 74 +- .../test/framework/ldap/LdifData.java | 47 +- .../test/framework/ldap/Record.java | 75 +- .../test/framework/ldap/RecordBuilder.java | 152 +- .../framework/log/LogCapturingAppender.java | 145 +- .../test/framework/log/LogMessage.java | 42 +- .../test/framework/log/LogsRule.java | 111 +- .../framework/matcher/AliasExistsMatcher.java | 57 +- ...NumberOfAuditsFulfillPredicateMatcher.java | 50 +- .../matcher/AuditMessageMatchers.java | 28 +- .../AuditsFulfillPredicateMatcher.java | 20 +- ...sponseContainExceptionsAtIndexMatcher.java | 92 +- .../BulkResponseContainExceptionsMatcher.java | 88 +- .../matcher/BulkResponseMatchers.java | 30 +- ...usterContainDocumentCountIndexMatcher.java | 40 +- .../ClusterContainSuccessSnapshotMatcher.java | 75 +- .../ClusterContainTemplateMatcher.java | 35 +- ...lusterContainTemplateWithAliasMatcher.java | 77 +- .../ClusterContainsDocumentMatcher.java | 61 +- ...ContainsDocumentWithFieldValueMatcher.java | 97 +- ...sterContainsSnapshotRepositoryMatcher.java | 69 +- .../framework/matcher/ClusterMatchers.java | 107 +- .../ContainNotEmptyScrollingIdMatcher.java | 26 +- ...ainsAggregationWithNameAndTypeMatcher.java | 66 +- .../ContainsExactlyIndicesMatcher.java | 46 +- .../matcher/ContainsFieldWithTypeMatcher.java | 58 +- ...ePitContainsExactlyIdsResponseMatcher.java | 46 +- .../matcher/DeleteResponseMatchers.java | 8 +- ...NumberOfAuditsFulfillPredicateMatcher.java | 46 +- .../matcher/ExceptionErrorMessageMatcher.java | 44 +- .../matcher/ExceptionHasCauseMatcher.java | 42 +- .../matcher/ExceptionMatcherAssert.java | 36 +- .../matcher/FailureBulkResponseMatcher.java | 24 +- .../FieldCapabilitiesResponseMatchers.java | 20 +- ...PitsContainsExactlyIdsResponseMatcher.java | 46 +- ...etIndexResponseContainsIndicesMatcher.java | 52 +- ...appingsResponseContainsIndicesMatcher.java | 52 +- ...tResponseContainOnlyDocumentIdMatcher.java | 63 +- ...ResponseContainsDocumentWithIdMatcher.java | 70 +- ...ContainsExactlyFieldsWithNamesMatcher.java | 52 +- ...nseDocumentDoesNotContainFieldMatcher.java | 48 +- .../GetResponseDocumentFieldValueMatcher.java | 66 +- .../matcher/GetResponseMatchers.java | 34 +- ...ettingsResponseContainsIndicesMatcher.java | 54 +- .../framework/matcher/IndexExistsMatcher.java | 47 +- .../matcher/IndexMappingIsEqualToMatcher.java | 74 +- .../matcher/IndexResponseMatchers.java | 48 +- .../IndexSettingsContainValuesMatcher.java | 79 +- .../matcher/IndexStateIsEqualToMatcher.java | 66 +- .../matcher/MultiGetResponseMatchers.java | 15 +- .../matcher/MultiSearchResponseMatchers.java | 15 +- .../NumberOfFieldsIsEqualToMatcher.java | 33 +- ...berOfGetItemResponsesIsEqualToMatcher.java | 40 +- .../NumberOfHitsInPageIsEqualToMatcher.java | 44 +- ...OfSearchItemResponsesIsEqualToMatcher.java | 42 +- .../NumberOfTotalHitsIsEqualToMatcher.java | 68 +- .../matcher/OpenSearchExceptionMatchers.java | 26 +- .../OpenSearchStatusExceptionMatcher.java | 56 +- .../matcher/PitResponseMatchers.java | 27 +- ...earchHitContainsFieldWithValueMatcher.java | 85 +- .../SearchHitDoesNotContainFieldMatcher.java | 78 +- ...earchHitsContainDocumentWithIdMatcher.java | 80 +- ...HitsContainDocumentsInAnyOrderMatcher.java | 86 +- .../matcher/SearchResponseMatchers.java | 122 +- .../SearchResponseWithStatusCodeMatcher.java | 32 +- .../SnapshotInClusterDoesNotExist.java | 46 +- .../matcher/SuccessBulkResponseMatcher.java | 44 +- ...ssfulClearIndicesCacheResponseMatcher.java | 32 +- .../SuccessfulCloseIndexResponseMatcher.java | 32 +- .../SuccessfulCreateIndexResponseMatcher.java | 58 +- .../SuccessfulCreatePitResponseMatcher.java | 32 +- .../SuccessfulDeletePitResponseMatcher.java | 38 +- .../SuccessfulDeleteResponseMatcher.java | 34 +- .../SuccessfulMultiGetResponseMatcher.java | 32 +- .../SuccessfulMultiSearchResponseMatcher.java | 28 +- .../SuccessfulOpenIndexResponseMatcher.java | 32 +- .../SuccessfulResizeResponseMatcher.java | 58 +- .../SuccessfulSearchResponseMatcher.java | 32 +- .../SuccessfulUpdateResponseMatcher.java | 34 +- .../matcher/UpdateResponseMatchers.java | 8 +- .../org/opensearch/bootstrap/JarHell.java | 9 +- .../org/opensearch/node/PluginAwareNode.java | 7 +- 133 files changed, 7207 insertions(+), 6884 deletions(-) diff --git a/build.gradle b/build.gradle index 863a4ae234..15eaad0803 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,7 @@ spotless { // non-standard places target '**/com/amazon/dlic/**/*.java' target '**/com/amazon/security/**/*.java' - target '**/test/java/org/opensearch/security/**/*.java' + target '**/test/**/*.java' removeUnusedImports() eclipse().configFile rootProject.file('formatter/formatterConfig.xml') @@ -112,17 +112,11 @@ spotless { target '**/*.java' targetExclude '**/com/amazon/dlic/**/*.java' targetExclude '**/com/amazon/security/**/*.java' - targetExclude '**/test/java/org/opensearch/security/**/*.java' - targetExclude 'src/integrationTest/**' + targetExclude '**/test/**/*.java' trimTrailingWhitespace() endWithNewline(); } - format("integrationTest", JavaExtension) { - target('src/integrationTest/java/**/*.java') - importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') - indentWithTabs(4) - } } licenseFile = rootProject.file('LICENSE.txt') diff --git a/bwc-test/src/test/java/SecurityBackwardsCompatibilityIT.java b/bwc-test/src/test/java/SecurityBackwardsCompatibilityIT.java index d3c3658245..59c2a26c03 100644 --- a/bwc-test/src/test/java/SecurityBackwardsCompatibilityIT.java +++ b/bwc-test/src/test/java/SecurityBackwardsCompatibilityIT.java @@ -12,15 +12,11 @@ import java.util.Set; import java.util.stream.Collectors; -import com.google.common.collect.ImmutableMap; -import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.opensearch.Version; -import org.opensearch.client.Response; import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestStatus; import org.opensearch.test.rest.OpenSearchRestTestCase; import static org.hamcrest.MatcherAssert.assertThat; @@ -56,14 +52,13 @@ protected boolean preserveTemplatesUponCompletion() { @Override protected final Settings restClientSettings() { - return Settings - .builder() - .put(super.restClientSettings()) - // increase the timeout here to 90 seconds to handle long waits for a green - // cluster health. the waits for green need to be longer than a minute to - // account for delayed shards - .put(OpenSearchRestTestCase.CLIENT_SOCKET_TIMEOUT, "90s") - .build(); + return Settings.builder() + .put(super.restClientSettings()) + // increase the timeout here to 90 seconds to handle long waits for a green + // cluster health. the waits for green need to be longer than a minute to + // account for delayed shards + .put(OpenSearchRestTestCase.CLIENT_SOCKET_TIMEOUT, "90s") + .build(); } public void testBasicBackwardsCompatibility() throws Exception { diff --git a/src/integrationTest/java/org/opensearch/node/PluginAwareNode.java b/src/integrationTest/java/org/opensearch/node/PluginAwareNode.java index 1599cd2a37..62b0199824 100644 --- a/src/integrationTest/java/org/opensearch/node/PluginAwareNode.java +++ b/src/integrationTest/java/org/opensearch/node/PluginAwareNode.java @@ -33,7 +33,7 @@ import org.opensearch.plugins.Plugin; public class PluginAwareNode extends Node { - + private final boolean clusterManagerEligible; @SafeVarargs @@ -41,7 +41,7 @@ public PluginAwareNode(boolean clusterManagerEligible, final Settings preparedSe super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, Collections.emptyMap(), null, () -> System.getenv("HOSTNAME")), Arrays.asList(plugins), true); this.clusterManagerEligible = clusterManagerEligible; } - + public boolean isClusterManagerEligible() { return clusterManagerEligible; diff --git a/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java b/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java index d93a168341..c25d28ef12 100644 --- a/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java +++ b/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java @@ -47,9 +47,9 @@ public void testSecurityRoles() throws Exception { HttpResponse response = client.getAuthInfo(); response.assertStatusCode(HttpStatus.SC_OK); - // Check username + // Check username assertThat(response.getTextFromJsonBody("/user_name"), equalTo("sr_user")); - + // Check security roles assertThat(response.getTextFromJsonBody("/roles/0"), equalTo("user_sr_user__abc_ber")); assertThat(response.getTextFromJsonBody("/roles/1"), equalTo("user_sr_user__def_efg")); diff --git a/src/integrationTest/java/org/opensearch/security/http/CommonProxyAuthenticationTests.java b/src/integrationTest/java/org/opensearch/security/http/CommonProxyAuthenticationTests.java index 244bd6b80e..c17ccb8ea3 100644 --- a/src/integrationTest/java/org/opensearch/security/http/CommonProxyAuthenticationTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/CommonProxyAuthenticationTests.java @@ -36,7 +36,7 @@ abstract class CommonProxyAuthenticationTests { protected static final String ATTRIBUTE_DEPARTMENT = "department"; protected static final String ATTRIBUTE_SKILLS = "skills"; - + protected static final String USER_ATTRIBUTE_DEPARTMENT_NAME = "attr.proxy." + ATTRIBUTE_DEPARTMENT; protected static final String USER_ATTRIBUTE_SKILLS_NAME = "attr.proxy." + ATTRIBUTE_SKILLS; protected static final String USER_ATTRIBUTE_USERNAME_NAME = "attr.proxy.username"; @@ -82,7 +82,7 @@ abstract class CommonProxyAuthenticationTests { protected static final RolesMapping ROLES_MAPPING_FIRST_MATE = new RolesMapping(ROLE_ALL_INDEX_SEARCH) .backendRoles(BACKEND_ROLE_FIRST_MATE); - + protected abstract LocalCluster getCluster(); protected void shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured() { diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuditCompliance.java b/src/integrationTest/java/org/opensearch/test/framework/AuditCompliance.java index c9d5f8b40a..d75fc0e4e5 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/AuditCompliance.java +++ b/src/integrationTest/java/org/opensearch/test/framework/AuditCompliance.java @@ -18,89 +18,90 @@ public class AuditCompliance implements ToXContentObject { - private boolean enabled = false; - - private Boolean writeLogDiffs; - - private List readIgnoreUsers; - - private List writeWatchedIndices; - - private List writeIgnoreUsers; - - private Boolean readMetadataOnly; - - private Boolean writeMetadataOnly; - - private Boolean externalConfig; - - private Boolean internalConfig; - - public AuditCompliance enabled(boolean enabled) { - this.enabled = enabled; - this.writeLogDiffs = false; - this.readIgnoreUsers = Collections.emptyList(); - this.writeWatchedIndices = Collections.emptyList(); - this.writeIgnoreUsers = Collections.emptyList(); - this.readMetadataOnly = false; - this.writeMetadataOnly = false; - this.externalConfig = false; - this.internalConfig = false; - return this; - } - - public AuditCompliance writeLogDiffs(boolean writeLogDiffs) { - this.writeLogDiffs = writeLogDiffs; - return this; - } - - public AuditCompliance readIgnoreUsers(List list) { - this.readIgnoreUsers = list; - return this; - } - - public AuditCompliance writeWatchedIndices(List list) { - this.writeWatchedIndices = list; - return this; - } - - public AuditCompliance writeIgnoreUsers(List list) { - this.writeIgnoreUsers = list; - return this; - } - - public AuditCompliance readMetadataOnly(boolean readMetadataOnly) { - this.readMetadataOnly = readMetadataOnly; - return this; - } - - public AuditCompliance writeMetadataOnly(boolean writeMetadataOnly) { - this.writeMetadataOnly = writeMetadataOnly; - return this; - } - - public AuditCompliance externalConfig(boolean externalConfig) { - this.externalConfig = externalConfig; - return this; - } - - public AuditCompliance internalConfig(boolean internalConfig) { - this.internalConfig = internalConfig; - return this; - } - - @Override public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - xContentBuilder.field("enabled", enabled); - xContentBuilder.field("write_log_diffs", writeLogDiffs); - xContentBuilder.field("read_ignore_users", readIgnoreUsers); - xContentBuilder.field("write_watched_indices", writeWatchedIndices); - xContentBuilder.field("write_ignore_users", writeIgnoreUsers); - xContentBuilder.field("read_metadata_only", readMetadataOnly); - xContentBuilder.field("write_metadata_only", writeMetadataOnly); - xContentBuilder.field("external_config", externalConfig); - xContentBuilder.field("internal_config", internalConfig); - xContentBuilder.endObject(); - return xContentBuilder; - } + private boolean enabled = false; + + private Boolean writeLogDiffs; + + private List readIgnoreUsers; + + private List writeWatchedIndices; + + private List writeIgnoreUsers; + + private Boolean readMetadataOnly; + + private Boolean writeMetadataOnly; + + private Boolean externalConfig; + + private Boolean internalConfig; + + public AuditCompliance enabled(boolean enabled) { + this.enabled = enabled; + this.writeLogDiffs = false; + this.readIgnoreUsers = Collections.emptyList(); + this.writeWatchedIndices = Collections.emptyList(); + this.writeIgnoreUsers = Collections.emptyList(); + this.readMetadataOnly = false; + this.writeMetadataOnly = false; + this.externalConfig = false; + this.internalConfig = false; + return this; + } + + public AuditCompliance writeLogDiffs(boolean writeLogDiffs) { + this.writeLogDiffs = writeLogDiffs; + return this; + } + + public AuditCompliance readIgnoreUsers(List list) { + this.readIgnoreUsers = list; + return this; + } + + public AuditCompliance writeWatchedIndices(List list) { + this.writeWatchedIndices = list; + return this; + } + + public AuditCompliance writeIgnoreUsers(List list) { + this.writeIgnoreUsers = list; + return this; + } + + public AuditCompliance readMetadataOnly(boolean readMetadataOnly) { + this.readMetadataOnly = readMetadataOnly; + return this; + } + + public AuditCompliance writeMetadataOnly(boolean writeMetadataOnly) { + this.writeMetadataOnly = writeMetadataOnly; + return this; + } + + public AuditCompliance externalConfig(boolean externalConfig) { + this.externalConfig = externalConfig; + return this; + } + + public AuditCompliance internalConfig(boolean internalConfig) { + this.internalConfig = internalConfig; + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("enabled", enabled); + xContentBuilder.field("write_log_diffs", writeLogDiffs); + xContentBuilder.field("read_ignore_users", readIgnoreUsers); + xContentBuilder.field("write_watched_indices", writeWatchedIndices); + xContentBuilder.field("write_ignore_users", writeIgnoreUsers); + xContentBuilder.field("read_metadata_only", readMetadataOnly); + xContentBuilder.field("write_metadata_only", writeMetadataOnly); + xContentBuilder.field("external_config", externalConfig); + xContentBuilder.field("internal_config", internalConfig); + xContentBuilder.endObject(); + return xContentBuilder; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuditConfiguration.java b/src/integrationTest/java/org/opensearch/test/framework/AuditConfiguration.java index 783fa19af0..1b3f11cc83 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/AuditConfiguration.java +++ b/src/integrationTest/java/org/opensearch/test/framework/AuditConfiguration.java @@ -15,41 +15,42 @@ import org.opensearch.core.xcontent.XContentBuilder; public class AuditConfiguration implements ToXContentObject { - private final boolean enabled; + private final boolean enabled; - private AuditFilters filters; + private AuditFilters filters; - private AuditCompliance compliance; + private AuditCompliance compliance; - public AuditConfiguration(boolean enabled) { - this.filters = new AuditFilters(); - this.compliance = new AuditCompliance(); - this.enabled = enabled; - } + public AuditConfiguration(boolean enabled) { + this.filters = new AuditFilters(); + this.compliance = new AuditCompliance(); + this.enabled = enabled; + } - public boolean isEnabled() { - return enabled; - } + public boolean isEnabled() { + return enabled; + } - public AuditConfiguration filters(AuditFilters filters) { - this.filters = filters; - return this; - } + public AuditConfiguration filters(AuditFilters filters) { + this.filters = filters; + return this; + } - public AuditConfiguration compliance(AuditCompliance auditCompliance) { - this.compliance = auditCompliance; - return this; - } + public AuditConfiguration compliance(AuditCompliance auditCompliance) { + this.compliance = auditCompliance; + return this; + } - @Override public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - // json built here must be deserialized to org.opensearch.security.auditlog.config.AuditConfig - xContentBuilder.startObject(); - xContentBuilder.field("enabled", enabled); + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + // json built here must be deserialized to org.opensearch.security.auditlog.config.AuditConfig + xContentBuilder.startObject(); + xContentBuilder.field("enabled", enabled); - xContentBuilder.field("audit", filters); - xContentBuilder.field("compliance", compliance); + xContentBuilder.field("audit", filters); + xContentBuilder.field("compliance", compliance); - xContentBuilder.endObject(); - return xContentBuilder; - } + xContentBuilder.endObject(); + return xContentBuilder; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuditFilters.java b/src/integrationTest/java/org/opensearch/test/framework/AuditFilters.java index bc9f972906..f984becefa 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/AuditFilters.java +++ b/src/integrationTest/java/org/opensearch/test/framework/AuditFilters.java @@ -18,104 +18,105 @@ public class AuditFilters implements ToXContentObject { - private Boolean enabledRest; - - private Boolean enabledTransport; - - private Boolean logRequestBody; - - private Boolean resolveIndices; - - private Boolean resolveBulkRequests; - - private Boolean excludeSensitiveHeaders; - - private List ignoreUsers; - - private List ignoreRequests; - - private List disabledRestCategories; - - private List disabledTransportCategories; - - public AuditFilters() { - this.enabledRest = false; - this.enabledTransport = false; - - this.logRequestBody = true; - this.resolveIndices = true; - this.resolveBulkRequests = false; - this.excludeSensitiveHeaders = true; - - this.ignoreUsers = Collections.emptyList(); - this.ignoreRequests = Collections.emptyList(); - this.disabledRestCategories = Collections.emptyList(); - this.disabledTransportCategories = Collections.emptyList(); - } - - public AuditFilters enabledRest(boolean enabled) { - this.enabledRest = enabled; - return this; - } - - public AuditFilters enabledTransport(boolean enabled) { - this.enabledTransport = enabled; - return this; - } - - public AuditFilters logRequestBody(boolean logRequestBody) { - this.logRequestBody = logRequestBody; - return this; - } - - public AuditFilters resolveIndices(boolean resolveIndices) { - this.resolveIndices = resolveIndices; - return this; - } - - public AuditFilters resolveBulkRequests(boolean resolveBulkRequests) { - this.resolveBulkRequests = resolveBulkRequests; - return this; - } - - public AuditFilters excludeSensitiveHeaders(boolean excludeSensitiveHeaders) { - this.excludeSensitiveHeaders = excludeSensitiveHeaders; - return this; - } - - public AuditFilters ignoreUsers(List ignoreUsers) { - this.ignoreUsers = ignoreUsers; - return this; - } - - public AuditFilters ignoreRequests(List ignoreRequests) { - this.ignoreRequests = ignoreRequests; - return this; - } - - public AuditFilters disabledRestCategories(List disabledRestCategories) { - this.disabledRestCategories = disabledRestCategories; - return this; - } - - public AuditFilters disabledTransportCategories(List disabledTransportCategories) { - this.disabledTransportCategories = disabledTransportCategories; - return this; - } - - @Override public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - xContentBuilder.field("enable_rest", enabledRest); - xContentBuilder.field("enable_transport", enabledTransport); - xContentBuilder.field("resolve_indices", resolveIndices); - xContentBuilder.field("log_request_body", logRequestBody); - xContentBuilder.field("resolve_bulk_requests", resolveBulkRequests); - xContentBuilder.field("exclude_sensitive_headers", excludeSensitiveHeaders); - xContentBuilder.field("ignore_users", ignoreUsers); - xContentBuilder.field("ignore_requests", ignoreRequests); - xContentBuilder.field("disabled_rest_categories", disabledRestCategories); - xContentBuilder.field("disabled_transport_categories", disabledTransportCategories); - xContentBuilder.endObject(); - return xContentBuilder; - } + private Boolean enabledRest; + + private Boolean enabledTransport; + + private Boolean logRequestBody; + + private Boolean resolveIndices; + + private Boolean resolveBulkRequests; + + private Boolean excludeSensitiveHeaders; + + private List ignoreUsers; + + private List ignoreRequests; + + private List disabledRestCategories; + + private List disabledTransportCategories; + + public AuditFilters() { + this.enabledRest = false; + this.enabledTransport = false; + + this.logRequestBody = true; + this.resolveIndices = true; + this.resolveBulkRequests = false; + this.excludeSensitiveHeaders = true; + + this.ignoreUsers = Collections.emptyList(); + this.ignoreRequests = Collections.emptyList(); + this.disabledRestCategories = Collections.emptyList(); + this.disabledTransportCategories = Collections.emptyList(); + } + + public AuditFilters enabledRest(boolean enabled) { + this.enabledRest = enabled; + return this; + } + + public AuditFilters enabledTransport(boolean enabled) { + this.enabledTransport = enabled; + return this; + } + + public AuditFilters logRequestBody(boolean logRequestBody) { + this.logRequestBody = logRequestBody; + return this; + } + + public AuditFilters resolveIndices(boolean resolveIndices) { + this.resolveIndices = resolveIndices; + return this; + } + + public AuditFilters resolveBulkRequests(boolean resolveBulkRequests) { + this.resolveBulkRequests = resolveBulkRequests; + return this; + } + + public AuditFilters excludeSensitiveHeaders(boolean excludeSensitiveHeaders) { + this.excludeSensitiveHeaders = excludeSensitiveHeaders; + return this; + } + + public AuditFilters ignoreUsers(List ignoreUsers) { + this.ignoreUsers = ignoreUsers; + return this; + } + + public AuditFilters ignoreRequests(List ignoreRequests) { + this.ignoreRequests = ignoreRequests; + return this; + } + + public AuditFilters disabledRestCategories(List disabledRestCategories) { + this.disabledRestCategories = disabledRestCategories; + return this; + } + + public AuditFilters disabledTransportCategories(List disabledTransportCategories) { + this.disabledTransportCategories = disabledTransportCategories; + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("enable_rest", enabledRest); + xContentBuilder.field("enable_transport", enabledTransport); + xContentBuilder.field("resolve_indices", resolveIndices); + xContentBuilder.field("log_request_body", logRequestBody); + xContentBuilder.field("resolve_bulk_requests", resolveBulkRequests); + xContentBuilder.field("exclude_sensitive_headers", excludeSensitiveHeaders); + xContentBuilder.field("ignore_users", ignoreUsers); + xContentBuilder.field("ignore_requests", ignoreRequests); + xContentBuilder.field("disabled_rest_categories", disabledRestCategories); + xContentBuilder.field("disabled_transport_categories", disabledTransportCategories); + xContentBuilder.endObject(); + return xContentBuilder; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java b/src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java index 5d467bd754..472d3d8d08 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java +++ b/src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java @@ -19,21 +19,21 @@ public class AuthFailureListeners implements ToXContentObject { - private Map limits = new LinkedHashMap<>(); + private Map limits = new LinkedHashMap<>(); - public AuthFailureListeners addRateLimit(RateLimiting rateLimiting) { - Objects.requireNonNull(rateLimiting, "Rate limiting is required"); - limits.put(rateLimiting.getName(), rateLimiting); - return this; - } + public AuthFailureListeners addRateLimit(RateLimiting rateLimiting) { + Objects.requireNonNull(rateLimiting, "Rate limiting is required"); + limits.put(rateLimiting.getName(), rateLimiting); + return this; + } - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - for(Map.Entry entry : limits.entrySet()) { - xContentBuilder.field(entry.getKey(), entry.getValue()); - } - xContentBuilder.endObject(); - return xContentBuilder; - } + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + for (Map.Entry entry : limits.entrySet()) { + xContentBuilder.field(entry.getKey(), entry.getValue()); + } + xContentBuilder.endObject(); + return xContentBuilder; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuthorizationBackend.java b/src/integrationTest/java/org/opensearch/test/framework/AuthorizationBackend.java index 2d9d1c1b66..521d35ed46 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/AuthorizationBackend.java +++ b/src/integrationTest/java/org/opensearch/test/framework/AuthorizationBackend.java @@ -18,28 +18,28 @@ import org.opensearch.core.xcontent.XContentBuilder; public class AuthorizationBackend implements ToXContentObject { - private final String type; - private Supplier> config; + private final String type; + private Supplier> config; - public AuthorizationBackend(String type) { - this.type = type; - } + public AuthorizationBackend(String type) { + this.type = type; + } - public AuthorizationBackend config(Map ldapConfig) { - return config(() -> ldapConfig); - } + public AuthorizationBackend config(Map ldapConfig) { + return config(() -> ldapConfig); + } - public AuthorizationBackend config(Supplier> ldapConfigSupplier) { - this.config = Objects.requireNonNull(ldapConfigSupplier, "Configuration supplier is required"); - return this; - } + public AuthorizationBackend config(Supplier> ldapConfigSupplier) { + this.config = Objects.requireNonNull(ldapConfigSupplier, "Configuration supplier is required"); + return this; + } - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - xContentBuilder.field("type", type); - xContentBuilder.field("config", config.get()); - xContentBuilder.endObject(); - return xContentBuilder; - } + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("type", type); + xContentBuilder.field("config", config.get()); + xContentBuilder.endObject(); + return xContentBuilder; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/AuthzDomain.java b/src/integrationTest/java/org/opensearch/test/framework/AuthzDomain.java index 7d611881e0..5ccf1f9ee0 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/AuthzDomain.java +++ b/src/integrationTest/java/org/opensearch/test/framework/AuthzDomain.java @@ -19,52 +19,52 @@ */ public class AuthzDomain implements ToXContentObject { - private final String id; + private final String id; - private String description; + private String description; - private boolean httpEnabled; + private boolean httpEnabled; - private boolean transportEnabled; + private boolean transportEnabled; - private AuthorizationBackend authorizationBackend; + private AuthorizationBackend authorizationBackend; - public AuthzDomain(String id) { - this.id = id; - } + public AuthzDomain(String id) { + this.id = id; + } - public String getId() { - return id; - } + public String getId() { + return id; + } - public AuthzDomain description(String description) { - this.description = description; - return this; - } + public AuthzDomain description(String description) { + this.description = description; + return this; + } - public AuthzDomain httpEnabled(boolean httpEnabled) { - this.httpEnabled = httpEnabled; - return this; - } + public AuthzDomain httpEnabled(boolean httpEnabled) { + this.httpEnabled = httpEnabled; + return this; + } - public AuthzDomain authorizationBackend(AuthorizationBackend authorizationBackend) { - this.authorizationBackend = authorizationBackend; - return this; - } + public AuthzDomain authorizationBackend(AuthorizationBackend authorizationBackend) { + this.authorizationBackend = authorizationBackend; + return this; + } - public AuthzDomain transportEnabled(boolean transportEnabled) { - this.transportEnabled = transportEnabled; - return this; - } + public AuthzDomain transportEnabled(boolean transportEnabled) { + this.transportEnabled = transportEnabled; + return this; + } - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - xContentBuilder.field("description", description); - xContentBuilder.field("http_enabled", httpEnabled); - xContentBuilder.field("transport_enabled", transportEnabled); - xContentBuilder.field("authorization_backend", authorizationBackend); - xContentBuilder.endObject(); - return xContentBuilder; - } + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("description", description); + xContentBuilder.field("http_enabled", httpEnabled); + xContentBuilder.field("transport_enabled", transportEnabled); + xContentBuilder.field("authorization_backend", authorizationBackend); + xContentBuilder.endObject(); + return xContentBuilder; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/JwtConfigBuilder.java b/src/integrationTest/java/org/opensearch/test/framework/JwtConfigBuilder.java index 5b1ea2c678..48dfa128e0 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/JwtConfigBuilder.java +++ b/src/integrationTest/java/org/opensearch/test/framework/JwtConfigBuilder.java @@ -17,46 +17,46 @@ import static org.apache.commons.lang3.StringUtils.isNoneBlank; public class JwtConfigBuilder { - private String jwtHeader; - private String signingKey; - private String subjectKey; - private String rolesKey; - - public JwtConfigBuilder jwtHeader(String jwtHeader) { - this.jwtHeader = jwtHeader; - return this; - } - - public JwtConfigBuilder signingKey(String signingKey) { - this.signingKey = signingKey; - return this; - } - - public JwtConfigBuilder subjectKey(String subjectKey) { - this.subjectKey = subjectKey; - return this; - } - - public JwtConfigBuilder rolesKey(String rolesKey) { - this.rolesKey = rolesKey; - return this; - } - - public Map build() { - Builder builder = new Builder<>(); - if(Objects.isNull(signingKey)) { - throw new IllegalStateException("Signing key is required."); - } - builder.put("signing_key", signingKey); - if(isNoneBlank(jwtHeader)) { - builder.put("jwt_header", jwtHeader); - } - if(isNoneBlank(subjectKey)) { - builder.put("subject_key", subjectKey); - } - if(isNoneBlank(rolesKey)) { - builder.put("roles_key", rolesKey); - } - return builder.build(); - } + private String jwtHeader; + private String signingKey; + private String subjectKey; + private String rolesKey; + + public JwtConfigBuilder jwtHeader(String jwtHeader) { + this.jwtHeader = jwtHeader; + return this; + } + + public JwtConfigBuilder signingKey(String signingKey) { + this.signingKey = signingKey; + return this; + } + + public JwtConfigBuilder subjectKey(String subjectKey) { + this.subjectKey = subjectKey; + return this; + } + + public JwtConfigBuilder rolesKey(String rolesKey) { + this.rolesKey = rolesKey; + return this; + } + + public Map build() { + Builder builder = new Builder<>(); + if (Objects.isNull(signingKey)) { + throw new IllegalStateException("Signing key is required."); + } + builder.put("signing_key", signingKey); + if (isNoneBlank(jwtHeader)) { + builder.put("jwt_header", jwtHeader); + } + if (isNoneBlank(subjectKey)) { + builder.put("subject_key", subjectKey); + } + if (isNoneBlank(rolesKey)) { + builder.put("roles_key", rolesKey); + } + return builder.build(); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/LdapAuthenticationConfigBuilder.java b/src/integrationTest/java/org/opensearch/test/framework/LdapAuthenticationConfigBuilder.java index b6519f7b87..07f1836b59 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/LdapAuthenticationConfigBuilder.java +++ b/src/integrationTest/java/org/opensearch/test/framework/LdapAuthenticationConfigBuilder.java @@ -20,100 +20,100 @@ * {@link LdapAuthorizationConfigBuilder} */ public class LdapAuthenticationConfigBuilder { - private boolean enableSsl = false; - private boolean enableStartTls = false; - private boolean enableSslClientAuth = false; - private boolean verifyHostnames = false; - private List hosts; - private String bindDn; - private String password; - private String userBase; - private String userSearch; - private String usernameAttribute; - - private String penTrustedCasFilePath; - - /** - * Subclass of this - */ - private final T builderSubclass; - - protected LdapAuthenticationConfigBuilder(Function thisCastFunction) { - this.builderSubclass = thisCastFunction.apply(this); - } - - public static LdapAuthenticationConfigBuilder config() { - return new LdapAuthenticationConfigBuilder<>(Function.identity()); - } - - public T enableSsl(boolean enableSsl) { - this.enableSsl = enableSsl; - return builderSubclass; - } - - public T enableStartTls(boolean enableStartTls) { - this.enableStartTls = enableStartTls; - return builderSubclass; - } - - public T enableSslClientAuth(boolean enableSslClientAuth) { - this.enableSslClientAuth = enableSslClientAuth; - return builderSubclass; - } - - public T verifyHostnames(boolean verifyHostnames) { - this.verifyHostnames = verifyHostnames; - return builderSubclass; - } - - public T hosts(List hosts) { - this.hosts = hosts; - return builderSubclass; - } - - public T bindDn(String bindDn) { - this.bindDn = bindDn; - return builderSubclass; - } - - public T password(String password) { - this.password = password; - return builderSubclass; - } - - public T userBase(String userBase) { - this.userBase = userBase; - return builderSubclass; - } - - public T userSearch(String userSearch) { - this.userSearch = userSearch; - return builderSubclass; - } - - public T usernameAttribute(String usernameAttribute) { - this.usernameAttribute = usernameAttribute; - return builderSubclass; - } - - public T penTrustedCasFilePath(String penTrustedCasFilePath) { - this.penTrustedCasFilePath = penTrustedCasFilePath; - return builderSubclass; - } - - public Map build() { - HashMap config = new HashMap<>(); - config.put("enable_ssl", enableSsl); - config.put("enable_start_tls", enableStartTls); - config.put("enable_ssl_client_auth", enableSslClientAuth); - config.put("verify_hostnames", verifyHostnames); - config.put("hosts", hosts); - config.put("bind_dn", bindDn); - config.put("password", password); - config.put("userbase", userBase); - config.put("usersearch", userSearch); - config.put("username_attribute", usernameAttribute); - config.put("pemtrustedcas_filepath", penTrustedCasFilePath); - return config; - } + private boolean enableSsl = false; + private boolean enableStartTls = false; + private boolean enableSslClientAuth = false; + private boolean verifyHostnames = false; + private List hosts; + private String bindDn; + private String password; + private String userBase; + private String userSearch; + private String usernameAttribute; + + private String penTrustedCasFilePath; + + /** + * Subclass of this + */ + private final T builderSubclass; + + protected LdapAuthenticationConfigBuilder(Function thisCastFunction) { + this.builderSubclass = thisCastFunction.apply(this); + } + + public static LdapAuthenticationConfigBuilder config() { + return new LdapAuthenticationConfigBuilder<>(Function.identity()); + } + + public T enableSsl(boolean enableSsl) { + this.enableSsl = enableSsl; + return builderSubclass; + } + + public T enableStartTls(boolean enableStartTls) { + this.enableStartTls = enableStartTls; + return builderSubclass; + } + + public T enableSslClientAuth(boolean enableSslClientAuth) { + this.enableSslClientAuth = enableSslClientAuth; + return builderSubclass; + } + + public T verifyHostnames(boolean verifyHostnames) { + this.verifyHostnames = verifyHostnames; + return builderSubclass; + } + + public T hosts(List hosts) { + this.hosts = hosts; + return builderSubclass; + } + + public T bindDn(String bindDn) { + this.bindDn = bindDn; + return builderSubclass; + } + + public T password(String password) { + this.password = password; + return builderSubclass; + } + + public T userBase(String userBase) { + this.userBase = userBase; + return builderSubclass; + } + + public T userSearch(String userSearch) { + this.userSearch = userSearch; + return builderSubclass; + } + + public T usernameAttribute(String usernameAttribute) { + this.usernameAttribute = usernameAttribute; + return builderSubclass; + } + + public T penTrustedCasFilePath(String penTrustedCasFilePath) { + this.penTrustedCasFilePath = penTrustedCasFilePath; + return builderSubclass; + } + + public Map build() { + HashMap config = new HashMap<>(); + config.put("enable_ssl", enableSsl); + config.put("enable_start_tls", enableStartTls); + config.put("enable_ssl_client_auth", enableSslClientAuth); + config.put("verify_hostnames", verifyHostnames); + config.put("hosts", hosts); + config.put("bind_dn", bindDn); + config.put("password", password); + config.put("userbase", userBase); + config.put("usersearch", userSearch); + config.put("username_attribute", usernameAttribute); + config.put("pemtrustedcas_filepath", penTrustedCasFilePath); + return config; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/LdapAuthorizationConfigBuilder.java b/src/integrationTest/java/org/opensearch/test/framework/LdapAuthorizationConfigBuilder.java index 43611d8d08..9f2a0abd83 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/LdapAuthorizationConfigBuilder.java +++ b/src/integrationTest/java/org/opensearch/test/framework/LdapAuthorizationConfigBuilder.java @@ -13,63 +13,63 @@ import java.util.Map; public class LdapAuthorizationConfigBuilder extends LdapAuthenticationConfigBuilder { - private List skipUsers; - private String roleBase; - private String roleSearch; - private String userRoleAttribute; - private String userRoleName; - private String roleName; - private boolean resolveNestedRoles; + private List skipUsers; + private String roleBase; + private String roleSearch; + private String userRoleAttribute; + private String userRoleName; + private String roleName; + private boolean resolveNestedRoles; - public LdapAuthorizationConfigBuilder() { - super(LdapAuthorizationConfigBuilder.class::cast); - } + public LdapAuthorizationConfigBuilder() { + super(LdapAuthorizationConfigBuilder.class::cast); + } - public LdapAuthorizationConfigBuilder skipUsers(List skipUsers) { - this.skipUsers = skipUsers; - return this; - } + public LdapAuthorizationConfigBuilder skipUsers(List skipUsers) { + this.skipUsers = skipUsers; + return this; + } - public LdapAuthorizationConfigBuilder roleBase(String roleBase) { - this.roleBase = roleBase; - return this; - } + public LdapAuthorizationConfigBuilder roleBase(String roleBase) { + this.roleBase = roleBase; + return this; + } - public LdapAuthorizationConfigBuilder roleSearch(String roleSearch) { - this.roleSearch = roleSearch; - return this; - } + public LdapAuthorizationConfigBuilder roleSearch(String roleSearch) { + this.roleSearch = roleSearch; + return this; + } - public LdapAuthorizationConfigBuilder userRoleAttribute(String userRoleAttribute) { - this.userRoleAttribute = userRoleAttribute; - return this; - } + public LdapAuthorizationConfigBuilder userRoleAttribute(String userRoleAttribute) { + this.userRoleAttribute = userRoleAttribute; + return this; + } - public LdapAuthorizationConfigBuilder userRoleName(String userRoleName) { - this.userRoleName = userRoleName; - return this; - } + public LdapAuthorizationConfigBuilder userRoleName(String userRoleName) { + this.userRoleName = userRoleName; + return this; + } - public LdapAuthorizationConfigBuilder roleName(String roleName) { - this.roleName = roleName; - return this; - } + public LdapAuthorizationConfigBuilder roleName(String roleName) { + this.roleName = roleName; + return this; + } - public LdapAuthorizationConfigBuilder resolveNestedRoles(boolean resolveNestedRoles) { - this.resolveNestedRoles = resolveNestedRoles; - return this; - } + public LdapAuthorizationConfigBuilder resolveNestedRoles(boolean resolveNestedRoles) { + this.resolveNestedRoles = resolveNestedRoles; + return this; + } - @Override - public Map build() { - Map map = super.build(); - map.put("skip_users", skipUsers); - map.put("rolebase", roleBase); - map.put("rolesearch", roleSearch); - map.put("userroleattribute", userRoleAttribute); - map.put("userrolename", userRoleName); - map.put("rolename", roleName); - map.put("resolve_nested_roles", resolveNestedRoles); - return map; - } + @Override + public Map build() { + Map map = super.build(); + map.put("skip_users", skipUsers); + map.put("rolebase", roleBase); + map.put("rolesearch", roleSearch); + map.put("userroleattribute", userRoleAttribute); + map.put("userrolename", userRoleName); + map.put("rolename", roleName); + map.put("resolve_nested_roles", resolveNestedRoles); + return map; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/RateLimiting.java b/src/integrationTest/java/org/opensearch/test/framework/RateLimiting.java index b41c6f8f1d..bd38aac1e5 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/RateLimiting.java +++ b/src/integrationTest/java/org/opensearch/test/framework/RateLimiting.java @@ -17,69 +17,69 @@ public class RateLimiting implements ToXContentObject { - private final String name; - private String type; - private String authenticationBackend; - private Integer allowedTries; - private Integer timeWindowSeconds; - private Integer blockExpirySeconds; - private Integer maxBlockedClients; - private Integer maxTrackedClients; + private final String name; + private String type; + private String authenticationBackend; + private Integer allowedTries; + private Integer timeWindowSeconds; + private Integer blockExpirySeconds; + private Integer maxBlockedClients; + private Integer maxTrackedClients; - public String getName() { - return name; - } + public String getName() { + return name; + } - public RateLimiting(String name) { - this.name = Objects.requireNonNull(name, "Rate limit name is required."); - } + public RateLimiting(String name) { + this.name = Objects.requireNonNull(name, "Rate limit name is required."); + } - public RateLimiting type(String type) { - this.type = type; - return this; - } + public RateLimiting type(String type) { + this.type = type; + return this; + } - public RateLimiting authenticationBackend(String authenticationBackend) { - this.authenticationBackend = authenticationBackend; - return this; - } + public RateLimiting authenticationBackend(String authenticationBackend) { + this.authenticationBackend = authenticationBackend; + return this; + } - public RateLimiting allowedTries(Integer allowedTries) { - this.allowedTries = allowedTries; - return this; - } + public RateLimiting allowedTries(Integer allowedTries) { + this.allowedTries = allowedTries; + return this; + } - public RateLimiting timeWindowSeconds(Integer timeWindowSeconds) { - this.timeWindowSeconds = timeWindowSeconds; - return this; - } + public RateLimiting timeWindowSeconds(Integer timeWindowSeconds) { + this.timeWindowSeconds = timeWindowSeconds; + return this; + } - public RateLimiting blockExpirySeconds(Integer blockExpirySeconds) { - this.blockExpirySeconds = blockExpirySeconds; - return this; - } + public RateLimiting blockExpirySeconds(Integer blockExpirySeconds) { + this.blockExpirySeconds = blockExpirySeconds; + return this; + } - public RateLimiting maxBlockedClients(Integer maxBlockedClients) { - this.maxBlockedClients = maxBlockedClients; - return this; - } + public RateLimiting maxBlockedClients(Integer maxBlockedClients) { + this.maxBlockedClients = maxBlockedClients; + return this; + } - public RateLimiting maxTrackedClients(Integer maxTrackedClients) { - this.maxTrackedClients = maxTrackedClients; - return this; - } + public RateLimiting maxTrackedClients(Integer maxTrackedClients) { + this.maxTrackedClients = maxTrackedClients; + return this; + } - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - xContentBuilder.field("type", type); - xContentBuilder.field("authentication_backend", authenticationBackend); - xContentBuilder.field("allowed_tries", allowedTries); - xContentBuilder.field("time_window_seconds", timeWindowSeconds); - xContentBuilder.field("block_expiry_seconds", blockExpirySeconds); - xContentBuilder.field("max_blocked_clients", maxBlockedClients); - xContentBuilder.field("max_tracked_clients", maxTrackedClients); - xContentBuilder.endObject(); - return xContentBuilder; - } + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("type", type); + xContentBuilder.field("authentication_backend", authenticationBackend); + xContentBuilder.field("allowed_tries", allowedTries); + xContentBuilder.field("time_window_seconds", timeWindowSeconds); + xContentBuilder.field("block_expiry_seconds", blockExpirySeconds); + xContentBuilder.field("max_blocked_clients", maxBlockedClients); + xContentBuilder.field("max_tracked_clients", maxTrackedClients); + xContentBuilder.endObject(); + return xContentBuilder; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java b/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java index a0f048f953..75c0325474 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java +++ b/src/integrationTest/java/org/opensearch/test/framework/RolesMapping.java @@ -27,71 +27,69 @@ */ public class RolesMapping implements ToXContentObject { - /** - * OpenSearch role name - */ - private String roleName; + /** + * OpenSearch role name + */ + private String roleName; - /** - * Backend role names - */ - private List backendRoles; + /** + * Backend role names + */ + private List backendRoles; - private boolean reserved = false; + private boolean reserved = false; - /** - * Creates roles mapping to OpenSearch role defined by parameter role - * @param role OpenSearch role, must not be null. - */ - public RolesMapping(Role role) { - requireNonNull(role); - this.roleName = requireNonNull(role.getName()); - this.backendRoles = new ArrayList<>(); - } + /** + * Creates roles mapping to OpenSearch role defined by parameter role + * @param role OpenSearch role, must not be null. + */ + public RolesMapping(Role role) { + requireNonNull(role); + this.roleName = requireNonNull(role.getName()); + this.backendRoles = new ArrayList<>(); + } + /** + * Defines backend role names + * @param backendRoles backend roles names + * @return current {@link RolesMapping} instance + */ + public RolesMapping backendRoles(String... backendRoles) { + this.backendRoles.addAll(Arrays.asList(backendRoles)); + return this; + } - /** - * Defines backend role names - * @param backendRoles backend roles names - * @return current {@link RolesMapping} instance - */ - public RolesMapping backendRoles(String...backendRoles) { - this.backendRoles.addAll(Arrays.asList(backendRoles)); - return this; - } + /** + * Determines if role is reserved + * @param reserved true for reserved roles + * @return current {@link RolesMapping} instance + */ + public RolesMapping reserved(boolean reserved) { + this.reserved = reserved; + return this; + } - /** - * Determines if role is reserved - * @param reserved true for reserved roles - * @return current {@link RolesMapping} instance - */ - public RolesMapping reserved(boolean reserved) { - this.reserved = reserved; - return this; - } + /** + * Returns OpenSearch role name + * @return role name + */ + public String getRoleName() { + return roleName; + } - - /** - * Returns OpenSearch role name - * @return role name - */ - public String getRoleName() { - return roleName; - } - - /** - * Controls serialization to JSON - * @param xContentBuilder must not be null - * @param params not used parameter, but required by the interface {@link ToXContentObject} - * @return builder form parameter xContentBuilder - * @throws IOException denotes error during serialization to JSON - */ - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - xContentBuilder.field("reserved", reserved); - xContentBuilder.field("backend_roles", backendRoles); - xContentBuilder.endObject(); - return xContentBuilder; - } + /** + * Controls serialization to JSON + * @param xContentBuilder must not be null + * @param params not used parameter, but required by the interface {@link ToXContentObject} + * @return builder form parameter xContentBuilder + * @throws IOException denotes error during serialization to JSON + */ + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("reserved", reserved); + xContentBuilder.field("backend_roles", backendRoles); + xContentBuilder.endObject(); + return xContentBuilder; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestIndex.java b/src/integrationTest/java/org/opensearch/test/framework/TestIndex.java index 9d5feb9eee..6f6bd935a5 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestIndex.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestIndex.java @@ -34,51 +34,50 @@ public class TestIndex { - private final String name; - private final Settings settings; - - public TestIndex(String name, Settings settings) { - this.name = name; - this.settings = settings; - - } - - public void create(Client client) { - client.admin().indices().create(new CreateIndexRequest(name).settings(settings)).actionGet(); - } - - public String getName() { - return name; - } - - - public static Builder name(String name) { - return new Builder().name(name); - } - - public static class Builder { - private String name; - private Settings.Builder settings = Settings.builder(); - - public Builder name(String name) { - this.name = name; - return this; - } - - public Builder setting(String name, int value) { - settings.put(name, value); - return this; - } - - public Builder shards(int value) { - settings.put("index.number_of_shards", 5); - return this; - } - - public TestIndex build() { - return new TestIndex(name, settings.build()); - } - - } + private final String name; + private final Settings settings; + + public TestIndex(String name, Settings settings) { + this.name = name; + this.settings = settings; + + } + + public void create(Client client) { + client.admin().indices().create(new CreateIndexRequest(name).settings(settings)).actionGet(); + } + + public String getName() { + return name; + } + + public static Builder name(String name) { + return new Builder().name(name); + } + + public static class Builder { + private String name; + private Settings.Builder settings = Settings.builder(); + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder setting(String name, int value) { + settings.put(name, value); + return this; + } + + public Builder shards(int value) { + settings.put("index.number_of_shards", 5); + return this; + } + + public TestIndex build() { + return new TestIndex(name, settings.build()); + } + + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index 43b98b02ce..a702102e6b 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -76,630 +76,642 @@ */ public class TestSecurityConfig { - private static final Logger log = LogManager.getLogger(TestSecurityConfig.class); - - private Config config = new Config(); - private Map internalUsers = new LinkedHashMap<>(); - private Map roles = new LinkedHashMap<>(); - private AuditConfiguration auditConfiguration; - private Map rolesMapping = new LinkedHashMap<>(); - - private String indexName = ".opendistro_security"; - - public TestSecurityConfig() { - - } - - public TestSecurityConfig configIndexName(String configIndexName) { - this.indexName = configIndexName; - return this; - } - - public TestSecurityConfig authFailureListeners(AuthFailureListeners listener) { - config.authFailureListeners(listener); - return this; - } - - public TestSecurityConfig anonymousAuth(boolean anonymousAuthEnabled) { - config.anonymousAuth(anonymousAuthEnabled); - return this; - } - - public TestSecurityConfig doNotFailOnForbidden(boolean doNotFailOnForbidden) { - config.doNotFailOnForbidden(doNotFailOnForbidden); - return this; - } - - public TestSecurityConfig xff(XffConfig xffConfig) { - config.xffConfig(xffConfig); - return this; - } - - public TestSecurityConfig authc(AuthcDomain authcDomain) { - config.authc(authcDomain); - return this; - } - - public TestSecurityConfig authz(AuthzDomain authzDomain) { - config.authz(authzDomain); - return this; - } - public TestSecurityConfig user(User user) { - this.internalUsers.put(user.name, user); - - for (Role role : user.roles) { - this.roles.put(role.name, role); - } - - return this; - } - - public List getUsers() { - return new ArrayList<>(internalUsers.values()); - } - - public TestSecurityConfig roles(Role... roles) { - for (Role role : roles) { - if(this.roles.containsKey(role.name)) { - throw new IllegalStateException("Role with name " + role.name + " is already defined"); - } - this.roles.put(role.name, role); - } - - return this; - } - - public TestSecurityConfig audit(AuditConfiguration auditConfiguration) { - this.auditConfiguration = auditConfiguration; - return this; - } - - public TestSecurityConfig rolesMapping(RolesMapping...mappings) { - for (RolesMapping mapping : mappings) { - String roleName = mapping.getRoleName(); - if(rolesMapping.containsKey(roleName)) { - throw new IllegalArgumentException("Role mapping " + roleName + " already exists"); - } - this.rolesMapping.put(roleName, mapping); - } - return this; - } - - public static class Config implements ToXContentObject { - private boolean anonymousAuth; - - private Boolean doNotFailOnForbidden; - private XffConfig xffConfig; - private Map authcDomainMap = new LinkedHashMap<>(); - - private AuthFailureListeners authFailureListeners; - private Map authzDomainMap = new LinkedHashMap<>(); - - public Config anonymousAuth(boolean anonymousAuth) { - this.anonymousAuth = anonymousAuth; - return this; - } - - public Config doNotFailOnForbidden(Boolean doNotFailOnForbidden) { - this.doNotFailOnForbidden = doNotFailOnForbidden; - return this; - } - - public Config xffConfig(XffConfig xffConfig) { - this.xffConfig = xffConfig; - return this; - } - - public Config authc(AuthcDomain authcDomain) { - authcDomainMap.put(authcDomain.id, authcDomain); - return this; - } - - public Config authFailureListeners(AuthFailureListeners authFailureListeners) { - this.authFailureListeners = authFailureListeners; - return this; - } - - public Config authz(AuthzDomain authzDomain) { - authzDomainMap.put(authzDomain.getId(), authzDomain); - return this; - } - - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - xContentBuilder.startObject("dynamic"); - - if (anonymousAuth || (xffConfig != null)) { - xContentBuilder.startObject("http"); - xContentBuilder.field("anonymous_auth_enabled", anonymousAuth); - if(xffConfig != null) { - xContentBuilder.field("xff", xffConfig); - } - xContentBuilder.endObject(); - } - if(doNotFailOnForbidden != null) { - xContentBuilder.field("do_not_fail_on_forbidden", doNotFailOnForbidden); - } - - xContentBuilder.field("authc", authcDomainMap); - if(authzDomainMap.isEmpty() == false) { - xContentBuilder.field("authz", authzDomainMap); - } - - if(authFailureListeners != null) { - xContentBuilder.field("auth_failure_listeners", authFailureListeners); - } - - xContentBuilder.endObject(); - xContentBuilder.endObject(); - return xContentBuilder; - } - } - - public static class User implements UserCredentialsHolder, ToXContentObject { - - public final static TestSecurityConfig.User USER_ADMIN = new TestSecurityConfig.User("admin") - .roles(new Role("allaccess").indexPermissions("*").on("*").clusterPermissions("*")); - - String name; - private String password; - List roles = new ArrayList<>(); - private Map attributes = new HashMap<>(); - - public User(String name) { - this.name = name; - this.password = "secret"; - } - - public User password(String password) { - this.password = password; - return this; - } - - public User roles(Role... roles) { - // We scope the role names by user to keep tests free of potential side effects - String roleNamePrefix = "user_" + this.getName() + "__"; - this.roles.addAll(Arrays.asList(roles).stream().map((r) -> r.clone().name(roleNamePrefix + r.getName())).collect(Collectors.toSet())); - return this; - } - - public User attr(String key, Object value) { - this.attributes.put(key, value); - return this; - } - - public String getName() { - return name; - } - - public String getPassword() { - return password; - } - - public Set getRoleNames() { - return roles.stream().map(Role::getName).collect(Collectors.toSet()); - } - - public Object getAttribute(String attributeName) { - return attributes.get(attributeName); - } - - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - - xContentBuilder.field("hash", hash(password.toCharArray())); - - Set roleNames = getRoleNames(); - - if (!roleNames.isEmpty()) { - xContentBuilder.field("opendistro_security_roles", roleNames); - } - - if (attributes != null && attributes.size() != 0) { - xContentBuilder.field("attributes", attributes); - } - - xContentBuilder.endObject(); - return xContentBuilder; - } - } - - public static class Role implements ToXContentObject { - public static Role ALL_ACCESS = new Role("all_access").clusterPermissions("*").indexPermissions("*").on("*"); - - private String name; - private List clusterPermissions = new ArrayList<>(); - - private List indexPermissions = new ArrayList<>(); - - public Role(String name) { - this.name = name; - } - - public Role clusterPermissions(String... clusterPermissions) { - this.clusterPermissions.addAll(Arrays.asList(clusterPermissions)); - return this; - } - - public IndexPermission indexPermissions(String... indexPermissions) { - return new IndexPermission(this, indexPermissions); - } - - public Role name(String name) { - this.name = name; - return this; - } - - public String getName() { - return name; - } - - public Role clone() { - Role role = new Role(this.name); - role.clusterPermissions.addAll(this.clusterPermissions); - role.indexPermissions.addAll(this.indexPermissions); - return role; - } - - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - - if (!clusterPermissions.isEmpty()) { - xContentBuilder.field("cluster_permissions", clusterPermissions); - } - - if (!indexPermissions.isEmpty()) { - xContentBuilder.field("index_permissions", indexPermissions); - } - - xContentBuilder.endObject(); - return xContentBuilder; - } - } - - public static class IndexPermission implements ToXContentObject { - private List allowedActions; - private List indexPatterns; - private Role role; - private String dlsQuery; - private List fls; - private List maskedFields; - - IndexPermission(Role role, String... allowedActions) { - this.allowedActions = Arrays.asList(allowedActions); - this.role = role; - } - - public IndexPermission dls(String dlsQuery) { - this.dlsQuery = dlsQuery; - return this; - } - - public IndexPermission fls(String... fls) { - this.fls = Arrays.asList(fls); - return this; - } - - public IndexPermission maskedFields(String... maskedFields) { - this.maskedFields = Arrays.asList(maskedFields); - return this; - } - - public Role on(String... indexPatterns) { - this.indexPatterns = Arrays.asList(indexPatterns); - this.role.indexPermissions.add(this); - return this.role; - } - - public Role on(TestIndex... testindices) { - this.indexPatterns = Arrays.asList(testindices).stream().map(TestIndex::getName).collect(Collectors.toList()); - this.role.indexPermissions.add(this); - return this.role; - } - - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - - xContentBuilder.field("index_patterns", indexPatterns); - xContentBuilder.field("allowed_actions", allowedActions); - - if (dlsQuery != null) { - xContentBuilder.field("dls", dlsQuery); - } - - if (fls != null) { - xContentBuilder.field("fls", fls); - } - - if (maskedFields != null) { - xContentBuilder.field("masked_fields", maskedFields); - } - - xContentBuilder.endObject(); - return xContentBuilder; - } - } - - public static class AuthcDomain implements ToXContentObject { - - private static String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqZbjLUAWc+DZTkinQAdvy1GFjPHPnxheU89hSiWoDD3NOW76H3u3T7cCDdOah2msdxSlBmCBH6wik8qLYkcV8owWukQg3PQmbEhrdPaKo0QCgomWs4nLgtmEYqcZ+QQldd82MdTlQ1QmoQmI9Uxqs1SuaKZASp3Gy19y8su5CV+FZ6BruUw9HELK055sAwl3X7j5ouabXGbcib2goBF3P52LkvbJLuWr5HDZEOeSkwIeqSeMojASM96K5SdotD+HwEyjaTjzRPL2Aa1BEQFWOQ6CFJLyLH7ZStDuPM1mJU1VxIVfMbZrhsUBjAnIhRynmWxML7YlNqkP9j6jyOIYQIDAQAB"; - - public static final int BASIC_AUTH_DOMAIN_ORDER = 0; - public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL = new TestSecurityConfig.AuthcDomain("basic", BASIC_AUTH_DOMAIN_ORDER) - .httpAuthenticatorWithChallenge("basic").backend("internal"); - - public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE = new TestSecurityConfig.AuthcDomain("basic", - BASIC_AUTH_DOMAIN_ORDER) - .httpAuthenticator("basic").backend("internal"); - - public final static AuthcDomain DISABLED_AUTHC_HTTPBASIC_INTERNAL = new TestSecurityConfig - .AuthcDomain("basic", BASIC_AUTH_DOMAIN_ORDER, false).httpAuthenticator("basic").backend("internal"); - - public final static AuthcDomain JWT_AUTH_DOMAIN = new TestSecurityConfig - .AuthcDomain("jwt", 1) - .jwtHttpAuthenticator(new JwtConfigBuilder().jwtHeader(AUTHORIZATION).signingKey(PUBLIC_KEY)).backend("noop"); - - private final String id; - private boolean enabled = true; - private int order; - private List skipUsers = new ArrayList<>(); - private HttpAuthenticator httpAuthenticator; - private AuthenticationBackend authenticationBackend; - - public AuthcDomain(String id, int order, boolean enabled) { - this.id = id; - this.order = order; - this.enabled = enabled; - } - - public AuthcDomain(String id, int order) { - this(id, order, true); - } - - public AuthcDomain httpAuthenticator(String type) { - this.httpAuthenticator = new HttpAuthenticator(type); - return this; - } - - public AuthcDomain jwtHttpAuthenticator(JwtConfigBuilder builder) { - this.httpAuthenticator = new HttpAuthenticator("jwt") - .challenge(false).config(builder.build()); - return this; - } - - public AuthcDomain httpAuthenticatorWithChallenge(String type) { - this.httpAuthenticator = new HttpAuthenticator(type).challenge(true); - return this; - } - - public AuthcDomain httpAuthenticator(HttpAuthenticator httpAuthenticator) { - this.httpAuthenticator = httpAuthenticator; - return this; - } - - public AuthcDomain backend(String type) { - this.authenticationBackend = new AuthenticationBackend(type); - return this; - } - - public AuthcDomain backend(AuthenticationBackend authenticationBackend) { - this.authenticationBackend = authenticationBackend; - return this; - } - - public AuthcDomain skipUsers(String... users) { - this.skipUsers.addAll(Arrays.asList(users)); - return this; - } - - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - - xContentBuilder.field("http_enabled", enabled); - xContentBuilder.field("order", order); - - if (httpAuthenticator != null) { - xContentBuilder.field("http_authenticator", httpAuthenticator); - } - - if (authenticationBackend != null) { - xContentBuilder.field("authentication_backend", authenticationBackend); - } - - if (skipUsers != null && skipUsers.size() > 0) { - xContentBuilder.field("skip_users", skipUsers); - } - - xContentBuilder.endObject(); - return xContentBuilder; - } - - public static class HttpAuthenticator implements ToXContentObject { - private final String type; - private boolean challenge; - private Map config = new HashMap(); - - public HttpAuthenticator(String type) { - this.type = type; - } - - public HttpAuthenticator challenge(boolean challenge) { - this.challenge = challenge; - return this; - } - - public HttpAuthenticator config(Map config) { - this.config.putAll(config); - return this; - } - - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - - xContentBuilder.field("type", type); - xContentBuilder.field("challenge", challenge); - xContentBuilder.field("config", config); - - xContentBuilder.endObject(); - return xContentBuilder; - } - } - - public static class AuthenticationBackend implements ToXContentObject { - private final String type; - private Supplier> config = () -> new HashMap(); - - public AuthenticationBackend(String type) { - this.type = type; - } - - public AuthenticationBackend config(Map config) { - Map configCopy = new HashMap<>(config); - this.config = () -> configCopy; - return this; - } - - public AuthenticationBackend config(Supplier> configSupplier) { - this.config = configSupplier; - return this; - } - - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - - xContentBuilder.field("type", type); - xContentBuilder.field("config", config.get()); - - xContentBuilder.endObject(); - return xContentBuilder; - } - } - } - - public void initIndex(Client client) { - Map settings = new HashMap<>(); - if (indexName.startsWith(".")) { - settings.put("index.hidden", true); - } - client.admin().indices().create(new CreateIndexRequest(indexName).settings(settings)).actionGet(); - - writeSingleEntryConfigToIndex(client, CType.CONFIG, config); - if(auditConfiguration != null) { - writeSingleEntryConfigToIndex(client, CType.AUDIT, "config", auditConfiguration); - } - writeConfigToIndex(client, CType.ROLES, roles); - writeConfigToIndex(client, CType.INTERNALUSERS, internalUsers); - writeConfigToIndex(client, CType.ROLESMAPPING, rolesMapping); - writeEmptyConfigToIndex(client, CType.ACTIONGROUPS); - writeEmptyConfigToIndex(client, CType.TENANTS); - } - - public void updateInternalUsersConfiguration(Client client, List users) { - Map userMap = new HashMap<>(); - for(User user : users) { - userMap.put(user.getName(), user); - } - updateConfigInIndex(client, CType.INTERNALUSERS, userMap); - } - - - static String hash(final char[] clearTextPassword) { - final byte[] salt = new byte[16]; - new SecureRandom().nextBytes(salt); - final String hash = OpenBSDBCrypt.generate((Objects.requireNonNull(clearTextPassword)), salt, 12); - Arrays.fill(salt, (byte) 0); - Arrays.fill(clearTextPassword, '\0'); - return hash; - } - - private void writeEmptyConfigToIndex(Client client, CType configType) { - writeConfigToIndex(client, configType, Collections.emptyMap()); - } - - private void writeConfigToIndex(Client client, CType configType, Map config) { - try { - String json = configToJson(configType, config); - - log.info("Writing security configuration into index " + configType + ":\n" + json); - - BytesReference bytesReference = toByteReference(json); - client.index(new IndexRequest(indexName).id(configType.toLCString()) - .setRefreshPolicy(IMMEDIATE).source(configType.toLCString(), bytesReference)) - .actionGet(); - } catch (Exception e) { - throw new RuntimeException("Error while initializing config for " + indexName, e); - } - } - - private static BytesReference toByteReference(String string) throws UnsupportedEncodingException { - return BytesReference.fromByteBuffer(ByteBuffer.wrap(string.getBytes("utf-8"))); - } - - private void updateConfigInIndex(Client client, CType configType, Map config) { - try { - String json = configToJson(configType, config); - BytesReference bytesReference = toByteReference(json); - log.info("Update configuration of type '{}' in index '{}', new value '{}'.", configType, indexName, json); - UpdateRequest upsert = new UpdateRequest(indexName, configType.toLCString()).doc(configType.toLCString(), bytesReference) - .setRefreshPolicy(IMMEDIATE); - client.update(upsert).actionGet(); - } catch (Exception e) { - throw new RuntimeException("Error while updating config for " + indexName, e); - } - } - - private static String configToJson(CType configType, Map config) throws IOException { - XContentBuilder builder = XContentFactory.jsonBuilder(); - - builder.startObject(); - builder.startObject("_meta"); - builder.field("type", configType.toLCString()); - builder.field("config_version", 2); - builder.endObject(); - - for (Map.Entry entry : config.entrySet()) { - builder.field(entry.getKey(), entry.getValue()); - } - - builder.endObject(); - - return Strings.toString(builder); - } - - private void writeSingleEntryConfigToIndex(Client client, CType configType, ToXContentObject config) { - writeSingleEntryConfigToIndex(client, configType, configType.toLCString(), config); - } - - private void writeSingleEntryConfigToIndex(Client client, CType configType, String configurationRoot, ToXContentObject config) { - try { - XContentBuilder builder = XContentFactory.jsonBuilder(); - - builder.startObject(); - builder.startObject("_meta"); - builder.field("type", configType.toLCString()); - builder.field("config_version", 2); - builder.endObject(); - - builder.field(configurationRoot, config); - - builder.endObject(); - - String json = Strings.toString(builder); - - log.info("Writing security plugin configuration into index " + configType + ":\n" + json); - - client.index(new IndexRequest(indexName).id(configType.toLCString()) - .setRefreshPolicy(IMMEDIATE).source(configType.toLCString(), toByteReference(json))) - .actionGet(); - } catch (Exception e) { - throw new RuntimeException("Error while initializing config for " + indexName, e); - } - } + private static final Logger log = LogManager.getLogger(TestSecurityConfig.class); + + private Config config = new Config(); + private Map internalUsers = new LinkedHashMap<>(); + private Map roles = new LinkedHashMap<>(); + private AuditConfiguration auditConfiguration; + private Map rolesMapping = new LinkedHashMap<>(); + + private String indexName = ".opendistro_security"; + + public TestSecurityConfig() { + + } + + public TestSecurityConfig configIndexName(String configIndexName) { + this.indexName = configIndexName; + return this; + } + + public TestSecurityConfig authFailureListeners(AuthFailureListeners listener) { + config.authFailureListeners(listener); + return this; + } + + public TestSecurityConfig anonymousAuth(boolean anonymousAuthEnabled) { + config.anonymousAuth(anonymousAuthEnabled); + return this; + } + + public TestSecurityConfig doNotFailOnForbidden(boolean doNotFailOnForbidden) { + config.doNotFailOnForbidden(doNotFailOnForbidden); + return this; + } + + public TestSecurityConfig xff(XffConfig xffConfig) { + config.xffConfig(xffConfig); + return this; + } + + public TestSecurityConfig authc(AuthcDomain authcDomain) { + config.authc(authcDomain); + return this; + } + + public TestSecurityConfig authz(AuthzDomain authzDomain) { + config.authz(authzDomain); + return this; + } + + public TestSecurityConfig user(User user) { + this.internalUsers.put(user.name, user); + + for (Role role : user.roles) { + this.roles.put(role.name, role); + } + + return this; + } + + public List getUsers() { + return new ArrayList<>(internalUsers.values()); + } + + public TestSecurityConfig roles(Role... roles) { + for (Role role : roles) { + if (this.roles.containsKey(role.name)) { + throw new IllegalStateException("Role with name " + role.name + " is already defined"); + } + this.roles.put(role.name, role); + } + + return this; + } + + public TestSecurityConfig audit(AuditConfiguration auditConfiguration) { + this.auditConfiguration = auditConfiguration; + return this; + } + + public TestSecurityConfig rolesMapping(RolesMapping... mappings) { + for (RolesMapping mapping : mappings) { + String roleName = mapping.getRoleName(); + if (rolesMapping.containsKey(roleName)) { + throw new IllegalArgumentException("Role mapping " + roleName + " already exists"); + } + this.rolesMapping.put(roleName, mapping); + } + return this; + } + + public static class Config implements ToXContentObject { + private boolean anonymousAuth; + + private Boolean doNotFailOnForbidden; + private XffConfig xffConfig; + private Map authcDomainMap = new LinkedHashMap<>(); + + private AuthFailureListeners authFailureListeners; + private Map authzDomainMap = new LinkedHashMap<>(); + + public Config anonymousAuth(boolean anonymousAuth) { + this.anonymousAuth = anonymousAuth; + return this; + } + + public Config doNotFailOnForbidden(Boolean doNotFailOnForbidden) { + this.doNotFailOnForbidden = doNotFailOnForbidden; + return this; + } + + public Config xffConfig(XffConfig xffConfig) { + this.xffConfig = xffConfig; + return this; + } + + public Config authc(AuthcDomain authcDomain) { + authcDomainMap.put(authcDomain.id, authcDomain); + return this; + } + + public Config authFailureListeners(AuthFailureListeners authFailureListeners) { + this.authFailureListeners = authFailureListeners; + return this; + } + + public Config authz(AuthzDomain authzDomain) { + authzDomainMap.put(authzDomain.getId(), authzDomain); + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.startObject("dynamic"); + + if (anonymousAuth || (xffConfig != null)) { + xContentBuilder.startObject("http"); + xContentBuilder.field("anonymous_auth_enabled", anonymousAuth); + if (xffConfig != null) { + xContentBuilder.field("xff", xffConfig); + } + xContentBuilder.endObject(); + } + if (doNotFailOnForbidden != null) { + xContentBuilder.field("do_not_fail_on_forbidden", doNotFailOnForbidden); + } + + xContentBuilder.field("authc", authcDomainMap); + if (authzDomainMap.isEmpty() == false) { + xContentBuilder.field("authz", authzDomainMap); + } + + if (authFailureListeners != null) { + xContentBuilder.field("auth_failure_listeners", authFailureListeners); + } + + xContentBuilder.endObject(); + xContentBuilder.endObject(); + return xContentBuilder; + } + } + + public static class User implements UserCredentialsHolder, ToXContentObject { + + public final static TestSecurityConfig.User USER_ADMIN = new TestSecurityConfig.User("admin").roles( + new Role("allaccess").indexPermissions("*").on("*").clusterPermissions("*") + ); + + String name; + private String password; + List roles = new ArrayList<>(); + private Map attributes = new HashMap<>(); + + public User(String name) { + this.name = name; + this.password = "secret"; + } + + public User password(String password) { + this.password = password; + return this; + } + + public User roles(Role... roles) { + // We scope the role names by user to keep tests free of potential side effects + String roleNamePrefix = "user_" + this.getName() + "__"; + this.roles.addAll( + Arrays.asList(roles).stream().map((r) -> r.clone().name(roleNamePrefix + r.getName())).collect(Collectors.toSet()) + ); + return this; + } + + public User attr(String key, Object value) { + this.attributes.put(key, value); + return this; + } + + public String getName() { + return name; + } + + public String getPassword() { + return password; + } + + public Set getRoleNames() { + return roles.stream().map(Role::getName).collect(Collectors.toSet()); + } + + public Object getAttribute(String attributeName) { + return attributes.get(attributeName); + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + + xContentBuilder.field("hash", hash(password.toCharArray())); + + Set roleNames = getRoleNames(); + + if (!roleNames.isEmpty()) { + xContentBuilder.field("opendistro_security_roles", roleNames); + } + + if (attributes != null && attributes.size() != 0) { + xContentBuilder.field("attributes", attributes); + } + + xContentBuilder.endObject(); + return xContentBuilder; + } + } + + public static class Role implements ToXContentObject { + public static Role ALL_ACCESS = new Role("all_access").clusterPermissions("*").indexPermissions("*").on("*"); + + private String name; + private List clusterPermissions = new ArrayList<>(); + + private List indexPermissions = new ArrayList<>(); + + public Role(String name) { + this.name = name; + } + + public Role clusterPermissions(String... clusterPermissions) { + this.clusterPermissions.addAll(Arrays.asList(clusterPermissions)); + return this; + } + + public IndexPermission indexPermissions(String... indexPermissions) { + return new IndexPermission(this, indexPermissions); + } + + public Role name(String name) { + this.name = name; + return this; + } + + public String getName() { + return name; + } + + public Role clone() { + Role role = new Role(this.name); + role.clusterPermissions.addAll(this.clusterPermissions); + role.indexPermissions.addAll(this.indexPermissions); + return role; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + + if (!clusterPermissions.isEmpty()) { + xContentBuilder.field("cluster_permissions", clusterPermissions); + } + + if (!indexPermissions.isEmpty()) { + xContentBuilder.field("index_permissions", indexPermissions); + } + + xContentBuilder.endObject(); + return xContentBuilder; + } + } + + public static class IndexPermission implements ToXContentObject { + private List allowedActions; + private List indexPatterns; + private Role role; + private String dlsQuery; + private List fls; + private List maskedFields; + + IndexPermission(Role role, String... allowedActions) { + this.allowedActions = Arrays.asList(allowedActions); + this.role = role; + } + + public IndexPermission dls(String dlsQuery) { + this.dlsQuery = dlsQuery; + return this; + } + + public IndexPermission fls(String... fls) { + this.fls = Arrays.asList(fls); + return this; + } + + public IndexPermission maskedFields(String... maskedFields) { + this.maskedFields = Arrays.asList(maskedFields); + return this; + } + + public Role on(String... indexPatterns) { + this.indexPatterns = Arrays.asList(indexPatterns); + this.role.indexPermissions.add(this); + return this.role; + } + + public Role on(TestIndex... testindices) { + this.indexPatterns = Arrays.asList(testindices).stream().map(TestIndex::getName).collect(Collectors.toList()); + this.role.indexPermissions.add(this); + return this.role; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + + xContentBuilder.field("index_patterns", indexPatterns); + xContentBuilder.field("allowed_actions", allowedActions); + + if (dlsQuery != null) { + xContentBuilder.field("dls", dlsQuery); + } + + if (fls != null) { + xContentBuilder.field("fls", fls); + } + + if (maskedFields != null) { + xContentBuilder.field("masked_fields", maskedFields); + } + + xContentBuilder.endObject(); + return xContentBuilder; + } + } + + public static class AuthcDomain implements ToXContentObject { + + private static String PUBLIC_KEY = + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqZbjLUAWc+DZTkinQAdvy1GFjPHPnxheU89hSiWoDD3NOW76H3u3T7cCDdOah2msdxSlBmCBH6wik8qLYkcV8owWukQg3PQmbEhrdPaKo0QCgomWs4nLgtmEYqcZ+QQldd82MdTlQ1QmoQmI9Uxqs1SuaKZASp3Gy19y8su5CV+FZ6BruUw9HELK055sAwl3X7j5ouabXGbcib2goBF3P52LkvbJLuWr5HDZEOeSkwIeqSeMojASM96K5SdotD+HwEyjaTjzRPL2Aa1BEQFWOQ6CFJLyLH7ZStDuPM1mJU1VxIVfMbZrhsUBjAnIhRynmWxML7YlNqkP9j6jyOIYQIDAQAB"; + + public static final int BASIC_AUTH_DOMAIN_ORDER = 0; + public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL = new TestSecurityConfig.AuthcDomain("basic", BASIC_AUTH_DOMAIN_ORDER) + .httpAuthenticatorWithChallenge("basic") + .backend("internal"); + + public final static AuthcDomain AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE = new TestSecurityConfig.AuthcDomain( + "basic", + BASIC_AUTH_DOMAIN_ORDER + ).httpAuthenticator("basic").backend("internal"); + + public final static AuthcDomain DISABLED_AUTHC_HTTPBASIC_INTERNAL = new TestSecurityConfig.AuthcDomain( + "basic", + BASIC_AUTH_DOMAIN_ORDER, + false + ).httpAuthenticator("basic").backend("internal"); + + public final static AuthcDomain JWT_AUTH_DOMAIN = new TestSecurityConfig.AuthcDomain("jwt", 1).jwtHttpAuthenticator( + new JwtConfigBuilder().jwtHeader(AUTHORIZATION).signingKey(PUBLIC_KEY) + ).backend("noop"); + + private final String id; + private boolean enabled = true; + private int order; + private List skipUsers = new ArrayList<>(); + private HttpAuthenticator httpAuthenticator; + private AuthenticationBackend authenticationBackend; + + public AuthcDomain(String id, int order, boolean enabled) { + this.id = id; + this.order = order; + this.enabled = enabled; + } + + public AuthcDomain(String id, int order) { + this(id, order, true); + } + + public AuthcDomain httpAuthenticator(String type) { + this.httpAuthenticator = new HttpAuthenticator(type); + return this; + } + + public AuthcDomain jwtHttpAuthenticator(JwtConfigBuilder builder) { + this.httpAuthenticator = new HttpAuthenticator("jwt").challenge(false).config(builder.build()); + return this; + } + + public AuthcDomain httpAuthenticatorWithChallenge(String type) { + this.httpAuthenticator = new HttpAuthenticator(type).challenge(true); + return this; + } + + public AuthcDomain httpAuthenticator(HttpAuthenticator httpAuthenticator) { + this.httpAuthenticator = httpAuthenticator; + return this; + } + + public AuthcDomain backend(String type) { + this.authenticationBackend = new AuthenticationBackend(type); + return this; + } + + public AuthcDomain backend(AuthenticationBackend authenticationBackend) { + this.authenticationBackend = authenticationBackend; + return this; + } + + public AuthcDomain skipUsers(String... users) { + this.skipUsers.addAll(Arrays.asList(users)); + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + + xContentBuilder.field("http_enabled", enabled); + xContentBuilder.field("order", order); + + if (httpAuthenticator != null) { + xContentBuilder.field("http_authenticator", httpAuthenticator); + } + + if (authenticationBackend != null) { + xContentBuilder.field("authentication_backend", authenticationBackend); + } + + if (skipUsers != null && skipUsers.size() > 0) { + xContentBuilder.field("skip_users", skipUsers); + } + + xContentBuilder.endObject(); + return xContentBuilder; + } + + public static class HttpAuthenticator implements ToXContentObject { + private final String type; + private boolean challenge; + private Map config = new HashMap(); + + public HttpAuthenticator(String type) { + this.type = type; + } + + public HttpAuthenticator challenge(boolean challenge) { + this.challenge = challenge; + return this; + } + + public HttpAuthenticator config(Map config) { + this.config.putAll(config); + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + + xContentBuilder.field("type", type); + xContentBuilder.field("challenge", challenge); + xContentBuilder.field("config", config); + + xContentBuilder.endObject(); + return xContentBuilder; + } + } + + public static class AuthenticationBackend implements ToXContentObject { + private final String type; + private Supplier> config = () -> new HashMap(); + + public AuthenticationBackend(String type) { + this.type = type; + } + + public AuthenticationBackend config(Map config) { + Map configCopy = new HashMap<>(config); + this.config = () -> configCopy; + return this; + } + + public AuthenticationBackend config(Supplier> configSupplier) { + this.config = configSupplier; + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + + xContentBuilder.field("type", type); + xContentBuilder.field("config", config.get()); + + xContentBuilder.endObject(); + return xContentBuilder; + } + } + } + + public void initIndex(Client client) { + Map settings = new HashMap<>(); + if (indexName.startsWith(".")) { + settings.put("index.hidden", true); + } + client.admin().indices().create(new CreateIndexRequest(indexName).settings(settings)).actionGet(); + + writeSingleEntryConfigToIndex(client, CType.CONFIG, config); + if (auditConfiguration != null) { + writeSingleEntryConfigToIndex(client, CType.AUDIT, "config", auditConfiguration); + } + writeConfigToIndex(client, CType.ROLES, roles); + writeConfigToIndex(client, CType.INTERNALUSERS, internalUsers); + writeConfigToIndex(client, CType.ROLESMAPPING, rolesMapping); + writeEmptyConfigToIndex(client, CType.ACTIONGROUPS); + writeEmptyConfigToIndex(client, CType.TENANTS); + } + + public void updateInternalUsersConfiguration(Client client, List users) { + Map userMap = new HashMap<>(); + for (User user : users) { + userMap.put(user.getName(), user); + } + updateConfigInIndex(client, CType.INTERNALUSERS, userMap); + } + + static String hash(final char[] clearTextPassword) { + final byte[] salt = new byte[16]; + new SecureRandom().nextBytes(salt); + final String hash = OpenBSDBCrypt.generate((Objects.requireNonNull(clearTextPassword)), salt, 12); + Arrays.fill(salt, (byte) 0); + Arrays.fill(clearTextPassword, '\0'); + return hash; + } + + private void writeEmptyConfigToIndex(Client client, CType configType) { + writeConfigToIndex(client, configType, Collections.emptyMap()); + } + + private void writeConfigToIndex(Client client, CType configType, Map config) { + try { + String json = configToJson(configType, config); + + log.info("Writing security configuration into index " + configType + ":\n" + json); + + BytesReference bytesReference = toByteReference(json); + client.index( + new IndexRequest(indexName).id(configType.toLCString()) + .setRefreshPolicy(IMMEDIATE) + .source(configType.toLCString(), bytesReference) + ).actionGet(); + } catch (Exception e) { + throw new RuntimeException("Error while initializing config for " + indexName, e); + } + } + + private static BytesReference toByteReference(String string) throws UnsupportedEncodingException { + return BytesReference.fromByteBuffer(ByteBuffer.wrap(string.getBytes("utf-8"))); + } + + private void updateConfigInIndex(Client client, CType configType, Map config) { + try { + String json = configToJson(configType, config); + BytesReference bytesReference = toByteReference(json); + log.info("Update configuration of type '{}' in index '{}', new value '{}'.", configType, indexName, json); + UpdateRequest upsert = new UpdateRequest(indexName, configType.toLCString()).doc(configType.toLCString(), bytesReference) + .setRefreshPolicy(IMMEDIATE); + client.update(upsert).actionGet(); + } catch (Exception e) { + throw new RuntimeException("Error while updating config for " + indexName, e); + } + } + + private static String configToJson(CType configType, Map config) throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder(); + + builder.startObject(); + builder.startObject("_meta"); + builder.field("type", configType.toLCString()); + builder.field("config_version", 2); + builder.endObject(); + + for (Map.Entry entry : config.entrySet()) { + builder.field(entry.getKey(), entry.getValue()); + } + + builder.endObject(); + + return Strings.toString(builder); + } + + private void writeSingleEntryConfigToIndex(Client client, CType configType, ToXContentObject config) { + writeSingleEntryConfigToIndex(client, configType, configType.toLCString(), config); + } + + private void writeSingleEntryConfigToIndex(Client client, CType configType, String configurationRoot, ToXContentObject config) { + try { + XContentBuilder builder = XContentFactory.jsonBuilder(); + + builder.startObject(); + builder.startObject("_meta"); + builder.field("type", configType.toLCString()); + builder.field("config_version", 2); + builder.endObject(); + + builder.field(configurationRoot, config); + + builder.endObject(); + + String json = Strings.toString(builder); + + log.info("Writing security plugin configuration into index " + configType + ":\n" + json); + + client.index( + new IndexRequest(indexName).id(configType.toLCString()) + .setRefreshPolicy(IMMEDIATE) + .source(configType.toLCString(), toByteReference(json)) + ).actionGet(); + } catch (Exception e) { + throw new RuntimeException("Error while initializing config for " + indexName, e); + } + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/XffConfig.java b/src/integrationTest/java/org/opensearch/test/framework/XffConfig.java index fa4ec5d849..b1c10bfd73 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/XffConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/XffConfig.java @@ -39,44 +39,44 @@ */ public class XffConfig implements ToXContentObject { - private final boolean enabled; + private final boolean enabled; - /** - * Regular expression used to determine if HTTP proxy is trusted or not. IP address of trusted proxies must match the regular - * expression defined by the below field. - */ - private String internalProxiesRegexp; + /** + * Regular expression used to determine if HTTP proxy is trusted or not. IP address of trusted proxies must match the regular + * expression defined by the below field. + */ + private String internalProxiesRegexp; - private String remoteIpHeader; + private String remoteIpHeader; - public XffConfig(boolean enabled) { - this.enabled = enabled; - } + public XffConfig(boolean enabled) { + this.enabled = enabled; + } - /** - * Builder-like method used to set value of the field {@link #internalProxiesRegexp} - * @param internalProxiesRegexp regular expression which matches IP address of a HTTP proxies if the proxies are trusted. - * @return builder - */ - public XffConfig internalProxiesRegexp(String internalProxiesRegexp) { - this.internalProxiesRegexp = internalProxiesRegexp; - return this; - } + /** + * Builder-like method used to set value of the field {@link #internalProxiesRegexp} + * @param internalProxiesRegexp regular expression which matches IP address of a HTTP proxies if the proxies are trusted. + * @return builder + */ + public XffConfig internalProxiesRegexp(String internalProxiesRegexp) { + this.internalProxiesRegexp = internalProxiesRegexp; + return this; + } - public XffConfig remoteIpHeader(String remoteIpHeader) { - this.remoteIpHeader = remoteIpHeader; - return this; - } + public XffConfig remoteIpHeader(String remoteIpHeader) { + this.remoteIpHeader = remoteIpHeader; + return this; + } - @Override - public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { - xContentBuilder.startObject(); - xContentBuilder.field("enabled", enabled); - xContentBuilder.field("internalProxies", internalProxiesRegexp); - if(StringUtils.isNoneBlank(remoteIpHeader)) { - xContentBuilder.field("remoteIpHeader", remoteIpHeader); - } - xContentBuilder.endObject(); - return xContentBuilder; - } + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("enabled", enabled); + xContentBuilder.field("internalProxies", internalProxiesRegexp); + if (StringUtils.isNoneBlank(remoteIpHeader)) { + xContentBuilder.field("remoteIpHeader", remoteIpHeader); + } + xContentBuilder.endObject(); + return xContentBuilder; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/audit/AuditLogsRule.java b/src/integrationTest/java/org/opensearch/test/framework/audit/AuditLogsRule.java index 9f19b4e5db..911173e14a 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/audit/AuditLogsRule.java +++ b/src/integrationTest/java/org/opensearch/test/framework/audit/AuditLogsRule.java @@ -36,94 +36,98 @@ public class AuditLogsRule implements TestRule { - private static final Logger log = LogManager.getLogger(AuditLogsRule.class); - - private List currentTestAuditMessages; - - public void waitForAuditLogs() { - try { - TimeUnit.SECONDS.sleep(3); - afterWaitingForAuditLogs(); - } catch (InterruptedException e) { - throw new RuntimeException("Waiting for audit logs interrupted.", e); - } - } - - private void afterWaitingForAuditLogs() { - if(log.isDebugEnabled()) { - log.debug("Audit records captured during test:\n{}", auditMessagesToString(currentTestAuditMessages)); - } - } - - public void assertExactlyOne(Predicate predicate) { - assertExactly(1, predicate); - } - - public void assertAuditLogsCount(int from, int to) { - int actualCount = currentTestAuditMessages.size(); - String message = "Expected audit log count is between " + from + " and " + to + " but was " + actualCount; - assertThat(message, actualCount, allOf(greaterThanOrEqualTo(from), lessThanOrEqualTo(to))); - } - - public void assertExactly(long expectedNumberOfAuditMessages, Predicate predicate) { - assertExactly(exactNumberOfAuditsFulfillPredicate(expectedNumberOfAuditMessages, predicate)); - } - - private void assertExactly(Matcher> matcher) { - //pollDelay - initial delay before first evaluation - Awaitility.await("Await for audit logs") - .atMost(3, TimeUnit.SECONDS).pollDelay(0, TimeUnit.MICROSECONDS) - .until(() -> new ArrayList<>(currentTestAuditMessages), matcher); - } - - public void assertAtLeast(long minCount, Predicate predicate) { - assertExactly(atLeastCertainNumberOfAuditsFulfillPredicate(minCount, predicate)); - } - - private static String auditMessagesToString(List audits) { - return audits.stream().map(AuditMessage::toString).collect(Collectors.joining(",\n")); - } - - @Override - public Statement apply(Statement statement, Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - String methodName = description.getMethodName(); - beforeTest(methodName); - try { - statement.evaluate(); - } catch (ConditionTimeoutException ex) { - whenTimeoutOccurs(methodName); - throw ex; - } - finally { - afterTest(); - } - } - }; - } - - private void whenTimeoutOccurs(String methodName) { - List copy = new ArrayList<>(currentTestAuditMessages); - String auditMessages = auditMessagesToString(copy); - log.error("Timeout occured due to insufficient number ('{}') of captured audit messages during test '{}'\n{}", - copy.size(), methodName, auditMessages); - } - - private void afterTest() { - TestRuleAuditLogSink.unregisterListener(); - this.currentTestAuditMessages = null; - } - - private void beforeTest(String methodName) { - log.info("Start collecting audit logs before test {}", methodName); - this.currentTestAuditMessages = synchronizedList(new ArrayList<>()); - TestRuleAuditLogSink.registerListener(this); - } - - public void onAuditMessage(AuditMessage auditMessage) { - currentTestAuditMessages.add(auditMessage); - log.debug("New audit message received '{}', total number of audit messages '{}'.", auditMessage, currentTestAuditMessages.size()); - } + private static final Logger log = LogManager.getLogger(AuditLogsRule.class); + + private List currentTestAuditMessages; + + public void waitForAuditLogs() { + try { + TimeUnit.SECONDS.sleep(3); + afterWaitingForAuditLogs(); + } catch (InterruptedException e) { + throw new RuntimeException("Waiting for audit logs interrupted.", e); + } + } + + private void afterWaitingForAuditLogs() { + if (log.isDebugEnabled()) { + log.debug("Audit records captured during test:\n{}", auditMessagesToString(currentTestAuditMessages)); + } + } + + public void assertExactlyOne(Predicate predicate) { + assertExactly(1, predicate); + } + + public void assertAuditLogsCount(int from, int to) { + int actualCount = currentTestAuditMessages.size(); + String message = "Expected audit log count is between " + from + " and " + to + " but was " + actualCount; + assertThat(message, actualCount, allOf(greaterThanOrEqualTo(from), lessThanOrEqualTo(to))); + } + + public void assertExactly(long expectedNumberOfAuditMessages, Predicate predicate) { + assertExactly(exactNumberOfAuditsFulfillPredicate(expectedNumberOfAuditMessages, predicate)); + } + + private void assertExactly(Matcher> matcher) { + // pollDelay - initial delay before first evaluation + Awaitility.await("Await for audit logs") + .atMost(3, TimeUnit.SECONDS) + .pollDelay(0, TimeUnit.MICROSECONDS) + .until(() -> new ArrayList<>(currentTestAuditMessages), matcher); + } + + public void assertAtLeast(long minCount, Predicate predicate) { + assertExactly(atLeastCertainNumberOfAuditsFulfillPredicate(minCount, predicate)); + } + + private static String auditMessagesToString(List audits) { + return audits.stream().map(AuditMessage::toString).collect(Collectors.joining(",\n")); + } + + @Override + public Statement apply(Statement statement, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + String methodName = description.getMethodName(); + beforeTest(methodName); + try { + statement.evaluate(); + } catch (ConditionTimeoutException ex) { + whenTimeoutOccurs(methodName); + throw ex; + } finally { + afterTest(); + } + } + }; + } + + private void whenTimeoutOccurs(String methodName) { + List copy = new ArrayList<>(currentTestAuditMessages); + String auditMessages = auditMessagesToString(copy); + log.error( + "Timeout occured due to insufficient number ('{}') of captured audit messages during test '{}'\n{}", + copy.size(), + methodName, + auditMessages + ); + } + + private void afterTest() { + TestRuleAuditLogSink.unregisterListener(); + this.currentTestAuditMessages = null; + } + + private void beforeTest(String methodName) { + log.info("Start collecting audit logs before test {}", methodName); + this.currentTestAuditMessages = synchronizedList(new ArrayList<>()); + TestRuleAuditLogSink.registerListener(this); + } + + public void onAuditMessage(AuditMessage auditMessage) { + currentTestAuditMessages.add(auditMessage); + log.debug("New audit message received '{}', total number of audit messages '{}'.", auditMessage, currentTestAuditMessages.size()); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/audit/AuditMessagePredicate.java b/src/integrationTest/java/org/opensearch/test/framework/audit/AuditMessagePredicate.java index 940294b938..922f2d54aa 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/audit/AuditMessagePredicate.java +++ b/src/integrationTest/java/org/opensearch/test/framework/audit/AuditMessagePredicate.java @@ -33,122 +33,201 @@ public class AuditMessagePredicate implements Predicate { - private final AuditCategory category; - private final Origin requestLayer; - private final String restRequestPath; - private final String initiatingUser; - private final Method requestMethod; - private final String transportRequestType; - private final String effectiveUser; - private final String index; - - private AuditMessagePredicate(AuditCategory category, Origin requestLayer, String restRequestPath, - String initiatingUser, Method requestMethod, String transportRequestType, String effectiveUser, String index) { - this.category = category; - this.requestLayer = requestLayer; - this.restRequestPath = restRequestPath; - this.initiatingUser = initiatingUser; - this.requestMethod = requestMethod; - this.transportRequestType = transportRequestType; - this.effectiveUser = effectiveUser; - this.index = index; - } - - private AuditMessagePredicate(AuditCategory category) { - this(category, null, null, null, null, null, null, null); - } - - public static AuditMessagePredicate auditPredicate(AuditCategory category) { - return new AuditMessagePredicate(category); - } - - public static AuditMessagePredicate userAuthenticated(User user) { - return auditPredicate(AUTHENTICATED).withInitiatingUser(user); - } - - public static AuditMessagePredicate grantedPrivilege(User user, String requestType) { - return auditPredicate(GRANTED_PRIVILEGES).withLayer(Origin.TRANSPORT).withEffectiveUser(user) - .withTransportRequestType(requestType); - } - - public static AuditMessagePredicate missingPrivilege(User user, String requestType) { - return auditPredicate(MISSING_PRIVILEGES).withLayer(Origin.TRANSPORT).withEffectiveUser(user) - .withTransportRequestType(requestType); - } - - public AuditMessagePredicate withLayer(Origin layer) { - return new AuditMessagePredicate(category, layer, restRequestPath, initiatingUser, requestMethod, - transportRequestType, effectiveUser, index); - } - - public AuditMessagePredicate withRequestPath(String path) { - return new AuditMessagePredicate(category, requestLayer, path, initiatingUser, requestMethod, transportRequestType, effectiveUser, - index); - } - - public AuditMessagePredicate withInitiatingUser(String user) { - return new AuditMessagePredicate(category, requestLayer, restRequestPath, user, requestMethod, transportRequestType, effectiveUser, - index); - } - - public AuditMessagePredicate withInitiatingUser(User user) { - return withInitiatingUser(user.getName()); - } - - public AuditMessagePredicate withRestMethod(Method method) { - return new AuditMessagePredicate(category, requestLayer, restRequestPath, initiatingUser, method, - transportRequestType, effectiveUser, index); - } - - public AuditMessagePredicate withTransportRequestType(String type) { - return new AuditMessagePredicate(category, requestLayer, restRequestPath, initiatingUser, requestMethod, type, effectiveUser, index); - } - - public AuditMessagePredicate withEffectiveUser(String user) { - return new AuditMessagePredicate(category, requestLayer, restRequestPath, initiatingUser, requestMethod, transportRequestType, user, - index); - } - - public AuditMessagePredicate withEffectiveUser(User user) { - return withEffectiveUser(user.getName()); - } - - public AuditMessagePredicate withRestRequest(Method method, String path) { - return this.withLayer(Origin.REST).withRestMethod(method).withRequestPath(path); - } - - public AuditMessagePredicate withIndex(String indexName) { - return new AuditMessagePredicate(category, requestLayer, restRequestPath, initiatingUser, requestMethod, transportRequestType, - effectiveUser, indexName); - } - - @Override - public boolean test(AuditMessage auditMessage) { - List> predicates = new ArrayList<>(); - predicates.add(audit -> Objects.isNull(category) || category.equals(audit.getCategory())); - predicates.add(audit -> Objects.isNull(requestLayer) || requestLayer.equals(audit.getAsMap().get(REQUEST_LAYER))); - predicates.add(audit -> Objects.isNull(restRequestPath) || restRequestPath.equals(audit.getAsMap().get(REST_REQUEST_PATH))); - predicates.add(audit -> Objects.isNull(initiatingUser) || initiatingUser.equals(audit.getInitiatingUser())); - predicates.add(audit -> Objects.isNull(requestMethod) || requestMethod.equals(audit.getRequestMethod())); - predicates.add(audit -> Objects.isNull(transportRequestType) || transportRequestType.equals(audit.getRequestType())); - predicates.add(audit -> Objects.isNull(effectiveUser) || effectiveUser.equals(audit.getEffectiveUser())); - predicates.add(audit -> Objects.isNull(index) || containIndex(audit, index)); - return predicates.stream().reduce(Predicate::and).orElseThrow().test(auditMessage); - } - - private boolean containIndex(AuditMessage auditMessage, String indexName) { - Map audit = auditMessage.getAsMap(); - return Optional.ofNullable(audit.get(RESOLVED_INDICES)) - .filter(String[].class::isInstance) - .map(String[].class::cast) - .stream() - .flatMap(Arrays::stream) - .collect(Collectors.toSet()) - .contains(indexName); - } - - @Override - public String toString() { - return "AuditMessagePredicate{" + "category=" + category + ", requestLayer=" + requestLayer + ", restRequestPath='" + restRequestPath + '\'' + ", requestInitiatingUser='" + initiatingUser + '\'' + ", requestMethod=" + requestMethod + ", transportRequestType='" + transportRequestType + '\'' + '}'; - } + private final AuditCategory category; + private final Origin requestLayer; + private final String restRequestPath; + private final String initiatingUser; + private final Method requestMethod; + private final String transportRequestType; + private final String effectiveUser; + private final String index; + + private AuditMessagePredicate( + AuditCategory category, + Origin requestLayer, + String restRequestPath, + String initiatingUser, + Method requestMethod, + String transportRequestType, + String effectiveUser, + String index + ) { + this.category = category; + this.requestLayer = requestLayer; + this.restRequestPath = restRequestPath; + this.initiatingUser = initiatingUser; + this.requestMethod = requestMethod; + this.transportRequestType = transportRequestType; + this.effectiveUser = effectiveUser; + this.index = index; + } + + private AuditMessagePredicate(AuditCategory category) { + this(category, null, null, null, null, null, null, null); + } + + public static AuditMessagePredicate auditPredicate(AuditCategory category) { + return new AuditMessagePredicate(category); + } + + public static AuditMessagePredicate userAuthenticated(User user) { + return auditPredicate(AUTHENTICATED).withInitiatingUser(user); + } + + public static AuditMessagePredicate grantedPrivilege(User user, String requestType) { + return auditPredicate(GRANTED_PRIVILEGES).withLayer(Origin.TRANSPORT).withEffectiveUser(user).withTransportRequestType(requestType); + } + + public static AuditMessagePredicate missingPrivilege(User user, String requestType) { + return auditPredicate(MISSING_PRIVILEGES).withLayer(Origin.TRANSPORT).withEffectiveUser(user).withTransportRequestType(requestType); + } + + public AuditMessagePredicate withLayer(Origin layer) { + return new AuditMessagePredicate( + category, + layer, + restRequestPath, + initiatingUser, + requestMethod, + transportRequestType, + effectiveUser, + index + ); + } + + public AuditMessagePredicate withRequestPath(String path) { + return new AuditMessagePredicate( + category, + requestLayer, + path, + initiatingUser, + requestMethod, + transportRequestType, + effectiveUser, + index + ); + } + + public AuditMessagePredicate withInitiatingUser(String user) { + return new AuditMessagePredicate( + category, + requestLayer, + restRequestPath, + user, + requestMethod, + transportRequestType, + effectiveUser, + index + ); + } + + public AuditMessagePredicate withInitiatingUser(User user) { + return withInitiatingUser(user.getName()); + } + + public AuditMessagePredicate withRestMethod(Method method) { + return new AuditMessagePredicate( + category, + requestLayer, + restRequestPath, + initiatingUser, + method, + transportRequestType, + effectiveUser, + index + ); + } + + public AuditMessagePredicate withTransportRequestType(String type) { + return new AuditMessagePredicate( + category, + requestLayer, + restRequestPath, + initiatingUser, + requestMethod, + type, + effectiveUser, + index + ); + } + + public AuditMessagePredicate withEffectiveUser(String user) { + return new AuditMessagePredicate( + category, + requestLayer, + restRequestPath, + initiatingUser, + requestMethod, + transportRequestType, + user, + index + ); + } + + public AuditMessagePredicate withEffectiveUser(User user) { + return withEffectiveUser(user.getName()); + } + + public AuditMessagePredicate withRestRequest(Method method, String path) { + return this.withLayer(Origin.REST).withRestMethod(method).withRequestPath(path); + } + + public AuditMessagePredicate withIndex(String indexName) { + return new AuditMessagePredicate( + category, + requestLayer, + restRequestPath, + initiatingUser, + requestMethod, + transportRequestType, + effectiveUser, + indexName + ); + } + + @Override + public boolean test(AuditMessage auditMessage) { + List> predicates = new ArrayList<>(); + predicates.add(audit -> Objects.isNull(category) || category.equals(audit.getCategory())); + predicates.add(audit -> Objects.isNull(requestLayer) || requestLayer.equals(audit.getAsMap().get(REQUEST_LAYER))); + predicates.add(audit -> Objects.isNull(restRequestPath) || restRequestPath.equals(audit.getAsMap().get(REST_REQUEST_PATH))); + predicates.add(audit -> Objects.isNull(initiatingUser) || initiatingUser.equals(audit.getInitiatingUser())); + predicates.add(audit -> Objects.isNull(requestMethod) || requestMethod.equals(audit.getRequestMethod())); + predicates.add(audit -> Objects.isNull(transportRequestType) || transportRequestType.equals(audit.getRequestType())); + predicates.add(audit -> Objects.isNull(effectiveUser) || effectiveUser.equals(audit.getEffectiveUser())); + predicates.add(audit -> Objects.isNull(index) || containIndex(audit, index)); + return predicates.stream().reduce(Predicate::and).orElseThrow().test(auditMessage); + } + + private boolean containIndex(AuditMessage auditMessage, String indexName) { + Map audit = auditMessage.getAsMap(); + return Optional.ofNullable(audit.get(RESOLVED_INDICES)) + .filter(String[].class::isInstance) + .map(String[].class::cast) + .stream() + .flatMap(Arrays::stream) + .collect(Collectors.toSet()) + .contains(indexName); + } + + @Override + public String toString() { + return "AuditMessagePredicate{" + + "category=" + + category + + ", requestLayer=" + + requestLayer + + ", restRequestPath='" + + restRequestPath + + '\'' + + ", requestInitiatingUser='" + + initiatingUser + + '\'' + + ", requestMethod=" + + requestMethod + + ", transportRequestType='" + + transportRequestType + + '\'' + + '}'; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/audit/TestRuleAuditLogSink.java b/src/integrationTest/java/org/opensearch/test/framework/audit/TestRuleAuditLogSink.java index 0a9abaeb0d..c73d57d23c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/audit/TestRuleAuditLogSink.java +++ b/src/integrationTest/java/org/opensearch/test/framework/audit/TestRuleAuditLogSink.java @@ -17,35 +17,35 @@ import org.opensearch.security.auditlog.sink.AuditLogSink; public class TestRuleAuditLogSink extends AuditLogSink { - private static final Logger log = LogManager.getLogger(TestRuleAuditLogSink.class); - - private static volatile AuditLogsRule listener; - - public TestRuleAuditLogSink(String name, Settings settings, String settingsPrefix, AuditLogSink fallbackSink) { - super(name, settings, settingsPrefix, fallbackSink); - log.info("Test rule audit log sink created"); - } - - @Override - protected boolean doStore(AuditMessage auditMessage) { - log.debug("New audit message received '{}'.", auditMessage); - AuditLogsRule currentListener = listener; - if(currentListener != null) { - currentListener.onAuditMessage(auditMessage); - } - return true; - } - - public static void registerListener(AuditLogsRule auditLogsRule) { - listener = auditLogsRule; - } - - public static void unregisterListener() { - listener = null; - } - - @Override - public boolean isHandlingBackpressure() { - return true; - } + private static final Logger log = LogManager.getLogger(TestRuleAuditLogSink.class); + + private static volatile AuditLogsRule listener; + + public TestRuleAuditLogSink(String name, Settings settings, String settingsPrefix, AuditLogSink fallbackSink) { + super(name, settings, settingsPrefix, fallbackSink); + log.info("Test rule audit log sink created"); + } + + @Override + protected boolean doStore(AuditMessage auditMessage) { + log.debug("New audit message received '{}'.", auditMessage); + AuditLogsRule currentListener = listener; + if (currentListener != null) { + currentListener.onAuditMessage(auditMessage); + } + return true; + } + + public static void registerListener(AuditLogsRule auditLogsRule) { + listener = auditLogsRule; + } + + public static void unregisterListener() { + listener = null; + } + + @Override + public boolean isHandlingBackpressure() { + return true; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/AlgorithmKit.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/AlgorithmKit.java index 270a2bad6f..60ae56410c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/AlgorithmKit.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/AlgorithmKit.java @@ -48,99 +48,100 @@ */ class AlgorithmKit { - private static final Logger log = LogManager.getLogger(AlgorithmKit.class); - public static final String SIGNATURE_ALGORITHM_SHA_256_WITH_RSA = "SHA256withRSA"; - public static final String SIGNATURE_ALGORITHM_SHA_256_WITH_ECDSA = "SHA256withECDSA"; - - private final String signatureAlgorithmName; - private final Supplier keyPairSupplier; - - private AlgorithmKit(String signatureAlgorithmName, Supplier keyPairSupplier) { - notEmptyAlgorithmName(signatureAlgorithmName); - this.signatureAlgorithmName = signatureAlgorithmName; - this.keyPairSupplier = requireNonNull(keyPairSupplier, "Key pair supplier is required."); - } - - private static void notEmptyAlgorithmName(String signatureAlgorithmName) { - if(Strings.isNullOrEmpty(signatureAlgorithmName)){ - throw new RuntimeException("Algorithm name is required."); - } - } - - /** - * Static factory method. ECDSA algorithm used for key pair creation. Signature algorithm is defined by field - * {@link #SIGNATURE_ALGORITHM_SHA_256_WITH_ECDSA} - * - * @param securityProvider determines cryptographic algorithm implementation - * @param ellipticCurve - * @return new instance of class {@link AlgorithmKit} - */ - public static AlgorithmKit ecdsaSha256withEcdsa(Provider securityProvider, String ellipticCurve) { - notEmptyAlgorithmName(ellipticCurve); - Supplier supplier = ecdsaKeyPairSupplier(requireNonNull(securityProvider, "Security provider is required"), ellipticCurve); - return new AlgorithmKit(SIGNATURE_ALGORITHM_SHA_256_WITH_ECDSA, supplier); - } - - /** - * Static factory method. It creates object of {@link AlgorithmKit} which enforces usage of RSA algorithm for key pair generation. - * Signature algorithm is defined by {@link #SIGNATURE_ALGORITHM_SHA_256_WITH_RSA} - * - * @param securityProvider determines cryptographic algorithm implementation - * @param keySize defines key size for RSA algorithm - * @return new instance of class {@link AlgorithmKit} - */ - public static AlgorithmKit rsaSha256withRsa(Provider securityProvider, int keySize) { - positiveKeySize(keySize); - Supplier supplier = rsaKeyPairSupplier(securityProvider, keySize); - return new AlgorithmKit(SIGNATURE_ALGORITHM_SHA_256_WITH_RSA, supplier); - } - - private static void positiveKeySize(int keySize) { - if(keySize <= 0) { - throw new RuntimeException("Key size must be a positive integer value, provided: " + keySize); - } - } - - /** - * It determines algorithm used for digital signature - * @return algorithm name - */ - public String getSignatureAlgorithmName(){ - return signatureAlgorithmName; - } - - /** - * It creates new private and public key pair - * @return new pair of keys - */ - public KeyPair generateKeyPair(){ - return keyPairSupplier.get(); - } - private static Supplier rsaKeyPairSupplier(Provider securityProvider, int keySize) { - try { - KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", securityProvider); - log.info("Initialize key pair generator with keySize: {}", keySize); - generator.initialize(keySize); - return generator::generateKeyPair; - } catch (NoSuchAlgorithmException e) { - String message = "Error while initializing RSA asymmetric key generator."; - log.error(message, e); - throw new RuntimeException(message, e); - } - } - - private static Supplier ecdsaKeyPairSupplier(Provider securityProvider, String ellipticCurve) { - try { - KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", securityProvider); - log.info("Initialize key pair generator with elliptic curve: {}", ellipticCurve); - ECGenParameterSpec ecsp = new ECGenParameterSpec(ellipticCurve); - generator.initialize(ecsp); - return generator::generateKeyPair; - } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { - String message = "Error while initializing ECDSA asymmetric key generator."; - log.error(message, e); - throw new RuntimeException(message, e); - } - } + private static final Logger log = LogManager.getLogger(AlgorithmKit.class); + public static final String SIGNATURE_ALGORITHM_SHA_256_WITH_RSA = "SHA256withRSA"; + public static final String SIGNATURE_ALGORITHM_SHA_256_WITH_ECDSA = "SHA256withECDSA"; + + private final String signatureAlgorithmName; + private final Supplier keyPairSupplier; + + private AlgorithmKit(String signatureAlgorithmName, Supplier keyPairSupplier) { + notEmptyAlgorithmName(signatureAlgorithmName); + this.signatureAlgorithmName = signatureAlgorithmName; + this.keyPairSupplier = requireNonNull(keyPairSupplier, "Key pair supplier is required."); + } + + private static void notEmptyAlgorithmName(String signatureAlgorithmName) { + if (Strings.isNullOrEmpty(signatureAlgorithmName)) { + throw new RuntimeException("Algorithm name is required."); + } + } + + /** + * Static factory method. ECDSA algorithm used for key pair creation. Signature algorithm is defined by field + * {@link #SIGNATURE_ALGORITHM_SHA_256_WITH_ECDSA} + * + * @param securityProvider determines cryptographic algorithm implementation + * @param ellipticCurve + * @return new instance of class {@link AlgorithmKit} + */ + public static AlgorithmKit ecdsaSha256withEcdsa(Provider securityProvider, String ellipticCurve) { + notEmptyAlgorithmName(ellipticCurve); + Supplier supplier = ecdsaKeyPairSupplier(requireNonNull(securityProvider, "Security provider is required"), ellipticCurve); + return new AlgorithmKit(SIGNATURE_ALGORITHM_SHA_256_WITH_ECDSA, supplier); + } + + /** + * Static factory method. It creates object of {@link AlgorithmKit} which enforces usage of RSA algorithm for key pair generation. + * Signature algorithm is defined by {@link #SIGNATURE_ALGORITHM_SHA_256_WITH_RSA} + * + * @param securityProvider determines cryptographic algorithm implementation + * @param keySize defines key size for RSA algorithm + * @return new instance of class {@link AlgorithmKit} + */ + public static AlgorithmKit rsaSha256withRsa(Provider securityProvider, int keySize) { + positiveKeySize(keySize); + Supplier supplier = rsaKeyPairSupplier(securityProvider, keySize); + return new AlgorithmKit(SIGNATURE_ALGORITHM_SHA_256_WITH_RSA, supplier); + } + + private static void positiveKeySize(int keySize) { + if (keySize <= 0) { + throw new RuntimeException("Key size must be a positive integer value, provided: " + keySize); + } + } + + /** + * It determines algorithm used for digital signature + * @return algorithm name + */ + public String getSignatureAlgorithmName() { + return signatureAlgorithmName; + } + + /** + * It creates new private and public key pair + * @return new pair of keys + */ + public KeyPair generateKeyPair() { + return keyPairSupplier.get(); + } + + private static Supplier rsaKeyPairSupplier(Provider securityProvider, int keySize) { + try { + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", securityProvider); + log.info("Initialize key pair generator with keySize: {}", keySize); + generator.initialize(keySize); + return generator::generateKeyPair; + } catch (NoSuchAlgorithmException e) { + String message = "Error while initializing RSA asymmetric key generator."; + log.error(message, e); + throw new RuntimeException(message, e); + } + } + + private static Supplier ecdsaKeyPairSupplier(Provider securityProvider, String ellipticCurve) { + try { + KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", securityProvider); + log.info("Initialize key pair generator with elliptic curve: {}", ellipticCurve); + ECGenParameterSpec ecsp = new ECGenParameterSpec(ellipticCurve); + generator.initialize(ecsp); + return generator::generateKeyPair; + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { + String message = "Error while initializing ECDSA asymmetric key generator."; + log.error(message, e); + throw new RuntimeException(message, e); + } + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateData.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateData.java index 3712bd1763..09d0f931e6 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateData.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateData.java @@ -40,50 +40,50 @@ */ public class CertificateData { - private final X509CertificateHolder certificate; - private final KeyPair keyPair; + private final X509CertificateHolder certificate; + private final KeyPair keyPair; - public CertificateData(X509CertificateHolder certificate, KeyPair keyPair) { - this.certificate = certificate; - this.keyPair = keyPair; - } + public CertificateData(X509CertificateHolder certificate, KeyPair keyPair) { + this.certificate = certificate; + this.keyPair = keyPair; + } - /** - * The method returns X.509 certificate encoded in PEM format. PEM format is defined by - * RFC 1421. - * @return Certificate in PEM format - */ - public String certificateInPemFormat() { - return PemConverter.toPem(certificate); - } + /** + * The method returns X.509 certificate encoded in PEM format. PEM format is defined by + * RFC 1421. + * @return Certificate in PEM format + */ + public String certificateInPemFormat() { + return PemConverter.toPem(certificate); + } - public X509Certificate certificate() { - try { - return new JcaX509CertificateConverter().getCertificate(certificate); - } catch (CertificateException e) { - throw new RuntimeException("Cannot retrieve certificate", e); - } - } + public X509Certificate certificate() { + try { + return new JcaX509CertificateConverter().getCertificate(certificate); + } catch (CertificateException e) { + throw new RuntimeException("Cannot retrieve certificate", e); + } + } - /** - * It returns the private key associated with certificate encoded in PEM format. PEM format is defined by - * RFC 1421. - * @param privateKeyPassword password used for private key encryption. null for unencrypted key. - * @return private key encoded in PEM format - */ - public String privateKeyInPemFormat(String privateKeyPassword) { - return PemConverter.toPem(keyPair.getPrivate(), privateKeyPassword); - } + /** + * It returns the private key associated with certificate encoded in PEM format. PEM format is defined by + * RFC 1421. + * @param privateKeyPassword password used for private key encryption. null for unencrypted key. + * @return private key encoded in PEM format + */ + public String privateKeyInPemFormat(String privateKeyPassword) { + return PemConverter.toPem(keyPair.getPrivate(), privateKeyPassword); + } - X500Name getCertificateSubject() { - return certificate.getSubject(); - } + X500Name getCertificateSubject() { + return certificate.getSubject(); + } - KeyPair getKeyPair() { - return keyPair; - } + KeyPair getKeyPair() { + return keyPair; + } - public Key getKey() { - return keyPair.getPrivate(); - } + public Key getKey() { + return keyPair.getPrivate(); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java index fbd2c1f8e8..cc94621f72 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java @@ -42,180 +42,179 @@ * */ class CertificateMetadata { - /** - * Certification subject (person, company, web server, IoT device). The subject of certificate is an owner of the certificate - * (simplification). The format of this field must adhere to RFC 4514. - * @see RFC 4514 - */ - private final String subject; - - /** - * It describes certificate expiration date - */ - private final int validityDays; - - /** - * Optionally used by Open Search to indicate that the certificate can be used by Open Search node to confirm the node identity. The - * value becomes a part of - * SAN (Subject Alternative Name) extension - * - * @see #dnsNames - * @see SAN (Subject Alternative Name) extension - */ - private final String nodeOid; - - /** - * The certificate contains only one {@link #subject}. This is a common limitation when a certificate is used by a web server which is - * associated with a few domains. To overcome this limitation SAN (Subject Alternative Name) extension was introduced. - * The field contains additional subject names which enables creation of so called multi-domain certificates. The extension is defined - * in section 4.2.1.6 of RFC 5280 - * - * @see RFC 5280 - */ - private final List dnsNames; - - /** - * Similar to {@link #dnsNames} but contains IP addresses instead of domains. - */ - private final List ipAddresses; - - /** - * If a private key associated with certificate is used to sign other certificate then this field has to be true. - */ - private final boolean basicConstrainIsCa; - - /** - * Allowed usages for public key associated with certificate - */ - private final Set keyUsages; - - - private CertificateMetadata(String subject, - int validityDays, - String nodeOid, - List dnsNames, - List ipAddresses, - boolean basicConstrainIsCa, - Set keyUsages) { - this.subject = subject; - this.validityDays = validityDays; - this.nodeOid = nodeOid; - this.dnsNames = requireNonNull(dnsNames, "List of dns names must not be null."); - this.ipAddresses = requireNonNull(ipAddresses, "List of IP addresses must not be null"); - this.basicConstrainIsCa = basicConstrainIsCa; - this.keyUsages = requireNonNull(keyUsages, "Key usage set must not be null."); - } - - /** - * Static factory method. It creates metadata which contains only basic information. - * @param subjectName please see {@link #subject} - * @param validityDays please see {@link #validityDays} - * @return new instance of {@link CertificateMetadata} - */ - public static CertificateMetadata basicMetadata(String subjectName, int validityDays) { - return new CertificateMetadata(subjectName, validityDays, null, emptyList(), emptyList(), false, emptySet()); - } - - /** - * It is related to private key associated with certificate. It specifies metadata related to allowed private key usage. - * @param basicConstrainIsCa {@link #basicConstrainIsCa} - * @param keyUsages {@link #keyUsages} - * @return returns newly created instance of {@link CertificateData} - */ - public CertificateMetadata withKeyUsage(boolean basicConstrainIsCa, PublicKeyUsage...keyUsages){ - Set usages = arrayToEnumSet(keyUsages); - return new CertificateMetadata(subject, validityDays, nodeOid, dnsNames, ipAddresses, basicConstrainIsCa, usages); - } - - private > Set arrayToEnumSet(T[] enumArray) { - if((enumArray == null) || (enumArray.length == 0)){ - return Collections.emptySet(); - } - return EnumSet.copyOf(asList(enumArray)); - } - - /** - * The method defines metadata related to SAN (Subject Alternative Name) extension. - * @param nodeOid {@link #nodeOid} - * @param dnsNames {@link #dnsNames} - * @param ipAddresses {@link #ipAddresses} - * @return new instance of {@link CertificateMetadata} - * @see SAN (Subject Alternative Name) extension - */ - public CertificateMetadata withSubjectAlternativeName(String nodeOid, List dnsNames, String...ipAddresses) { - return new CertificateMetadata(subject, validityDays, nodeOid, dnsNames, asList(ipAddresses), basicConstrainIsCa, keyUsages); - } - - /** - * {@link #subject} - * @return Subject name - */ - public String getSubject() { - return subject; - } - - /** - * {@link #validityDays} - * @return determines certificate expiration date - */ - public int getValidityDays() { - return validityDays; - } - - /** - * {@link #basicConstrainIsCa} - * @return Determines if another certificate can be derived from certificate. - */ - public boolean isBasicConstrainIsCa() { - return basicConstrainIsCa; - } - - KeyUsage asKeyUsage() { - Integer keyUsageBitMask = keyUsages - .stream() - .filter(PublicKeyUsage::isNotExtendedUsage) - .map(PublicKeyUsage::asInt) - .reduce(0, (accumulator, currentValue) -> accumulator | currentValue); - return new KeyUsage(keyUsageBitMask); - } - - boolean hasSubjectAlternativeNameExtension() { - return ((ipAddresses.size() + dnsNames.size()) > 0) || (Strings.isNullOrEmpty(nodeOid) == false); - } - - DERSequence createSubjectAlternativeNames() { - List subjectAlternativeNameList = new ArrayList<>(); - if (!Strings.isNullOrEmpty(nodeOid)) { - subjectAlternativeNameList.add(new GeneralName(GeneralName.registeredID, nodeOid)); - } - if (isNotEmpty(dnsNames)) { - for (String dnsName : dnsNames) { - subjectAlternativeNameList.add(new GeneralName(GeneralName.dNSName, dnsName)); - } - } - if (isNotEmpty(ipAddresses)) { - for (String ip : ipAddresses) { - subjectAlternativeNameList.add(new GeneralName(GeneralName.iPAddress, ip)); - } - } - return new DERSequence(subjectAlternativeNameList.toArray(ASN1Encodable[]::new)); - } - - private static boolean isNotEmpty(Collection collection) { - return (collection != null) && (!collection.isEmpty()); - } - - boolean hasExtendedKeyUsage() { - return keyUsages.stream().anyMatch(PublicKeyUsage::isNotExtendedUsage); - } - - ExtendedKeyUsage getExtendedKeyUsage() { - KeyPurposeId[] usages = keyUsages - .stream() - .filter(PublicKeyUsage::isExtendedUsage) - .map(PublicKeyUsage::getKeyPurposeId) - .toArray(KeyPurposeId[]::new); - return new ExtendedKeyUsage(usages); - } + /** + * Certification subject (person, company, web server, IoT device). The subject of certificate is an owner of the certificate + * (simplification). The format of this field must adhere to RFC 4514. + * @see RFC 4514 + */ + private final String subject; + + /** + * It describes certificate expiration date + */ + private final int validityDays; + + /** + * Optionally used by Open Search to indicate that the certificate can be used by Open Search node to confirm the node identity. The + * value becomes a part of + * SAN (Subject Alternative Name) extension + * + * @see #dnsNames + * @see SAN (Subject Alternative Name) extension + */ + private final String nodeOid; + + /** + * The certificate contains only one {@link #subject}. This is a common limitation when a certificate is used by a web server which is + * associated with a few domains. To overcome this limitation SAN (Subject Alternative Name) extension was introduced. + * The field contains additional subject names which enables creation of so called multi-domain certificates. The extension is defined + * in section 4.2.1.6 of RFC 5280 + * + * @see RFC 5280 + */ + private final List dnsNames; + + /** + * Similar to {@link #dnsNames} but contains IP addresses instead of domains. + */ + private final List ipAddresses; + + /** + * If a private key associated with certificate is used to sign other certificate then this field has to be true. + */ + private final boolean basicConstrainIsCa; + + /** + * Allowed usages for public key associated with certificate + */ + private final Set keyUsages; + + private CertificateMetadata( + String subject, + int validityDays, + String nodeOid, + List dnsNames, + List ipAddresses, + boolean basicConstrainIsCa, + Set keyUsages + ) { + this.subject = subject; + this.validityDays = validityDays; + this.nodeOid = nodeOid; + this.dnsNames = requireNonNull(dnsNames, "List of dns names must not be null."); + this.ipAddresses = requireNonNull(ipAddresses, "List of IP addresses must not be null"); + this.basicConstrainIsCa = basicConstrainIsCa; + this.keyUsages = requireNonNull(keyUsages, "Key usage set must not be null."); + } + + /** + * Static factory method. It creates metadata which contains only basic information. + * @param subjectName please see {@link #subject} + * @param validityDays please see {@link #validityDays} + * @return new instance of {@link CertificateMetadata} + */ + public static CertificateMetadata basicMetadata(String subjectName, int validityDays) { + return new CertificateMetadata(subjectName, validityDays, null, emptyList(), emptyList(), false, emptySet()); + } + + /** + * It is related to private key associated with certificate. It specifies metadata related to allowed private key usage. + * @param basicConstrainIsCa {@link #basicConstrainIsCa} + * @param keyUsages {@link #keyUsages} + * @return returns newly created instance of {@link CertificateData} + */ + public CertificateMetadata withKeyUsage(boolean basicConstrainIsCa, PublicKeyUsage... keyUsages) { + Set usages = arrayToEnumSet(keyUsages); + return new CertificateMetadata(subject, validityDays, nodeOid, dnsNames, ipAddresses, basicConstrainIsCa, usages); + } + + private > Set arrayToEnumSet(T[] enumArray) { + if ((enumArray == null) || (enumArray.length == 0)) { + return Collections.emptySet(); + } + return EnumSet.copyOf(asList(enumArray)); + } + + /** + * The method defines metadata related to SAN (Subject Alternative Name) extension. + * @param nodeOid {@link #nodeOid} + * @param dnsNames {@link #dnsNames} + * @param ipAddresses {@link #ipAddresses} + * @return new instance of {@link CertificateMetadata} + * @see SAN (Subject Alternative Name) extension + */ + public CertificateMetadata withSubjectAlternativeName(String nodeOid, List dnsNames, String... ipAddresses) { + return new CertificateMetadata(subject, validityDays, nodeOid, dnsNames, asList(ipAddresses), basicConstrainIsCa, keyUsages); + } + + /** + * {@link #subject} + * @return Subject name + */ + public String getSubject() { + return subject; + } + + /** + * {@link #validityDays} + * @return determines certificate expiration date + */ + public int getValidityDays() { + return validityDays; + } + + /** + * {@link #basicConstrainIsCa} + * @return Determines if another certificate can be derived from certificate. + */ + public boolean isBasicConstrainIsCa() { + return basicConstrainIsCa; + } + + KeyUsage asKeyUsage() { + Integer keyUsageBitMask = keyUsages.stream() + .filter(PublicKeyUsage::isNotExtendedUsage) + .map(PublicKeyUsage::asInt) + .reduce(0, (accumulator, currentValue) -> accumulator | currentValue); + return new KeyUsage(keyUsageBitMask); + } + + boolean hasSubjectAlternativeNameExtension() { + return ((ipAddresses.size() + dnsNames.size()) > 0) || (Strings.isNullOrEmpty(nodeOid) == false); + } + + DERSequence createSubjectAlternativeNames() { + List subjectAlternativeNameList = new ArrayList<>(); + if (!Strings.isNullOrEmpty(nodeOid)) { + subjectAlternativeNameList.add(new GeneralName(GeneralName.registeredID, nodeOid)); + } + if (isNotEmpty(dnsNames)) { + for (String dnsName : dnsNames) { + subjectAlternativeNameList.add(new GeneralName(GeneralName.dNSName, dnsName)); + } + } + if (isNotEmpty(ipAddresses)) { + for (String ip : ipAddresses) { + subjectAlternativeNameList.add(new GeneralName(GeneralName.iPAddress, ip)); + } + } + return new DERSequence(subjectAlternativeNameList.toArray(ASN1Encodable[]::new)); + } + + private static boolean isNotEmpty(Collection collection) { + return (collection != null) && (!collection.isEmpty()); + } + + boolean hasExtendedKeyUsage() { + return keyUsages.stream().anyMatch(PublicKeyUsage::isNotExtendedUsage); + } + + ExtendedKeyUsage getExtendedKeyUsage() { + KeyPurposeId[] usages = keyUsages.stream() + .filter(PublicKeyUsage::isExtendedUsage) + .map(PublicKeyUsage::getKeyPurposeId) + .toArray(KeyPurposeId[]::new); + return new ExtendedKeyUsage(usages); + } } // CS-ENFORCE-SINGLE diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java index 8d821a4571..6facf5f2ac 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java @@ -83,144 +83,159 @@ */ class CertificatesIssuer { - private static final Logger log = LogManager.getLogger(CertificatesIssuer.class); - - private static final AtomicLong ID_COUNTER = new AtomicLong(System.currentTimeMillis()); - - private final Provider securityProvider; - private final AlgorithmKit algorithmKit; - private final JcaX509ExtensionUtils extUtils; - - CertificatesIssuer(Provider securityProvider, AlgorithmKit algorithmKit) { - this.securityProvider = securityProvider; - this.algorithmKit = algorithmKit; - this.extUtils = getExtUtils(); - } - - /** - * The method creates a certificate with provided metadata and public key obtained from {@link #algorithmKit}. The result of invocation - * contains required data to use a certificate by its owner. - * - * @param certificateMetadata metadata which should be embedded into created certificate - * @return {@link CertificateData} which contain certificate and private key associated with the certificate. - */ - public CertificateData issueSelfSignedCertificate(CertificateMetadata certificateMetadata) { - try { - KeyPair publicAndPrivateKey = algorithmKit.generateKeyPair(); - X500Name issuerName = stringToX500Name(requireNonNull(certificateMetadata.getSubject(), "Certificate metadata are required.")); - X509CertificateHolder x509CertificateHolder = buildCertificateHolder( - certificateMetadata, - issuerName, - publicAndPrivateKey.getPublic(), - publicAndPrivateKey); - return new CertificateData(x509CertificateHolder, publicAndPrivateKey); - } catch (OperatorCreationException | CertIOException e) { - log.error("Error while generating certificate", e); - throw new RuntimeException("Error while generating self signed certificate", e); - } - } - - /** - * The method is similar to {@link #issueSignedCertificate(CertificateMetadata, CertificateData)} but additionally it signs created - * certificate using data from parentCertificateData. - * - * @param metadata metadata which should be embedded into created certificate - * @param parentCertificateData data required to signe a newly issued certificate (private key among others things). - * @return {@link CertificateData} which contain certificate and private key associated with the certificate. - */ - public CertificateData issueSignedCertificate(CertificateMetadata metadata, CertificateData parentCertificateData) { - try { - KeyPair publicAndPrivateKey = algorithmKit.generateKeyPair(); - KeyPair parentKeyPair = requireNonNull(parentCertificateData, "Issuer certificate data are required") - .getKeyPair(); - X500Name issuerName = parentCertificateData.getCertificateSubject(); - var x509CertificateHolder = buildCertificateHolder(requireNonNull(metadata, "Certificate metadata are required"), - issuerName, - publicAndPrivateKey.getPublic(), - parentKeyPair); - return new CertificateData(x509CertificateHolder, publicAndPrivateKey); - } catch (OperatorCreationException | CertIOException e) { - log.error("Error while generating signed certificate", e); - throw new RuntimeException("Error while generating signed certificate", e); - } - } - - private X509CertificateHolder buildCertificateHolder(CertificateMetadata certificateMetadata, - X500Name issuerName, - PublicKey certificatePublicKey, - KeyPair parentKeyPair) throws CertIOException, OperatorCreationException { - X509v3CertificateBuilder builder = builderWithBasicExtensions(certificateMetadata, issuerName, certificatePublicKey, parentKeyPair.getPublic()); - addSubjectAlternativeNameExtension(builder, certificateMetadata); - addExtendedKeyUsageExtension(builder, certificateMetadata); - return builder.build(createContentSigner(parentKeyPair.getPrivate())); - } - - private ContentSigner createContentSigner(PrivateKey privateKey) throws OperatorCreationException { - return new JcaContentSignerBuilder(algorithmKit.getSignatureAlgorithmName()) - .setProvider(securityProvider) - .build(privateKey); - } - - private void addExtendedKeyUsageExtension(X509v3CertificateBuilder builder, CertificateMetadata certificateMetadata) throws CertIOException { - if(certificateMetadata.hasExtendedKeyUsage()) { - builder.addExtension(Extension.extendedKeyUsage, true, certificateMetadata.getExtendedKeyUsage()); - } - } - - private X509v3CertificateBuilder builderWithBasicExtensions(CertificateMetadata certificateMetadata, - X500Name issuerName, - PublicKey certificatePublicKey, - PublicKey parentPublicKey) throws CertIOException { - X500Name subjectName = stringToX500Name(certificateMetadata.getSubject()); - Date validityStartDate = new Date(System.currentTimeMillis() - (24 * 3600 * 1000)); - Date validityEndDate = getEndDate(validityStartDate, certificateMetadata.getValidityDays()); - - BigInteger certificateSerialNumber = generateNextCertificateSerialNumber(); - return new X509v3CertificateBuilder(issuerName, certificateSerialNumber, validityStartDate, - validityEndDate, subjectName, SubjectPublicKeyInfo.getInstance(certificatePublicKey.getEncoded())) - .addExtension(Extension.basicConstraints, true, new BasicConstraints(certificateMetadata.isBasicConstrainIsCa())) - .addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(parentPublicKey)) - .addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(certificatePublicKey)) - .addExtension(Extension.keyUsage, true, certificateMetadata.asKeyUsage()); - } - - private void addSubjectAlternativeNameExtension(X509v3CertificateBuilder builder, CertificateMetadata metadata) throws CertIOException { - if(metadata.hasSubjectAlternativeNameExtension()){ - DERSequence subjectAlternativeNames = metadata.createSubjectAlternativeNames(); - builder.addExtension(Extension.subjectAlternativeName, false, subjectAlternativeNames); - } - } - - private Date getEndDate(Date startDate, int validityDays) { - Calendar calendar = Calendar.getInstance(); - calendar.setTime(startDate); - calendar.add(Calendar.DATE, validityDays); - return calendar.getTime(); - } - - private static JcaX509ExtensionUtils getExtUtils() { - try { - return new JcaX509ExtensionUtils(); - } catch (NoSuchAlgorithmException e) { - log.error("Getting certificate extension utils failed", e); - throw new RuntimeException("Getting certificate extension utils failed", e); - } - } - - private X500Name stringToX500Name(String distinguishedName) { - if (Strings.isNullOrEmpty(distinguishedName)) { - throw new RuntimeException("No DN (distinguished name) must not be null or empty"); - } - try { - return new X500Name(RFC4519Style.INSTANCE, distinguishedName); - } catch (IllegalArgumentException e) { - String message = String.format("Invalid DN (distinguished name) specified for %s certificate.", distinguishedName); - throw new RuntimeException(message, e); - } - } - - private BigInteger generateNextCertificateSerialNumber() { - return BigInteger.valueOf(ID_COUNTER.incrementAndGet()); - } + private static final Logger log = LogManager.getLogger(CertificatesIssuer.class); + + private static final AtomicLong ID_COUNTER = new AtomicLong(System.currentTimeMillis()); + + private final Provider securityProvider; + private final AlgorithmKit algorithmKit; + private final JcaX509ExtensionUtils extUtils; + + CertificatesIssuer(Provider securityProvider, AlgorithmKit algorithmKit) { + this.securityProvider = securityProvider; + this.algorithmKit = algorithmKit; + this.extUtils = getExtUtils(); + } + + /** + * The method creates a certificate with provided metadata and public key obtained from {@link #algorithmKit}. The result of invocation + * contains required data to use a certificate by its owner. + * + * @param certificateMetadata metadata which should be embedded into created certificate + * @return {@link CertificateData} which contain certificate and private key associated with the certificate. + */ + public CertificateData issueSelfSignedCertificate(CertificateMetadata certificateMetadata) { + try { + KeyPair publicAndPrivateKey = algorithmKit.generateKeyPair(); + X500Name issuerName = stringToX500Name(requireNonNull(certificateMetadata.getSubject(), "Certificate metadata are required.")); + X509CertificateHolder x509CertificateHolder = buildCertificateHolder( + certificateMetadata, + issuerName, + publicAndPrivateKey.getPublic(), + publicAndPrivateKey + ); + return new CertificateData(x509CertificateHolder, publicAndPrivateKey); + } catch (OperatorCreationException | CertIOException e) { + log.error("Error while generating certificate", e); + throw new RuntimeException("Error while generating self signed certificate", e); + } + } + + /** + * The method is similar to {@link #issueSignedCertificate(CertificateMetadata, CertificateData)} but additionally it signs created + * certificate using data from parentCertificateData. + * + * @param metadata metadata which should be embedded into created certificate + * @param parentCertificateData data required to signe a newly issued certificate (private key among others things). + * @return {@link CertificateData} which contain certificate and private key associated with the certificate. + */ + public CertificateData issueSignedCertificate(CertificateMetadata metadata, CertificateData parentCertificateData) { + try { + KeyPair publicAndPrivateKey = algorithmKit.generateKeyPair(); + KeyPair parentKeyPair = requireNonNull(parentCertificateData, "Issuer certificate data are required").getKeyPair(); + X500Name issuerName = parentCertificateData.getCertificateSubject(); + var x509CertificateHolder = buildCertificateHolder( + requireNonNull(metadata, "Certificate metadata are required"), + issuerName, + publicAndPrivateKey.getPublic(), + parentKeyPair + ); + return new CertificateData(x509CertificateHolder, publicAndPrivateKey); + } catch (OperatorCreationException | CertIOException e) { + log.error("Error while generating signed certificate", e); + throw new RuntimeException("Error while generating signed certificate", e); + } + } + + private X509CertificateHolder buildCertificateHolder( + CertificateMetadata certificateMetadata, + X500Name issuerName, + PublicKey certificatePublicKey, + KeyPair parentKeyPair + ) throws CertIOException, OperatorCreationException { + X509v3CertificateBuilder builder = builderWithBasicExtensions( + certificateMetadata, + issuerName, + certificatePublicKey, + parentKeyPair.getPublic() + ); + addSubjectAlternativeNameExtension(builder, certificateMetadata); + addExtendedKeyUsageExtension(builder, certificateMetadata); + return builder.build(createContentSigner(parentKeyPair.getPrivate())); + } + + private ContentSigner createContentSigner(PrivateKey privateKey) throws OperatorCreationException { + return new JcaContentSignerBuilder(algorithmKit.getSignatureAlgorithmName()).setProvider(securityProvider).build(privateKey); + } + + private void addExtendedKeyUsageExtension(X509v3CertificateBuilder builder, CertificateMetadata certificateMetadata) + throws CertIOException { + if (certificateMetadata.hasExtendedKeyUsage()) { + builder.addExtension(Extension.extendedKeyUsage, true, certificateMetadata.getExtendedKeyUsage()); + } + } + + private X509v3CertificateBuilder builderWithBasicExtensions( + CertificateMetadata certificateMetadata, + X500Name issuerName, + PublicKey certificatePublicKey, + PublicKey parentPublicKey + ) throws CertIOException { + X500Name subjectName = stringToX500Name(certificateMetadata.getSubject()); + Date validityStartDate = new Date(System.currentTimeMillis() - (24 * 3600 * 1000)); + Date validityEndDate = getEndDate(validityStartDate, certificateMetadata.getValidityDays()); + + BigInteger certificateSerialNumber = generateNextCertificateSerialNumber(); + return new X509v3CertificateBuilder( + issuerName, + certificateSerialNumber, + validityStartDate, + validityEndDate, + subjectName, + SubjectPublicKeyInfo.getInstance(certificatePublicKey.getEncoded()) + ).addExtension(Extension.basicConstraints, true, new BasicConstraints(certificateMetadata.isBasicConstrainIsCa())) + .addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(parentPublicKey)) + .addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(certificatePublicKey)) + .addExtension(Extension.keyUsage, true, certificateMetadata.asKeyUsage()); + } + + private void addSubjectAlternativeNameExtension(X509v3CertificateBuilder builder, CertificateMetadata metadata) throws CertIOException { + if (metadata.hasSubjectAlternativeNameExtension()) { + DERSequence subjectAlternativeNames = metadata.createSubjectAlternativeNames(); + builder.addExtension(Extension.subjectAlternativeName, false, subjectAlternativeNames); + } + } + + private Date getEndDate(Date startDate, int validityDays) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(startDate); + calendar.add(Calendar.DATE, validityDays); + return calendar.getTime(); + } + + private static JcaX509ExtensionUtils getExtUtils() { + try { + return new JcaX509ExtensionUtils(); + } catch (NoSuchAlgorithmException e) { + log.error("Getting certificate extension utils failed", e); + throw new RuntimeException("Getting certificate extension utils failed", e); + } + } + + private X500Name stringToX500Name(String distinguishedName) { + if (Strings.isNullOrEmpty(distinguishedName)) { + throw new RuntimeException("No DN (distinguished name) must not be null or empty"); + } + try { + return new X500Name(RFC4519Style.INSTANCE, distinguishedName); + } catch (IllegalArgumentException e) { + String message = String.format("Invalid DN (distinguished name) specified for %s certificate.", distinguishedName); + throw new RuntimeException(message, e); + } + } + + private BigInteger generateNextCertificateSerialNumber() { + return BigInteger.valueOf(ID_COUNTER.incrementAndGet()); + } } // CS-ENFORCE-SINGLE diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuerFactory.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuerFactory.java index 823a0c04ca..f68ccf6022 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuerFactory.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuerFactory.java @@ -24,45 +24,45 @@ */ class CertificatesIssuerFactory { - private static final int KEY_SIZE = 2048; + private static final int KEY_SIZE = 2048; - private CertificatesIssuerFactory() { + private CertificatesIssuerFactory() { - } + } - private static final Provider DEFAULT_SECURITY_PROVIDER = new BouncyCastleProvider(); + private static final Provider DEFAULT_SECURITY_PROVIDER = new BouncyCastleProvider(); - /** - * @see {@link #rsaBaseCertificateIssuer(Provider)} - */ - public static CertificatesIssuer rsaBaseCertificateIssuer() { - return rsaBaseCertificateIssuer(null); - } + /** + * @see {@link #rsaBaseCertificateIssuer(Provider)} + */ + public static CertificatesIssuer rsaBaseCertificateIssuer() { + return rsaBaseCertificateIssuer(null); + } - /** - * The method creates {@link CertificatesIssuer} which uses RSA algorithm for certificate creation. - * @param securityProvider determines cryptographic algorithm implementation, can be null. - * @return new instance of {@link CertificatesIssuer} - */ - public static CertificatesIssuer rsaBaseCertificateIssuer(Provider securityProvider) { - Provider provider = Optional.ofNullable(securityProvider).orElse(DEFAULT_SECURITY_PROVIDER); - return new CertificatesIssuer(provider, rsaSha256withRsa(provider, KEY_SIZE)); - } + /** + * The method creates {@link CertificatesIssuer} which uses RSA algorithm for certificate creation. + * @param securityProvider determines cryptographic algorithm implementation, can be null. + * @return new instance of {@link CertificatesIssuer} + */ + public static CertificatesIssuer rsaBaseCertificateIssuer(Provider securityProvider) { + Provider provider = Optional.ofNullable(securityProvider).orElse(DEFAULT_SECURITY_PROVIDER); + return new CertificatesIssuer(provider, rsaSha256withRsa(provider, KEY_SIZE)); + } - /** - * {@link #rsaBaseCertificateIssuer(Provider)} - */ - public static CertificatesIssuer ecdsaBaseCertificatesIssuer() { - return ecdsaBaseCertificatesIssuer(null); - } + /** + * {@link #rsaBaseCertificateIssuer(Provider)} + */ + public static CertificatesIssuer ecdsaBaseCertificatesIssuer() { + return ecdsaBaseCertificatesIssuer(null); + } - /** - * It creates {@link CertificatesIssuer} which uses asymmetric cryptography algorithm which relays on elliptic curves. - * @param securityProvider determines cryptographic algorithm implementation, can be null. - * @return new instance of {@link CertificatesIssuer} - */ - public static CertificatesIssuer ecdsaBaseCertificatesIssuer(Provider securityProvider) { - Provider provider = Optional.ofNullable(securityProvider).orElse(DEFAULT_SECURITY_PROVIDER); - return new CertificatesIssuer(provider, ecdsaSha256withEcdsa(securityProvider, "P-384")); - } + /** + * It creates {@link CertificatesIssuer} which uses asymmetric cryptography algorithm which relays on elliptic curves. + * @param securityProvider determines cryptographic algorithm implementation, can be null. + * @return new instance of {@link CertificatesIssuer} + */ + public static CertificatesIssuer ecdsaBaseCertificatesIssuer(Provider securityProvider) { + Provider provider = Optional.ofNullable(securityProvider).orElse(DEFAULT_SECURITY_PROVIDER); + return new CertificatesIssuer(provider, ecdsaSha256withEcdsa(securityProvider, "P-384")); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/PemConverter.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/PemConverter.java index a92e2036d7..749ab232bc 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/PemConverter.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/PemConverter.java @@ -53,68 +53,67 @@ */ class PemConverter { - private PemConverter() { - } + private PemConverter() {} - private static final Logger log = LogManager.getLogger(PemConverter.class); - private static final SecureRandom secureRandom = new SecureRandom(); + private static final Logger log = LogManager.getLogger(PemConverter.class); + private static final SecureRandom secureRandom = new SecureRandom(); - /** - * It converts certificate represented by {@link X509CertificateHolder} object to PEM format - * @param certificate is a certificate to convert - * @return {@link String} which contains PEM encoded certificate - */ - public static String toPem(X509CertificateHolder certificate) { - StringWriter stringWriter = new StringWriter(); - try (JcaPEMWriter writer = new JcaPEMWriter(stringWriter)) { - writer.writeObject(requireNonNull(certificate, "Certificate is required.")); - } catch (Exception e) { - throw new RuntimeException("Cannot write certificate in PEM format", e); - } - return stringWriter.toString(); - } + /** + * It converts certificate represented by {@link X509CertificateHolder} object to PEM format + * @param certificate is a certificate to convert + * @return {@link String} which contains PEM encoded certificate + */ + public static String toPem(X509CertificateHolder certificate) { + StringWriter stringWriter = new StringWriter(); + try (JcaPEMWriter writer = new JcaPEMWriter(stringWriter)) { + writer.writeObject(requireNonNull(certificate, "Certificate is required.")); + } catch (Exception e) { + throw new RuntimeException("Cannot write certificate in PEM format", e); + } + return stringWriter.toString(); + } - /** - * It converts private key represented by class {@link PrivateKey} to PEM format. - * @param privateKey is a private key, cannot be null - * @param privateKeyPassword is a password used to encode private key, null for unencrypted private key - * @return {@link String} which contains PEM encoded private key - */ - public static String toPem(PrivateKey privateKey, String privateKeyPassword) { - try(StringWriter stringWriter = new StringWriter()){ - savePrivateKey(stringWriter, requireNonNull(privateKey, "Private key is required."), privateKeyPassword); - return stringWriter.toString(); - } catch (IOException e) { - throw new RuntimeException("Cannot convert private key into PEM format.", e); - } - } + /** + * It converts private key represented by class {@link PrivateKey} to PEM format. + * @param privateKey is a private key, cannot be null + * @param privateKeyPassword is a password used to encode private key, null for unencrypted private key + * @return {@link String} which contains PEM encoded private key + */ + public static String toPem(PrivateKey privateKey, String privateKeyPassword) { + try (StringWriter stringWriter = new StringWriter()) { + savePrivateKey(stringWriter, requireNonNull(privateKey, "Private key is required."), privateKeyPassword); + return stringWriter.toString(); + } catch (IOException e) { + throw new RuntimeException("Cannot convert private key into PEM format.", e); + } + } - private static void savePrivateKey(Writer out, PrivateKey privateKey, String privateKeyPassword) { - try (JcaPEMWriter writer = new JcaPEMWriter(out)) { - writer.writeObject(createPkcs8PrivateKeyPem(privateKey, privateKeyPassword)); - } catch (Exception e) { - log.error("Error while writing private key.", e); - throw new RuntimeException("Error while writing private key ", e); - } - } + private static void savePrivateKey(Writer out, PrivateKey privateKey, String privateKeyPassword) { + try (JcaPEMWriter writer = new JcaPEMWriter(out)) { + writer.writeObject(createPkcs8PrivateKeyPem(privateKey, privateKeyPassword)); + } catch (Exception e) { + log.error("Error while writing private key.", e); + throw new RuntimeException("Error while writing private key ", e); + } + } - private static PemObject createPkcs8PrivateKeyPem(PrivateKey privateKey, String password) { - try { - OutputEncryptor outputEncryptor = password == null ? null : getPasswordEncryptor(password); - return new PKCS8Generator(PrivateKeyInfo.getInstance(privateKey.getEncoded()), outputEncryptor).generate(); - } catch (PemGenerationException | OperatorCreationException e) { - log.error("Creating PKCS8 private key failed", e); - throw new RuntimeException("Creating PKCS8 private key failed", e); - } - } + private static PemObject createPkcs8PrivateKeyPem(PrivateKey privateKey, String password) { + try { + OutputEncryptor outputEncryptor = password == null ? null : getPasswordEncryptor(password); + return new PKCS8Generator(PrivateKeyInfo.getInstance(privateKey.getEncoded()), outputEncryptor).generate(); + } catch (PemGenerationException | OperatorCreationException e) { + log.error("Creating PKCS8 private key failed", e); + throw new RuntimeException("Creating PKCS8 private key failed", e); + } + } - private static OutputEncryptor getPasswordEncryptor(String password) throws OperatorCreationException { - if (!Strings.isNullOrEmpty(password)) { - JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(PKCS8Generator.PBE_SHA1_3DES); - encryptorBuilder.setRandom(secureRandom); - encryptorBuilder.setPassword(password.toCharArray()); - return encryptorBuilder.build(); - } - return null; - } + private static OutputEncryptor getPasswordEncryptor(String password) throws OperatorCreationException { + if (!Strings.isNullOrEmpty(password)) { + JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(PKCS8Generator.PBE_SHA1_3DES); + encryptorBuilder.setRandom(secureRandom); + encryptorBuilder.setPassword(password.toCharArray()); + return encryptorBuilder.build(); + } + return null; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java index 9ee2ec8a02..af37c66001 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java @@ -28,47 +28,48 @@ */ // CS-ENFORCE-SINGLE enum PublicKeyUsage { - DIGITAL_SIGNATURE(KeyUsage.digitalSignature), - KEY_CERT_SIGN(KeyUsage.keyCertSign), - CRL_SIGN(KeyUsage.cRLSign), - NON_REPUDIATION(KeyUsage.nonRepudiation), - KEY_ENCIPHERMENT(KeyUsage.keyEncipherment), + DIGITAL_SIGNATURE(KeyUsage.digitalSignature), + KEY_CERT_SIGN(KeyUsage.keyCertSign), + CRL_SIGN(KeyUsage.cRLSign), + NON_REPUDIATION(KeyUsage.nonRepudiation), + KEY_ENCIPHERMENT(KeyUsage.keyEncipherment), - SERVER_AUTH(KeyPurposeId.id_kp_serverAuth), + SERVER_AUTH(KeyPurposeId.id_kp_serverAuth), - CLIENT_AUTH(KeyPurposeId.id_kp_clientAuth); + CLIENT_AUTH(KeyPurposeId.id_kp_clientAuth); - private final int keyUsage; - private final KeyPurposeId id; + private final int keyUsage; + private final KeyPurposeId id; - PublicKeyUsage(int keyUsage) { - this.keyUsage = keyUsage; - this.id = null; - } + PublicKeyUsage(int keyUsage) { + this.keyUsage = keyUsage; + this.id = null; + } - PublicKeyUsage(KeyPurposeId id) { - this.id = Objects.requireNonNull(id, "Key purpose id is required."); - this.keyUsage = 0; - } + PublicKeyUsage(KeyPurposeId id) { + this.id = Objects.requireNonNull(id, "Key purpose id is required."); + this.keyUsage = 0; + } - boolean isExtendedUsage() { - return this.id != null; - } + boolean isExtendedUsage() { + return this.id != null; + } - boolean isNotExtendedUsage() { - return this.id == null; - } + boolean isNotExtendedUsage() { + return this.id == null; + } - int asInt(){ - if(isExtendedUsage()) { - throw new RuntimeException("Integer value is not available for extended key usage"); - } - return keyUsage; - } - KeyPurposeId getKeyPurposeId() { - if(isExtendedUsage() == false){ - throw new RuntimeException("Key purpose id is not available."); - } - return id; - } + int asInt() { + if (isExtendedUsage()) { + throw new RuntimeException("Integer value is not available for extended key usage"); + } + return keyUsage; + } + + KeyPurposeId getKeyPurposeId() { + if (isExtendedUsage() == false) { + throw new RuntimeException("Key purpose id is not available."); + } + return id; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java index bc5f5f267d..2dd1dd5eea 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java @@ -53,169 +53,162 @@ */ public class TestCertificates { - private static final Logger log = LogManager.getLogger(TestCertificates.class); - - public static final Integer MAX_NUMBER_OF_NODE_CERTIFICATES = 3; - - private static final String CA_SUBJECT = "DC=com,DC=example,O=Example Com Inc.,OU=Example Com Inc. Root CA,CN=Example Com Inc. Root CA"; - private static final String ADMIN_DN = "CN=kirk,OU=client,O=client,L=test,C=de"; - private static final int CERTIFICATE_VALIDITY_DAYS = 365; - private static final String CERTIFICATE_FILE_EXT = ".cert"; - private static final String KEY_FILE_EXT = ".key"; - private final CertificateData caCertificate; - private final CertificateData adminCertificate; - private final List nodeCertificates; - - private final CertificateData ldapCertificate; - - public TestCertificates() { - this.caCertificate = createCaCertificate(); - this.nodeCertificates = IntStream.range(0, MAX_NUMBER_OF_NODE_CERTIFICATES) - .mapToObj(this::createNodeCertificate) - .collect(Collectors.toList()); - this.ldapCertificate = createLdapCertificate(); - this.adminCertificate = createAdminCertificate(ADMIN_DN); - log.info("Test certificates successfully generated"); - } - - private CertificateData createCaCertificate() { - CertificateMetadata metadata = CertificateMetadata.basicMetadata(CA_SUBJECT, CERTIFICATE_VALIDITY_DAYS) - .withKeyUsage(true, DIGITAL_SIGNATURE, KEY_CERT_SIGN, CRL_SIGN); - return CertificatesIssuerFactory - .rsaBaseCertificateIssuer() - .issueSelfSignedCertificate(metadata); - } - - public CertificateData createAdminCertificate(String adminDn) { - CertificateMetadata metadata = CertificateMetadata.basicMetadata(adminDn, CERTIFICATE_VALIDITY_DAYS) - .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH); - return CertificatesIssuerFactory - .rsaBaseCertificateIssuer() - .issueSignedCertificate(metadata, caCertificate); - } - - public CertificateData createSelfSignedCertificate(String distinguishedName) { - CertificateMetadata metadata = CertificateMetadata.basicMetadata(distinguishedName, CERTIFICATE_VALIDITY_DAYS); - return CertificatesIssuerFactory - .rsaBaseCertificateIssuer() - .issueSelfSignedCertificate(metadata); - } - - /** - * It returns the most trusted certificate. Certificates for nodes and users are derived from this certificate. - * @return file which contains certificate in PEM format, defined by RFC 1421 - */ - public File getRootCertificate() { - return createTempFile("root", CERTIFICATE_FILE_EXT, caCertificate.certificateInPemFormat()); - } - - public CertificateData getRootCertificateData() { - return caCertificate; - } - - /** - * Certificate for Open Search node. The certificate is derived from root certificate, returned by method {@link #getRootCertificate()} - * @param node is a node index. It has to be less than {@link #MAX_NUMBER_OF_NODE_CERTIFICATES} - * @return file which contains certificate in PEM format, defined by RFC 1421 - */ - public File getNodeCertificate(int node) { - CertificateData certificateData = getNodeCertificateData(node); - return createTempFile("node-" + node, CERTIFICATE_FILE_EXT, certificateData.certificateInPemFormat()); - } - - public CertificateData getNodeCertificateData(int node) { - isCorrectNodeNumber(node); - return nodeCertificates.get(node); - } - - private void isCorrectNodeNumber(int node) { - if (node >= MAX_NUMBER_OF_NODE_CERTIFICATES) { - String message = String.format("Cannot get certificate for node %d, number of created certificates for nodes is %d", node, - MAX_NUMBER_OF_NODE_CERTIFICATES); - throw new RuntimeException(message); - } - } - - private CertificateData createNodeCertificate(Integer node) { - String subject = String.format("DC=de,L=test,O=node,OU=node,CN=node-%d.example.com", node); - String domain = String.format("node-%d.example.com", node); - CertificateMetadata metadata = CertificateMetadata.basicMetadata(subject, CERTIFICATE_VALIDITY_DAYS) - .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH) - .withSubjectAlternativeName("1.2.3.4.5.5", List.of(domain, "localhost"), "127.0.0.1"); - return CertificatesIssuerFactory - .rsaBaseCertificateIssuer() - .issueSignedCertificate(metadata, caCertificate); - } - - public CertificateData issueUserCertificate(String organizationUnit, String username) { - String subject = String.format("DC=de,L=test,O=users,OU=%s,CN=%s", organizationUnit, username); - CertificateMetadata metadata = CertificateMetadata.basicMetadata(subject, CERTIFICATE_VALIDITY_DAYS).withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH); - return CertificatesIssuerFactory.rsaBaseCertificateIssuer().issueSignedCertificate(metadata, caCertificate); - } - - private CertificateData createLdapCertificate() { - String subject = "DC=de,L=test,O=node,OU=node,CN=ldap.example.com"; - CertificateMetadata metadata = CertificateMetadata.basicMetadata(subject, CERTIFICATE_VALIDITY_DAYS) - .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH) - .withSubjectAlternativeName(null, List.of("localhost"), "127.0.0.1"); - return CertificatesIssuerFactory - .rsaBaseCertificateIssuer() - .issueSignedCertificate(metadata, caCertificate); - } - - - public CertificateData getLdapCertificateData() { - return ldapCertificate; - } - - /** - * It returns private key associated with node certificate returned by method {@link #getNodeCertificate(int)} - * - * @param node is a node index. It has to be less than {@link #MAX_NUMBER_OF_NODE_CERTIFICATES} - * @param privateKeyPassword is a password used to encode private key, can be null to retrieve unencrypted key. - * @return file which contains private key encoded in PEM format, defined - * by RFC 1421 - */ - public File getNodeKey(int node, String privateKeyPassword) { - CertificateData certificateData = nodeCertificates.get(node); - return createTempFile("node-" + node, KEY_FILE_EXT, certificateData.privateKeyInPemFormat(privateKeyPassword)); - } - - /** - * Certificate which proofs admin user identity. Certificate is derived from root certificate returned by - * method {@link #getRootCertificate()} - * @return file which contains certificate in PEM format, defined by RFC 1421 - */ - public File getAdminCertificate() { - return createTempFile("admin", CERTIFICATE_FILE_EXT, adminCertificate.certificateInPemFormat()); - } - - public CertificateData getAdminCertificateData() { - return adminCertificate; - } - - /** - * It returns private key associated with admin certificate returned by {@link #getAdminCertificate()}. - * - * @param privateKeyPassword is a password used to encode private key, can be null to retrieve unencrypted key. - * @return file which contains private key encoded in PEM format, defined - * by RFC 1421 - */ - public File getAdminKey(String privateKeyPassword) { - return createTempFile("admin", KEY_FILE_EXT, adminCertificate.privateKeyInPemFormat(privateKeyPassword)); - } - - public String[] getAdminDNs() { - return new String[] {ADMIN_DN}; - } - - private File createTempFile(String name, String suffix, String contents) { - try { - Path path = Files.createTempFile(name, suffix); - Files.writeString(path, contents); - return path.toFile(); - } catch (IOException ex) { - throw new RuntimeException("Cannot create temp file with name " + name + " and suffix " + suffix); - } - } + private static final Logger log = LogManager.getLogger(TestCertificates.class); + + public static final Integer MAX_NUMBER_OF_NODE_CERTIFICATES = 3; + + private static final String CA_SUBJECT = "DC=com,DC=example,O=Example Com Inc.,OU=Example Com Inc. Root CA,CN=Example Com Inc. Root CA"; + private static final String ADMIN_DN = "CN=kirk,OU=client,O=client,L=test,C=de"; + private static final int CERTIFICATE_VALIDITY_DAYS = 365; + private static final String CERTIFICATE_FILE_EXT = ".cert"; + private static final String KEY_FILE_EXT = ".key"; + private final CertificateData caCertificate; + private final CertificateData adminCertificate; + private final List nodeCertificates; + + private final CertificateData ldapCertificate; + + public TestCertificates() { + this.caCertificate = createCaCertificate(); + this.nodeCertificates = IntStream.range(0, MAX_NUMBER_OF_NODE_CERTIFICATES) + .mapToObj(this::createNodeCertificate) + .collect(Collectors.toList()); + this.ldapCertificate = createLdapCertificate(); + this.adminCertificate = createAdminCertificate(ADMIN_DN); + log.info("Test certificates successfully generated"); + } + + private CertificateData createCaCertificate() { + CertificateMetadata metadata = CertificateMetadata.basicMetadata(CA_SUBJECT, CERTIFICATE_VALIDITY_DAYS) + .withKeyUsage(true, DIGITAL_SIGNATURE, KEY_CERT_SIGN, CRL_SIGN); + return CertificatesIssuerFactory.rsaBaseCertificateIssuer().issueSelfSignedCertificate(metadata); + } + + public CertificateData createAdminCertificate(String adminDn) { + CertificateMetadata metadata = CertificateMetadata.basicMetadata(adminDn, CERTIFICATE_VALIDITY_DAYS) + .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH); + return CertificatesIssuerFactory.rsaBaseCertificateIssuer().issueSignedCertificate(metadata, caCertificate); + } + + public CertificateData createSelfSignedCertificate(String distinguishedName) { + CertificateMetadata metadata = CertificateMetadata.basicMetadata(distinguishedName, CERTIFICATE_VALIDITY_DAYS); + return CertificatesIssuerFactory.rsaBaseCertificateIssuer().issueSelfSignedCertificate(metadata); + } + + /** + * It returns the most trusted certificate. Certificates for nodes and users are derived from this certificate. + * @return file which contains certificate in PEM format, defined by RFC 1421 + */ + public File getRootCertificate() { + return createTempFile("root", CERTIFICATE_FILE_EXT, caCertificate.certificateInPemFormat()); + } + + public CertificateData getRootCertificateData() { + return caCertificate; + } + + /** + * Certificate for Open Search node. The certificate is derived from root certificate, returned by method {@link #getRootCertificate()} + * @param node is a node index. It has to be less than {@link #MAX_NUMBER_OF_NODE_CERTIFICATES} + * @return file which contains certificate in PEM format, defined by RFC 1421 + */ + public File getNodeCertificate(int node) { + CertificateData certificateData = getNodeCertificateData(node); + return createTempFile("node-" + node, CERTIFICATE_FILE_EXT, certificateData.certificateInPemFormat()); + } + + public CertificateData getNodeCertificateData(int node) { + isCorrectNodeNumber(node); + return nodeCertificates.get(node); + } + + private void isCorrectNodeNumber(int node) { + if (node >= MAX_NUMBER_OF_NODE_CERTIFICATES) { + String message = String.format( + "Cannot get certificate for node %d, number of created certificates for nodes is %d", + node, + MAX_NUMBER_OF_NODE_CERTIFICATES + ); + throw new RuntimeException(message); + } + } + + private CertificateData createNodeCertificate(Integer node) { + String subject = String.format("DC=de,L=test,O=node,OU=node,CN=node-%d.example.com", node); + String domain = String.format("node-%d.example.com", node); + CertificateMetadata metadata = CertificateMetadata.basicMetadata(subject, CERTIFICATE_VALIDITY_DAYS) + .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH) + .withSubjectAlternativeName("1.2.3.4.5.5", List.of(domain, "localhost"), "127.0.0.1"); + return CertificatesIssuerFactory.rsaBaseCertificateIssuer().issueSignedCertificate(metadata, caCertificate); + } + + public CertificateData issueUserCertificate(String organizationUnit, String username) { + String subject = String.format("DC=de,L=test,O=users,OU=%s,CN=%s", organizationUnit, username); + CertificateMetadata metadata = CertificateMetadata.basicMetadata(subject, CERTIFICATE_VALIDITY_DAYS) + .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH); + return CertificatesIssuerFactory.rsaBaseCertificateIssuer().issueSignedCertificate(metadata, caCertificate); + } + + private CertificateData createLdapCertificate() { + String subject = "DC=de,L=test,O=node,OU=node,CN=ldap.example.com"; + CertificateMetadata metadata = CertificateMetadata.basicMetadata(subject, CERTIFICATE_VALIDITY_DAYS) + .withKeyUsage(false, DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, CLIENT_AUTH, SERVER_AUTH) + .withSubjectAlternativeName(null, List.of("localhost"), "127.0.0.1"); + return CertificatesIssuerFactory.rsaBaseCertificateIssuer().issueSignedCertificate(metadata, caCertificate); + } + + public CertificateData getLdapCertificateData() { + return ldapCertificate; + } + + /** + * It returns private key associated with node certificate returned by method {@link #getNodeCertificate(int)} + * + * @param node is a node index. It has to be less than {@link #MAX_NUMBER_OF_NODE_CERTIFICATES} + * @param privateKeyPassword is a password used to encode private key, can be null to retrieve unencrypted key. + * @return file which contains private key encoded in PEM format, defined + * by RFC 1421 + */ + public File getNodeKey(int node, String privateKeyPassword) { + CertificateData certificateData = nodeCertificates.get(node); + return createTempFile("node-" + node, KEY_FILE_EXT, certificateData.privateKeyInPemFormat(privateKeyPassword)); + } + + /** + * Certificate which proofs admin user identity. Certificate is derived from root certificate returned by + * method {@link #getRootCertificate()} + * @return file which contains certificate in PEM format, defined by RFC 1421 + */ + public File getAdminCertificate() { + return createTempFile("admin", CERTIFICATE_FILE_EXT, adminCertificate.certificateInPemFormat()); + } + + public CertificateData getAdminCertificateData() { + return adminCertificate; + } + + /** + * It returns private key associated with admin certificate returned by {@link #getAdminCertificate()}. + * + * @param privateKeyPassword is a password used to encode private key, can be null to retrieve unencrypted key. + * @return file which contains private key encoded in PEM format, defined + * by RFC 1421 + */ + public File getAdminKey(String privateKeyPassword) { + return createTempFile("admin", KEY_FILE_EXT, adminCertificate.privateKeyInPemFormat(privateKeyPassword)); + } + + public String[] getAdminDNs() { + return new String[] { ADMIN_DN }; + } + + private File createTempFile(String name, String suffix, String contents) { + try { + Path path = Files.createTempFile(name, suffix); + Files.writeString(path, contents); + return path.toFile(); + } catch (IOException ex) { + throw new RuntimeException("Cannot create temp file with name " + name + " and suffix " + suffix); + } + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/CloseableHttpClientFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/CloseableHttpClientFactory.java index e0e57d2ef1..ed236dcd0c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/CloseableHttpClientFactory.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/CloseableHttpClientFactory.java @@ -27,44 +27,50 @@ class CloseableHttpClientFactory { - private final SSLContext sslContext; + private final SSLContext sslContext; - private final RequestConfig requestConfig; + private final RequestConfig requestConfig; - private final HttpRoutePlanner routePlanner; + private final HttpRoutePlanner routePlanner; - private final String[] supportedCipherSuit; + private final String[] supportedCipherSuit; - public CloseableHttpClientFactory(SSLContext sslContext, - RequestConfig requestConfig, - HttpRoutePlanner routePlanner, - String[] supportedCipherSuit) { - this.sslContext = Objects.requireNonNull(sslContext, "SSL context is required."); - this.requestConfig = requestConfig; - this.routePlanner = routePlanner; - this.supportedCipherSuit = supportedCipherSuit; - } + public CloseableHttpClientFactory( + SSLContext sslContext, + RequestConfig requestConfig, + HttpRoutePlanner routePlanner, + String[] supportedCipherSuit + ) { + this.sslContext = Objects.requireNonNull(sslContext, "SSL context is required."); + this.requestConfig = requestConfig; + this.routePlanner = routePlanner; + this.supportedCipherSuit = supportedCipherSuit; + } - public CloseableHttpClient getHTTPClient() { + public CloseableHttpClient getHTTPClient() { - final HttpClientBuilder hcb = HttpClients.custom(); + final HttpClientBuilder hcb = HttpClients.custom(); - final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(this.sslContext, null, supportedCipherSuit, - NoopHostnameVerifier.INSTANCE); + final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( + this.sslContext, + null, + supportedCipherSuit, + NoopHostnameVerifier.INSTANCE + ); - final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslsf) - .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60, TimeUnit.SECONDS).build()) - .build(); - hcb.setConnectionManager(cm); - if(routePlanner != null) { - hcb.setRoutePlanner(routePlanner); - } + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslsf) + .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60, TimeUnit.SECONDS).build()) + .build(); + hcb.setConnectionManager(cm); + if (routePlanner != null) { + hcb.setRoutePlanner(routePlanner); + } - if (requestConfig != null) { - hcb.setDefaultRequestConfig(requestConfig); - } + if (requestConfig != null) { + hcb.setDefaultRequestConfig(requestConfig); + } - return hcb.build(); - } + return hcb.build(); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java index 35c5229bfb..dff40e395c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/ClusterManager.java @@ -1,10 +1,10 @@ /* * Copyright 2015-2017 floragunn GmbH -* +* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at -* +* * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,7 +12,7 @@ * 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. -* +* */ /* @@ -52,105 +52,121 @@ import static org.opensearch.test.framework.cluster.NodeType.DATA; public enum ClusterManager { - //3 nodes (1m, 2d) - DEFAULT(new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.DATA), new NodeSettings(NodeRole.DATA)), - - //1 node (1md) - SINGLENODE(new NodeSettings(NodeRole.CLUSTER_MANAGER, NodeRole.DATA)), - - SINGLE_REMOTE_CLIENT(new NodeSettings(NodeRole.CLUSTER_MANAGER, NodeRole.DATA, NodeRole.REMOTE_CLUSTER_CLIENT)), - - //4 node (1m, 2d, 1c) - CLIENTNODE(new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.DATA), new NodeSettings(NodeRole.DATA), new NodeSettings()), - - THREE_CLUSTER_MANAGERS(new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.DATA), new NodeSettings(NodeRole.DATA)); - - private List nodeSettings = new LinkedList<>(); - - private ClusterManager(NodeSettings... settings) { - nodeSettings.addAll(Arrays.asList(settings)); - } - - public List getNodeSettings() { - return unmodifiableList(nodeSettings); - } - - public List getClusterManagerNodeSettings() { - return unmodifiableList(nodeSettings.stream().filter(a -> a.containRole(NodeRole.CLUSTER_MANAGER)).collect(Collectors.toList())); - } - - public List getNonClusterManagerNodeSettings() { - return unmodifiableList(nodeSettings.stream().filter(a -> !a.containRole(NodeRole.CLUSTER_MANAGER)).collect(Collectors.toList())); - } - - public int getNodes() { - return nodeSettings.size(); - } - - public int getClusterManagerNodes() { - return (int) nodeSettings.stream().filter(a -> a.containRole(NodeRole.CLUSTER_MANAGER)).count(); - } - - public int getDataNodes() { - return (int) nodeSettings.stream().filter(a -> a.containRole(NodeRole.DATA)).count(); - } - - public int getClientNodes() { - return (int) nodeSettings.stream().filter(a -> a.isClientNode()).count(); - } - - - public static class NodeSettings { - - private final static List> DEFAULT_PLUGINS = List.of(Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class, - MatrixAggregationModulePlugin.class, ParentJoinModulePlugin.class, PercolatorModulePlugin.class, ReindexModulePlugin.class); - - private final Set roles; - public final List> plugins; - - public NodeSettings(NodeRole...roles) { - this(roles.length == 0 ? Collections.emptySet() : EnumSet.copyOf(Arrays.asList(roles)), Collections.emptyList()); - } - - public NodeSettings(Set roles, List> additionalPlugins) { - super(); - this.roles = Objects.requireNonNull(roles, "Node roles set must not be null"); - this.plugins = mergePlugins(additionalPlugins, DEFAULT_PLUGINS); - } - - public boolean containRole(NodeRole nodeRole) { - return roles.contains(nodeRole); - } - - public boolean isClientNode() { - return (roles.contains(NodeRole.DATA) == false) && (roles.contains(NodeRole.CLUSTER_MANAGER)); - } - - NodeType recognizeNodeType() { - if (roles.contains(NodeRole.CLUSTER_MANAGER)) { - return CLUSTER_MANAGER; - } else if (roles.contains(NodeRole.DATA)) { - return DATA; - } else { - return CLIENT; - } - } - - private List> mergePlugins(Collection>...plugins) { - List> mergedPlugins = Arrays.stream(plugins) - .filter(Objects::nonNull) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - return unmodifiableList(mergedPlugins); - } - - @SuppressWarnings("unchecked") - public Class[] getPlugins() { - return plugins.toArray(new Class[0]); - } - - public Class[] pluginsWithAddition(List> additionalPlugins) { - return mergePlugins(plugins, additionalPlugins).toArray(Class[]::new); - } - } + // 3 nodes (1m, 2d) + DEFAULT(new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.DATA), new NodeSettings(NodeRole.DATA)), + + // 1 node (1md) + SINGLENODE(new NodeSettings(NodeRole.CLUSTER_MANAGER, NodeRole.DATA)), + + SINGLE_REMOTE_CLIENT(new NodeSettings(NodeRole.CLUSTER_MANAGER, NodeRole.DATA, NodeRole.REMOTE_CLUSTER_CLIENT)), + + // 4 node (1m, 2d, 1c) + CLIENTNODE( + new NodeSettings(NodeRole.CLUSTER_MANAGER), + new NodeSettings(NodeRole.DATA), + new NodeSettings(NodeRole.DATA), + new NodeSettings() + ), + + THREE_CLUSTER_MANAGERS( + new NodeSettings(NodeRole.CLUSTER_MANAGER), + new NodeSettings(NodeRole.CLUSTER_MANAGER), + new NodeSettings(NodeRole.CLUSTER_MANAGER), + new NodeSettings(NodeRole.DATA), + new NodeSettings(NodeRole.DATA) + ); + + private List nodeSettings = new LinkedList<>(); + + private ClusterManager(NodeSettings... settings) { + nodeSettings.addAll(Arrays.asList(settings)); + } + + public List getNodeSettings() { + return unmodifiableList(nodeSettings); + } + + public List getClusterManagerNodeSettings() { + return unmodifiableList(nodeSettings.stream().filter(a -> a.containRole(NodeRole.CLUSTER_MANAGER)).collect(Collectors.toList())); + } + + public List getNonClusterManagerNodeSettings() { + return unmodifiableList(nodeSettings.stream().filter(a -> !a.containRole(NodeRole.CLUSTER_MANAGER)).collect(Collectors.toList())); + } + + public int getNodes() { + return nodeSettings.size(); + } + + public int getClusterManagerNodes() { + return (int) nodeSettings.stream().filter(a -> a.containRole(NodeRole.CLUSTER_MANAGER)).count(); + } + + public int getDataNodes() { + return (int) nodeSettings.stream().filter(a -> a.containRole(NodeRole.DATA)).count(); + } + + public int getClientNodes() { + return (int) nodeSettings.stream().filter(a -> a.isClientNode()).count(); + } + + public static class NodeSettings { + + private final static List> DEFAULT_PLUGINS = List.of( + Netty4ModulePlugin.class, + OpenSearchSecurityPlugin.class, + MatrixAggregationModulePlugin.class, + ParentJoinModulePlugin.class, + PercolatorModulePlugin.class, + ReindexModulePlugin.class + ); + + private final Set roles; + public final List> plugins; + + public NodeSettings(NodeRole... roles) { + this(roles.length == 0 ? Collections.emptySet() : EnumSet.copyOf(Arrays.asList(roles)), Collections.emptyList()); + } + + public NodeSettings(Set roles, List> additionalPlugins) { + super(); + this.roles = Objects.requireNonNull(roles, "Node roles set must not be null"); + this.plugins = mergePlugins(additionalPlugins, DEFAULT_PLUGINS); + } + + public boolean containRole(NodeRole nodeRole) { + return roles.contains(nodeRole); + } + + public boolean isClientNode() { + return (roles.contains(NodeRole.DATA) == false) && (roles.contains(NodeRole.CLUSTER_MANAGER)); + } + + NodeType recognizeNodeType() { + if (roles.contains(NodeRole.CLUSTER_MANAGER)) { + return CLUSTER_MANAGER; + } else if (roles.contains(NodeRole.DATA)) { + return DATA; + } else { + return CLIENT; + } + } + + private List> mergePlugins(Collection>... plugins) { + List> mergedPlugins = Arrays.stream(plugins) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + return unmodifiableList(mergedPlugins); + } + + @SuppressWarnings("unchecked") + public Class[] getPlugins() { + return plugins.toArray(new Class[0]); + } + + public Class[] pluginsWithAddition(List> additionalPlugins) { + return mergePlugins(plugins, additionalPlugins).toArray(Class[]::new); + } + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/ContextHeaderDecoratorClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/ContextHeaderDecoratorClient.java index 890de49ed7..2b05807fa2 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/ContextHeaderDecoratorClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/ContextHeaderDecoratorClient.java @@ -27,23 +27,29 @@ */ public class ContextHeaderDecoratorClient extends FilterClient { - private Map headers; - - public ContextHeaderDecoratorClient(Client in, Map headers) { - super(in); - this.headers = headers != null ? headers : Collections.emptyMap(); - } - - @Override - protected void doExecute(ActionType action, Request request, - ActionListener listener) { - - ThreadContext threadContext = threadPool().getThreadContext(); - ContextPreservingActionListener wrappedListener = new ContextPreservingActionListener<>(threadContext.newRestorableContext(true), listener); - - try (StoredContext ctx = threadContext.stashContext()) { - threadContext.putHeader(this.headers); - super.doExecute(action, request, wrappedListener); - } - } + private Map headers; + + public ContextHeaderDecoratorClient(Client in, Map headers) { + super(in); + this.headers = headers != null ? headers : Collections.emptyMap(); + } + + @Override + protected void doExecute( + ActionType action, + Request request, + ActionListener listener + ) { + + ThreadContext threadContext = threadPool().getThreadContext(); + ContextPreservingActionListener wrappedListener = new ContextPreservingActionListener<>( + threadContext.newRestorableContext(true), + listener + ); + + try (StoredContext ctx = threadContext.stashContext()) { + threadContext.putHeader(this.headers); + super.doExecute(action, request, wrappedListener); + } + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalAddressRoutePlanner.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalAddressRoutePlanner.java index ab29d3206e..9181186f52 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalAddressRoutePlanner.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalAddressRoutePlanner.java @@ -23,26 +23,26 @@ */ class LocalAddressRoutePlanner extends DefaultRoutePlanner { - /** - * IP address of one of the local network interfaces. - */ - private final InetAddress localAddress; + /** + * IP address of one of the local network interfaces. + */ + private final InetAddress localAddress; - /** - * Creates {@link LocalAddressRoutePlanner} - * @param localAddress IP address of one of the local network interfaces. Client socket used by Apache HTTP client will be bind to - * address from this parameter. The parameter must not be null. - */ - public LocalAddressRoutePlanner(InetAddress localAddress) { - super(DefaultSchemePortResolver.INSTANCE); - this.localAddress = Objects.requireNonNull(localAddress); - } + /** + * Creates {@link LocalAddressRoutePlanner} + * @param localAddress IP address of one of the local network interfaces. Client socket used by Apache HTTP client will be bind to + * address from this parameter. The parameter must not be null. + */ + public LocalAddressRoutePlanner(InetAddress localAddress) { + super(DefaultSchemePortResolver.INSTANCE); + this.localAddress = Objects.requireNonNull(localAddress); + } - /** - * Determines IP address used by the client socket of Apache HTTP client - */ - @Override - protected InetAddress determineLocalAddress(HttpHost firstHop, HttpContext context) { - return localAddress; - } + /** + * Determines IP address used by the client socket of Apache HTTP client + */ + @Override + protected InetAddress determineLocalAddress(HttpHost firstHop, HttpContext context) { + return localAddress; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index e9bb7b5be5..539e15fb57 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -74,414 +74,455 @@ */ public class LocalCluster extends ExternalResource implements AutoCloseable, OpenSearchClientProvider { - private static final Logger log = LogManager.getLogger(LocalCluster.class); - - public static final String INIT_CONFIGURATION_DIR = "security.default_init.dir"; - - protected static final AtomicLong num = new AtomicLong(); - - private boolean sslOnly; - - private final List> plugins; - private final ClusterManager clusterManager; - private final TestSecurityConfig testSecurityConfig; - private Settings nodeOverride; - private final String clusterName; - private final MinimumSecuritySettingsSupplierFactory minimumOpenSearchSettingsSupplierFactory; - private final TestCertificates testCertificates; - private final List clusterDependencies; - private final Map remotes; - private volatile LocalOpenSearchCluster localOpenSearchCluster; - private final List testIndices; - - private boolean loadConfigurationIntoIndex; - - private LocalCluster(String clusterName, TestSecurityConfig testSgConfig, boolean sslOnly, Settings nodeOverride, - ClusterManager clusterManager, List> plugins, TestCertificates testCertificates, - List clusterDependencies, Map remotes, List testIndices, - boolean loadConfigurationIntoIndex, String defaultConfigurationInitDirectory) { - this.plugins = plugins; - this.testCertificates = testCertificates; - this.clusterManager = clusterManager; - this.testSecurityConfig = testSgConfig; - this.sslOnly = sslOnly; - this.nodeOverride = nodeOverride; - this.clusterName = clusterName; - this.minimumOpenSearchSettingsSupplierFactory = new MinimumSecuritySettingsSupplierFactory(testCertificates); - this.remotes = remotes; - this.clusterDependencies = clusterDependencies; - this.testIndices = testIndices; - this.loadConfigurationIntoIndex = loadConfigurationIntoIndex; - if(StringUtils.isNoneBlank(defaultConfigurationInitDirectory)) { - System.setProperty(INIT_CONFIGURATION_DIR, defaultConfigurationInitDirectory); - } - } - - public String getSnapshotDirPath() { - return localOpenSearchCluster.getSnapshotDirPath(); - } - - @Override - public void before() throws Throwable { - if (localOpenSearchCluster == null) { - for (LocalCluster dependency : clusterDependencies) { - if (!dependency.isStarted()) { - dependency.before(); - } - } - - for (Map.Entry entry : remotes.entrySet()) { - @SuppressWarnings("resource") - InetSocketAddress transportAddress = entry.getValue().localOpenSearchCluster.clusterManagerNode().getTransportAddress(); - String key = "cluster.remote." + entry.getKey() + ".seeds"; - String value = transportAddress.getHostString() + ":" + transportAddress.getPort(); - log.info("Remote cluster '{}' added to configuration with the following seed '{}'", key, value); - nodeOverride = Settings.builder().put(nodeOverride) - .putList(key, value) - .build(); - } - start(); - } - } - - @Override - protected void after() { - System.clearProperty(INIT_CONFIGURATION_DIR); - close(); - } - - @Override - public void close() { - if (localOpenSearchCluster != null && localOpenSearchCluster.isStarted()) { - try { - localOpenSearchCluster.destroy(); - } catch (Exception e) { - throw new RuntimeException(e); - } finally { - localOpenSearchCluster = null; - } - } - } - - @Override - public String getClusterName() { - return clusterName; - } - - @Override - public InetSocketAddress getHttpAddress() { - return localOpenSearchCluster.clientNode().getHttpAddress(); - } - - public int getHttpPort() { - return getHttpAddress().getPort(); - } - - @Override - public InetSocketAddress getTransportAddress() { - return localOpenSearchCluster.clientNode().getTransportAddress(); - } - - /** - * Returns a Client object that performs cluster-internal requests. As these requests are regard as cluster-internal, - * no authentication is performed and no user-information is attached to these requests. Thus, this client should - * be only used for preparing test environments, but not as a test subject. - */ - public Client getInternalNodeClient() { - return localOpenSearchCluster.clientNode().getInternalNodeClient(); - } - - /** - * Returns a random node of this cluster. - */ - public PluginAwareNode node() { - return this.localOpenSearchCluster.clusterManagerNode().esNode(); - } - - /** - * Returns all nodes of this cluster. - */ - public List nodes() { - return this.localOpenSearchCluster.getNodes(); - } - - public LocalOpenSearchCluster.Node getNodeByName(String name) { - return this.localOpenSearchCluster.getNodeByName(name); - } - - public boolean isStarted() { - return localOpenSearchCluster != null; - } - - public List getConfiguredUsers() { - return testSecurityConfig.getUsers(); - } - - public Random getRandom() { - return localOpenSearchCluster.getRandom(); - } - - private void start() { - try { - NodeSettingsSupplier nodeSettingsSupplier = minimumOpenSearchSettingsSupplierFactory.minimumOpenSearchSettings(sslOnly, nodeOverride); - localOpenSearchCluster = new LocalOpenSearchCluster(clusterName, clusterManager, nodeSettingsSupplier, plugins, testCertificates); - - localOpenSearchCluster.start(); - - - if (loadConfigurationIntoIndex) { - initSecurityIndex(testSecurityConfig); - } - - try (Client client = getInternalNodeClient()) { - for (TestIndex index : this.testIndices) { - index.create(client); - } - } - - } catch (Exception e) { - log.error("Local ES cluster start failed", e); - throw new RuntimeException(e); - } - } - - private void initSecurityIndex(TestSecurityConfig testSecurityConfig) { - log.info("Initializing OpenSearch Security index"); - try(Client client = new ContextHeaderDecoratorClient(this.getInternalNodeClient(), Map.of(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER , "true"))) { - testSecurityConfig.initIndex(client); - triggerConfigurationReload(client); - } - } - - public void updateUserConfiguration(List users) { - try(Client client = new ContextHeaderDecoratorClient(this.getInternalNodeClient(), Map.of(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER , "true"))) { - testSecurityConfig.updateInternalUsersConfiguration(client, users); - triggerConfigurationReload(client); - } - } - - private static void triggerConfigurationReload(Client client) { - ConfigUpdateResponse configUpdateResponse = client.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0]))).actionGet(); - if (configUpdateResponse.hasFailures()) { - throw new RuntimeException("ConfigUpdateResponse produced failures: " + configUpdateResponse.failures()); - } - } - - public CertificateData getAdminCertificate() { - return testCertificates.getAdminCertificateData(); - } - - public static class Builder { - - private final Settings.Builder nodeOverrideSettingsBuilder = Settings.builder(); - - private boolean sslOnly = false; - private final List> plugins = new ArrayList<>(); - private Map remoteClusters = new HashMap<>(); - private List clusterDependencies = new ArrayList<>(); - private List testIndices = new ArrayList<>(); - private ClusterManager clusterManager = ClusterManager.DEFAULT; - private TestSecurityConfig testSecurityConfig = new TestSecurityConfig(); - private String clusterName = "local_cluster"; - private TestCertificates testCertificates; - - private boolean loadConfigurationIntoIndex = true; - - private String defaultConfigurationInitDirectory = null; - - public Builder() { - } - - public Builder dependsOn(Object object) { - // We just want to make sure that the object is already done - if (object == null) { - throw new IllegalStateException("Dependency not fulfilled"); - } - return this; - } - - public Builder clusterManager(ClusterManager clusterManager) { - this.clusterManager = clusterManager; - return this; - } - - /** - * Starts a cluster with only one node and thus saves some resources during startup. This shall be only used - * for tests where the node interactions are not relevant to the test. An example for this would be - * authentication tests, as authentication is always done on the directly connected node. - */ - public Builder singleNode() { - this.clusterManager = ClusterManager.SINGLENODE; - return this; - } - - /** - * Specifies the configuration of the security plugin that shall be used by this cluster. - */ - public Builder config(TestSecurityConfig testSecurityConfig) { - this.testSecurityConfig = testSecurityConfig; - return this; - } - - public Builder sslOnly(boolean sslOnly) { - this.sslOnly = sslOnly; - return this; - } - - public Builder nodeSettings(Map settings) { - settings.forEach((key, value) -> { - if (value instanceof List) { - List values = ((List) value).stream().map(String::valueOf).collect(Collectors.toList()); - nodeOverrideSettingsBuilder.putList(key, values); - } else { - nodeOverrideSettingsBuilder.put(key, String.valueOf(value)); - } - }); - - return this; - } - - /** - * Adds additional plugins to the cluster - */ - public Builder plugin(Class plugin) { - this.plugins.add(plugin); - - return this; - } - - public Builder authFailureListeners(AuthFailureListeners listener) { - testSecurityConfig.authFailureListeners(listener); - return this; - } - - /** - * Specifies a remote cluster and its name. The remote cluster can be then used in Cross Cluster Search - * operations with the specified name. - */ - public Builder remote(String name, LocalCluster anotherCluster) { - remoteClusters.put(name, anotherCluster); - - clusterDependencies.add(anotherCluster); - - return this; - } - - /** - * Specifies test indices that shall be created upon startup of the cluster. - */ - public Builder indices(TestIndex... indices) { - this.testIndices.addAll(Arrays.asList(indices)); - return this; - } - - public Builder users(TestSecurityConfig.User... users) { - for (TestSecurityConfig.User user : users) { - testSecurityConfig.user(user); - } - return this; - } - - public Builder audit(AuditConfiguration auditConfiguration) { - if (auditConfiguration != null) { - testSecurityConfig.audit(auditConfiguration); - } - if (auditConfiguration.isEnabled()) { - nodeOverrideSettingsBuilder.put("plugins.security.audit.type", TestRuleAuditLogSink.class.getName()); - } else { - nodeOverrideSettingsBuilder.put("plugins.security.audit.type", "noop"); - } - return this; - } - - public List getUsers() { - return testSecurityConfig.getUsers(); - } - - public Builder roles(Role... roles) { - testSecurityConfig.roles(roles); - return this; - } - - public Builder rolesMapping(RolesMapping...mappings) { - testSecurityConfig.rolesMapping(mappings); - return this; - } - - public Builder authc(TestSecurityConfig.AuthcDomain authc) { - testSecurityConfig.authc(authc); - return this; - } - - public Builder authz(AuthzDomain authzDomain) { - testSecurityConfig.authz(authzDomain); - return this; - } - - public Builder clusterName(String clusterName) { - this.clusterName = clusterName; - return this; - } - - public Builder configIndexName(String configIndexName) { - testSecurityConfig.configIndexName(configIndexName); - return this; - } - - public Builder testCertificates(TestCertificates certificates) { - this.testCertificates = certificates; - return this; - } - - public Builder anonymousAuth(boolean anonAuthEnabled) { - testSecurityConfig.anonymousAuth(anonAuthEnabled); - return this; - } - - public Builder xff(XffConfig xffConfig){ - testSecurityConfig.xff(xffConfig); - return this; - } - - public Builder loadConfigurationIntoIndex(boolean loadConfigurationIntoIndex) { - this.loadConfigurationIntoIndex = loadConfigurationIntoIndex; - return this; - } - public Builder certificates(TestCertificates certificates) { - this.testCertificates = certificates; - return this; - } - - public Builder doNotFailOnForbidden(boolean doNotFailOnForbidden) { - testSecurityConfig.doNotFailOnForbidden(doNotFailOnForbidden); - return this; - } - - public Builder defaultConfigurationInitDirectory(String defaultConfigurationInitDirectory){ - this.defaultConfigurationInitDirectory = defaultConfigurationInitDirectory; - return this; - } - - public LocalCluster build() { - try { - if(testCertificates == null) { - testCertificates = new TestCertificates(); - } - clusterName += "_" + num.incrementAndGet(); - Settings settings = nodeOverrideSettingsBuilder.build(); - return new LocalCluster(clusterName, testSecurityConfig, sslOnly, settings, clusterManager, plugins, testCertificates, - clusterDependencies, remoteClusters, testIndices, loadConfigurationIntoIndex, defaultConfigurationInitDirectory); - } catch (Exception e) { - log.error("Failed to build LocalCluster", e); - throw new RuntimeException(e); - } - } - - } - - @Override - public TestCertificates getTestCertificates() { - return testCertificates; - } + private static final Logger log = LogManager.getLogger(LocalCluster.class); + + public static final String INIT_CONFIGURATION_DIR = "security.default_init.dir"; + + protected static final AtomicLong num = new AtomicLong(); + + private boolean sslOnly; + + private final List> plugins; + private final ClusterManager clusterManager; + private final TestSecurityConfig testSecurityConfig; + private Settings nodeOverride; + private final String clusterName; + private final MinimumSecuritySettingsSupplierFactory minimumOpenSearchSettingsSupplierFactory; + private final TestCertificates testCertificates; + private final List clusterDependencies; + private final Map remotes; + private volatile LocalOpenSearchCluster localOpenSearchCluster; + private final List testIndices; + + private boolean loadConfigurationIntoIndex; + + private LocalCluster( + String clusterName, + TestSecurityConfig testSgConfig, + boolean sslOnly, + Settings nodeOverride, + ClusterManager clusterManager, + List> plugins, + TestCertificates testCertificates, + List clusterDependencies, + Map remotes, + List testIndices, + boolean loadConfigurationIntoIndex, + String defaultConfigurationInitDirectory + ) { + this.plugins = plugins; + this.testCertificates = testCertificates; + this.clusterManager = clusterManager; + this.testSecurityConfig = testSgConfig; + this.sslOnly = sslOnly; + this.nodeOverride = nodeOverride; + this.clusterName = clusterName; + this.minimumOpenSearchSettingsSupplierFactory = new MinimumSecuritySettingsSupplierFactory(testCertificates); + this.remotes = remotes; + this.clusterDependencies = clusterDependencies; + this.testIndices = testIndices; + this.loadConfigurationIntoIndex = loadConfigurationIntoIndex; + if (StringUtils.isNoneBlank(defaultConfigurationInitDirectory)) { + System.setProperty(INIT_CONFIGURATION_DIR, defaultConfigurationInitDirectory); + } + } + + public String getSnapshotDirPath() { + return localOpenSearchCluster.getSnapshotDirPath(); + } + + @Override + public void before() throws Throwable { + if (localOpenSearchCluster == null) { + for (LocalCluster dependency : clusterDependencies) { + if (!dependency.isStarted()) { + dependency.before(); + } + } + + for (Map.Entry entry : remotes.entrySet()) { + @SuppressWarnings("resource") + InetSocketAddress transportAddress = entry.getValue().localOpenSearchCluster.clusterManagerNode().getTransportAddress(); + String key = "cluster.remote." + entry.getKey() + ".seeds"; + String value = transportAddress.getHostString() + ":" + transportAddress.getPort(); + log.info("Remote cluster '{}' added to configuration with the following seed '{}'", key, value); + nodeOverride = Settings.builder().put(nodeOverride).putList(key, value).build(); + } + start(); + } + } + + @Override + protected void after() { + System.clearProperty(INIT_CONFIGURATION_DIR); + close(); + } + + @Override + public void close() { + if (localOpenSearchCluster != null && localOpenSearchCluster.isStarted()) { + try { + localOpenSearchCluster.destroy(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + localOpenSearchCluster = null; + } + } + } + + @Override + public String getClusterName() { + return clusterName; + } + + @Override + public InetSocketAddress getHttpAddress() { + return localOpenSearchCluster.clientNode().getHttpAddress(); + } + + public int getHttpPort() { + return getHttpAddress().getPort(); + } + + @Override + public InetSocketAddress getTransportAddress() { + return localOpenSearchCluster.clientNode().getTransportAddress(); + } + + /** + * Returns a Client object that performs cluster-internal requests. As these requests are regard as cluster-internal, + * no authentication is performed and no user-information is attached to these requests. Thus, this client should + * be only used for preparing test environments, but not as a test subject. + */ + public Client getInternalNodeClient() { + return localOpenSearchCluster.clientNode().getInternalNodeClient(); + } + + /** + * Returns a random node of this cluster. + */ + public PluginAwareNode node() { + return this.localOpenSearchCluster.clusterManagerNode().esNode(); + } + + /** + * Returns all nodes of this cluster. + */ + public List nodes() { + return this.localOpenSearchCluster.getNodes(); + } + + public LocalOpenSearchCluster.Node getNodeByName(String name) { + return this.localOpenSearchCluster.getNodeByName(name); + } + + public boolean isStarted() { + return localOpenSearchCluster != null; + } + + public List getConfiguredUsers() { + return testSecurityConfig.getUsers(); + } + + public Random getRandom() { + return localOpenSearchCluster.getRandom(); + } + + private void start() { + try { + NodeSettingsSupplier nodeSettingsSupplier = minimumOpenSearchSettingsSupplierFactory.minimumOpenSearchSettings( + sslOnly, + nodeOverride + ); + localOpenSearchCluster = new LocalOpenSearchCluster( + clusterName, + clusterManager, + nodeSettingsSupplier, + plugins, + testCertificates + ); + + localOpenSearchCluster.start(); + + if (loadConfigurationIntoIndex) { + initSecurityIndex(testSecurityConfig); + } + + try (Client client = getInternalNodeClient()) { + for (TestIndex index : this.testIndices) { + index.create(client); + } + } + + } catch (Exception e) { + log.error("Local ES cluster start failed", e); + throw new RuntimeException(e); + } + } + + private void initSecurityIndex(TestSecurityConfig testSecurityConfig) { + log.info("Initializing OpenSearch Security index"); + try ( + Client client = new ContextHeaderDecoratorClient( + this.getInternalNodeClient(), + Map.of(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true") + ) + ) { + testSecurityConfig.initIndex(client); + triggerConfigurationReload(client); + } + } + + public void updateUserConfiguration(List users) { + try ( + Client client = new ContextHeaderDecoratorClient( + this.getInternalNodeClient(), + Map.of(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true") + ) + ) { + testSecurityConfig.updateInternalUsersConfiguration(client, users); + triggerConfigurationReload(client); + } + } + + private static void triggerConfigurationReload(Client client) { + ConfigUpdateResponse configUpdateResponse = client.execute( + ConfigUpdateAction.INSTANCE, + new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0])) + ).actionGet(); + if (configUpdateResponse.hasFailures()) { + throw new RuntimeException("ConfigUpdateResponse produced failures: " + configUpdateResponse.failures()); + } + } + + public CertificateData getAdminCertificate() { + return testCertificates.getAdminCertificateData(); + } + + public static class Builder { + + private final Settings.Builder nodeOverrideSettingsBuilder = Settings.builder(); + + private boolean sslOnly = false; + private final List> plugins = new ArrayList<>(); + private Map remoteClusters = new HashMap<>(); + private List clusterDependencies = new ArrayList<>(); + private List testIndices = new ArrayList<>(); + private ClusterManager clusterManager = ClusterManager.DEFAULT; + private TestSecurityConfig testSecurityConfig = new TestSecurityConfig(); + private String clusterName = "local_cluster"; + private TestCertificates testCertificates; + + private boolean loadConfigurationIntoIndex = true; + + private String defaultConfigurationInitDirectory = null; + + public Builder() {} + + public Builder dependsOn(Object object) { + // We just want to make sure that the object is already done + if (object == null) { + throw new IllegalStateException("Dependency not fulfilled"); + } + return this; + } + + public Builder clusterManager(ClusterManager clusterManager) { + this.clusterManager = clusterManager; + return this; + } + + /** + * Starts a cluster with only one node and thus saves some resources during startup. This shall be only used + * for tests where the node interactions are not relevant to the test. An example for this would be + * authentication tests, as authentication is always done on the directly connected node. + */ + public Builder singleNode() { + this.clusterManager = ClusterManager.SINGLENODE; + return this; + } + + /** + * Specifies the configuration of the security plugin that shall be used by this cluster. + */ + public Builder config(TestSecurityConfig testSecurityConfig) { + this.testSecurityConfig = testSecurityConfig; + return this; + } + + public Builder sslOnly(boolean sslOnly) { + this.sslOnly = sslOnly; + return this; + } + + public Builder nodeSettings(Map settings) { + settings.forEach((key, value) -> { + if (value instanceof List) { + List values = ((List) value).stream().map(String::valueOf).collect(Collectors.toList()); + nodeOverrideSettingsBuilder.putList(key, values); + } else { + nodeOverrideSettingsBuilder.put(key, String.valueOf(value)); + } + }); + + return this; + } + + /** + * Adds additional plugins to the cluster + */ + public Builder plugin(Class plugin) { + this.plugins.add(plugin); + + return this; + } + + public Builder authFailureListeners(AuthFailureListeners listener) { + testSecurityConfig.authFailureListeners(listener); + return this; + } + + /** + * Specifies a remote cluster and its name. The remote cluster can be then used in Cross Cluster Search + * operations with the specified name. + */ + public Builder remote(String name, LocalCluster anotherCluster) { + remoteClusters.put(name, anotherCluster); + + clusterDependencies.add(anotherCluster); + + return this; + } + + /** + * Specifies test indices that shall be created upon startup of the cluster. + */ + public Builder indices(TestIndex... indices) { + this.testIndices.addAll(Arrays.asList(indices)); + return this; + } + + public Builder users(TestSecurityConfig.User... users) { + for (TestSecurityConfig.User user : users) { + testSecurityConfig.user(user); + } + return this; + } + + public Builder audit(AuditConfiguration auditConfiguration) { + if (auditConfiguration != null) { + testSecurityConfig.audit(auditConfiguration); + } + if (auditConfiguration.isEnabled()) { + nodeOverrideSettingsBuilder.put("plugins.security.audit.type", TestRuleAuditLogSink.class.getName()); + } else { + nodeOverrideSettingsBuilder.put("plugins.security.audit.type", "noop"); + } + return this; + } + + public List getUsers() { + return testSecurityConfig.getUsers(); + } + + public Builder roles(Role... roles) { + testSecurityConfig.roles(roles); + return this; + } + + public Builder rolesMapping(RolesMapping... mappings) { + testSecurityConfig.rolesMapping(mappings); + return this; + } + + public Builder authc(TestSecurityConfig.AuthcDomain authc) { + testSecurityConfig.authc(authc); + return this; + } + + public Builder authz(AuthzDomain authzDomain) { + testSecurityConfig.authz(authzDomain); + return this; + } + + public Builder clusterName(String clusterName) { + this.clusterName = clusterName; + return this; + } + + public Builder configIndexName(String configIndexName) { + testSecurityConfig.configIndexName(configIndexName); + return this; + } + + public Builder testCertificates(TestCertificates certificates) { + this.testCertificates = certificates; + return this; + } + + public Builder anonymousAuth(boolean anonAuthEnabled) { + testSecurityConfig.anonymousAuth(anonAuthEnabled); + return this; + } + + public Builder xff(XffConfig xffConfig) { + testSecurityConfig.xff(xffConfig); + return this; + } + + public Builder loadConfigurationIntoIndex(boolean loadConfigurationIntoIndex) { + this.loadConfigurationIntoIndex = loadConfigurationIntoIndex; + return this; + } + + public Builder certificates(TestCertificates certificates) { + this.testCertificates = certificates; + return this; + } + + public Builder doNotFailOnForbidden(boolean doNotFailOnForbidden) { + testSecurityConfig.doNotFailOnForbidden(doNotFailOnForbidden); + return this; + } + + public Builder defaultConfigurationInitDirectory(String defaultConfigurationInitDirectory) { + this.defaultConfigurationInitDirectory = defaultConfigurationInitDirectory; + return this; + } + + public LocalCluster build() { + try { + if (testCertificates == null) { + testCertificates = new TestCertificates(); + } + clusterName += "_" + num.incrementAndGet(); + Settings settings = nodeOverrideSettingsBuilder.build(); + return new LocalCluster( + clusterName, + testSecurityConfig, + sslOnly, + settings, + clusterManager, + plugins, + testCertificates, + clusterDependencies, + remoteClusters, + testIndices, + loadConfigurationIntoIndex, + defaultConfigurationInitDirectory + ); + } catch (Exception e) { + log.error("Failed to build LocalCluster", e); + throw new RuntimeException(e); + } + } + + } + + @Override + public TestCertificates getTestCertificates() { + return testCertificates; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java index 1b7befbcfc..2a4b417145 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalOpenSearchCluster.java @@ -83,444 +83,491 @@ */ public class LocalOpenSearchCluster { - static { - System.setProperty("opensearch.enforce.bootstrap.checks", "true"); - } - - private static final Logger log = LogManager.getLogger(LocalOpenSearchCluster.class); - - private final String clusterName; - private final ClusterManager clusterManager; - private final NodeSettingsSupplier nodeSettingsSupplier; - private final List> additionalPlugins; - private final List nodes = new ArrayList<>(); - private final TestCertificates testCertificates; - - private File clusterHomeDir; - private List seedHosts; - private List initialClusterManagerHosts; - private int retry = 0; - private boolean started; - private Random random = new Random(); - - private File snapshotDir; - - public LocalOpenSearchCluster(String clusterName, ClusterManager clusterManager, NodeSettingsSupplier nodeSettingsSupplier, - List> additionalPlugins, TestCertificates testCertificates) { - this.clusterName = clusterName; - this.clusterManager = clusterManager; - this.nodeSettingsSupplier = nodeSettingsSupplier; - this.additionalPlugins = additionalPlugins; - this.testCertificates = testCertificates; - try { - createClusterDirectory(clusterName); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - - public String getSnapshotDirPath() { - return snapshotDir.getAbsolutePath(); - } - - private void createClusterDirectory(String clusterName) throws IOException { - this.clusterHomeDir = Files.createTempDirectory("local_cluster_" + clusterName).toFile(); - log.debug("Cluster home directory '{}'.", clusterHomeDir.getAbsolutePath()); - this.snapshotDir = new File(this.clusterHomeDir, "snapshots"); - this.snapshotDir.mkdir(); - } - - private List getNodesByType(NodeType nodeType) { - return nodes.stream() - .filter(currentNode -> currentNode.hasAssignedType(nodeType)) - .collect(Collectors.toList()); - } - - private long countNodesByType(NodeType nodeType) { - return getNodesByType(nodeType).stream().count(); - } - - public void start() throws Exception { - log.info("Starting {}", clusterName); - - int clusterManagerNodeCount = clusterManager.getClusterManagerNodes(); - int nonClusterManagerNodeCount = clusterManager.getDataNodes() + clusterManager.getClientNodes(); - - SortedSet clusterManagerNodeTransportPorts = TCP.allocate(clusterName, Math.max(clusterManagerNodeCount, 4), 5000 + 42 * 1000 + 300); - SortedSet clusterManagerNodeHttpPorts = TCP.allocate(clusterName, clusterManagerNodeCount, 5000 + 42 * 1000 + 200); - - this.seedHosts = toHostList(clusterManagerNodeTransportPorts); - Set clusterManagerPorts = clusterManagerNodeTransportPorts - .stream().limit(clusterManagerNodeCount).collect(Collectors.toSet()); - this.initialClusterManagerHosts = toHostList(clusterManagerPorts); - - started = true; - - CompletableFuture clusterManagerNodeFuture = startNodes( - clusterManager.getClusterManagerNodeSettings(), clusterManagerNodeTransportPorts, - clusterManagerNodeHttpPorts); - - SortedSet nonClusterManagerNodeTransportPorts = TCP.allocate(clusterName, nonClusterManagerNodeCount, 5000 + 42 * 1000 + 310); - SortedSet nonClusterManagerNodeHttpPorts = TCP.allocate(clusterName, nonClusterManagerNodeCount, 5000 + 42 * 1000 + 210); - - CompletableFuture nonClusterManagerNodeFuture = startNodes( - clusterManager.getNonClusterManagerNodeSettings(), nonClusterManagerNodeTransportPorts, - nonClusterManagerNodeHttpPorts); - - CompletableFuture.allOf(clusterManagerNodeFuture, nonClusterManagerNodeFuture).join(); - - if (isNodeFailedWithPortCollision()) { - log.info("Detected port collision for cluster manager node. Retrying."); - - retry(); - return; - } - - log.info("Startup finished. Waiting for GREEN"); - - waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(10), nodes.size()); - - log.info("Started: {}", this); - - } - - public String getClusterName() { - return clusterName; - } - - public boolean isStarted() { - return started; - } - - public void stop() { - List> stopFutures = new ArrayList<>(); - for (Node node : nodes) { - stopFutures.add(node.stop(2, TimeUnit.SECONDS)); - } - CompletableFuture.allOf(stopFutures.toArray(size -> new CompletableFuture[size])).join(); - } - - public void destroy() { - stop(); - nodes.clear(); - - try { - FileUtils.deleteDirectory(clusterHomeDir); - } catch (IOException e) { - log.warn("Error while deleting " + clusterHomeDir, e); - } - } - - public Node clientNode() { - return findRunningNode(getNodesByType(CLIENT), getNodesByType(DATA), getNodesByType(CLUSTER_MANAGER)); - } - - public Node clusterManagerNode() { - return findRunningNode(getNodesByType(CLUSTER_MANAGER)); - } - - public List getNodes() { - return Collections.unmodifiableList(nodes); - } - - public Node getNodeByName(String name) { - return nodes.stream().filter(node -> node.getNodeName().equals(name)).findAny().orElseThrow(() -> new RuntimeException( - "No such node with name: " + name + "; available: " + nodes.stream().map(Node::getNodeName).collect(Collectors.toList()))); - } - - private boolean isNodeFailedWithPortCollision() { - return nodes.stream().anyMatch(Node::isPortCollision); - } - - private void retry() throws Exception { - retry++; - - if (retry > 10) { - throw new RuntimeException("Detected port collisions for cluster manager node. Giving up."); - } - - stop(); - - this.nodes.clear(); - this.seedHosts = null; - this.initialClusterManagerHosts = null; - createClusterDirectory("local_cluster_" + clusterName + "_retry_" + retry); - start(); - } - - @SafeVarargs - private final Node findRunningNode(List nodes, List... moreNodes) { - for (Node node : nodes) { - if (node.isRunning()) { - return node; - } - } - - if (moreNodes != null && moreNodes.length > 0) { - for (List nodesList : moreNodes) { - for (Node node : nodesList) { - if (node.isRunning()) { - return node; - } - } - } - } - - return null; - } - - private CompletableFuture startNodes(List nodeSettingList, SortedSet transportPorts, SortedSet httpPorts) { - Iterator transportPortIterator = transportPorts.iterator(); - Iterator httpPortIterator = httpPorts.iterator(); - List> futures = new ArrayList<>(); - - for (NodeSettings nodeSettings : nodeSettingList) { - Node node = new Node(nodeSettings, transportPortIterator.next(), httpPortIterator.next()); - futures.add(node.start()); - } - - return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - } - - public void waitForCluster(ClusterHealthStatus status, TimeValue timeout, int expectedNodeCount) throws IOException { - Client client = clientNode().getInternalNodeClient(); - - - log.debug("waiting for cluster state {} and {} nodes", status.name(), expectedNodeCount); - AdminClient adminClient = client.admin(); - - final ClusterHealthResponse healthResponse = adminClient.cluster().prepareHealth().setWaitForStatus(status).setTimeout(timeout) - .setClusterManagerNodeTimeout(timeout).setWaitForNodes("" + expectedNodeCount).execute().actionGet(); - - if (log.isDebugEnabled()) { - log.debug("Current ClusterState:\n{}", Strings.toString(XContentType.JSON,healthResponse)); - } - - if (healthResponse.isTimedOut()) { - throw new IOException( - "cluster state is " + healthResponse.getStatus().name() + " with " + healthResponse.getNumberOfNodes() + " nodes"); - } else { - log.debug("... cluster state ok {} with {} nodes", healthResponse.getStatus().name(), healthResponse.getNumberOfNodes()); - } - - assertEquals(expectedNodeCount, healthResponse.getNumberOfNodes()); - - } - - @Override - public String toString() { - String clusterManagerNodes = nodeByTypeToString(CLUSTER_MANAGER); - String dataNodes = nodeByTypeToString(DATA); - String clientNodes = nodeByTypeToString(CLIENT); - return "\nES Cluster " + clusterName - + "\ncluster manager nodes: " + clusterManagerNodes - + "\n data nodes: " + dataNodes - + "\nclient nodes: " + clientNodes + "\n"; - } - - private String nodeByTypeToString(NodeType type) { - return getNodesByType(type).stream().map(Objects::toString).collect(Collectors.joining(", ")); - } - - private static List toHostList(Collection ports) { - return ports.stream().map(port -> "127.0.0.1:" + port).collect(Collectors.toList()); - } - - private String createNextNodeName(NodeSettings nodeSettings) { - NodeType type = nodeSettings.recognizeNodeType(); - long nodeTypeCount = countNodesByType(type); - String nodeType = type.name().toLowerCase(Locale.ROOT); - return nodeType + "_" + nodeTypeCount; - } - - public class Node implements OpenSearchClientProvider { - private final NodeType nodeType; - private final String nodeName; - private final NodeSettings nodeSettings; - private final File nodeHomeDir; - private final File dataDir; - private final File logsDir; - private final int transportPort; - private final int httpPort; - private final InetSocketAddress httpAddress; - private final InetSocketAddress transportAddress; - private PluginAwareNode node; - private boolean running = false; - private boolean portCollision = false; - - Node(NodeSettings nodeSettings, int transportPort, int httpPort) { - this.nodeName = createNextNodeName(requireNonNull(nodeSettings, "Node settings are required.")); - this.nodeSettings = nodeSettings; - this.nodeHomeDir = new File(clusterHomeDir, nodeName); - this.dataDir = new File(this.nodeHomeDir, "data"); - this.logsDir = new File(this.nodeHomeDir, "logs"); - this.transportPort = transportPort; - this.httpPort = httpPort; - InetAddress hostAddress = InetAddresses.forString("127.0.0.1"); - this.httpAddress = new InetSocketAddress(hostAddress, httpPort); - this.transportAddress = new InetSocketAddress(hostAddress, transportPort); - - this.nodeType = nodeSettings.recognizeNodeType(); - nodes.add(this); - } - - boolean hasAssignedType(NodeType type) { - return requireNonNull(type, "Node type is required.").equals(this.nodeType); - } - - CompletableFuture start() { - CompletableFuture completableFuture = new CompletableFuture<>(); - Class[] mergedPlugins = nodeSettings.pluginsWithAddition(additionalPlugins); - this.node = new PluginAwareNode(nodeSettings.containRole(NodeRole.CLUSTER_MANAGER), getOpenSearchSettings(), mergedPlugins); - - new Thread(new Runnable() { - - @Override - public void run() { - try { - node.start(); - running = true; - completableFuture.complete(StartStage.INITIALIZED); - } catch (BindTransportException | BindHttpException e) { - log.warn("Port collision detected for {}", this, e); - portCollision = true; - try { - node.close(); - } catch (IOException e1) { - log.error(e1); - } - - node = null; - TCP.reserve(transportPort, httpPort); - - completableFuture.complete(StartStage.RETRY); - - } catch (Throwable e) { - log.error("Unable to start {}", this, e); - node = null; - completableFuture.completeExceptionally(e); - } - } - }).start(); - - return completableFuture; - } - - public Client getInternalNodeClient() { - return node.client(); - } - - public PluginAwareNode esNode() { - return node; - } - - public boolean isRunning() { - return running; - } - - public X getInjectable(Class clazz) { - return node.injector().getInstance(clazz); - } - - public CompletableFuture stop(long timeout, TimeUnit timeUnit) { - return CompletableFuture.supplyAsync(() -> { - try { - log.info("Stopping {}", this); - - running = false; - - if (node != null) { - node.close(); - boolean stopped = node.awaitClose(timeout, timeUnit); - node = null; - return stopped; - } else { - return false; - } - } catch (Throwable e) { - String message = "Error while stopping " + this; - log.warn(message, e); - throw new RuntimeException(message, e); - } - }); - } - - @Override - public String toString() { - String state = running ? "RUNNING" : node != null ? "INITIALIZING" : "STOPPED"; - - return nodeName + " " + state + " [" + transportPort + ", " + httpPort + "]"; - } - - public boolean isPortCollision() { - return portCollision; - } - - public String getNodeName() { - return nodeName; - } - - @Override - public InetSocketAddress getHttpAddress() { - return httpAddress; - } - - @Override - public InetSocketAddress getTransportAddress() { - return transportAddress; - } - - private Settings getOpenSearchSettings() { - Settings settings = Settings.builder() - .put(getMinimalOpenSearchSettings()) - .putList("path.repo", List.of(getSnapshotDirPath())) - .build(); - - if (nodeSettingsSupplier != null) { - // TODO node number - return Settings.builder().put(settings).put(nodeSettingsSupplier.get(0)).build(); - } - return settings; - } - - private Settings getMinimalOpenSearchSettings() { - return Settings.builder().put("node.name", nodeName).putList("node.roles", createNodeRolesSettings()) - .put("cluster.name", clusterName).put("path.home", nodeHomeDir.toPath()).put("path.data", dataDir.toPath()) - .put("path.logs", logsDir.toPath()).putList("cluster.initial_cluster_manager_nodes", initialClusterManagerHosts) - .put("discovery.initial_state_timeout", "8s").putList("discovery.seed_hosts", seedHosts).put("transport.tcp.port", transportPort) - .put("http.port", httpPort).put("cluster.routing.allocation.disk.threshold_enabled", false) - .put("discovery.probe.connect_timeout", "10s").put("discovery.probe.handshake_timeout", "10s").put("http.cors.enabled", true) - .put("gateway.auto_import_dangling_indices", "true") - .build(); - } - - private List createNodeRolesSettings() { - final ImmutableList.Builder nodeRolesBuilder = ImmutableList.builder(); - if (nodeSettings.containRole(NodeRole.DATA)) { - nodeRolesBuilder.add("data"); - } - if (nodeSettings.containRole(NodeRole.CLUSTER_MANAGER)) { - nodeRolesBuilder.add("cluster_manager"); - } - if(nodeSettings.containRole(NodeRole.REMOTE_CLUSTER_CLIENT)) { - nodeRolesBuilder.add("remote_cluster_client"); - } - return nodeRolesBuilder.build(); - } - - @Override - public String getClusterName() { - return clusterName; - } - - @Override - public TestCertificates getTestCertificates() { - return testCertificates; - } - } - - public Random getRandom() { - return random; - } + static { + System.setProperty("opensearch.enforce.bootstrap.checks", "true"); + } + + private static final Logger log = LogManager.getLogger(LocalOpenSearchCluster.class); + + private final String clusterName; + private final ClusterManager clusterManager; + private final NodeSettingsSupplier nodeSettingsSupplier; + private final List> additionalPlugins; + private final List nodes = new ArrayList<>(); + private final TestCertificates testCertificates; + + private File clusterHomeDir; + private List seedHosts; + private List initialClusterManagerHosts; + private int retry = 0; + private boolean started; + private Random random = new Random(); + + private File snapshotDir; + + public LocalOpenSearchCluster( + String clusterName, + ClusterManager clusterManager, + NodeSettingsSupplier nodeSettingsSupplier, + List> additionalPlugins, + TestCertificates testCertificates + ) { + this.clusterName = clusterName; + this.clusterManager = clusterManager; + this.nodeSettingsSupplier = nodeSettingsSupplier; + this.additionalPlugins = additionalPlugins; + this.testCertificates = testCertificates; + try { + createClusterDirectory(clusterName); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public String getSnapshotDirPath() { + return snapshotDir.getAbsolutePath(); + } + + private void createClusterDirectory(String clusterName) throws IOException { + this.clusterHomeDir = Files.createTempDirectory("local_cluster_" + clusterName).toFile(); + log.debug("Cluster home directory '{}'.", clusterHomeDir.getAbsolutePath()); + this.snapshotDir = new File(this.clusterHomeDir, "snapshots"); + this.snapshotDir.mkdir(); + } + + private List getNodesByType(NodeType nodeType) { + return nodes.stream().filter(currentNode -> currentNode.hasAssignedType(nodeType)).collect(Collectors.toList()); + } + + private long countNodesByType(NodeType nodeType) { + return getNodesByType(nodeType).stream().count(); + } + + public void start() throws Exception { + log.info("Starting {}", clusterName); + + int clusterManagerNodeCount = clusterManager.getClusterManagerNodes(); + int nonClusterManagerNodeCount = clusterManager.getDataNodes() + clusterManager.getClientNodes(); + + SortedSet clusterManagerNodeTransportPorts = TCP.allocate( + clusterName, + Math.max(clusterManagerNodeCount, 4), + 5000 + 42 * 1000 + 300 + ); + SortedSet clusterManagerNodeHttpPorts = TCP.allocate(clusterName, clusterManagerNodeCount, 5000 + 42 * 1000 + 200); + + this.seedHosts = toHostList(clusterManagerNodeTransportPorts); + Set clusterManagerPorts = clusterManagerNodeTransportPorts.stream() + .limit(clusterManagerNodeCount) + .collect(Collectors.toSet()); + this.initialClusterManagerHosts = toHostList(clusterManagerPorts); + + started = true; + + CompletableFuture clusterManagerNodeFuture = startNodes( + clusterManager.getClusterManagerNodeSettings(), + clusterManagerNodeTransportPorts, + clusterManagerNodeHttpPorts + ); + + SortedSet nonClusterManagerNodeTransportPorts = TCP.allocate( + clusterName, + nonClusterManagerNodeCount, + 5000 + 42 * 1000 + 310 + ); + SortedSet nonClusterManagerNodeHttpPorts = TCP.allocate(clusterName, nonClusterManagerNodeCount, 5000 + 42 * 1000 + 210); + + CompletableFuture nonClusterManagerNodeFuture = startNodes( + clusterManager.getNonClusterManagerNodeSettings(), + nonClusterManagerNodeTransportPorts, + nonClusterManagerNodeHttpPorts + ); + + CompletableFuture.allOf(clusterManagerNodeFuture, nonClusterManagerNodeFuture).join(); + + if (isNodeFailedWithPortCollision()) { + log.info("Detected port collision for cluster manager node. Retrying."); + + retry(); + return; + } + + log.info("Startup finished. Waiting for GREEN"); + + waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(10), nodes.size()); + + log.info("Started: {}", this); + + } + + public String getClusterName() { + return clusterName; + } + + public boolean isStarted() { + return started; + } + + public void stop() { + List> stopFutures = new ArrayList<>(); + for (Node node : nodes) { + stopFutures.add(node.stop(2, TimeUnit.SECONDS)); + } + CompletableFuture.allOf(stopFutures.toArray(size -> new CompletableFuture[size])).join(); + } + + public void destroy() { + stop(); + nodes.clear(); + + try { + FileUtils.deleteDirectory(clusterHomeDir); + } catch (IOException e) { + log.warn("Error while deleting " + clusterHomeDir, e); + } + } + + public Node clientNode() { + return findRunningNode(getNodesByType(CLIENT), getNodesByType(DATA), getNodesByType(CLUSTER_MANAGER)); + } + + public Node clusterManagerNode() { + return findRunningNode(getNodesByType(CLUSTER_MANAGER)); + } + + public List getNodes() { + return Collections.unmodifiableList(nodes); + } + + public Node getNodeByName(String name) { + return nodes.stream() + .filter(node -> node.getNodeName().equals(name)) + .findAny() + .orElseThrow( + () -> new RuntimeException( + "No such node with name: " + name + "; available: " + nodes.stream().map(Node::getNodeName).collect(Collectors.toList()) + ) + ); + } + + private boolean isNodeFailedWithPortCollision() { + return nodes.stream().anyMatch(Node::isPortCollision); + } + + private void retry() throws Exception { + retry++; + + if (retry > 10) { + throw new RuntimeException("Detected port collisions for cluster manager node. Giving up."); + } + + stop(); + + this.nodes.clear(); + this.seedHosts = null; + this.initialClusterManagerHosts = null; + createClusterDirectory("local_cluster_" + clusterName + "_retry_" + retry); + start(); + } + + @SafeVarargs + private final Node findRunningNode(List nodes, List... moreNodes) { + for (Node node : nodes) { + if (node.isRunning()) { + return node; + } + } + + if (moreNodes != null && moreNodes.length > 0) { + for (List nodesList : moreNodes) { + for (Node node : nodesList) { + if (node.isRunning()) { + return node; + } + } + } + } + + return null; + } + + private CompletableFuture startNodes( + List nodeSettingList, + SortedSet transportPorts, + SortedSet httpPorts + ) { + Iterator transportPortIterator = transportPorts.iterator(); + Iterator httpPortIterator = httpPorts.iterator(); + List> futures = new ArrayList<>(); + + for (NodeSettings nodeSettings : nodeSettingList) { + Node node = new Node(nodeSettings, transportPortIterator.next(), httpPortIterator.next()); + futures.add(node.start()); + } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + public void waitForCluster(ClusterHealthStatus status, TimeValue timeout, int expectedNodeCount) throws IOException { + Client client = clientNode().getInternalNodeClient(); + + log.debug("waiting for cluster state {} and {} nodes", status.name(), expectedNodeCount); + AdminClient adminClient = client.admin(); + + final ClusterHealthResponse healthResponse = adminClient.cluster() + .prepareHealth() + .setWaitForStatus(status) + .setTimeout(timeout) + .setClusterManagerNodeTimeout(timeout) + .setWaitForNodes("" + expectedNodeCount) + .execute() + .actionGet(); + + if (log.isDebugEnabled()) { + log.debug("Current ClusterState:\n{}", Strings.toString(XContentType.JSON, healthResponse)); + } + + if (healthResponse.isTimedOut()) { + throw new IOException( + "cluster state is " + healthResponse.getStatus().name() + " with " + healthResponse.getNumberOfNodes() + " nodes" + ); + } else { + log.debug("... cluster state ok {} with {} nodes", healthResponse.getStatus().name(), healthResponse.getNumberOfNodes()); + } + + assertEquals(expectedNodeCount, healthResponse.getNumberOfNodes()); + + } + + @Override + public String toString() { + String clusterManagerNodes = nodeByTypeToString(CLUSTER_MANAGER); + String dataNodes = nodeByTypeToString(DATA); + String clientNodes = nodeByTypeToString(CLIENT); + return "\nES Cluster " + + clusterName + + "\ncluster manager nodes: " + + clusterManagerNodes + + "\n data nodes: " + + dataNodes + + "\nclient nodes: " + + clientNodes + + "\n"; + } + + private String nodeByTypeToString(NodeType type) { + return getNodesByType(type).stream().map(Objects::toString).collect(Collectors.joining(", ")); + } + + private static List toHostList(Collection ports) { + return ports.stream().map(port -> "127.0.0.1:" + port).collect(Collectors.toList()); + } + + private String createNextNodeName(NodeSettings nodeSettings) { + NodeType type = nodeSettings.recognizeNodeType(); + long nodeTypeCount = countNodesByType(type); + String nodeType = type.name().toLowerCase(Locale.ROOT); + return nodeType + "_" + nodeTypeCount; + } + + public class Node implements OpenSearchClientProvider { + private final NodeType nodeType; + private final String nodeName; + private final NodeSettings nodeSettings; + private final File nodeHomeDir; + private final File dataDir; + private final File logsDir; + private final int transportPort; + private final int httpPort; + private final InetSocketAddress httpAddress; + private final InetSocketAddress transportAddress; + private PluginAwareNode node; + private boolean running = false; + private boolean portCollision = false; + + Node(NodeSettings nodeSettings, int transportPort, int httpPort) { + this.nodeName = createNextNodeName(requireNonNull(nodeSettings, "Node settings are required.")); + this.nodeSettings = nodeSettings; + this.nodeHomeDir = new File(clusterHomeDir, nodeName); + this.dataDir = new File(this.nodeHomeDir, "data"); + this.logsDir = new File(this.nodeHomeDir, "logs"); + this.transportPort = transportPort; + this.httpPort = httpPort; + InetAddress hostAddress = InetAddresses.forString("127.0.0.1"); + this.httpAddress = new InetSocketAddress(hostAddress, httpPort); + this.transportAddress = new InetSocketAddress(hostAddress, transportPort); + + this.nodeType = nodeSettings.recognizeNodeType(); + nodes.add(this); + } + + boolean hasAssignedType(NodeType type) { + return requireNonNull(type, "Node type is required.").equals(this.nodeType); + } + + CompletableFuture start() { + CompletableFuture completableFuture = new CompletableFuture<>(); + Class[] mergedPlugins = nodeSettings.pluginsWithAddition(additionalPlugins); + this.node = new PluginAwareNode(nodeSettings.containRole(NodeRole.CLUSTER_MANAGER), getOpenSearchSettings(), mergedPlugins); + + new Thread(new Runnable() { + + @Override + public void run() { + try { + node.start(); + running = true; + completableFuture.complete(StartStage.INITIALIZED); + } catch (BindTransportException | BindHttpException e) { + log.warn("Port collision detected for {}", this, e); + portCollision = true; + try { + node.close(); + } catch (IOException e1) { + log.error(e1); + } + + node = null; + TCP.reserve(transportPort, httpPort); + + completableFuture.complete(StartStage.RETRY); + + } catch (Throwable e) { + log.error("Unable to start {}", this, e); + node = null; + completableFuture.completeExceptionally(e); + } + } + }).start(); + + return completableFuture; + } + + public Client getInternalNodeClient() { + return node.client(); + } + + public PluginAwareNode esNode() { + return node; + } + + public boolean isRunning() { + return running; + } + + public X getInjectable(Class clazz) { + return node.injector().getInstance(clazz); + } + + public CompletableFuture stop(long timeout, TimeUnit timeUnit) { + return CompletableFuture.supplyAsync(() -> { + try { + log.info("Stopping {}", this); + + running = false; + + if (node != null) { + node.close(); + boolean stopped = node.awaitClose(timeout, timeUnit); + node = null; + return stopped; + } else { + return false; + } + } catch (Throwable e) { + String message = "Error while stopping " + this; + log.warn(message, e); + throw new RuntimeException(message, e); + } + }); + } + + @Override + public String toString() { + String state = running ? "RUNNING" : node != null ? "INITIALIZING" : "STOPPED"; + + return nodeName + " " + state + " [" + transportPort + ", " + httpPort + "]"; + } + + public boolean isPortCollision() { + return portCollision; + } + + public String getNodeName() { + return nodeName; + } + + @Override + public InetSocketAddress getHttpAddress() { + return httpAddress; + } + + @Override + public InetSocketAddress getTransportAddress() { + return transportAddress; + } + + private Settings getOpenSearchSettings() { + Settings settings = Settings.builder() + .put(getMinimalOpenSearchSettings()) + .putList("path.repo", List.of(getSnapshotDirPath())) + .build(); + + if (nodeSettingsSupplier != null) { + // TODO node number + return Settings.builder().put(settings).put(nodeSettingsSupplier.get(0)).build(); + } + return settings; + } + + private Settings getMinimalOpenSearchSettings() { + return Settings.builder() + .put("node.name", nodeName) + .putList("node.roles", createNodeRolesSettings()) + .put("cluster.name", clusterName) + .put("path.home", nodeHomeDir.toPath()) + .put("path.data", dataDir.toPath()) + .put("path.logs", logsDir.toPath()) + .putList("cluster.initial_cluster_manager_nodes", initialClusterManagerHosts) + .put("discovery.initial_state_timeout", "8s") + .putList("discovery.seed_hosts", seedHosts) + .put("transport.tcp.port", transportPort) + .put("http.port", httpPort) + .put("cluster.routing.allocation.disk.threshold_enabled", false) + .put("discovery.probe.connect_timeout", "10s") + .put("discovery.probe.handshake_timeout", "10s") + .put("http.cors.enabled", true) + .put("gateway.auto_import_dangling_indices", "true") + .build(); + } + + private List createNodeRolesSettings() { + final ImmutableList.Builder nodeRolesBuilder = ImmutableList.builder(); + if (nodeSettings.containRole(NodeRole.DATA)) { + nodeRolesBuilder.add("data"); + } + if (nodeSettings.containRole(NodeRole.CLUSTER_MANAGER)) { + nodeRolesBuilder.add("cluster_manager"); + } + if (nodeSettings.containRole(NodeRole.REMOTE_CLUSTER_CLIENT)) { + nodeRolesBuilder.add("remote_cluster_client"); + } + return nodeRolesBuilder.build(); + } + + @Override + public String getClusterName() { + return clusterName; + } + + @Override + public TestCertificates getTestCertificates() { + return testCertificates; + } + } + + public Random getRandom() { + return random; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java index 318dce63d6..4ad5f8420e 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/MinimumSecuritySettingsSupplierFactory.java @@ -34,45 +34,51 @@ public class MinimumSecuritySettingsSupplierFactory { - private final String PRIVATE_KEY_HTTP_PASSWORD = "aWVV63OJ4qzZyPrBwl2MFny4ZV8lQRZchjL"; - private final String PRIVATE_KEY_TRANSPORT_PASSWORD = "iWbUv9w79sbd5tcxvSJNfHXS9GhcPCvdw9x"; + private final String PRIVATE_KEY_HTTP_PASSWORD = "aWVV63OJ4qzZyPrBwl2MFny4ZV8lQRZchjL"; + private final String PRIVATE_KEY_TRANSPORT_PASSWORD = "iWbUv9w79sbd5tcxvSJNfHXS9GhcPCvdw9x"; - private TestCertificates testCertificates; + private TestCertificates testCertificates; - public MinimumSecuritySettingsSupplierFactory(TestCertificates testCertificates) { - if (testCertificates == null) { - throw new IllegalArgumentException("certificates must not be null"); - } - this.testCertificates = testCertificates; + public MinimumSecuritySettingsSupplierFactory(TestCertificates testCertificates) { + if (testCertificates == null) { + throw new IllegalArgumentException("certificates must not be null"); + } + this.testCertificates = testCertificates; - } + } - public NodeSettingsSupplier minimumOpenSearchSettings(boolean sslOnly, Settings other) { - return i -> minimumOpenSearchSettingsBuilder(i, sslOnly).put(other).build(); - } + public NodeSettingsSupplier minimumOpenSearchSettings(boolean sslOnly, Settings other) { + return i -> minimumOpenSearchSettingsBuilder(i, sslOnly).put(other).build(); + } - private Settings.Builder minimumOpenSearchSettingsBuilder(int node, boolean sslOnly) { + private Settings.Builder minimumOpenSearchSettingsBuilder(int node, boolean sslOnly) { - Settings.Builder builder = Settings.builder(); + Settings.Builder builder = Settings.builder(); - builder.put("plugins.security.ssl.transport.pemtrustedcas_filepath", testCertificates.getRootCertificate().getAbsolutePath()); - builder.put("plugins.security.ssl.transport.pemcert_filepath", testCertificates.getNodeCertificate(node).getAbsolutePath()); - builder.put("plugins.security.ssl.transport.pemkey_filepath", testCertificates.getNodeKey(node, PRIVATE_KEY_TRANSPORT_PASSWORD).getAbsolutePath()); - builder.put("plugins.security.ssl.transport.pemkey_password", PRIVATE_KEY_TRANSPORT_PASSWORD); + builder.put("plugins.security.ssl.transport.pemtrustedcas_filepath", testCertificates.getRootCertificate().getAbsolutePath()); + builder.put("plugins.security.ssl.transport.pemcert_filepath", testCertificates.getNodeCertificate(node).getAbsolutePath()); + builder.put( + "plugins.security.ssl.transport.pemkey_filepath", + testCertificates.getNodeKey(node, PRIVATE_KEY_TRANSPORT_PASSWORD).getAbsolutePath() + ); + builder.put("plugins.security.ssl.transport.pemkey_password", PRIVATE_KEY_TRANSPORT_PASSWORD); - builder.put("plugins.security.ssl.http.enabled", true); - builder.put("plugins.security.ssl.http.pemtrustedcas_filepath", testCertificates.getRootCertificate().getAbsolutePath()); - builder.put("plugins.security.ssl.http.pemcert_filepath", testCertificates.getNodeCertificate(node).getAbsolutePath()); - builder.put("plugins.security.ssl.http.pemkey_filepath", testCertificates.getNodeKey(node, PRIVATE_KEY_HTTP_PASSWORD).getAbsolutePath()); - builder.put("plugins.security.ssl.http.pemkey_password", PRIVATE_KEY_HTTP_PASSWORD); - if(sslOnly == false) { - builder.put(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, false); - builder.putList("plugins.security.authcz.admin_dn", testCertificates.getAdminDNs()); - builder.put("plugins.security.compliance.salt", "1234567890123456"); - builder.put("plugins.security.audit.type", "noop"); - builder.put("plugins.security.background_init_if_securityindex_not_exist", "false"); - } - return builder; + builder.put("plugins.security.ssl.http.enabled", true); + builder.put("plugins.security.ssl.http.pemtrustedcas_filepath", testCertificates.getRootCertificate().getAbsolutePath()); + builder.put("plugins.security.ssl.http.pemcert_filepath", testCertificates.getNodeCertificate(node).getAbsolutePath()); + builder.put( + "plugins.security.ssl.http.pemkey_filepath", + testCertificates.getNodeKey(node, PRIVATE_KEY_HTTP_PASSWORD).getAbsolutePath() + ); + builder.put("plugins.security.ssl.http.pemkey_password", PRIVATE_KEY_HTTP_PASSWORD); + if (sslOnly == false) { + builder.put(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, false); + builder.putList("plugins.security.authcz.admin_dn", testCertificates.getAdminDNs()); + builder.put("plugins.security.compliance.salt", "1234567890123456"); + builder.put("plugins.security.audit.type", "noop"); + builder.put("plugins.security.background_init_if_securityindex_not_exist", "false"); + } + return builder; - } + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeRole.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeRole.java index 7dc77ef37e..0d465fa119 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeRole.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeRole.java @@ -10,5 +10,7 @@ package org.opensearch.test.framework.cluster; enum NodeRole { - DATA, CLUSTER_MANAGER, REMOTE_CLUSTER_CLIENT + DATA, + CLUSTER_MANAGER, + REMOTE_CLUSTER_CLIENT } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeSettingsSupplier.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeSettingsSupplier.java index 75c728287b..cab3a760ca 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeSettingsSupplier.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeSettingsSupplier.java @@ -30,5 +30,5 @@ @FunctionalInterface public interface NodeSettingsSupplier { - Settings get(int i); + Settings get(int i); } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeType.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeType.java index 915f99daa8..8ae8941e8d 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeType.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/NodeType.java @@ -11,5 +11,7 @@ package org.opensearch.test.framework.cluster; enum NodeType { - CLIENT, DATA, CLUSTER_MANAGER + CLIENT, + DATA, + CLUSTER_MANAGER } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java index e395bfceda..45a68994f8 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -79,197 +79,203 @@ */ public interface OpenSearchClientProvider { - String getClusterName(); - - TestCertificates getTestCertificates(); - - InetSocketAddress getHttpAddress(); - - InetSocketAddress getTransportAddress(); - - default URI getHttpAddressAsURI() { - InetSocketAddress address = getHttpAddress(); - return URI.create("https://" + address.getHostString() + ":" + address.getPort()); - } - - /** - * Returns a REST client that sends requests with basic authentication for the specified User object. Optionally, - * additional HTTP headers can be specified which will be sent with each request. - * - * This method should be usually preferred. The other getRestClient() methods shall be only used for specific - * situations. - */ - default TestRestClient getRestClient(UserCredentialsHolder user, CertificateData useCertificateData, Header... headers) { - return getRestClient(user.getName(), user.getPassword(), useCertificateData, headers); - } - - default TestRestClient getRestClient(UserCredentialsHolder user, Header... headers) { - return getRestClient(user.getName(), user.getPassword(), null, headers); - } - - default RestHighLevelClient getRestHighLevelClient(String username, String password, Header... headers) { - return getRestHighLevelClient(new UserCredentialsHolder() { - @Override - public String getName() { - return username; - } - - @Override - public String getPassword() { - return password; - } - }, Arrays.asList(headers)); - } - - - default RestHighLevelClient getRestHighLevelClient(UserCredentialsHolder user) { - return getRestHighLevelClient(user, Collections.emptySet()); - } - - default RestHighLevelClient getRestHighLevelClient(UserCredentialsHolder user, Collection defaultHeaders) { - - BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials(new AuthScope(null, -1), new UsernamePasswordCredentials(user.getName(), user.getPassword().toCharArray())); - - return getRestHighLevelClient(credentialsProvider, defaultHeaders); - } - - default RestHighLevelClient getRestHighLevelClient(Collection defaultHeaders) { - return getRestHighLevelClient((BasicCredentialsProvider)null, defaultHeaders); - } - - default RestHighLevelClient getRestHighLevelClient(BasicCredentialsProvider credentialsProvider, Collection defaultHeaders) { - RestClientBuilder.HttpClientConfigCallback configCallback = httpClientBuilder -> { - TlsStrategy tlsStrategy = ClientTlsStrategyBuilder - .create() - .setSslContext(getSSLContext()) - .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) - // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 - .setTlsDetailsFactory(new Factory() { - @Override - public TlsDetails create(final SSLEngine sslEngine) { - return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); - } - }) - .build(); - - final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() - .setTlsStrategy(tlsStrategy) - .build(); - - if(credentialsProvider != null) { - httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); - } - httpClientBuilder.setDefaultHeaders(defaultHeaders); - httpClientBuilder.setConnectionManager(cm); - httpClientBuilder.setDefaultHeaders(defaultHeaders); - return httpClientBuilder; - }; - - InetSocketAddress httpAddress = getHttpAddress(); - RestClientBuilder builder = RestClient.builder(new HttpHost("https", httpAddress.getHostString(), httpAddress.getPort())) - .setHttpClientConfigCallback(configCallback); - - - return new RestHighLevelClient(builder); - } - - default CloseableHttpClient getClosableHttpClient(String[] supportedCipherSuit) { - CloseableHttpClientFactory factory = new CloseableHttpClientFactory(getSSLContext(), null, null, supportedCipherSuit); - return factory.getHTTPClient(); - } - - /** - * Returns a REST client that sends requests with basic authentication for the specified user name and password. Optionally, - * additional HTTP headers can be specified which will be sent with each request. - * - * Normally, you should use the method with the User object argument instead. Use this only if you need more - * control over username and password - for example, when you want to send a wrong password. - */ - default TestRestClient getRestClient(String user, String password, Header... headers) { - return createGenericClientRestClient(new TestRestClientConfiguration().username(user).password(password).headers(headers)); - } - default TestRestClient getRestClient(String user, String password, CertificateData useCertificateData, Header... headers) { - Header basicAuthHeader = getBasicAuthHeader(user, password); - if (headers != null && headers.length > 0) { - List
concatenatedHeaders = Stream.concat(Stream.of(basicAuthHeader), Stream.of(headers)).collect(Collectors.toList()); - return getRestClient(concatenatedHeaders, useCertificateData); - } - return getRestClient(useCertificateData, basicAuthHeader); - } - /** - * Returns a REST client. You can specify additional HTTP headers that will be sent with each request. Use this - * method to test non-basic authentication, such as JWT bearer authentication. - */ - default TestRestClient getRestClient(CertificateData useCertificateData, Header... headers) { - return getRestClient(Arrays.asList(headers), useCertificateData); - } - - default TestRestClient getRestClient(Header... headers) { - return getRestClient((CertificateData) null, headers); - } - - - default TestRestClient getRestClient(List
headers) { - return createGenericClientRestClient(new TestRestClientConfiguration().headers(headers)); - - } - - default TestRestClient getRestClient(List
headers, CertificateData useCertificateData) { - return createGenericClientRestClient(headers, useCertificateData, null); - } - - default TestRestClient createGenericClientRestClient(List
headers, CertificateData useCertificateData, - InetAddress sourceInetAddress) { - return new TestRestClient(getHttpAddress(), headers, getSSLContext(useCertificateData), sourceInetAddress); - } - - default TestRestClient createGenericClientRestClient(TestRestClientConfiguration configuration) { - return new TestRestClient(getHttpAddress(), configuration.getHeaders(), getSSLContext(), configuration.getSourceInetAddress()); - } - - private SSLContext getSSLContext() { - return getSSLContext(null); - } - - private SSLContext getSSLContext(CertificateData useCertificateData) { - X509Certificate[] trustCertificates; - - try { - trustCertificates = PemKeyReader.loadCertificatesFromFile(getTestCertificates().getRootCertificate() ); - - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - - ks.load(null); - - for (int i = 0; i < trustCertificates.length; i++) { - ks.setCertificateEntry("caCert-" + i, trustCertificates[i]); - } - KeyManager[] keyManagers = null; - if(useCertificateData != null) { - Certificate[] chainOfTrust = {useCertificateData.certificate()}; - ks.setKeyEntry("admin-certificate", useCertificateData.getKey(), null, chainOfTrust); - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(ks, null); - keyManagers = keyManagerFactory.getKeyManagers(); - } - - tmf.init(ks); - - SSLContext sslContext = SSLContext.getInstance("TLS"); - - sslContext.init(keyManagers, tmf.getTrustManagers(), null); - return sslContext; - - } catch (Exception e) { - throw new RuntimeException("Error loading root CA ", e); - } - } - - public interface UserCredentialsHolder { - String getName(); - String getPassword(); - } + String getClusterName(); + + TestCertificates getTestCertificates(); + + InetSocketAddress getHttpAddress(); + + InetSocketAddress getTransportAddress(); + + default URI getHttpAddressAsURI() { + InetSocketAddress address = getHttpAddress(); + return URI.create("https://" + address.getHostString() + ":" + address.getPort()); + } + + /** + * Returns a REST client that sends requests with basic authentication for the specified User object. Optionally, + * additional HTTP headers can be specified which will be sent with each request. + * + * This method should be usually preferred. The other getRestClient() methods shall be only used for specific + * situations. + */ + default TestRestClient getRestClient(UserCredentialsHolder user, CertificateData useCertificateData, Header... headers) { + return getRestClient(user.getName(), user.getPassword(), useCertificateData, headers); + } + + default TestRestClient getRestClient(UserCredentialsHolder user, Header... headers) { + return getRestClient(user.getName(), user.getPassword(), null, headers); + } + + default RestHighLevelClient getRestHighLevelClient(String username, String password, Header... headers) { + return getRestHighLevelClient(new UserCredentialsHolder() { + @Override + public String getName() { + return username; + } + + @Override + public String getPassword() { + return password; + } + }, Arrays.asList(headers)); + } + + default RestHighLevelClient getRestHighLevelClient(UserCredentialsHolder user) { + return getRestHighLevelClient(user, Collections.emptySet()); + } + + default RestHighLevelClient getRestHighLevelClient(UserCredentialsHolder user, Collection defaultHeaders) { + + BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope(null, -1), + new UsernamePasswordCredentials(user.getName(), user.getPassword().toCharArray()) + ); + + return getRestHighLevelClient(credentialsProvider, defaultHeaders); + } + + default RestHighLevelClient getRestHighLevelClient(Collection defaultHeaders) { + return getRestHighLevelClient((BasicCredentialsProvider) null, defaultHeaders); + } + + default RestHighLevelClient getRestHighLevelClient( + BasicCredentialsProvider credentialsProvider, + Collection defaultHeaders + ) { + RestClientBuilder.HttpClientConfigCallback configCallback = httpClientBuilder -> { + TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() + .setSslContext(getSSLContext()) + .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) + // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 + .setTlsDetailsFactory(new Factory() { + @Override + public TlsDetails create(final SSLEngine sslEngine) { + return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); + } + }) + .build(); + + final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create().setTlsStrategy(tlsStrategy).build(); + + if (credentialsProvider != null) { + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + } + httpClientBuilder.setDefaultHeaders(defaultHeaders); + httpClientBuilder.setConnectionManager(cm); + httpClientBuilder.setDefaultHeaders(defaultHeaders); + return httpClientBuilder; + }; + + InetSocketAddress httpAddress = getHttpAddress(); + RestClientBuilder builder = RestClient.builder(new HttpHost("https", httpAddress.getHostString(), httpAddress.getPort())) + .setHttpClientConfigCallback(configCallback); + + return new RestHighLevelClient(builder); + } + + default CloseableHttpClient getClosableHttpClient(String[] supportedCipherSuit) { + CloseableHttpClientFactory factory = new CloseableHttpClientFactory(getSSLContext(), null, null, supportedCipherSuit); + return factory.getHTTPClient(); + } + + /** + * Returns a REST client that sends requests with basic authentication for the specified user name and password. Optionally, + * additional HTTP headers can be specified which will be sent with each request. + * + * Normally, you should use the method with the User object argument instead. Use this only if you need more + * control over username and password - for example, when you want to send a wrong password. + */ + default TestRestClient getRestClient(String user, String password, Header... headers) { + return createGenericClientRestClient(new TestRestClientConfiguration().username(user).password(password).headers(headers)); + } + + default TestRestClient getRestClient(String user, String password, CertificateData useCertificateData, Header... headers) { + Header basicAuthHeader = getBasicAuthHeader(user, password); + if (headers != null && headers.length > 0) { + List
concatenatedHeaders = Stream.concat(Stream.of(basicAuthHeader), Stream.of(headers)).collect(Collectors.toList()); + return getRestClient(concatenatedHeaders, useCertificateData); + } + return getRestClient(useCertificateData, basicAuthHeader); + } + + /** + * Returns a REST client. You can specify additional HTTP headers that will be sent with each request. Use this + * method to test non-basic authentication, such as JWT bearer authentication. + */ + default TestRestClient getRestClient(CertificateData useCertificateData, Header... headers) { + return getRestClient(Arrays.asList(headers), useCertificateData); + } + + default TestRestClient getRestClient(Header... headers) { + return getRestClient((CertificateData) null, headers); + } + + default TestRestClient getRestClient(List
headers) { + return createGenericClientRestClient(new TestRestClientConfiguration().headers(headers)); + + } + + default TestRestClient getRestClient(List
headers, CertificateData useCertificateData) { + return createGenericClientRestClient(headers, useCertificateData, null); + } + + default TestRestClient createGenericClientRestClient( + List
headers, + CertificateData useCertificateData, + InetAddress sourceInetAddress + ) { + return new TestRestClient(getHttpAddress(), headers, getSSLContext(useCertificateData), sourceInetAddress); + } + + default TestRestClient createGenericClientRestClient(TestRestClientConfiguration configuration) { + return new TestRestClient(getHttpAddress(), configuration.getHeaders(), getSSLContext(), configuration.getSourceInetAddress()); + } + + private SSLContext getSSLContext() { + return getSSLContext(null); + } + + private SSLContext getSSLContext(CertificateData useCertificateData) { + X509Certificate[] trustCertificates; + + try { + trustCertificates = PemKeyReader.loadCertificatesFromFile(getTestCertificates().getRootCertificate()); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + + ks.load(null); + + for (int i = 0; i < trustCertificates.length; i++) { + ks.setCertificateEntry("caCert-" + i, trustCertificates[i]); + } + KeyManager[] keyManagers = null; + if (useCertificateData != null) { + Certificate[] chainOfTrust = { useCertificateData.certificate() }; + ks.setKeyEntry("admin-certificate", useCertificateData.getKey(), null, chainOfTrust); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(ks, null); + keyManagers = keyManagerFactory.getKeyManagers(); + } + + tmf.init(ks); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + + sslContext.init(keyManagers, tmf.getTrustManagers(), null); + return sslContext; + + } catch (Exception e) { + throw new RuntimeException("Error loading root CA ", e); + } + } + + public interface UserCredentialsHolder { + String getName(); + + String getPassword(); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/PortAllocator.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/PortAllocator.java index ed72fae91f..139378fd22 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/PortAllocator.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/PortAllocator.java @@ -47,118 +47,119 @@ */ public class PortAllocator { - public static final PortAllocator TCP = new PortAllocator(SocketType.TCP, Duration.ofSeconds(100)); - public static final PortAllocator UDP = new PortAllocator(SocketType.UDP, Duration.ofSeconds(100)); + public static final PortAllocator TCP = new PortAllocator(SocketType.TCP, Duration.ofSeconds(100)); + public static final PortAllocator UDP = new PortAllocator(SocketType.UDP, Duration.ofSeconds(100)); - private final SocketType socketType; - private final Duration timeoutDuration; - private final Map allocatedPorts = new HashMap<>(); + private final SocketType socketType; + private final Duration timeoutDuration; + private final Map allocatedPorts = new HashMap<>(); - PortAllocator(SocketType socketType, Duration timeoutDuration) { - this.socketType = socketType; - this.timeoutDuration = timeoutDuration; - } + PortAllocator(SocketType socketType, Duration timeoutDuration) { + this.socketType = socketType; + this.timeoutDuration = timeoutDuration; + } - public SortedSet allocate(String clientName, int numRequested, int minPort) { + public SortedSet allocate(String clientName, int numRequested, int minPort) { - int startPort = minPort; + int startPort = minPort; - while (!isAvailable(startPort)) { - startPort += 10; - } + while (!isAvailable(startPort)) { + startPort += 10; + } - SortedSet foundPorts = new TreeSet<>(); + SortedSet foundPorts = new TreeSet<>(); - for (int currentPort = startPort; foundPorts.size() < numRequested && currentPort < SocketUtils.PORT_RANGE_MAX - && (currentPort - startPort) < 10000; currentPort++) { - if (allocate(clientName, currentPort)) { - foundPorts.add(currentPort); - } - } + for (int currentPort = startPort; foundPorts.size() < numRequested + && currentPort < SocketUtils.PORT_RANGE_MAX + && (currentPort - startPort) < 10000; currentPort++) { + if (allocate(clientName, currentPort)) { + foundPorts.add(currentPort); + } + } - if (foundPorts.size() < numRequested) { - throw new IllegalStateException("Could not find " + numRequested + " free ports starting at " + minPort + " for " + clientName); - } + if (foundPorts.size() < numRequested) { + throw new IllegalStateException("Could not find " + numRequested + " free ports starting at " + minPort + " for " + clientName); + } - return foundPorts; - } + return foundPorts; + } - public int allocateSingle(String clientName, int minPort) { + public int allocateSingle(String clientName, int minPort) { - int startPort = minPort; + int startPort = minPort; - for (int currentPort = startPort; currentPort < SocketUtils.PORT_RANGE_MAX && (currentPort - startPort) < 10000; currentPort++) { - if (allocate(clientName, currentPort)) { - return currentPort; - } - } + for (int currentPort = startPort; currentPort < SocketUtils.PORT_RANGE_MAX && (currentPort - startPort) < 10000; currentPort++) { + if (allocate(clientName, currentPort)) { + return currentPort; + } + } - throw new IllegalStateException("Could not find free port starting at " + minPort + " for " + clientName); + throw new IllegalStateException("Could not find free port starting at " + minPort + " for " + clientName); - } + } - public void reserve(int... ports) { + public void reserve(int... ports) { - for (int port : ports) { - allocate("reserved", port); - } - } + for (int port : ports) { + allocate("reserved", port); + } + } - private boolean isInUse(int port) { - boolean result = !this.socketType.isPortAvailable(port); + private boolean isInUse(int port) { + boolean result = !this.socketType.isPortAvailable(port); - if (result) { - synchronized (this) { - allocatedPorts.put(port, new AllocatedPort("external")); - } - } + if (result) { + synchronized (this) { + allocatedPorts.put(port, new AllocatedPort("external")); + } + } - return result; - } + return result; + } - private boolean isAvailable(int port) { - return !isAllocated(port) && !isInUse(port); - } + private boolean isAvailable(int port) { + return !isAllocated(port) && !isInUse(port); + } - private synchronized boolean isAllocated(int port) { - AllocatedPort allocatedPort = this.allocatedPorts.get(port); + private synchronized boolean isAllocated(int port) { + AllocatedPort allocatedPort = this.allocatedPorts.get(port); - return allocatedPort != null && !allocatedPort.isTimedOut(); - } + return allocatedPort != null && !allocatedPort.isTimedOut(); + } - private synchronized boolean allocate(String clientName, int port) { + private synchronized boolean allocate(String clientName, int port) { - AllocatedPort allocatedPort = allocatedPorts.get(port); + AllocatedPort allocatedPort = allocatedPorts.get(port); - if (allocatedPort != null && allocatedPort.isTimedOut()) { - allocatedPort = null; - allocatedPorts.remove(port); - } + if (allocatedPort != null && allocatedPort.isTimedOut()) { + allocatedPort = null; + allocatedPorts.remove(port); + } - if (allocatedPort == null && !isInUse(port)) { - allocatedPorts.put(port, new AllocatedPort(clientName)); - return true; - } else { - return false; - } - } + if (allocatedPort == null && !isInUse(port)) { + allocatedPorts.put(port, new AllocatedPort(clientName)); + return true; + } else { + return false; + } + } - private class AllocatedPort { - final String client; - final Instant allocatedAt; + private class AllocatedPort { + final String client; + final Instant allocatedAt; - AllocatedPort(String client) { - this.client = client; - this.allocatedAt = Instant.now(); - } + AllocatedPort(String client) { + this.client = client; + this.allocatedAt = Instant.now(); + } - boolean isTimedOut() { - return allocatedAt.plus(timeoutDuration).isBefore(Instant.now()); - } + boolean isTimedOut() { + return allocatedAt.plus(timeoutDuration).isBefore(Instant.now()); + } - @Override - public String toString() { - return "AllocatedPort [client=" + client + ", allocatedAt=" + allocatedAt + "]"; - } - } + @Override + public String toString() { + return "AllocatedPort [client=" + client + ", allocatedAt=" + allocatedAt + "]"; + } + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/RestClientException.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/RestClientException.java index d53258f020..0023d65e98 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/RestClientException.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/RestClientException.java @@ -10,7 +10,7 @@ package org.opensearch.test.framework.cluster; public class RestClientException extends RuntimeException { - RestClientException(String message, Throwable cause) { - super(message, cause); - } + RestClientException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java index 781ad4505c..b40aa9cfcb 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/SearchRequestFactory.java @@ -23,82 +23,82 @@ public final class SearchRequestFactory { - private SearchRequestFactory() { + private SearchRequestFactory() { - } + } - public static SearchRequest queryByIdsRequest(String indexName, String... ids) { - SearchRequest searchRequest = new SearchRequest(indexName); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - searchSourceBuilder.query(QueryBuilders.idsQuery().addIds(ids)); - searchRequest.source(searchSourceBuilder); - return searchRequest; - } + public static SearchRequest queryByIdsRequest(String indexName, String... ids) { + SearchRequest searchRequest = new SearchRequest(indexName); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.idsQuery().addIds(ids)); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } - public static SearchRequest queryStringQueryRequest(String indexName, String queryString) { - SearchRequest searchRequest = new SearchRequest(indexName); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - searchSourceBuilder.query(QueryBuilders.queryStringQuery(queryString)); - searchRequest.source(searchSourceBuilder); - return searchRequest; - } + public static SearchRequest queryStringQueryRequest(String indexName, String queryString) { + SearchRequest searchRequest = new SearchRequest(indexName); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.queryStringQuery(queryString)); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } - public static SearchRequest queryStringQueryRequest(String[] indicesNames, String queryString) { - SearchRequest searchRequest = new SearchRequest(indicesNames); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - searchSourceBuilder.query(QueryBuilders.queryStringQuery(queryString)); - searchRequest.source(searchSourceBuilder); - return searchRequest; - } + public static SearchRequest queryStringQueryRequest(String[] indicesNames, String queryString) { + SearchRequest searchRequest = new SearchRequest(indicesNames); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.queryStringQuery(queryString)); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } - public static SearchRequest queryStringQueryRequest(String queryString) { - SearchRequest searchRequest = new SearchRequest(); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - searchSourceBuilder.query(QueryBuilders.queryStringQuery(queryString)); - searchRequest.source(searchSourceBuilder); - return searchRequest; - } + public static SearchRequest queryStringQueryRequest(String queryString) { + SearchRequest searchRequest = new SearchRequest(); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.queryStringQuery(queryString)); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } - public static SearchRequest searchRequestWithScroll(String indexName, int pageSize) { - SearchRequest searchRequest = new SearchRequest(indexName); - searchRequest.scroll(new TimeValue(1, MINUTES)); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - searchSourceBuilder.query(QueryBuilders.matchAllQuery()); - searchSourceBuilder.sort(new FieldSortBuilder("_id").order(SortOrder.ASC)); - searchSourceBuilder.size(pageSize); - searchRequest.source(searchSourceBuilder); - return searchRequest; - } + public static SearchRequest searchRequestWithScroll(String indexName, int pageSize) { + SearchRequest searchRequest = new SearchRequest(indexName); + searchRequest.scroll(new TimeValue(1, MINUTES)); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.matchAllQuery()); + searchSourceBuilder.sort(new FieldSortBuilder("_id").order(SortOrder.ASC)); + searchSourceBuilder.size(pageSize); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } - public static SearchRequest searchAll(String...indexNames) { - SearchRequest searchRequest = new SearchRequest(indexNames); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - searchSourceBuilder.query(QueryBuilders.matchAllQuery()); - searchRequest.source(searchSourceBuilder); - return searchRequest; - } + public static SearchRequest searchAll(String... indexNames) { + SearchRequest searchRequest = new SearchRequest(indexNames); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.matchAllQuery()); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } - public static SearchScrollRequest getSearchScrollRequest(SearchResponse searchResponse) { - SearchScrollRequest scrollRequest = new SearchScrollRequest(searchResponse.getScrollId()); - scrollRequest.scroll(new TimeValue(1, MINUTES)); - return scrollRequest; - } + public static SearchScrollRequest getSearchScrollRequest(SearchResponse searchResponse) { + SearchScrollRequest scrollRequest = new SearchScrollRequest(searchResponse.getScrollId()); + scrollRequest.scroll(new TimeValue(1, MINUTES)); + return scrollRequest; + } - public static SearchRequest averageAggregationRequest(String indexName, String aggregationName, String fieldName) { - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - searchSourceBuilder.aggregation(AggregationBuilders.avg(aggregationName).field(fieldName)); - searchSourceBuilder.size(0); - SearchRequest searchRequest = new SearchRequest(indexName); - searchRequest.source(searchSourceBuilder); - return searchRequest; - } + public static SearchRequest averageAggregationRequest(String indexName, String aggregationName, String fieldName) { + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.aggregation(AggregationBuilders.avg(aggregationName).field(fieldName)); + searchSourceBuilder.size(0); + SearchRequest searchRequest = new SearchRequest(indexName); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } - public static SearchRequest statsAggregationRequest(String indexName, String aggregationName, String fieldName) { - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - searchSourceBuilder.aggregation(AggregationBuilders.stats(aggregationName).field(fieldName)); - searchSourceBuilder.size(0); - SearchRequest searchRequest = new SearchRequest(indexName); - searchRequest.source(searchSourceBuilder); - return searchRequest; - } + public static SearchRequest statsAggregationRequest(String indexName, String aggregationName, String fieldName) { + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.aggregation(AggregationBuilders.stats(aggregationName).field(fieldName)); + searchSourceBuilder.size(0); + SearchRequest searchRequest = new SearchRequest(indexName); + searchRequest.source(searchSourceBuilder); + return searchRequest; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtils.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtils.java index 92ec47d658..5895829243 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtils.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtils.java @@ -52,261 +52,260 @@ */ public class SocketUtils { - /** - * The default minimum value for port ranges used when finding an available - * socket port. - */ - public static final int PORT_RANGE_MIN = 1024; - - /** - * The default maximum value for port ranges used when finding an available - * socket port. - */ - public static final int PORT_RANGE_MAX = 65535; - - - private static final Random random = new Random(System.currentTimeMillis()); - - - /** - * Although {@code SocketUtils} consists solely of static utility methods, - * this constructor is intentionally {@code public}. - *

Rationale

- *

Static methods from this class may be invoked from within XML - * configuration files using the Spring Expression Language (SpEL) and the - * following syntax. - *

<bean id="bean1" ... p:port="#{T(org.springframework.util.SocketUtils).findAvailableTcpPort(12000)}" />
- * If this constructor were {@code private}, you would be required to supply - * the fully qualified class name to SpEL's {@code T()} function for each usage. - * Thus, the fact that this constructor is {@code public} allows you to reduce - * boilerplate configuration with SpEL as can be seen in the following example. - *
<bean id="socketUtils" class="org.springframework.util.SocketUtils" />
-	* <bean id="bean1" ... p:port="#{socketUtils.findAvailableTcpPort(12000)}" />
-	* <bean id="bean2" ... p:port="#{socketUtils.findAvailableTcpPort(30000)}" />
- */ - public SocketUtils() { - /* no-op */ - } - - - /** - * Find an available TCP port randomly selected from the range - * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. - * @return an available TCP port number - * @throws IllegalStateException if no available port could be found - */ - public static int findAvailableTcpPort() { - return findAvailableTcpPort(PORT_RANGE_MIN); - } - - /** - * Find an available TCP port randomly selected from the range - * [{@code minPort}, {@value #PORT_RANGE_MAX}]. - * @param minPort the minimum port number - * @return an available TCP port number - * @throws IllegalStateException if no available port could be found - */ - public static int findAvailableTcpPort(int minPort) { - return findAvailableTcpPort(minPort, PORT_RANGE_MAX); - } - - /** - * Find an available TCP port randomly selected from the range - * [{@code minPort}, {@code maxPort}]. - * @param minPort the minimum port number - * @param maxPort the maximum port number - * @return an available TCP port number - * @throws IllegalStateException if no available port could be found - */ - public static int findAvailableTcpPort(int minPort, int maxPort) { - return SocketType.TCP.findAvailablePort(minPort, maxPort); - } - - /** - * Find the requested number of available TCP ports, each randomly selected - * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. - * @param numRequested the number of available ports to find - * @return a sorted set of available TCP port numbers - * @throws IllegalStateException if the requested number of available ports could not be found - */ - public static SortedSet findAvailableTcpPorts(int numRequested) { - return findAvailableTcpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); - } - - /** - * Find the requested number of available TCP ports, each randomly selected - * from the range [{@code minPort}, {@code maxPort}]. - * @param numRequested the number of available ports to find - * @param minPort the minimum port number - * @param maxPort the maximum port number - * @return a sorted set of available TCP port numbers - * @throws IllegalStateException if the requested number of available ports could not be found - */ - public static SortedSet findAvailableTcpPorts(int numRequested, int minPort, int maxPort) { - return SocketType.TCP.findAvailablePorts(numRequested, minPort, maxPort); - } - - /** - * Find an available UDP port randomly selected from the range - * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. - * @return an available UDP port number - * @throws IllegalStateException if no available port could be found - */ - public static int findAvailableUdpPort() { - return findAvailableUdpPort(PORT_RANGE_MIN); - } - - /** - * Find an available UDP port randomly selected from the range - * [{@code minPort}, {@value #PORT_RANGE_MAX}]. - * @param minPort the minimum port number - * @return an available UDP port number - * @throws IllegalStateException if no available port could be found - */ - public static int findAvailableUdpPort(int minPort) { - return findAvailableUdpPort(minPort, PORT_RANGE_MAX); - } - - /** - * Find an available UDP port randomly selected from the range - * [{@code minPort}, {@code maxPort}]. - * @param minPort the minimum port number - * @param maxPort the maximum port number - * @return an available UDP port number - * @throws IllegalStateException if no available port could be found - */ - public static int findAvailableUdpPort(int minPort, int maxPort) { - return SocketType.UDP.findAvailablePort(minPort, maxPort); - } - - /** - * Find the requested number of available UDP ports, each randomly selected - * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. - * @param numRequested the number of available ports to find - * @return a sorted set of available UDP port numbers - * @throws IllegalStateException if the requested number of available ports could not be found - */ - public static SortedSet findAvailableUdpPorts(int numRequested) { - return findAvailableUdpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); - } - - /** - * Find the requested number of available UDP ports, each randomly selected - * from the range [{@code minPort}, {@code maxPort}]. - * @param numRequested the number of available ports to find - * @param minPort the minimum port number - * @param maxPort the maximum port number - * @return a sorted set of available UDP port numbers - * @throws IllegalStateException if the requested number of available ports could not be found - */ - public static SortedSet findAvailableUdpPorts(int numRequested, int minPort, int maxPort) { - return SocketType.UDP.findAvailablePorts(numRequested, minPort, maxPort); - } - - - public enum SocketType { - - TCP { - @Override - protected boolean isPortAvailable(int port) { - try { - ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket( - port, 1, InetAddress.getByName("localhost")); - serverSocket.close(); - return true; - } - catch (Exception ex) { - return false; - } - } - }, - - UDP { - @Override - protected boolean isPortAvailable(int port) { - try { - DatagramSocket socket = new DatagramSocket(port, InetAddress.getByName("localhost")); - socket.close(); - return true; - } - catch (Exception ex) { - return false; - } - } - }; - - /** - * Determine if the specified port for this {@code SocketType} is - * currently available on {@code localhost}. - */ - protected abstract boolean isPortAvailable(int port); - - /** - * Find a pseudo-random port number within the range - * [{@code minPort}, {@code maxPort}]. - * @param minPort the minimum port number - * @param maxPort the maximum port number - * @return a random port number within the specified range - */ - private int findRandomPort(int minPort, int maxPort) { - int portRange = maxPort - minPort; - return minPort + random.nextInt(portRange + 1); - } - - /** - * Find an available port for this {@code SocketType}, randomly selected - * from the range [{@code minPort}, {@code maxPort}]. - * @param minPort the minimum port number - * @param maxPort the maximum port number - * @return an available port number for this socket type - * @throws IllegalStateException if no available port could be found - */ - int findAvailablePort(int minPort, int maxPort) { - //Assert.assertTrue(minPort > 0, "'minPort' must be greater than 0"); - //Assert.isTrue(maxPort >= minPort, "'maxPort' must be greater than or equal to 'minPort'"); - //Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX); - - int portRange = maxPort - minPort; - int candidatePort; - int searchCounter = 0; - do { - if (searchCounter > portRange) { - throw new IllegalStateException(String.format( - "Could not find an available %s port in the range [%d, %d] after %d attempts", - name(), minPort, maxPort, searchCounter)); - } - candidatePort = findRandomPort(minPort, maxPort); - searchCounter++; - } - while (!isPortAvailable(candidatePort)); - - return candidatePort; - } - - /** - * Find the requested number of available ports for this {@code SocketType}, - * each randomly selected from the range [{@code minPort}, {@code maxPort}]. - * @param numRequested the number of available ports to find - * @param minPort the minimum port number - * @param maxPort the maximum port number - * @return a sorted set of available port numbers for this socket type - * @throws IllegalStateException if the requested number of available ports could not be found - */ - SortedSet findAvailablePorts(int numRequested, int minPort, int maxPort) { - SortedSet availablePorts = new TreeSet<>(); - int attemptCount = 0; - while ((++attemptCount <= numRequested + 100) && availablePorts.size() < numRequested) { - availablePorts.add(findAvailablePort(minPort, maxPort)); - } - - if (availablePorts.size() != numRequested) { - throw new IllegalStateException(String.format( - "Could not find %d available %s ports in the range [%d, %d]", - numRequested, name(), minPort, maxPort)); - } - - return availablePorts; - } - } + /** + * The default minimum value for port ranges used when finding an available + * socket port. + */ + public static final int PORT_RANGE_MIN = 1024; + + /** + * The default maximum value for port ranges used when finding an available + * socket port. + */ + public static final int PORT_RANGE_MAX = 65535; + + private static final Random random = new Random(System.currentTimeMillis()); + + /** + * Although {@code SocketUtils} consists solely of static utility methods, + * this constructor is intentionally {@code public}. + *

Rationale

+ *

Static methods from this class may be invoked from within XML + * configuration files using the Spring Expression Language (SpEL) and the + * following syntax. + *

<bean id="bean1" ... p:port="#{T(org.springframework.util.SocketUtils).findAvailableTcpPort(12000)}" />
+ * If this constructor were {@code private}, you would be required to supply + * the fully qualified class name to SpEL's {@code T()} function for each usage. + * Thus, the fact that this constructor is {@code public} allows you to reduce + * boilerplate configuration with SpEL as can be seen in the following example. + *
<bean id="socketUtils" class="org.springframework.util.SocketUtils" />
+    * <bean id="bean1" ... p:port="#{socketUtils.findAvailableTcpPort(12000)}" />
+    * <bean id="bean2" ... p:port="#{socketUtils.findAvailableTcpPort(30000)}" />
+ */ + public SocketUtils() { + /* no-op */ + } + + /** + * Find an available TCP port randomly selected from the range + * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort() { + return findAvailableTcpPort(PORT_RANGE_MIN); + } + + /** + * Find an available TCP port randomly selected from the range + * [{@code minPort}, {@value #PORT_RANGE_MAX}]. + * @param minPort the minimum port number + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort(int minPort) { + return findAvailableTcpPort(minPort, PORT_RANGE_MAX); + } + + /** + * Find an available TCP port randomly selected from the range + * [{@code minPort}, {@code maxPort}]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort(int minPort, int maxPort) { + return SocketType.TCP.findAvailablePort(minPort, maxPort); + } + + /** + * Find the requested number of available TCP ports, each randomly selected + * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @param numRequested the number of available ports to find + * @return a sorted set of available TCP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet findAvailableTcpPorts(int numRequested) { + return findAvailableTcpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + /** + * Find the requested number of available TCP ports, each randomly selected + * from the range [{@code minPort}, {@code maxPort}]. + * @param numRequested the number of available ports to find + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a sorted set of available TCP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet findAvailableTcpPorts(int numRequested, int minPort, int maxPort) { + return SocketType.TCP.findAvailablePorts(numRequested, minPort, maxPort); + } + + /** + * Find an available UDP port randomly selected from the range + * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @return an available UDP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableUdpPort() { + return findAvailableUdpPort(PORT_RANGE_MIN); + } + + /** + * Find an available UDP port randomly selected from the range + * [{@code minPort}, {@value #PORT_RANGE_MAX}]. + * @param minPort the minimum port number + * @return an available UDP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableUdpPort(int minPort) { + return findAvailableUdpPort(minPort, PORT_RANGE_MAX); + } + + /** + * Find an available UDP port randomly selected from the range + * [{@code minPort}, {@code maxPort}]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available UDP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableUdpPort(int minPort, int maxPort) { + return SocketType.UDP.findAvailablePort(minPort, maxPort); + } + + /** + * Find the requested number of available UDP ports, each randomly selected + * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @param numRequested the number of available ports to find + * @return a sorted set of available UDP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet findAvailableUdpPorts(int numRequested) { + return findAvailableUdpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + /** + * Find the requested number of available UDP ports, each randomly selected + * from the range [{@code minPort}, {@code maxPort}]. + * @param numRequested the number of available ports to find + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a sorted set of available UDP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet findAvailableUdpPorts(int numRequested, int minPort, int maxPort) { + return SocketType.UDP.findAvailablePorts(numRequested, minPort, maxPort); + } + + public enum SocketType { + + TCP { + @Override + protected boolean isPortAvailable(int port) { + try { + ServerSocket serverSocket = ServerSocketFactory.getDefault() + .createServerSocket(port, 1, InetAddress.getByName("localhost")); + serverSocket.close(); + return true; + } catch (Exception ex) { + return false; + } + } + }, + + UDP { + @Override + protected boolean isPortAvailable(int port) { + try { + DatagramSocket socket = new DatagramSocket(port, InetAddress.getByName("localhost")); + socket.close(); + return true; + } catch (Exception ex) { + return false; + } + } + }; + + /** + * Determine if the specified port for this {@code SocketType} is + * currently available on {@code localhost}. + */ + protected abstract boolean isPortAvailable(int port); + + /** + * Find a pseudo-random port number within the range + * [{@code minPort}, {@code maxPort}]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a random port number within the specified range + */ + private int findRandomPort(int minPort, int maxPort) { + int portRange = maxPort - minPort; + return minPort + random.nextInt(portRange + 1); + } + + /** + * Find an available port for this {@code SocketType}, randomly selected + * from the range [{@code minPort}, {@code maxPort}]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available port number for this socket type + * @throws IllegalStateException if no available port could be found + */ + int findAvailablePort(int minPort, int maxPort) { + // Assert.assertTrue(minPort > 0, "'minPort' must be greater than 0"); + // Assert.isTrue(maxPort >= minPort, "'maxPort' must be greater than or equal to 'minPort'"); + // Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX); + + int portRange = maxPort - minPort; + int candidatePort; + int searchCounter = 0; + do { + if (searchCounter > portRange) { + throw new IllegalStateException( + String.format( + "Could not find an available %s port in the range [%d, %d] after %d attempts", + name(), + minPort, + maxPort, + searchCounter + ) + ); + } + candidatePort = findRandomPort(minPort, maxPort); + searchCounter++; + } while (!isPortAvailable(candidatePort)); + + return candidatePort; + } + + /** + * Find the requested number of available ports for this {@code SocketType}, + * each randomly selected from the range [{@code minPort}, {@code maxPort}]. + * @param numRequested the number of available ports to find + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a sorted set of available port numbers for this socket type + * @throws IllegalStateException if the requested number of available ports could not be found + */ + SortedSet findAvailablePorts(int numRequested, int minPort, int maxPort) { + SortedSet availablePorts = new TreeSet<>(); + int attemptCount = 0; + while ((++attemptCount <= numRequested + 100) && availablePorts.size() < numRequested) { + availablePorts.add(findAvailablePort(minPort, maxPort)); + } + + if (availablePorts.size() != numRequested) { + throw new IllegalStateException( + String.format("Could not find %d available %s ports in the range [%d, %d]", numRequested, name(), minPort, maxPort) + ); + } + + return availablePorts; + } + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtilsTests.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtilsTests.java index 548bedbfa6..fb298c5283 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtilsTests.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/SocketUtilsTests.java @@ -55,158 +55,153 @@ */ public class SocketUtilsTests { - // TCP - - @Test - public void findAvailableTcpPort() { - int port = SocketUtils.findAvailableTcpPort(); - assertPortInRange(port, PORT_RANGE_MIN, PORT_RANGE_MAX); - } - - @Test - public void findAvailableTcpPortWithMinPortEqualToMaxPort() { - int minMaxPort = SocketUtils.findAvailableTcpPort(); - int port = SocketUtils.findAvailableTcpPort(minMaxPort, minMaxPort); - assertThat(port, equalTo(minMaxPort)); - } - - @Test - public void findAvailableTcpPortWhenPortOnLoopbackInterfaceIsNotAvailable() throws Exception { - int port = SocketUtils.findAvailableTcpPort(); - try (ServerSocket socket = ServerSocketFactory.getDefault().createServerSocket(port, 1, InetAddress.getByName("localhost"))) { - assertThat(socket, notNullValue()); - // will only look for the exact port - IllegalStateException exception = assertThrows( - IllegalStateException.class, - () -> SocketUtils.findAvailableTcpPort(port, port) - ); - assertThat(exception.getMessage(), startsWith("Could not find an available TCP port")); - assertThat(exception.getMessage(), endsWith("after 1 attempts")); - } - } - - @Test - public void findAvailableTcpPortWithMin() { - int port = SocketUtils.findAvailableTcpPort(50000); - assertPortInRange(port, 50000, PORT_RANGE_MAX); - } - - @Test - public void findAvailableTcpPortInRange() { - int minPort = 20000; - int maxPort = minPort + 1000; - int port = SocketUtils.findAvailableTcpPort(minPort, maxPort); - assertPortInRange(port, minPort, maxPort); - } - - @Test - public void find4AvailableTcpPorts() { - findAvailableTcpPorts(4); - } - - @Test - public void find50AvailableTcpPorts() { - findAvailableTcpPorts(50); - } - - @Test - public void find4AvailableTcpPortsInRange() { - findAvailableTcpPorts(4, 30000, 35000); - } - - @Test - public void find50AvailableTcpPortsInRange() { - findAvailableTcpPorts(50, 40000, 45000); - } - - // UDP - - @Test - public void findAvailableUdpPort() { - int port = SocketUtils.findAvailableUdpPort(); - assertPortInRange(port, PORT_RANGE_MIN, PORT_RANGE_MAX); - } - - @Test - public void findAvailableUdpPortWhenPortOnLoopbackInterfaceIsNotAvailable() throws Exception { - int port = SocketUtils.findAvailableUdpPort(); - try (DatagramSocket socket = new DatagramSocket(port, InetAddress.getByName("localhost"))) { - assertThat(socket, notNullValue()); - // will only look for the exact port - IllegalStateException exception = assertThrows( - IllegalStateException.class, - () -> SocketUtils.findAvailableUdpPort(port, port) - ); - assertThat(exception.getMessage(), startsWith("Could not find an available UDP port")); - assertThat(exception.getMessage(), endsWith("after 1 attempts")); - } - } - - @Test - public void findAvailableUdpPortWithMin() { - int port = SocketUtils.findAvailableUdpPort(50000); - assertPortInRange(port, 50000, PORT_RANGE_MAX); - } - - @Test - public void findAvailableUdpPortInRange() { - int minPort = 20000; - int maxPort = minPort + 1000; - int port = SocketUtils.findAvailableUdpPort(minPort, maxPort); - assertPortInRange(port, minPort, maxPort); - } - - @Test - public void find4AvailableUdpPorts() { - findAvailableUdpPorts(4); - } - - @Test - public void find50AvailableUdpPorts() { - findAvailableUdpPorts(50); - } - - @Test - public void find4AvailableUdpPortsInRange() { - findAvailableUdpPorts(4, 30000, 35000); - } - - @Test - public void find50AvailableUdpPortsInRange() { - findAvailableUdpPorts(50, 40000, 45000); - } - - // Helpers - - private void findAvailableTcpPorts(int numRequested) { - SortedSet ports = SocketUtils.findAvailableTcpPorts(numRequested); - assertAvailablePorts(ports, numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); - } - - private void findAvailableTcpPorts(int numRequested, int minPort, int maxPort) { - SortedSet ports = SocketUtils.findAvailableTcpPorts(numRequested, minPort, maxPort); - assertAvailablePorts(ports, numRequested, minPort, maxPort); - } - - private void findAvailableUdpPorts(int numRequested) { - SortedSet ports = SocketUtils.findAvailableUdpPorts(numRequested); - assertAvailablePorts(ports, numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); - } - - private void findAvailableUdpPorts(int numRequested, int minPort, int maxPort) { - SortedSet ports = SocketUtils.findAvailableUdpPorts(numRequested, minPort, maxPort); - assertAvailablePorts(ports, numRequested, minPort, maxPort); - } - private void assertPortInRange(int port, int minPort, int maxPort) { - assertThat("port [" + port + "] >= " + minPort, port, greaterThanOrEqualTo(minPort)); - assertThat("port [" + port + "] <= " + maxPort, port, lessThanOrEqualTo(maxPort)); - } - - private void assertAvailablePorts(SortedSet ports, int numRequested, int minPort, int maxPort) { - assertThat("number of ports requested", ports.size(), equalTo(numRequested)); - for (int port : ports) { - assertPortInRange(port, minPort, maxPort); - } - } + // TCP + + @Test + public void findAvailableTcpPort() { + int port = SocketUtils.findAvailableTcpPort(); + assertPortInRange(port, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + @Test + public void findAvailableTcpPortWithMinPortEqualToMaxPort() { + int minMaxPort = SocketUtils.findAvailableTcpPort(); + int port = SocketUtils.findAvailableTcpPort(minMaxPort, minMaxPort); + assertThat(port, equalTo(minMaxPort)); + } + + @Test + public void findAvailableTcpPortWhenPortOnLoopbackInterfaceIsNotAvailable() throws Exception { + int port = SocketUtils.findAvailableTcpPort(); + try (ServerSocket socket = ServerSocketFactory.getDefault().createServerSocket(port, 1, InetAddress.getByName("localhost"))) { + assertThat(socket, notNullValue()); + // will only look for the exact port + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> SocketUtils.findAvailableTcpPort(port, port)); + assertThat(exception.getMessage(), startsWith("Could not find an available TCP port")); + assertThat(exception.getMessage(), endsWith("after 1 attempts")); + } + } + + @Test + public void findAvailableTcpPortWithMin() { + int port = SocketUtils.findAvailableTcpPort(50000); + assertPortInRange(port, 50000, PORT_RANGE_MAX); + } + + @Test + public void findAvailableTcpPortInRange() { + int minPort = 20000; + int maxPort = minPort + 1000; + int port = SocketUtils.findAvailableTcpPort(minPort, maxPort); + assertPortInRange(port, minPort, maxPort); + } + + @Test + public void find4AvailableTcpPorts() { + findAvailableTcpPorts(4); + } + + @Test + public void find50AvailableTcpPorts() { + findAvailableTcpPorts(50); + } + + @Test + public void find4AvailableTcpPortsInRange() { + findAvailableTcpPorts(4, 30000, 35000); + } + + @Test + public void find50AvailableTcpPortsInRange() { + findAvailableTcpPorts(50, 40000, 45000); + } + + // UDP + + @Test + public void findAvailableUdpPort() { + int port = SocketUtils.findAvailableUdpPort(); + assertPortInRange(port, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + @Test + public void findAvailableUdpPortWhenPortOnLoopbackInterfaceIsNotAvailable() throws Exception { + int port = SocketUtils.findAvailableUdpPort(); + try (DatagramSocket socket = new DatagramSocket(port, InetAddress.getByName("localhost"))) { + assertThat(socket, notNullValue()); + // will only look for the exact port + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> SocketUtils.findAvailableUdpPort(port, port)); + assertThat(exception.getMessage(), startsWith("Could not find an available UDP port")); + assertThat(exception.getMessage(), endsWith("after 1 attempts")); + } + } + + @Test + public void findAvailableUdpPortWithMin() { + int port = SocketUtils.findAvailableUdpPort(50000); + assertPortInRange(port, 50000, PORT_RANGE_MAX); + } + + @Test + public void findAvailableUdpPortInRange() { + int minPort = 20000; + int maxPort = minPort + 1000; + int port = SocketUtils.findAvailableUdpPort(minPort, maxPort); + assertPortInRange(port, minPort, maxPort); + } + + @Test + public void find4AvailableUdpPorts() { + findAvailableUdpPorts(4); + } + + @Test + public void find50AvailableUdpPorts() { + findAvailableUdpPorts(50); + } + + @Test + public void find4AvailableUdpPortsInRange() { + findAvailableUdpPorts(4, 30000, 35000); + } + + @Test + public void find50AvailableUdpPortsInRange() { + findAvailableUdpPorts(50, 40000, 45000); + } + + // Helpers + + private void findAvailableTcpPorts(int numRequested) { + SortedSet ports = SocketUtils.findAvailableTcpPorts(numRequested); + assertAvailablePorts(ports, numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + private void findAvailableTcpPorts(int numRequested, int minPort, int maxPort) { + SortedSet ports = SocketUtils.findAvailableTcpPorts(numRequested, minPort, maxPort); + assertAvailablePorts(ports, numRequested, minPort, maxPort); + } + + private void findAvailableUdpPorts(int numRequested) { + SortedSet ports = SocketUtils.findAvailableUdpPorts(numRequested); + assertAvailablePorts(ports, numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + private void findAvailableUdpPorts(int numRequested, int minPort, int maxPort) { + SortedSet ports = SocketUtils.findAvailableUdpPorts(numRequested, minPort, maxPort); + assertAvailablePorts(ports, numRequested, minPort, maxPort); + } + + private void assertPortInRange(int port, int minPort, int maxPort) { + assertThat("port [" + port + "] >= " + minPort, port, greaterThanOrEqualTo(minPort)); + assertThat("port [" + port + "] <= " + maxPort, port, lessThanOrEqualTo(maxPort)); + } + + private void assertAvailablePorts(SortedSet ports, int numRequested, int minPort, int maxPort) { + assertThat("number of ports requested", ports.size(), equalTo(numRequested)); + for (int port : ports) { + assertPortInRange(port, minPort, maxPort); + } + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/StartStage.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/StartStage.java index 80db4ba87a..d5dce0056a 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/StartStage.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/StartStage.java @@ -10,6 +10,6 @@ package org.opensearch.test.framework.cluster; enum StartStage { - INITIALIZED, - RETRY + INITIALIZED, + RETRY } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java index 864b8db3f1..f446cac933 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClient.java @@ -88,347 +88,353 @@ * instance from OpenSearchClientProvider. */ public class TestRestClient implements AutoCloseable { - - private static final Logger log = LogManager.getLogger(TestRestClient.class); - - private boolean enableHTTPClientSSL = true; - private boolean sendHTTPClientCertificate = false; - private InetSocketAddress nodeHttpAddress; - private RequestConfig requestConfig; - private List
headers = new ArrayList<>(); - private Header CONTENT_TYPE_JSON = new BasicHeader("Content-Type", "application/json"); - private SSLContext sslContext; - - private final InetAddress sourceInetAddress; - - public TestRestClient(InetSocketAddress nodeHttpAddress, List
headers, SSLContext sslContext, InetAddress sourceInetAddress) { - this.nodeHttpAddress = nodeHttpAddress; - this.headers.addAll(headers); - this.sslContext = sslContext; - this.sourceInetAddress = sourceInetAddress; - } - - public HttpResponse get(String path, List queryParameters, Header... headers) { - try { - URI uri = new URIBuilder(getHttpServerUri()).setPath(path).addParameters(queryParameters).build(); - return executeRequest(new HttpGet(uri), headers); - } catch (URISyntaxException ex) { - throw new RuntimeException("Incorrect URI syntax", ex); - } - } - - public HttpResponse get(String path, Header... headers) { - return get(path, Collections.emptyList(), headers); - } - - public HttpResponse getWithJsonBody(String path, String body, Header... headers) { - try { - HttpGet httpGet = new HttpGet( new URIBuilder(getHttpServerUri()).setPath(path).build()); - httpGet.setEntity(toStringEntity(body)); - return executeRequest(httpGet, mergeHeaders(CONTENT_TYPE_JSON, headers)); - } catch (URISyntaxException ex) { - throw new RuntimeException("Incorrect URI syntax", ex); - } - } - - public HttpResponse getAuthInfo( Header... headers) { - return executeRequest(new HttpGet(getHttpServerUri() + "/_opendistro/_security/authinfo?pretty"), headers); - } - - public void assertCorrectCredentials(String expectedUserName) { - HttpResponse response = getAuthInfo(); - assertThat(response, notNullValue()); - response.assertStatusCode(200); - String username = response.getTextFromJsonBody("/user_name"); - String message = String.format("Expected user name is '%s', but was '%s'", expectedUserName, username); - assertThat(message, username, equalTo(expectedUserName)); - } - - public HttpResponse head(String path, Header... headers) { - return executeRequest(new HttpHead(getHttpServerUri() + "/" + path), headers); - } - - public HttpResponse options(String path, Header... headers) { - return executeRequest(new HttpOptions(getHttpServerUri() + "/" + path), headers); - } - - public HttpResponse putJson(String path, String body, Header... headers) { - HttpPut uriRequest = new HttpPut(getHttpServerUri() + "/" + path); - uriRequest.setEntity(toStringEntity(body)); - return executeRequest(uriRequest, mergeHeaders(CONTENT_TYPE_JSON, headers)); - } - - private StringEntity toStringEntity(String body) { - return new StringEntity(body); - } - - public HttpResponse putJson(String path, ToXContentObject body) { - return putJson(path, Strings.toString(XContentType.JSON, body)); - } - - public HttpResponse put(String path) { - HttpPut uriRequest = new HttpPut(getHttpServerUri() + "/" + path); - return executeRequest(uriRequest); - } - - public HttpResponse delete(String path, Header... headers) { - return executeRequest(new HttpDelete(getHttpServerUri() + "/" + path), headers); - } - - public HttpResponse postJson(String path, String body, Header... headers) { - HttpPost uriRequest = new HttpPost(getHttpServerUri() + "/" + path); - uriRequest.setEntity(toStringEntity(body)); - return executeRequest(uriRequest, mergeHeaders(CONTENT_TYPE_JSON, headers)); - } - - public HttpResponse postJson(String path, ToXContentObject body) { - return postJson(path, Strings.toString(XContentType.JSON, body)); - } - - public HttpResponse post(String path) { - HttpPost uriRequest = new HttpPost(getHttpServerUri() + "/" + path); - return executeRequest(uriRequest); - } - - public HttpResponse patch(String path, String body) { - HttpPatch uriRequest = new HttpPatch(getHttpServerUri() + "/" + path); - uriRequest.setEntity(toStringEntity(body)); - return executeRequest(uriRequest, CONTENT_TYPE_JSON); - } - - public HttpResponse assignRoleToUser(String username, String roleName) { - Objects.requireNonNull(roleName, "Role name is required"); - Objects.requireNonNull(username, "User name is required"); - String body = String.format("[{\"op\":\"add\",\"path\":\"/opendistro_security_roles\",\"value\":[\"%s\"]}]", roleName); - return patch("_plugins/_security/api/internalusers/" + username, body); - } - - public HttpResponse createRole(String roleName, ToXContentObject role) { - Objects.requireNonNull(roleName, "Role name is required"); - Objects.requireNonNull(role, "Role is required"); - return putJson("_plugins/_security/api/roles/" + roleName, role); - } - - public HttpResponse createUser(String userName, ToXContentObject user) { - Objects.requireNonNull(userName, "User name is required"); - Objects.requireNonNull(user, "User is required"); - return putJson("_plugins/_security/api/internalusers/" + userName, user); - } - - public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... requestSpecificHeaders) { - try(CloseableHttpClient httpClient = getHTTPClient()) { - - - if (requestSpecificHeaders != null && requestSpecificHeaders.length > 0) { - for (int i = 0; i < requestSpecificHeaders.length; i++) { - Header h = requestSpecificHeaders[i]; - uriRequest.addHeader(h); - } - } - - for (Header header : headers) { - uriRequest.addHeader(header); - } - - HttpResponse res = new HttpResponse(httpClient.execute(uriRequest)); - log.debug(res.getBody()); - return res; - } catch (IOException e) { - throw new RestClientException("Error occured during HTTP request execution", e); - } - } - - public void createRoleMapping(String backendRoleName, String roleName) { - requireNonNull(backendRoleName, "Backend role name is required"); - requireNonNull(roleName, "Role name is required"); - String path = "_plugins/_security/api/rolesmapping/" + roleName; - String body = String.format("{\"backend_roles\": [\"%s\"]}", backendRoleName); - HttpResponse response = putJson(path, body); - response.assertStatusCode(201); - } - - protected final String getHttpServerUri() { - return "http" + (enableHTTPClientSSL ? "s" : "") + "://" + nodeHttpAddress.getHostString() + ":" + nodeHttpAddress.getPort(); - } - - protected final CloseableHttpClient getHTTPClient() { - HttpRoutePlanner routePlanner = Optional.ofNullable(sourceInetAddress).map(LocalAddressRoutePlanner::new).orElse(null); - var factory = new CloseableHttpClientFactory(sslContext, requestConfig, routePlanner, null); - return factory.getHTTPClient(); - } - - private Header[] mergeHeaders(Header header, Header... headers) { - - if (headers == null || headers.length == 0) { - return new Header[] { header }; - } else { - Header[] result = new Header[headers.length + 1]; - result[0] = header; - System.arraycopy(headers, 0, result, 1, headers.length); - return result; - } - } - - public static class HttpResponse { - private final CloseableHttpResponse inner; - private final String body; - private final Header[] header; - private final int statusCode; - private final String statusReason; - - public HttpResponse(CloseableHttpResponse inner) throws IllegalStateException, IOException { - super(); - this.inner = inner; - final HttpEntity entity = inner.getEntity(); - if (entity == null) { //head request does not have a entity - this.body = ""; - } else { - this.body = IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8); - } - this.header = inner.getHeaders(); - this.statusCode = inner.getCode(); - this.statusReason = inner.getReasonPhrase(); - inner.close(); - } - - public String getContentType() { - Header h = getInner().getFirstHeader("content-type"); - if (h != null) { - return h.getValue(); - } - return null; - } - - public boolean isJsonContentType() { - String ct = getContentType(); - if (ct == null) { - return false; - } - return ct.contains("application/json"); - } - - public CloseableHttpResponse getInner() { - return inner; - } - - public String getBody() { - return body; - } - - public Header[] getHeader() { - return header; - } - - public Optional
findHeader(String name) { - return Arrays.stream(header) - .filter(header -> requireNonNull(name, "Header name is mandatory.").equalsIgnoreCase(header.getName())) - .findFirst(); - } - - public Header getHeader(String name) { - return findHeader(name).orElseThrow(); - } - - public boolean containHeader(String name) { - return findHeader(name).isPresent(); - } - - public int getStatusCode() { - return statusCode; - } - - public String getStatusReason() { - return statusReason; - } - - public List
getHeaders() { - return header == null ? Collections.emptyList() : Arrays.asList(header); - } - - public String getTextFromJsonBody(String jsonPointer) { - return getJsonNodeAt(jsonPointer).asText(); - } - - public List getTextArrayFromJsonBody(String jsonPointer) { - return StreamSupport.stream(getJsonNodeAt(jsonPointer).spliterator(), false) - .map(JsonNode::textValue) - .collect(Collectors.toList()); - } - - public int getIntFromJsonBody(String jsonPointer) { - return getJsonNodeAt(jsonPointer).asInt(); - } - - public Boolean getBooleanFromJsonBody(String jsonPointer) { - return getJsonNodeAt(jsonPointer).asBoolean(); - } - - public Double getDoubleFromJsonBody(String jsonPointer) { - return getJsonNodeAt(jsonPointer).asDouble(); - } - - public Long getLongFromJsonBody(String jsonPointer) { - return getJsonNodeAt(jsonPointer).asLong(); - } - - private JsonNode getJsonNodeAt(String jsonPointer) { - try { - return toJsonNode().at(jsonPointer); - } catch (IOException e) { - throw new IllegalArgumentException("Cound not convert response body to JSON node ",e); - } - } - - private JsonNode toJsonNode() throws JsonProcessingException, IOException { - return DefaultObjectMapper.objectMapper.readTree(getBody()); - } - - - - @Override - public String toString() { - return "HttpResponse [inner=" + inner + ", body=" + body + ", header=" + Arrays.toString(header) + ", statusCode=" + statusCode - + ", statusReason=" + statusReason + "]"; - } - - public T getBodyAs(Class authInfoClass) { - try { - return DefaultObjectMapper.readValue(getBody(), authInfoClass); - } catch (IOException e) { - throw new RuntimeException("Cannot parse response body", e); - } - } - - public void assertStatusCode(int expectedHttpStatus) { - String reason = format("Expected status code is '%d', but was '%d'. Response body '%s'.", expectedHttpStatus, statusCode, body); - assertThat(reason, statusCode, equalTo(expectedHttpStatus)); - } - } - - @Override - public String toString() { - return "TestRestClient [server=" + getHttpServerUri() + ", node=" + nodeHttpAddress + "]"; - } - - public RequestConfig getRequestConfig() { - return requestConfig; - } - - public void setRequestConfig(RequestConfig requestConfig) { - this.requestConfig = requestConfig; - } - - public boolean isSendHTTPClientCertificate() { - return sendHTTPClientCertificate; - } - - public void setSendHTTPClientCertificate(boolean sendHTTPClientCertificate) { - this.sendHTTPClientCertificate = sendHTTPClientCertificate; - } - - @Override - public void close() { - // TODO: Is there anything to clean up here? - } + + private static final Logger log = LogManager.getLogger(TestRestClient.class); + + private boolean enableHTTPClientSSL = true; + private boolean sendHTTPClientCertificate = false; + private InetSocketAddress nodeHttpAddress; + private RequestConfig requestConfig; + private List
headers = new ArrayList<>(); + private Header CONTENT_TYPE_JSON = new BasicHeader("Content-Type", "application/json"); + private SSLContext sslContext; + + private final InetAddress sourceInetAddress; + + public TestRestClient(InetSocketAddress nodeHttpAddress, List
headers, SSLContext sslContext, InetAddress sourceInetAddress) { + this.nodeHttpAddress = nodeHttpAddress; + this.headers.addAll(headers); + this.sslContext = sslContext; + this.sourceInetAddress = sourceInetAddress; + } + + public HttpResponse get(String path, List queryParameters, Header... headers) { + try { + URI uri = new URIBuilder(getHttpServerUri()).setPath(path).addParameters(queryParameters).build(); + return executeRequest(new HttpGet(uri), headers); + } catch (URISyntaxException ex) { + throw new RuntimeException("Incorrect URI syntax", ex); + } + } + + public HttpResponse get(String path, Header... headers) { + return get(path, Collections.emptyList(), headers); + } + + public HttpResponse getWithJsonBody(String path, String body, Header... headers) { + try { + HttpGet httpGet = new HttpGet(new URIBuilder(getHttpServerUri()).setPath(path).build()); + httpGet.setEntity(toStringEntity(body)); + return executeRequest(httpGet, mergeHeaders(CONTENT_TYPE_JSON, headers)); + } catch (URISyntaxException ex) { + throw new RuntimeException("Incorrect URI syntax", ex); + } + } + + public HttpResponse getAuthInfo(Header... headers) { + return executeRequest(new HttpGet(getHttpServerUri() + "/_opendistro/_security/authinfo?pretty"), headers); + } + + public void assertCorrectCredentials(String expectedUserName) { + HttpResponse response = getAuthInfo(); + assertThat(response, notNullValue()); + response.assertStatusCode(200); + String username = response.getTextFromJsonBody("/user_name"); + String message = String.format("Expected user name is '%s', but was '%s'", expectedUserName, username); + assertThat(message, username, equalTo(expectedUserName)); + } + + public HttpResponse head(String path, Header... headers) { + return executeRequest(new HttpHead(getHttpServerUri() + "/" + path), headers); + } + + public HttpResponse options(String path, Header... headers) { + return executeRequest(new HttpOptions(getHttpServerUri() + "/" + path), headers); + } + + public HttpResponse putJson(String path, String body, Header... headers) { + HttpPut uriRequest = new HttpPut(getHttpServerUri() + "/" + path); + uriRequest.setEntity(toStringEntity(body)); + return executeRequest(uriRequest, mergeHeaders(CONTENT_TYPE_JSON, headers)); + } + + private StringEntity toStringEntity(String body) { + return new StringEntity(body); + } + + public HttpResponse putJson(String path, ToXContentObject body) { + return putJson(path, Strings.toString(XContentType.JSON, body)); + } + + public HttpResponse put(String path) { + HttpPut uriRequest = new HttpPut(getHttpServerUri() + "/" + path); + return executeRequest(uriRequest); + } + + public HttpResponse delete(String path, Header... headers) { + return executeRequest(new HttpDelete(getHttpServerUri() + "/" + path), headers); + } + + public HttpResponse postJson(String path, String body, Header... headers) { + HttpPost uriRequest = new HttpPost(getHttpServerUri() + "/" + path); + uriRequest.setEntity(toStringEntity(body)); + return executeRequest(uriRequest, mergeHeaders(CONTENT_TYPE_JSON, headers)); + } + + public HttpResponse postJson(String path, ToXContentObject body) { + return postJson(path, Strings.toString(XContentType.JSON, body)); + } + + public HttpResponse post(String path) { + HttpPost uriRequest = new HttpPost(getHttpServerUri() + "/" + path); + return executeRequest(uriRequest); + } + + public HttpResponse patch(String path, String body) { + HttpPatch uriRequest = new HttpPatch(getHttpServerUri() + "/" + path); + uriRequest.setEntity(toStringEntity(body)); + return executeRequest(uriRequest, CONTENT_TYPE_JSON); + } + + public HttpResponse assignRoleToUser(String username, String roleName) { + Objects.requireNonNull(roleName, "Role name is required"); + Objects.requireNonNull(username, "User name is required"); + String body = String.format("[{\"op\":\"add\",\"path\":\"/opendistro_security_roles\",\"value\":[\"%s\"]}]", roleName); + return patch("_plugins/_security/api/internalusers/" + username, body); + } + + public HttpResponse createRole(String roleName, ToXContentObject role) { + Objects.requireNonNull(roleName, "Role name is required"); + Objects.requireNonNull(role, "Role is required"); + return putJson("_plugins/_security/api/roles/" + roleName, role); + } + + public HttpResponse createUser(String userName, ToXContentObject user) { + Objects.requireNonNull(userName, "User name is required"); + Objects.requireNonNull(user, "User is required"); + return putJson("_plugins/_security/api/internalusers/" + userName, user); + } + + public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... requestSpecificHeaders) { + try (CloseableHttpClient httpClient = getHTTPClient()) { + + if (requestSpecificHeaders != null && requestSpecificHeaders.length > 0) { + for (int i = 0; i < requestSpecificHeaders.length; i++) { + Header h = requestSpecificHeaders[i]; + uriRequest.addHeader(h); + } + } + + for (Header header : headers) { + uriRequest.addHeader(header); + } + + HttpResponse res = new HttpResponse(httpClient.execute(uriRequest)); + log.debug(res.getBody()); + return res; + } catch (IOException e) { + throw new RestClientException("Error occured during HTTP request execution", e); + } + } + + public void createRoleMapping(String backendRoleName, String roleName) { + requireNonNull(backendRoleName, "Backend role name is required"); + requireNonNull(roleName, "Role name is required"); + String path = "_plugins/_security/api/rolesmapping/" + roleName; + String body = String.format("{\"backend_roles\": [\"%s\"]}", backendRoleName); + HttpResponse response = putJson(path, body); + response.assertStatusCode(201); + } + + protected final String getHttpServerUri() { + return "http" + (enableHTTPClientSSL ? "s" : "") + "://" + nodeHttpAddress.getHostString() + ":" + nodeHttpAddress.getPort(); + } + + protected final CloseableHttpClient getHTTPClient() { + HttpRoutePlanner routePlanner = Optional.ofNullable(sourceInetAddress).map(LocalAddressRoutePlanner::new).orElse(null); + var factory = new CloseableHttpClientFactory(sslContext, requestConfig, routePlanner, null); + return factory.getHTTPClient(); + } + + private Header[] mergeHeaders(Header header, Header... headers) { + + if (headers == null || headers.length == 0) { + return new Header[] { header }; + } else { + Header[] result = new Header[headers.length + 1]; + result[0] = header; + System.arraycopy(headers, 0, result, 1, headers.length); + return result; + } + } + + public static class HttpResponse { + private final CloseableHttpResponse inner; + private final String body; + private final Header[] header; + private final int statusCode; + private final String statusReason; + + public HttpResponse(CloseableHttpResponse inner) throws IllegalStateException, IOException { + super(); + this.inner = inner; + final HttpEntity entity = inner.getEntity(); + if (entity == null) { // head request does not have a entity + this.body = ""; + } else { + this.body = IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8); + } + this.header = inner.getHeaders(); + this.statusCode = inner.getCode(); + this.statusReason = inner.getReasonPhrase(); + inner.close(); + } + + public String getContentType() { + Header h = getInner().getFirstHeader("content-type"); + if (h != null) { + return h.getValue(); + } + return null; + } + + public boolean isJsonContentType() { + String ct = getContentType(); + if (ct == null) { + return false; + } + return ct.contains("application/json"); + } + + public CloseableHttpResponse getInner() { + return inner; + } + + public String getBody() { + return body; + } + + public Header[] getHeader() { + return header; + } + + public Optional
findHeader(String name) { + return Arrays.stream(header) + .filter(header -> requireNonNull(name, "Header name is mandatory.").equalsIgnoreCase(header.getName())) + .findFirst(); + } + + public Header getHeader(String name) { + return findHeader(name).orElseThrow(); + } + + public boolean containHeader(String name) { + return findHeader(name).isPresent(); + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatusReason() { + return statusReason; + } + + public List
getHeaders() { + return header == null ? Collections.emptyList() : Arrays.asList(header); + } + + public String getTextFromJsonBody(String jsonPointer) { + return getJsonNodeAt(jsonPointer).asText(); + } + + public List getTextArrayFromJsonBody(String jsonPointer) { + return StreamSupport.stream(getJsonNodeAt(jsonPointer).spliterator(), false) + .map(JsonNode::textValue) + .collect(Collectors.toList()); + } + + public int getIntFromJsonBody(String jsonPointer) { + return getJsonNodeAt(jsonPointer).asInt(); + } + + public Boolean getBooleanFromJsonBody(String jsonPointer) { + return getJsonNodeAt(jsonPointer).asBoolean(); + } + + public Double getDoubleFromJsonBody(String jsonPointer) { + return getJsonNodeAt(jsonPointer).asDouble(); + } + + public Long getLongFromJsonBody(String jsonPointer) { + return getJsonNodeAt(jsonPointer).asLong(); + } + + private JsonNode getJsonNodeAt(String jsonPointer) { + try { + return toJsonNode().at(jsonPointer); + } catch (IOException e) { + throw new IllegalArgumentException("Cound not convert response body to JSON node ", e); + } + } + + private JsonNode toJsonNode() throws JsonProcessingException, IOException { + return DefaultObjectMapper.objectMapper.readTree(getBody()); + } + + @Override + public String toString() { + return "HttpResponse [inner=" + + inner + + ", body=" + + body + + ", header=" + + Arrays.toString(header) + + ", statusCode=" + + statusCode + + ", statusReason=" + + statusReason + + "]"; + } + + public T getBodyAs(Class authInfoClass) { + try { + return DefaultObjectMapper.readValue(getBody(), authInfoClass); + } catch (IOException e) { + throw new RuntimeException("Cannot parse response body", e); + } + } + + public void assertStatusCode(int expectedHttpStatus) { + String reason = format("Expected status code is '%d', but was '%d'. Response body '%s'.", expectedHttpStatus, statusCode, body); + assertThat(reason, statusCode, equalTo(expectedHttpStatus)); + } + } + + @Override + public String toString() { + return "TestRestClient [server=" + getHttpServerUri() + ", node=" + nodeHttpAddress + "]"; + } + + public RequestConfig getRequestConfig() { + return requestConfig; + } + + public void setRequestConfig(RequestConfig requestConfig) { + this.requestConfig = requestConfig; + } + + public boolean isSendHTTPClientCertificate() { + return sendHTTPClientCertificate; + } + + public void setSendHTTPClientCertificate(boolean sendHTTPClientCertificate) { + this.sendHTTPClientCertificate = sendHTTPClientCertificate; + } + + @Override + public void close() { + // TODO: Is there anything to clean up here? + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClientConfiguration.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClientConfiguration.java index c1f7a7a737..3b75730303 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClientConfiguration.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/TestRestClientConfiguration.java @@ -36,134 +36,134 @@ */ public class TestRestClientConfiguration { - /** - * Username - */ - private String username; - /** - * Password - */ - private String password; - /** - * HTTP headers which should be attached to each HTTP request which is sent by {@link TestRestClient} - */ - private final List
headers = new ArrayList<>(); - /** - * IP address of client socket of {@link TestRestClient} - */ - private InetAddress sourceInetAddress; - - /** - * Set username - * @param username username - * @return builder - */ - public TestRestClientConfiguration username(String username) { - this.username = username; - return this; - } - - /** - * Set user's password - * @param password password - * @return builder - */ - public TestRestClientConfiguration password(String password) { - this.password = password; - return this; - } - - /** - * The method sets username and password read form userCredentialsHolder - * @param userCredentialsHolder source of credentials - * @return builder - */ - public TestRestClientConfiguration credentials(UserCredentialsHolder userCredentialsHolder) { - Objects.requireNonNull(userCredentialsHolder, "User credential holder is required."); - this.username = userCredentialsHolder.getName(); - this.password = userCredentialsHolder.getPassword(); - return this; - } - - /** - * Add HTTP headers which are attached to each HTTP request - * @param headers headers - * @return builder - */ - public TestRestClientConfiguration headers(Header...headers) { - this.headers.addAll(Arrays.asList(Objects.requireNonNull(headers, "Headers are required"))); - return this; - } - /** - * Add HTTP headers which are attached to each HTTP request - * @param headers list of headers - * @return builder - */ - public TestRestClientConfiguration headers(List
headers) { - this.headers.addAll(Objects.requireNonNull(headers, "Cannot add null headers")); - return this; - } - - /** - * Add HTTP header to each request - * @param name header name - * @param value header value - * @return builder - */ - public TestRestClientConfiguration header(String name, Object value) { - return headers(new BasicHeader(name, value)); - } - - /** - * Set IP address of client socket used by {@link TestRestClient} - * @param sourceInetAddress IP address - * @return builder - */ - public TestRestClientConfiguration sourceInetAddress(InetAddress sourceInetAddress) { - this.sourceInetAddress = sourceInetAddress; - return this; - } - - public TestRestClientConfiguration sourceInetAddress(String sourceInetAddress) { - try { - this.sourceInetAddress = InetAddress.getByName(sourceInetAddress); - return this; - } catch (UnknownHostException e) { - throw new RuntimeException("Cannot get IP address for string " + sourceInetAddress, e); - } - } - - public static TestRestClientConfiguration userWithSourceIp(UserCredentialsHolder credentials, String sourceIpAddress) { - return new TestRestClientConfiguration().credentials(credentials).sourceInetAddress(sourceIpAddress); - } - - /** - * Return complete header list. Basic authentication header is created using fields {@link #username} and {@link #password} - * @return header list - */ - List
getHeaders() { - return Stream.concat(createBasicAuthHeader().stream(), headers.stream()).collect(Collectors.toList()); - } - - private Optional
createBasicAuthHeader() { - if(containsCredentials()) { - return Optional.of(getBasicAuthHeader(username, password)); - } - return Optional.empty(); - } - - private boolean containsCredentials() { - return StringUtils.isNoneBlank(username) && StringUtils.isNoneBlank(password); - } - - InetAddress getSourceInetAddress() { - return sourceInetAddress; - } - - public static Header getBasicAuthHeader(String user, String password) { - String value ="Basic " + Base64.getEncoder() - .encodeToString((user + ":" + requireNonNull(password)) - .getBytes(StandardCharsets.UTF_8)); - return new BasicHeader("Authorization", value); - } + /** + * Username + */ + private String username; + /** + * Password + */ + private String password; + /** + * HTTP headers which should be attached to each HTTP request which is sent by {@link TestRestClient} + */ + private final List
headers = new ArrayList<>(); + /** + * IP address of client socket of {@link TestRestClient} + */ + private InetAddress sourceInetAddress; + + /** + * Set username + * @param username username + * @return builder + */ + public TestRestClientConfiguration username(String username) { + this.username = username; + return this; + } + + /** + * Set user's password + * @param password password + * @return builder + */ + public TestRestClientConfiguration password(String password) { + this.password = password; + return this; + } + + /** + * The method sets username and password read form userCredentialsHolder + * @param userCredentialsHolder source of credentials + * @return builder + */ + public TestRestClientConfiguration credentials(UserCredentialsHolder userCredentialsHolder) { + Objects.requireNonNull(userCredentialsHolder, "User credential holder is required."); + this.username = userCredentialsHolder.getName(); + this.password = userCredentialsHolder.getPassword(); + return this; + } + + /** + * Add HTTP headers which are attached to each HTTP request + * @param headers headers + * @return builder + */ + public TestRestClientConfiguration headers(Header... headers) { + this.headers.addAll(Arrays.asList(Objects.requireNonNull(headers, "Headers are required"))); + return this; + } + + /** + * Add HTTP headers which are attached to each HTTP request + * @param headers list of headers + * @return builder + */ + public TestRestClientConfiguration headers(List
headers) { + this.headers.addAll(Objects.requireNonNull(headers, "Cannot add null headers")); + return this; + } + + /** + * Add HTTP header to each request + * @param name header name + * @param value header value + * @return builder + */ + public TestRestClientConfiguration header(String name, Object value) { + return headers(new BasicHeader(name, value)); + } + + /** + * Set IP address of client socket used by {@link TestRestClient} + * @param sourceInetAddress IP address + * @return builder + */ + public TestRestClientConfiguration sourceInetAddress(InetAddress sourceInetAddress) { + this.sourceInetAddress = sourceInetAddress; + return this; + } + + public TestRestClientConfiguration sourceInetAddress(String sourceInetAddress) { + try { + this.sourceInetAddress = InetAddress.getByName(sourceInetAddress); + return this; + } catch (UnknownHostException e) { + throw new RuntimeException("Cannot get IP address for string " + sourceInetAddress, e); + } + } + + public static TestRestClientConfiguration userWithSourceIp(UserCredentialsHolder credentials, String sourceIpAddress) { + return new TestRestClientConfiguration().credentials(credentials).sourceInetAddress(sourceIpAddress); + } + + /** + * Return complete header list. Basic authentication header is created using fields {@link #username} and {@link #password} + * @return header list + */ + List
getHeaders() { + return Stream.concat(createBasicAuthHeader().stream(), headers.stream()).collect(Collectors.toList()); + } + + private Optional
createBasicAuthHeader() { + if (containsCredentials()) { + return Optional.of(getBasicAuthHeader(username, password)); + } + return Optional.empty(); + } + + private boolean containsCredentials() { + return StringUtils.isNoneBlank(username) && StringUtils.isNoneBlank(password); + } + + InetAddress getSourceInetAddress() { + return sourceInetAddress; + } + + public static Header getBasicAuthHeader(String user, String password) { + String value = "Basic " + + Base64.getEncoder().encodeToString((user + ":" + requireNonNull(password)).getBytes(StandardCharsets.UTF_8)); + return new BasicHeader("Authorization", value); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/ldap/EmbeddedLDAPServer.java b/src/integrationTest/java/org/opensearch/test/framework/ldap/EmbeddedLDAPServer.java index fd40a85292..583a0cdaeb 100755 --- a/src/integrationTest/java/org/opensearch/test/framework/ldap/EmbeddedLDAPServer.java +++ b/src/integrationTest/java/org/opensearch/test/framework/ldap/EmbeddedLDAPServer.java @@ -19,38 +19,38 @@ public class EmbeddedLDAPServer extends ExternalResource { - private final LdapServer server; - - private final LdifData ldifData; - - public EmbeddedLDAPServer(CertificateData trustAnchor, CertificateData ldapCertificate, LdifData ldifData) { - this.ldifData = Objects.requireNonNull(ldifData, "Ldif data is required"); - this.server = new LdapServer(trustAnchor, ldapCertificate); - } - - @Override - protected void before() { - try { - server.start(ldifData); - } catch (Exception e) { - throw new RuntimeException("Cannot start ldap server", e); - } - } - - @Override - protected void after() { - try { - server.stop(); - } catch (InterruptedException e) { - throw new RuntimeException("Cannot stop LDAP server.", e); - } - } - - public int getLdapNonTlsPort() { - return server.getLdapNonTlsPort(); - } - - public int getLdapTlsPort() { - return server.getLdapsTlsPort(); - } + private final LdapServer server; + + private final LdifData ldifData; + + public EmbeddedLDAPServer(CertificateData trustAnchor, CertificateData ldapCertificate, LdifData ldifData) { + this.ldifData = Objects.requireNonNull(ldifData, "Ldif data is required"); + this.server = new LdapServer(trustAnchor, ldapCertificate); + } + + @Override + protected void before() { + try { + server.start(ldifData); + } catch (Exception e) { + throw new RuntimeException("Cannot start ldap server", e); + } + } + + @Override + protected void after() { + try { + server.stop(); + } catch (InterruptedException e) { + throw new RuntimeException("Cannot stop LDAP server.", e); + } + } + + public int getLdapNonTlsPort() { + return server.getLdapNonTlsPort(); + } + + public int getLdapTlsPort() { + return server.getLdapsTlsPort(); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/ldap/LdapServer.java b/src/integrationTest/java/org/opensearch/test/framework/ldap/LdapServer.java index 13add742e4..18a14242cc 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/ldap/LdapServer.java +++ b/src/integrationTest/java/org/opensearch/test/framework/ldap/LdapServer.java @@ -52,173 +52,175 @@ * Based on class com.amazon.dlic.auth.ldap.srv.LdapServer from older tests */ final class LdapServer { - private static final Logger log = LogManager.getLogger(LdapServer.class); - - private static final int LOCK_TIMEOUT = 60; - private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS; - - private static final String LOCK_TIMEOUT_MSG = "Unable to obtain lock due to timeout after " + LOCK_TIMEOUT + " " + TIME_UNIT.toString(); - private static final String SERVER_NOT_STARTED = "The LDAP server is not started."; - private static final String SERVER_ALREADY_STARTED = "The LDAP server is already started."; - - private final CertificateData trustAnchor; - - private final CertificateData ldapCertificate; - - private InMemoryDirectoryServer server; - private final AtomicBoolean isStarted = new AtomicBoolean(Boolean.FALSE); - private final ReentrantLock serverStateLock = new ReentrantLock(); - - private int ldapNonTlsPort = -1; - private int ldapTlsPort = -1; - - - public LdapServer(CertificateData trustAnchor, CertificateData ldapCertificate) { - this.trustAnchor = trustAnchor; - this.ldapCertificate = ldapCertificate; - } - - public boolean isStarted() { - return this.isStarted.get(); - } - - public int getLdapNonTlsPort() { - return ldapNonTlsPort; - } - - public int getLdapsTlsPort() { - return ldapTlsPort; - } - - public void start(LdifData ldifData) throws Exception { - Objects.requireNonNull(ldifData, "Ldif data is required"); - boolean hasLock = false; - try { - hasLock = serverStateLock.tryLock(LdapServer.LOCK_TIMEOUT, LdapServer.TIME_UNIT); - if (hasLock) { - doStart(ldifData); - this.isStarted.set(Boolean.TRUE); - } else { - throw new IllegalStateException(LdapServer.LOCK_TIMEOUT_MSG); - } - } catch (InterruptedException ioe) { - //lock interrupted - log.error("LDAP server start lock interrupted", ioe); - throw ioe; - } finally { - if (hasLock) { - serverStateLock.unlock(); - } - } - } - - private void doStart(LdifData ldifData) throws Exception { - if (isStarted.get()) { - throw new IllegalStateException(LdapServer.SERVER_ALREADY_STARTED); - } - configureAndStartServer(ldifData); - } - - private Collection getInMemoryListenerConfigs() throws Exception { - KeyStore keyStore = createEmptyKeyStore(); - addLdapCertificatesToKeystore(keyStore); - final SSLUtil sslUtil = new SSLUtil(createKeyManager(keyStore), createTrustManagers(keyStore)); - - ldapNonTlsPort = SocketUtils.findAvailableTcpPort(); - ldapTlsPort = SocketUtils.findAvailableTcpPort(); - - Collection listenerConfigs = new ArrayList<>(); - listenerConfigs.add(InMemoryListenerConfig.createLDAPConfig("ldap", null, ldapNonTlsPort, sslUtil.createSSLSocketFactory())); - listenerConfigs.add(InMemoryListenerConfig.createLDAPSConfig("ldaps", ldapTlsPort, sslUtil.createSSLServerSocketFactory())); - return listenerConfigs; - } - - private static KeyManager[] createKeyManager(KeyStore keyStore) - throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, null); - return keyManagerFactory.getKeyManagers(); - } - - private static TrustManager[] createTrustManagers(KeyStore keyStore) throws NoSuchAlgorithmException, KeyStoreException { - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(keyStore); - return trustManagerFactory.getTrustManagers(); - } - - private void addLdapCertificatesToKeystore(KeyStore keyStore) throws KeyStoreException { - keyStore.setCertificateEntry("trustAnchor", trustAnchor.certificate()); - keyStore.setKeyEntry("ldap-key", ldapCertificate.getKey(), null, new Certificate[]{ ldapCertificate.certificate()}); - } - - private static KeyStore createEmptyKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(null); - return keyStore; - } - - private synchronized void configureAndStartServer(LdifData ldifData) throws Exception { - Collection listenerConfigs = getInMemoryListenerConfigs(); - - Schema schema = Schema.getDefaultStandardSchema(); - - final String rootObjectDN = ldifData.getRootDistinguishedName(); - InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(new DN(rootObjectDN)); - - config.setSchema(schema); //schema can be set on the rootDN too, per javadoc. - config.setListenerConfigs(listenerConfigs); - config.setEnforceAttributeSyntaxCompliance(false); - config.setEnforceSingleStructuralObjectClass(false); - - server = new InMemoryDirectoryServer(config); - - try { - /* Clear entries from server. */ - server.clear(); - server.startListening(); - loadLdifData(ldifData); - } catch (LDAPException ldape) { - if (ldape.getMessage().contains("java.net.BindException")) { - throw new BindException(ldape.getMessage()); - } - throw ldape; - } - - } - - public void stop() throws InterruptedException { - boolean hasLock = false; - try { - hasLock = serverStateLock.tryLock(LdapServer.LOCK_TIMEOUT, LdapServer.TIME_UNIT); - if (hasLock) { - if (!isStarted.get()) { - throw new IllegalStateException(LdapServer.SERVER_NOT_STARTED); - } - log.info("Shutting down in-Memory Ldap Server."); - server.shutDown(true); - } else { - throw new IllegalStateException(LdapServer.LOCK_TIMEOUT_MSG); - } - } catch (InterruptedException ioe) { - //lock interrupted - log.error("Canot stop LDAP server due to interruption", ioe); - throw ioe; - } finally { - if (hasLock) { - serverStateLock.unlock(); - } - } - } - - private void loadLdifData(LdifData ldifData) throws Exception { - try (LDIFReader r = new LDIFReader(new BufferedReader(new StringReader(ldifData.getContent())))){ - Entry entry; - while ((entry = r.readEntry()) != null) { - server.add(entry); - } - } catch(Exception e) { - log.error("Cannot load data into LDAP server", e); - throw e; - } - } + private static final Logger log = LogManager.getLogger(LdapServer.class); + + private static final int LOCK_TIMEOUT = 60; + private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS; + + private static final String LOCK_TIMEOUT_MSG = "Unable to obtain lock due to timeout after " + + LOCK_TIMEOUT + + " " + + TIME_UNIT.toString(); + private static final String SERVER_NOT_STARTED = "The LDAP server is not started."; + private static final String SERVER_ALREADY_STARTED = "The LDAP server is already started."; + + private final CertificateData trustAnchor; + + private final CertificateData ldapCertificate; + + private InMemoryDirectoryServer server; + private final AtomicBoolean isStarted = new AtomicBoolean(Boolean.FALSE); + private final ReentrantLock serverStateLock = new ReentrantLock(); + + private int ldapNonTlsPort = -1; + private int ldapTlsPort = -1; + + public LdapServer(CertificateData trustAnchor, CertificateData ldapCertificate) { + this.trustAnchor = trustAnchor; + this.ldapCertificate = ldapCertificate; + } + + public boolean isStarted() { + return this.isStarted.get(); + } + + public int getLdapNonTlsPort() { + return ldapNonTlsPort; + } + + public int getLdapsTlsPort() { + return ldapTlsPort; + } + + public void start(LdifData ldifData) throws Exception { + Objects.requireNonNull(ldifData, "Ldif data is required"); + boolean hasLock = false; + try { + hasLock = serverStateLock.tryLock(LdapServer.LOCK_TIMEOUT, LdapServer.TIME_UNIT); + if (hasLock) { + doStart(ldifData); + this.isStarted.set(Boolean.TRUE); + } else { + throw new IllegalStateException(LdapServer.LOCK_TIMEOUT_MSG); + } + } catch (InterruptedException ioe) { + // lock interrupted + log.error("LDAP server start lock interrupted", ioe); + throw ioe; + } finally { + if (hasLock) { + serverStateLock.unlock(); + } + } + } + + private void doStart(LdifData ldifData) throws Exception { + if (isStarted.get()) { + throw new IllegalStateException(LdapServer.SERVER_ALREADY_STARTED); + } + configureAndStartServer(ldifData); + } + + private Collection getInMemoryListenerConfigs() throws Exception { + KeyStore keyStore = createEmptyKeyStore(); + addLdapCertificatesToKeystore(keyStore); + final SSLUtil sslUtil = new SSLUtil(createKeyManager(keyStore), createTrustManagers(keyStore)); + + ldapNonTlsPort = SocketUtils.findAvailableTcpPort(); + ldapTlsPort = SocketUtils.findAvailableTcpPort(); + + Collection listenerConfigs = new ArrayList<>(); + listenerConfigs.add(InMemoryListenerConfig.createLDAPConfig("ldap", null, ldapNonTlsPort, sslUtil.createSSLSocketFactory())); + listenerConfigs.add(InMemoryListenerConfig.createLDAPSConfig("ldaps", ldapTlsPort, sslUtil.createSSLServerSocketFactory())); + return listenerConfigs; + } + + private static KeyManager[] createKeyManager(KeyStore keyStore) throws NoSuchAlgorithmException, KeyStoreException, + UnrecoverableKeyException { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, null); + return keyManagerFactory.getKeyManagers(); + } + + private static TrustManager[] createTrustManagers(KeyStore keyStore) throws NoSuchAlgorithmException, KeyStoreException { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + return trustManagerFactory.getTrustManagers(); + } + + private void addLdapCertificatesToKeystore(KeyStore keyStore) throws KeyStoreException { + keyStore.setCertificateEntry("trustAnchor", trustAnchor.certificate()); + keyStore.setKeyEntry("ldap-key", ldapCertificate.getKey(), null, new Certificate[] { ldapCertificate.certificate() }); + } + + private static KeyStore createEmptyKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null); + return keyStore; + } + + private synchronized void configureAndStartServer(LdifData ldifData) throws Exception { + Collection listenerConfigs = getInMemoryListenerConfigs(); + + Schema schema = Schema.getDefaultStandardSchema(); + + final String rootObjectDN = ldifData.getRootDistinguishedName(); + InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(new DN(rootObjectDN)); + + config.setSchema(schema); // schema can be set on the rootDN too, per javadoc. + config.setListenerConfigs(listenerConfigs); + config.setEnforceAttributeSyntaxCompliance(false); + config.setEnforceSingleStructuralObjectClass(false); + + server = new InMemoryDirectoryServer(config); + + try { + /* Clear entries from server. */ + server.clear(); + server.startListening(); + loadLdifData(ldifData); + } catch (LDAPException ldape) { + if (ldape.getMessage().contains("java.net.BindException")) { + throw new BindException(ldape.getMessage()); + } + throw ldape; + } + + } + + public void stop() throws InterruptedException { + boolean hasLock = false; + try { + hasLock = serverStateLock.tryLock(LdapServer.LOCK_TIMEOUT, LdapServer.TIME_UNIT); + if (hasLock) { + if (!isStarted.get()) { + throw new IllegalStateException(LdapServer.SERVER_NOT_STARTED); + } + log.info("Shutting down in-Memory Ldap Server."); + server.shutDown(true); + } else { + throw new IllegalStateException(LdapServer.LOCK_TIMEOUT_MSG); + } + } catch (InterruptedException ioe) { + // lock interrupted + log.error("Canot stop LDAP server due to interruption", ioe); + throw ioe; + } finally { + if (hasLock) { + serverStateLock.unlock(); + } + } + } + + private void loadLdifData(LdifData ldifData) throws Exception { + try (LDIFReader r = new LDIFReader(new BufferedReader(new StringReader(ldifData.getContent())))) { + Entry entry; + while ((entry = r.readEntry()) != null) { + server.add(entry); + } + } catch (Exception e) { + log.error("Cannot load data into LDAP server", e); + throw e; + } + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifBuilder.java b/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifBuilder.java index cea53a92f3..87f01a2bbc 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifBuilder.java +++ b/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifBuilder.java @@ -19,50 +19,48 @@ public class LdifBuilder { - private static final Logger log = LogManager.getLogger(LdifBuilder.class); + private static final Logger log = LogManager.getLogger(LdifBuilder.class); - private final List records; + private final List records; - private Record root; + private Record root; - public LdifBuilder() { - this.records = new ArrayList<>(); - } + public LdifBuilder() { + this.records = new ArrayList<>(); + } - public RecordBuilder root(String distinguishedName) { - if(root != null) { - throw new IllegalStateException("Root object is already defined"); - } - return new RecordBuilder(this, distinguishedName); - } + public RecordBuilder root(String distinguishedName) { + if (root != null) { + throw new IllegalStateException("Root object is already defined"); + } + return new RecordBuilder(this, distinguishedName); + } - RecordBuilder newRecord(String distinguishedName) { - if(root == null) { - throw new IllegalStateException("Define root object first"); - } - return new RecordBuilder(this, distinguishedName); - } + RecordBuilder newRecord(String distinguishedName) { + if (root == null) { + throw new IllegalStateException("Define root object first"); + } + return new RecordBuilder(this, distinguishedName); + } - void addRecord(Record record) { - Objects.requireNonNull(record, "Cannot add null record"); - if(records.isEmpty()) { - this.root = record; - } - records.add(Objects.requireNonNull(record, "Cannot add null record")); - } + void addRecord(Record record) { + Objects.requireNonNull(record, "Cannot add null record"); + if (records.isEmpty()) { + this.root = record; + } + records.add(Objects.requireNonNull(record, "Cannot add null record")); + } - public LdifData buildLdif() { - String ldif = records.stream() - .map(record -> record.toLdifRepresentation()) - .collect(Collectors.joining("\n##########\n")); - log.debug("Built ldif file: \n{}", ldif); - return new LdifData(getRootDistinguishedName(), ldif); - } + public LdifData buildLdif() { + String ldif = records.stream().map(record -> record.toLdifRepresentation()).collect(Collectors.joining("\n##########\n")); + log.debug("Built ldif file: \n{}", ldif); + return new LdifData(getRootDistinguishedName(), ldif); + } - private String getRootDistinguishedName() { - if(root == null) { - throw new IllegalStateException("Root object is not present."); - } - return root.getDistinguishedName(); - } + private String getRootDistinguishedName() { + if (root == null) { + throw new IllegalStateException("Root object is not present."); + } + return root.getDistinguishedName(); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifData.java b/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifData.java index a0f2026b73..4a1af5bc08 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifData.java +++ b/src/integrationTest/java/org/opensearch/test/framework/ldap/LdifData.java @@ -11,39 +11,38 @@ import org.apache.commons.lang3.StringUtils; - /** * Value object which represents LDIF file data and some metadata. Ensure type safety. */ public class LdifData { - - private final String rootDistinguishedName; - private final String content; + private final String rootDistinguishedName; + + private final String content; - LdifData(String rootDistinguishedName, String content) { - this.rootDistinguishedName = requireNotBlank(rootDistinguishedName, "Root distinguished name is required"); - this.content = requireNotBlank(content, "Ldif file content is required"); + LdifData(String rootDistinguishedName, String content) { + this.rootDistinguishedName = requireNotBlank(rootDistinguishedName, "Root distinguished name is required"); + this.content = requireNotBlank(content, "Ldif file content is required"); - } + } - private static String requireNotBlank(String string, String message) { - if(StringUtils.isBlank(string)) { - throw new IllegalArgumentException(message); - } - return string; - } + private static String requireNotBlank(String string, String message) { + if (StringUtils.isBlank(string)) { + throw new IllegalArgumentException(message); + } + return string; + } - String getContent() { - return content; - } + String getContent() { + return content; + } - String getRootDistinguishedName() { - return rootDistinguishedName; - } + String getRootDistinguishedName() { + return rootDistinguishedName; + } - @Override - public String toString() { - return "LdifData{" + "content='" + content + '\'' + '}'; - } + @Override + public String toString() { + return "LdifData{" + "content='" + content + '\'' + '}'; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/ldap/Record.java b/src/integrationTest/java/org/opensearch/test/framework/ldap/Record.java index 76eea1771d..48e7484777 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/ldap/Record.java +++ b/src/integrationTest/java/org/opensearch/test/framework/ldap/Record.java @@ -18,51 +18,50 @@ class Record { - private final String distinguishedName; + private final String distinguishedName; - private final List classes; - private final List> attributes; + private final List classes; + private final List> attributes; - public Record(String distinguishedName) { - this.distinguishedName = Objects.requireNonNull(distinguishedName, "Distinguished name is required"); - this.classes = new ArrayList<>(); - this.attributes = new ArrayList<>(); - } + public Record(String distinguishedName) { + this.distinguishedName = Objects.requireNonNull(distinguishedName, "Distinguished name is required"); + this.classes = new ArrayList<>(); + this.attributes = new ArrayList<>(); + } - public String getDistinguishedName() { - return distinguishedName; - } + public String getDistinguishedName() { + return distinguishedName; + } - public void addClass(String clazz) { - classes.add(Objects.requireNonNull(clazz, "Object class is required.")); - } + public void addClass(String clazz) { + classes.add(Objects.requireNonNull(clazz, "Object class is required.")); + } - public void addAttribute(String name, String value) { - Objects.requireNonNull(name, "Attribute name is required"); - Objects.requireNonNull(value, "Attribute value is required"); - attributes.add(Pair.of(name, value)); - } + public void addAttribute(String name, String value) { + Objects.requireNonNull(name, "Attribute name is required"); + Objects.requireNonNull(value, "Attribute value is required"); + attributes.add(Pair.of(name, value)); + } - boolean isValid() { - return classes.size() > 0; - } + boolean isValid() { + return classes.size() > 0; + } - String toLdifRepresentation() { - return new StringBuilder("dn: ").append(distinguishedName).append("\n") - .append(formattedClasses()).append("\n") - .append(formattedAttributes()).append("\n") - .toString(); - } + String toLdifRepresentation() { + return new StringBuilder("dn: ").append(distinguishedName) + .append("\n") + .append(formattedClasses()) + .append("\n") + .append(formattedAttributes()) + .append("\n") + .toString(); + } - private String formattedAttributes() { - return attributes.stream() - .map(pair -> pair.getKey() + ": " + pair.getValue()) - .collect(Collectors.joining("\n")); - } + private String formattedAttributes() { + return attributes.stream().map(pair -> pair.getKey() + ": " + pair.getValue()).collect(Collectors.joining("\n")); + } - private String formattedClasses() { - return classes.stream() - .map(clazz -> "objectClass: " + clazz) - .collect(Collectors.joining("\n")); - } + private String formattedClasses() { + return classes.stream().map(clazz -> "objectClass: " + clazz).collect(Collectors.joining("\n")); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/ldap/RecordBuilder.java b/src/integrationTest/java/org/opensearch/test/framework/ldap/RecordBuilder.java index a54caef2af..1df27c72fe 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/ldap/RecordBuilder.java +++ b/src/integrationTest/java/org/opensearch/test/framework/ldap/RecordBuilder.java @@ -13,80 +13,80 @@ public class RecordBuilder { - private final LdifBuilder builder; - private final Record record; - - RecordBuilder(LdifBuilder builder, String distinguishedName) { - this.builder = Objects.requireNonNull(builder, "LdifBuilder is required"); - this.record = new Record(distinguishedName); - } - - public RecordBuilder classes(String...classes) { - for(String clazz : classes) { - this.record.addClass(clazz); - } - return this; - } - - public RecordBuilder dn(String distinguishedName) { - record.addAttribute("dn", distinguishedName); - return this; - } - - public RecordBuilder dc(String domainComponent) { - record.addAttribute("dc", domainComponent); - return this; - } - - public RecordBuilder ou(String organizationUnit) { - record.addAttribute("ou", organizationUnit); - return this; - } - - public RecordBuilder cn(String commonName) { - record.addAttribute("cn", commonName); - return this; - } - - public RecordBuilder sn(String surname) { - record.addAttribute("sn", surname); - return this; - } - - public RecordBuilder uid(String userId) { - record.addAttribute("uid", userId); - return this; - } - - public RecordBuilder userPassword(String password) { - record.addAttribute("userpassword", password); - return this; - } - - public RecordBuilder mail(String emailAddress) { - record.addAttribute("mail", emailAddress); - return this; - } - - public RecordBuilder uniqueMember(String userDistinguishedName) { - record.addAttribute("uniquemember", userDistinguishedName); - return this; - } - - public RecordBuilder attribute(String name, String value) { - record.addAttribute(name, value); - return this; - } - - public LdifBuilder buildRecord() { - if(record.isValid() == false) { - throw new IllegalStateException("Record is invalid"); - } - builder.addRecord(record); - return builder; - } - - public RecordBuilder newRecord(String distinguishedName) { - return buildRecord().newRecord(distinguishedName); - } + private final LdifBuilder builder; + private final Record record; + + RecordBuilder(LdifBuilder builder, String distinguishedName) { + this.builder = Objects.requireNonNull(builder, "LdifBuilder is required"); + this.record = new Record(distinguishedName); + } + + public RecordBuilder classes(String... classes) { + for (String clazz : classes) { + this.record.addClass(clazz); + } + return this; + } + + public RecordBuilder dn(String distinguishedName) { + record.addAttribute("dn", distinguishedName); + return this; + } + + public RecordBuilder dc(String domainComponent) { + record.addAttribute("dc", domainComponent); + return this; + } + + public RecordBuilder ou(String organizationUnit) { + record.addAttribute("ou", organizationUnit); + return this; + } + + public RecordBuilder cn(String commonName) { + record.addAttribute("cn", commonName); + return this; + } + + public RecordBuilder sn(String surname) { + record.addAttribute("sn", surname); + return this; + } + + public RecordBuilder uid(String userId) { + record.addAttribute("uid", userId); + return this; + } + + public RecordBuilder userPassword(String password) { + record.addAttribute("userpassword", password); + return this; + } + + public RecordBuilder mail(String emailAddress) { + record.addAttribute("mail", emailAddress); + return this; + } + + public RecordBuilder uniqueMember(String userDistinguishedName) { + record.addAttribute("uniquemember", userDistinguishedName); + return this; + } + + public RecordBuilder attribute(String name, String value) { + record.addAttribute(name, value); + return this; + } + + public LdifBuilder buildRecord() { + if (record.isValid() == false) { + throw new IllegalStateException("Record is invalid"); + } + builder.addRecord(record); + return builder; + } + + public RecordBuilder newRecord(String distinguishedName) { + return buildRecord().newRecord(distinguishedName); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/log/LogCapturingAppender.java b/src/integrationTest/java/org/opensearch/test/framework/log/LogCapturingAppender.java index e0baec9cb2..63765dfd14 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/log/LogCapturingAppender.java +++ b/src/integrationTest/java/org/opensearch/test/framework/log/LogCapturingAppender.java @@ -47,84 +47,89 @@ @Plugin(name = PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) public class LogCapturingAppender extends AbstractAppender { - public final static String PLUGIN_NAME = "LogCapturingAppender"; - /** - * Appender stores only last MAX_SIZE messages to avoid excessive RAM memory usage. - */ - public static final int MAX_SIZE = 100; + public final static String PLUGIN_NAME = "LogCapturingAppender"; + /** + * Appender stores only last MAX_SIZE messages to avoid excessive RAM memory usage. + */ + public static final int MAX_SIZE = 100; - /** - * Buffer for captured log messages - */ - private static final Buffer messages = BufferUtils.synchronizedBuffer(new CircularFifoBuffer(MAX_SIZE)); + /** + * Buffer for captured log messages + */ + private static final Buffer messages = BufferUtils.synchronizedBuffer(new CircularFifoBuffer(MAX_SIZE)); - /** - * Log messages are stored in buffer {@link #messages} only for classes which are added to the {@link #activeLoggers} set. - */ - private static final Set activeLoggers = Collections.synchronizedSet(new HashSet<>()); + /** + * Log messages are stored in buffer {@link #messages} only for classes which are added to the {@link #activeLoggers} set. + */ + private static final Set activeLoggers = Collections.synchronizedSet(new HashSet<>()); - protected LogCapturingAppender(String name, Filter filter, Layout layout, boolean ignoreExceptions, Property[] properties) { - super(name, filter, layout, ignoreExceptions, properties); - } + protected LogCapturingAppender( + String name, + Filter filter, + Layout layout, + boolean ignoreExceptions, + Property[] properties + ) { + super(name, filter, layout, ignoreExceptions, properties); + } - /** - * Method used by Log4j2 to create appender - * @param name appender name from Log4j2 configuration - * @return newly created appender - */ - @PluginFactory - public static LogCapturingAppender createAppender(@PluginAttribute(value = "name", defaultString = "logCapturingAppender") String name) { - return new LogCapturingAppender(name, null, null, true, Property.EMPTY_ARRAY); - } + /** + * Method used by Log4j2 to create appender + * @param name appender name from Log4j2 configuration + * @return newly created appender + */ + @PluginFactory + public static LogCapturingAppender createAppender( + @PluginAttribute(value = "name", defaultString = "logCapturingAppender") String name + ) { + return new LogCapturingAppender(name, null, null, true, Property.EMPTY_ARRAY); + } - /** - * Method invoked by Log4j2 to append log events - * @param event The LogEvent, represents log message. - */ - @Override - public void append(LogEvent event) { - String loggerName = event.getLoggerName(); - boolean loggable = activeLoggers.contains(loggerName); - if(loggable) { - event.getThrown(); - messages.add(new LogMessage(event.getMessage().getFormattedMessage(), event.getThrown())); - } - } + /** + * Method invoked by Log4j2 to append log events + * @param event The LogEvent, represents log message. + */ + @Override + public void append(LogEvent event) { + String loggerName = event.getLoggerName(); + boolean loggable = activeLoggers.contains(loggerName); + if (loggable) { + event.getThrown(); + messages.add(new LogMessage(event.getMessage().getFormattedMessage(), event.getThrown())); + } + } - /** - * To collect log messages form given logger the logger name must be passed to {@link #enable(String...)} method. - * @param loggerNames logger names - */ - public static void enable(String...loggerNames) { - disable(); - activeLoggers.addAll(Arrays.asList(loggerNames)); - } + /** + * To collect log messages form given logger the logger name must be passed to {@link #enable(String...)} method. + * @param loggerNames logger names + */ + public static void enable(String... loggerNames) { + disable(); + activeLoggers.addAll(Arrays.asList(loggerNames)); + } - /** - * Invocation cause that appender stops collecting log messages. Additionally, memory used by collected messages so far is released. - */ - public static void disable() { - activeLoggers.clear(); - messages.clear(); - } + /** + * Invocation cause that appender stops collecting log messages. Additionally, memory used by collected messages so far is released. + */ + public static void disable() { + activeLoggers.clear(); + messages.clear(); + } - /** - * Is used to obtain gathered log messages - * @return Log messages - */ - public static List getLogMessages() { - return new ArrayList<>(messages); - } + /** + * Is used to obtain gathered log messages + * @return Log messages + */ + public static List getLogMessages() { + return new ArrayList<>(messages); + } - public static List getLogMessagesAsString() { - return getLogMessages() - .stream() - .map(LogMessage::getMessage) - .collect(Collectors.toList()); - } + public static List getLogMessagesAsString() { + return getLogMessages().stream().map(LogMessage::getMessage).collect(Collectors.toList()); + } - @Override - public String toString() { - return "LogCapturingAppender{}"; - } + @Override + public String toString() { + return "LogCapturingAppender{}"; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/log/LogMessage.java b/src/integrationTest/java/org/opensearch/test/framework/log/LogMessage.java index 327e91c759..9342c7ee30 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/log/LogMessage.java +++ b/src/integrationTest/java/org/opensearch/test/framework/log/LogMessage.java @@ -16,25 +16,25 @@ class LogMessage { - private final String message; - private final String stackTrace; - - public LogMessage(String message, Throwable throwable) { - this.message = message; - this.stackTrace = Optional.ofNullable(throwable).map(ExceptionUtils::getStackTrace).orElse(""); - } - - public boolean containMessage(String expectedMessage) { - Objects.requireNonNull(expectedMessage, "Expected message must not be null."); - return expectedMessage.equals(message); - } - - public boolean stackTraceContains(String stackTraceFragment) { - Objects.requireNonNull(stackTraceFragment, "Stack trace fragment is required."); - return stackTrace.contains(stackTraceFragment); - } - - public String getMessage() { - return message; - } + private final String message; + private final String stackTrace; + + public LogMessage(String message, Throwable throwable) { + this.message = message; + this.stackTrace = Optional.ofNullable(throwable).map(ExceptionUtils::getStackTrace).orElse(""); + } + + public boolean containMessage(String expectedMessage) { + Objects.requireNonNull(expectedMessage, "Expected message must not be null."); + return expectedMessage.equals(message); + } + + public boolean stackTraceContains(String stackTraceFragment) { + Objects.requireNonNull(stackTraceFragment, "Stack trace fragment is required."); + return stackTrace.contains(stackTraceFragment); + } + + public String getMessage() { + return message; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java b/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java index c2dfabf537..46fa252df4 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java +++ b/src/integrationTest/java/org/opensearch/test/framework/log/LogsRule.java @@ -27,66 +27,67 @@ */ public class LogsRule extends ExternalResource { - private final String[] loggerNames; + private final String[] loggerNames; - /** - * Constructor used to start gathering log messages from certain loggers - * @param loggerNames Loggers names. Log messages are collected only if the log message is associated with the logger with a name which - * is present in loggerNames parameter. - */ - public LogsRule(String...loggerNames) { - this.loggerNames = Objects.requireNonNull(loggerNames, "Logger names are required"); - } + /** + * Constructor used to start gathering log messages from certain loggers + * @param loggerNames Loggers names. Log messages are collected only if the log message is associated with the logger with a name which + * is present in loggerNames parameter. + */ + public LogsRule(String... loggerNames) { + this.loggerNames = Objects.requireNonNull(loggerNames, "Logger names are required"); + } - @Override - protected void before() { - LogCapturingAppender.enable(loggerNames); - } + @Override + protected void before() { + LogCapturingAppender.enable(loggerNames); + } - @Override - protected void after() { - LogCapturingAppender.disable(); - } + @Override + protected void after() { + LogCapturingAppender.disable(); + } - /** - * Check if during the tests certain log message was logged - * @param expectedLogMessage expected log message - */ - public void assertThatContainExactly(String expectedLogMessage) { - List messages = LogCapturingAppender.getLogMessagesAsString(); - String reason = reasonMessage(expectedLogMessage, messages); - assertThat(reason, messages, hasItem(expectedLogMessage)); - } + /** + * Check if during the tests certain log message was logged + * @param expectedLogMessage expected log message + */ + public void assertThatContainExactly(String expectedLogMessage) { + List messages = LogCapturingAppender.getLogMessagesAsString(); + String reason = reasonMessage(expectedLogMessage, messages); + assertThat(reason, messages, hasItem(expectedLogMessage)); + } - /** - * Check if during the tests certain log message was logged - * @param messageFragment expected log message fragment - */ - public void assertThatContain(String messageFragment) { - List messages = LogCapturingAppender.getLogMessagesAsString();; - String reason = reasonMessage(messageFragment, messages); - assertThat(reason, messages, hasItem(containsString(messageFragment))); - } + /** + * Check if during the tests certain log message was logged + * @param messageFragment expected log message fragment + */ + public void assertThatContain(String messageFragment) { + List messages = LogCapturingAppender.getLogMessagesAsString(); + ; + String reason = reasonMessage(messageFragment, messages); + assertThat(reason, messages, hasItem(containsString(messageFragment))); + } - /** - * Check if during the tests a stack trace was logged which contain given fragment - * @param stackTraceFragment stack trace fragment - */ - public void assertThatStackTraceContain(String stackTraceFragment) { - long count = LogCapturingAppender.getLogMessages() - .stream() - .filter(logMessage -> logMessage.stackTraceContains(stackTraceFragment)) - .count(); - String reason = "Stack trace does not contain element " + stackTraceFragment; - assertThat(reason, count, greaterThan(0L)); - } + /** + * Check if during the tests a stack trace was logged which contain given fragment + * @param stackTraceFragment stack trace fragment + */ + public void assertThatStackTraceContain(String stackTraceFragment) { + long count = LogCapturingAppender.getLogMessages() + .stream() + .filter(logMessage -> logMessage.stackTraceContains(stackTraceFragment)) + .count(); + String reason = "Stack trace does not contain element " + stackTraceFragment; + assertThat(reason, count, greaterThan(0L)); + } - private static String reasonMessage(String expectedLogMessage, List messages) { - String concatenatedLogMessages = messages.stream() - .map(message -> String.format("'%s'", message)) - .collect(Collectors.joining(", ")); - return String.format("Expected message '%s' has not been found in logs. All captured log messages: %s", - expectedLogMessage, - concatenatedLogMessages); - } + private static String reasonMessage(String expectedLogMessage, List messages) { + String concatenatedLogMessages = messages.stream().map(message -> String.format("'%s'", message)).collect(Collectors.joining(", ")); + return String.format( + "Expected message '%s' has not been found in logs. All captured log messages: %s", + expectedLogMessage, + concatenatedLogMessages + ); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java index 4d8c81b037..42723168ff 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/AliasExistsMatcher.java @@ -31,37 +31,36 @@ class AliasExistsMatcher extends TypeSafeDiagnosingMatcher { - private final String aliasName; + private final String aliasName; - public AliasExistsMatcher(String aliasName) { - this.aliasName = requireNonNull(aliasName, "Alias name is required"); - } + public AliasExistsMatcher(String aliasName) { + this.aliasName = requireNonNull(aliasName, "Alias name is required"); + } - @Override - protected boolean matchesSafely(Client client, Description mismatchDescription) { - try { - GetAliasesResponse response = client.admin().indices().getAliases(new GetAliasesRequest(aliasName)).get(); + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + try { + GetAliasesResponse response = client.admin().indices().getAliases(new GetAliasesRequest(aliasName)).get(); - Map> aliases = response.getAliases(); - Set actualAliasNames = StreamSupport.stream(spliteratorUnknownSize(aliases.values().iterator(), IMMUTABLE), false) - .flatMap(Collection::stream) - .map(AliasMetadata::getAlias) - .collect(Collectors.toSet()); - if(actualAliasNames.contains(aliasName) == false) { - String existingAliases = String.join(", ", actualAliasNames); - mismatchDescription.appendText(" alias does not exist, defined aliases ").appendValue(existingAliases); - return false; - } - return true; - } catch (InterruptedException | ExecutionException e) { - mismatchDescription.appendText("Error occurred during checking if cluster contains alias ") - .appendValue(e); - return false; - } - } + Map> aliases = response.getAliases(); + Set actualAliasNames = StreamSupport.stream(spliteratorUnknownSize(aliases.values().iterator(), IMMUTABLE), false) + .flatMap(Collection::stream) + .map(AliasMetadata::getAlias) + .collect(Collectors.toSet()); + if (actualAliasNames.contains(aliasName) == false) { + String existingAliases = String.join(", ", actualAliasNames); + mismatchDescription.appendText(" alias does not exist, defined aliases ").appendValue(existingAliases); + return false; + } + return true; + } catch (InterruptedException | ExecutionException e) { + mismatchDescription.appendText("Error occurred during checking if cluster contains alias ").appendValue(e); + return false; + } + } - @Override - public void describeTo(Description description) { - description.appendText("Cluster should contain ").appendValue(aliasName).appendText(" alias"); - } + @Override + public void describeTo(Description description) { + description.appendText("Cluster should contain ").appendValue(aliasName).appendText(" alias"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/AtLeastCertainNumberOfAuditsFulfillPredicateMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/AtLeastCertainNumberOfAuditsFulfillPredicateMatcher.java index 922917bc21..ba7feed4c3 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/AtLeastCertainNumberOfAuditsFulfillPredicateMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/AtLeastCertainNumberOfAuditsFulfillPredicateMatcher.java @@ -18,27 +18,31 @@ class AtLeastCertainNumberOfAuditsFulfillPredicateMatcher extends AuditsFulfillPredicateMatcher { - private final long minimumNumberOfAudits; - - public AtLeastCertainNumberOfAuditsFulfillPredicateMatcher(Predicate predicate, long minimumNumberOfAudits) { - super(predicate); - this.minimumNumberOfAudits = minimumNumberOfAudits; - } - - @Override - protected boolean matchesSafely(List audits, Description mismatchDescription) { - long count = countAuditsWhichMatchPredicate(audits); - if(count < minimumNumberOfAudits) { - mismatchDescription.appendText(" only ").appendValue(count).appendText(" match predicate. Examined audit logs ") - .appendText(auditMessagesToString(audits)); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Al least ").appendValue(minimumNumberOfAudits).appendText(" audits records should match predicate ") - .appendValue(predicate); - } + private final long minimumNumberOfAudits; + + public AtLeastCertainNumberOfAuditsFulfillPredicateMatcher(Predicate predicate, long minimumNumberOfAudits) { + super(predicate); + this.minimumNumberOfAudits = minimumNumberOfAudits; + } + + @Override + protected boolean matchesSafely(List audits, Description mismatchDescription) { + long count = countAuditsWhichMatchPredicate(audits); + if (count < minimumNumberOfAudits) { + mismatchDescription.appendText(" only ") + .appendValue(count) + .appendText(" match predicate. Examined audit logs ") + .appendText(auditMessagesToString(audits)); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Al least ") + .appendValue(minimumNumberOfAudits) + .appendText(" audits records should match predicate ") + .appendValue(predicate); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditMessageMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditMessageMatchers.java index f15750259f..080d4473e1 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditMessageMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditMessageMatchers.java @@ -18,15 +18,21 @@ public class AuditMessageMatchers { - private AuditMessageMatchers() { - - } - - public static Matcher> exactNumberOfAuditsFulfillPredicate(long exactNumberOfAuditMessages, Predicate predicate) { - return new ExactNumberOfAuditsFulfillPredicateMatcher(exactNumberOfAuditMessages, predicate); - } - - public static Matcher> atLeastCertainNumberOfAuditsFulfillPredicate(long minimumNumberOfAudits, Predicate predicate) { - return new AtLeastCertainNumberOfAuditsFulfillPredicateMatcher(predicate, minimumNumberOfAudits); - } + private AuditMessageMatchers() { + + } + + public static Matcher> exactNumberOfAuditsFulfillPredicate( + long exactNumberOfAuditMessages, + Predicate predicate + ) { + return new ExactNumberOfAuditsFulfillPredicateMatcher(exactNumberOfAuditMessages, predicate); + } + + public static Matcher> atLeastCertainNumberOfAuditsFulfillPredicate( + long minimumNumberOfAudits, + Predicate predicate + ) { + return new AtLeastCertainNumberOfAuditsFulfillPredicateMatcher(predicate, minimumNumberOfAudits); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditsFulfillPredicateMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditsFulfillPredicateMatcher.java index e73267b452..2864c1df81 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditsFulfillPredicateMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/AuditsFulfillPredicateMatcher.java @@ -19,18 +19,18 @@ abstract class AuditsFulfillPredicateMatcher extends TypeSafeDiagnosingMatcher> { - protected final Predicate predicate; + protected final Predicate predicate; - public AuditsFulfillPredicateMatcher(Predicate predicate) { - this.predicate = predicate; - } + public AuditsFulfillPredicateMatcher(Predicate predicate) { + this.predicate = predicate; + } - protected String auditMessagesToString(List audits) { - return audits.stream().map(AuditMessage::toString).collect(Collectors.joining(",\n")); - } + protected String auditMessagesToString(List audits) { + return audits.stream().map(AuditMessage::toString).collect(Collectors.joining(",\n")); + } - protected long countAuditsWhichMatchPredicate(List audits) { - return audits.stream().filter(predicate).count(); - } + protected long countAuditsWhichMatchPredicate(List audits) { + return audits.stream().filter(predicate).count(); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsAtIndexMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsAtIndexMatcher.java index d0b7e64673..2fb2cc5e74 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsAtIndexMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsAtIndexMatcher.java @@ -20,51 +20,57 @@ class BulkResponseContainExceptionsAtIndexMatcher extends TypeSafeDiagnosingMatcher { - private final int errorIndex; - private final Matcher exceptionMatcher; + private final int errorIndex; + private final Matcher exceptionMatcher; - public BulkResponseContainExceptionsAtIndexMatcher(int errorIndex, Matcher exceptionMatcher) { - this.errorIndex = errorIndex; - this.exceptionMatcher = requireNonNull(exceptionMatcher, "Exception matcher is required."); - } + public BulkResponseContainExceptionsAtIndexMatcher(int errorIndex, Matcher exceptionMatcher) { + this.errorIndex = errorIndex; + this.exceptionMatcher = requireNonNull(exceptionMatcher, "Exception matcher is required."); + } - @Override - protected boolean matchesSafely(BulkResponse response, Description mismatchDescription) { - if(response.hasFailures() == false) { - mismatchDescription.appendText("received successful bulk response what is not expected."); - return false; - } - BulkItemResponse[] items = response.getItems(); - if((items == null) || (items.length == 0) || (errorIndex >= items.length)) { - mismatchDescription.appendText("bulk response does not contain item with index ").appendValue(errorIndex); - return false; - } - BulkItemResponse item = items[errorIndex]; - if(item == null) { - mismatchDescription.appendText("bulk item response with index ").appendValue(errorIndex).appendText(" is null."); - return false; - } - BulkItemResponse.Failure failure = item.getFailure(); - if(failure == null) { - mismatchDescription.appendText("bulk response item with index ").appendValue(errorIndex).appendText(" does not contain failure"); - return false; - } - Exception exception = failure.getCause(); - if(exception == null) { - mismatchDescription.appendText("bulk response item with index ").appendValue(errorIndex).appendText(" does not contain exception."); - return false; - } - if(exceptionMatcher.matches(exception) == false) { - mismatchDescription.appendText("bulk response item with index ").appendValue(errorIndex) - .appendText(" contains incorrect exception which is ").appendValue(exception); - return false; - } + @Override + protected boolean matchesSafely(BulkResponse response, Description mismatchDescription) { + if (response.hasFailures() == false) { + mismatchDescription.appendText("received successful bulk response what is not expected."); + return false; + } + BulkItemResponse[] items = response.getItems(); + if ((items == null) || (items.length == 0) || (errorIndex >= items.length)) { + mismatchDescription.appendText("bulk response does not contain item with index ").appendValue(errorIndex); + return false; + } + BulkItemResponse item = items[errorIndex]; + if (item == null) { + mismatchDescription.appendText("bulk item response with index ").appendValue(errorIndex).appendText(" is null."); + return false; + } + BulkItemResponse.Failure failure = item.getFailure(); + if (failure == null) { + mismatchDescription.appendText("bulk response item with index ") + .appendValue(errorIndex) + .appendText(" does not contain failure"); + return false; + } + Exception exception = failure.getCause(); + if (exception == null) { + mismatchDescription.appendText("bulk response item with index ") + .appendValue(errorIndex) + .appendText(" does not contain exception."); + return false; + } + if (exceptionMatcher.matches(exception) == false) { + mismatchDescription.appendText("bulk response item with index ") + .appendValue(errorIndex) + .appendText(" contains incorrect exception which is ") + .appendValue(exception); + return false; + } - return true; - } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("bulk response should contain exceptions which indicate failure"); - } + @Override + public void describeTo(Description description) { + description.appendText("bulk response should contain exceptions which indicate failure"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsMatcher.java index 646a2aad93..b5e46ba9e2 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseContainExceptionsMatcher.java @@ -20,50 +20,52 @@ class BulkResponseContainExceptionsMatcher extends TypeSafeDiagnosingMatcher { - private final Matcher exceptionMatcher; + private final Matcher exceptionMatcher; - public BulkResponseContainExceptionsMatcher(Matcher exceptionMatcher) { - this.exceptionMatcher = requireNonNull(exceptionMatcher, "Exception matcher is required."); - } + public BulkResponseContainExceptionsMatcher(Matcher exceptionMatcher) { + this.exceptionMatcher = requireNonNull(exceptionMatcher, "Exception matcher is required."); + } - @Override - protected boolean matchesSafely(BulkResponse response, Description mismatchDescription) { - if(response.hasFailures() == false) { - mismatchDescription.appendText("received successful bulk response what is not expected."); - return false; - } - BulkItemResponse[] items = response.getItems(); - if((items == null) || (items.length == 0)) { - mismatchDescription.appendText("bulk response does not contain items ").appendValue(items); - return false; - } - for(int i = 0 ; i < items.length; ++i) { - BulkItemResponse item = items[i]; - if(item == null) { - mismatchDescription.appendText("bulk item response with index ").appendValue(i).appendText(" is null."); - return false; - } - BulkItemResponse.Failure failure = item.getFailure(); - if(failure == null) { - mismatchDescription.appendText("bulk response item with index ").appendValue(i).appendText(" does not contain failure"); - return false; - } - Exception exception = failure.getCause(); - if(exception == null) { - mismatchDescription.appendText("bulk response item with index ").appendValue(i).appendText(" does not contain exception."); - return false; - } - if(exceptionMatcher.matches(exception) == false) { - mismatchDescription.appendText("bulk response item with index ").appendValue(i) - .appendText(" contains incorrect exception which is ").appendValue(exception); - return false; - } - } - return true; - } + @Override + protected boolean matchesSafely(BulkResponse response, Description mismatchDescription) { + if (response.hasFailures() == false) { + mismatchDescription.appendText("received successful bulk response what is not expected."); + return false; + } + BulkItemResponse[] items = response.getItems(); + if ((items == null) || (items.length == 0)) { + mismatchDescription.appendText("bulk response does not contain items ").appendValue(items); + return false; + } + for (int i = 0; i < items.length; ++i) { + BulkItemResponse item = items[i]; + if (item == null) { + mismatchDescription.appendText("bulk item response with index ").appendValue(i).appendText(" is null."); + return false; + } + BulkItemResponse.Failure failure = item.getFailure(); + if (failure == null) { + mismatchDescription.appendText("bulk response item with index ").appendValue(i).appendText(" does not contain failure"); + return false; + } + Exception exception = failure.getCause(); + if (exception == null) { + mismatchDescription.appendText("bulk response item with index ").appendValue(i).appendText(" does not contain exception."); + return false; + } + if (exceptionMatcher.matches(exception) == false) { + mismatchDescription.appendText("bulk response item with index ") + .appendValue(i) + .appendText(" contains incorrect exception which is ") + .appendValue(exception); + return false; + } + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("bulk response should contain exceptions which indicate failure"); - } + @Override + public void describeTo(Description description) { + description.appendText("bulk response should contain exceptions which indicate failure"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseMatchers.java index 4a72a67aba..eedcd3a3a0 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/BulkResponseMatchers.java @@ -13,25 +13,25 @@ import org.opensearch.action.bulk.BulkResponse; -public class BulkResponseMatchers { +public class BulkResponseMatchers { - private BulkResponseMatchers(){ + private BulkResponseMatchers() { - } + } - public static Matcher successBulkResponse() { - return new SuccessBulkResponseMatcher(); - } + public static Matcher successBulkResponse() { + return new SuccessBulkResponseMatcher(); + } - public static Matcher failureBulkResponse() { - return new FailureBulkResponseMatcher(); - } + public static Matcher failureBulkResponse() { + return new FailureBulkResponseMatcher(); + } - public static Matcher bulkResponseContainExceptions(Matcher exceptionMatcher) { - return new BulkResponseContainExceptionsMatcher(exceptionMatcher); - } + public static Matcher bulkResponseContainExceptions(Matcher exceptionMatcher) { + return new BulkResponseContainExceptionsMatcher(exceptionMatcher); + } - public static Matcher bulkResponseContainExceptions(int index, Matcher exceptionMatcher) { - return new BulkResponseContainExceptionsAtIndexMatcher(index, exceptionMatcher); - } + public static Matcher bulkResponseContainExceptions(int index, Matcher exceptionMatcher) { + return new BulkResponseContainExceptionsAtIndexMatcher(index, exceptionMatcher); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainDocumentCountIndexMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainDocumentCountIndexMatcher.java index a8e1b5d78d..91d2a28ef8 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainDocumentCountIndexMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainDocumentCountIndexMatcher.java @@ -20,24 +20,24 @@ class ClusterContainDocumentCountIndexMatcher extends TypeSafeDiagnosingMatcher { - private final String indexName; - private final int expectedDocumentCount; - - public ClusterContainDocumentCountIndexMatcher(String indexName, int expectedDocumentCount) { - this.indexName = requireNonNull(indexName, "Index name is required."); - this.expectedDocumentCount = expectedDocumentCount; - } - - @Override - protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { - try(Client client = cluster.getInternalNodeClient()) { - GetIndexResponse response = client.admin().indices().getIndex(null).actionGet(); - } - return false; - } - - @Override - public void describeTo(Description description) { - description.appendText("contains ").appendValue(expectedDocumentCount).appendText(" in index ").appendText(indexName); - } + private final String indexName; + private final int expectedDocumentCount; + + public ClusterContainDocumentCountIndexMatcher(String indexName, int expectedDocumentCount) { + this.indexName = requireNonNull(indexName, "Index name is required."); + this.expectedDocumentCount = expectedDocumentCount; + } + + @Override + protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { + try (Client client = cluster.getInternalNodeClient()) { + GetIndexResponse response = client.admin().indices().getIndex(null).actionGet(); + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("contains ").appendValue(expectedDocumentCount).appendText(" in index ").appendText(indexName); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainSuccessSnapshotMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainSuccessSnapshotMatcher.java index f710e2c1fa..362663e07b 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainSuccessSnapshotMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainSuccessSnapshotMatcher.java @@ -24,43 +24,46 @@ class ClusterContainSuccessSnapshotMatcher extends TypeSafeDiagnosingMatcher { - private final String repositoryName; - private final String snapshotName; + private final String repositoryName; + private final String snapshotName; - public ClusterContainSuccessSnapshotMatcher(String repositoryName, String snapshotName) { - this.repositoryName = requireNonNull(repositoryName, "Snapshot repository name is required."); - this.snapshotName = requireNonNull(snapshotName, "Snapshot name is required."); - } + public ClusterContainSuccessSnapshotMatcher(String repositoryName, String snapshotName) { + this.repositoryName = requireNonNull(repositoryName, "Snapshot repository name is required."); + this.snapshotName = requireNonNull(snapshotName, "Snapshot name is required."); + } - @Override - protected boolean matchesSafely(Client client, Description mismatchDescription) { - try { - GetSnapshotsRequest request = new GetSnapshotsRequest(repositoryName, new String[] { snapshotName }); - GetSnapshotsResponse response = client.admin().cluster().getSnapshots(request).actionGet(); - long count = response.getSnapshots() - .stream() - .map(snapshot -> snapshot.state()) - .filter(status -> SnapshotState.SUCCESS.equals(status)) - .count(); - if(count != 1){ - String snapshotStatuses = response.getSnapshots() - .stream() - .map(info -> String.format("%s %s", info.snapshotId().getName(), info.state())) - .collect(Collectors.joining(", ")); - mismatchDescription.appendText("snapshot is not present or has incorrect state, snapshots statuses ") - .appendValue(snapshotStatuses); - return false; - } - }catch (SnapshotMissingException e) { - mismatchDescription.appendText(" snapshot does not exist"); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + try { + GetSnapshotsRequest request = new GetSnapshotsRequest(repositoryName, new String[] { snapshotName }); + GetSnapshotsResponse response = client.admin().cluster().getSnapshots(request).actionGet(); + long count = response.getSnapshots() + .stream() + .map(snapshot -> snapshot.state()) + .filter(status -> SnapshotState.SUCCESS.equals(status)) + .count(); + if (count != 1) { + String snapshotStatuses = response.getSnapshots() + .stream() + .map(info -> String.format("%s %s", info.snapshotId().getName(), info.state())) + .collect(Collectors.joining(", ")); + mismatchDescription.appendText("snapshot is not present or has incorrect state, snapshots statuses ") + .appendValue(snapshotStatuses); + return false; + } + } catch (SnapshotMissingException e) { + mismatchDescription.appendText(" snapshot does not exist"); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Cluster contain snapshot ").appendValue(snapshotName).appendText(" in repository ") - .appendValue(repositoryName).appendText(" with success status"); - } + @Override + public void describeTo(Description description) { + description.appendText("Cluster contain snapshot ") + .appendValue(snapshotName) + .appendText(" in repository ") + .appendValue(repositoryName) + .appendText(" with success status"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateMatcher.java index 2d64a95c65..119e3a6a2f 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateMatcher.java @@ -9,7 +9,6 @@ */ package org.opensearch.test.framework.matcher; - import org.hamcrest.Description; import org.hamcrest.TypeSafeDiagnosingMatcher; @@ -20,25 +19,25 @@ class ClusterContainTemplateMatcher extends TypeSafeDiagnosingMatcher { - private final String templateName; + private final String templateName; - public ClusterContainTemplateMatcher(String templateName) { - this.templateName = requireNonNull(templateName, "Index template name is required."); + public ClusterContainTemplateMatcher(String templateName) { + this.templateName = requireNonNull(templateName, "Index template name is required."); - } + } - @Override - protected boolean matchesSafely(Client client, Description mismatchDescription) { - var response = client.admin().indices().getTemplates(new GetIndexTemplatesRequest(templateName)).actionGet(); - if(response.getIndexTemplates().isEmpty()) { - mismatchDescription.appendText("But template does not exists"); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + var response = client.admin().indices().getTemplates(new GetIndexTemplatesRequest(templateName)).actionGet(); + if (response.getIndexTemplates().isEmpty()) { + mismatchDescription.appendText("But template does not exists"); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("template ").appendValue(templateName).appendText(" exists"); - } + @Override + public void describeTo(Description description) { + description.appendText("template ").appendValue(templateName).appendText(" exists"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java index 861aaa72e7..4addaa0dd5 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainTemplateWithAliasMatcher.java @@ -27,48 +27,47 @@ class ClusterContainTemplateWithAliasMatcher extends TypeSafeDiagnosingMatcher { - private final String templateName; - private final String aliasName; + private final String templateName; + private final String aliasName; - public ClusterContainTemplateWithAliasMatcher(String templateName, String aliasName) { - this.templateName = requireNonNull(templateName, "Index template name is required."); - this.aliasName = requireNonNull(aliasName, "Alias name is required."); - } + public ClusterContainTemplateWithAliasMatcher(String templateName, String aliasName) { + this.templateName = requireNonNull(templateName, "Index template name is required."); + this.aliasName = requireNonNull(aliasName, "Alias name is required."); + } - @Override - protected boolean matchesSafely(Client client, Description mismatchDescription) { - var response = client.admin().indices().getTemplates(new GetIndexTemplatesRequest(templateName)).actionGet(); - if(response.getIndexTemplates().isEmpty()) { - mismatchDescription.appendText("but template does not exists"); - return false; - } - Set aliases = getAliases(response); - if(aliases.contains(aliasName) == false) { - mismatchDescription.appendText("alias ").appendValue(aliasName) - .appendText(" is not present in template, other aliases in template ") - .appendValue(aliases.stream().collect(Collectors.joining(", "))); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + var response = client.admin().indices().getTemplates(new GetIndexTemplatesRequest(templateName)).actionGet(); + if (response.getIndexTemplates().isEmpty()) { + mismatchDescription.appendText("but template does not exists"); + return false; + } + Set aliases = getAliases(response); + if (aliases.contains(aliasName) == false) { + mismatchDescription.appendText("alias ") + .appendValue(aliasName) + .appendText(" is not present in template, other aliases in template ") + .appendValue(aliases.stream().collect(Collectors.joining(", "))); + return false; + } + return true; + } - private Set getAliases(GetIndexTemplatesResponse response) { - return response.getIndexTemplates() - .stream() - .map(metadata -> metadata.getAliases()) - .flatMap(aliasMap -> aliasNames(aliasMap)) - .collect(Collectors.toSet()); - } + private Set getAliases(GetIndexTemplatesResponse response) { + return response.getIndexTemplates() + .stream() + .map(metadata -> metadata.getAliases()) + .flatMap(aliasMap -> aliasNames(aliasMap)) + .collect(Collectors.toSet()); + } - private Stream aliasNames(Map aliasMap) { - Iterable> iterable = () -> aliasMap.entrySet().iterator(); - return StreamSupport.stream(iterable.spliterator(), false) - .map(entry -> entry.getValue().getAlias()); - } + private Stream aliasNames(Map aliasMap) { + Iterable> iterable = () -> aliasMap.entrySet().iterator(); + return StreamSupport.stream(iterable.spliterator(), false).map(entry -> entry.getValue().getAlias()); + } - - @Override - public void describeTo(Description description) { - description.appendText("template ").appendValue(templateName).appendText(" exists and "); - } + @Override + public void describeTo(Description description) { + description.appendText("template ").appendValue(templateName).appendText(" exists and "); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentMatcher.java index ffcceaa9cb..3153214213 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentMatcher.java @@ -24,35 +24,34 @@ class ClusterContainsDocumentMatcher extends TypeSafeDiagnosingMatcher { - private static final Logger log = LogManager.getLogger(ClusterContainsDocumentMatcher.class); - - private final String indexName; - private final String documentId; - - ClusterContainsDocumentMatcher(String indexName, String documentId) { - this.indexName = requireNonNull(indexName, "Index name is required."); - this.documentId = requireNonNull(documentId, "Document id is required."); - } - - @Override - protected boolean matchesSafely(Client client, Description mismatchDescription) { - try{ - GetResponse response = client.get(new GetRequest(indexName, documentId)).get(); - if(response.isExists() == false) { - mismatchDescription.appendText("Document does not exists"); - return false; - } - } catch (InterruptedException | ExecutionException e) { - log.error("Cannot verify if cluster contains document '{}' in index '{}'.", documentId, indexName, e); - mismatchDescription.appendText("Exception occured during verification if cluster contain document").appendValue(e); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Cluster contain document in index ").appendValue(indexName).appendText(" with id ") - .appendValue(documentId); - } + private static final Logger log = LogManager.getLogger(ClusterContainsDocumentMatcher.class); + + private final String indexName; + private final String documentId; + + ClusterContainsDocumentMatcher(String indexName, String documentId) { + this.indexName = requireNonNull(indexName, "Index name is required."); + this.documentId = requireNonNull(documentId, "Document id is required."); + } + + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + try { + GetResponse response = client.get(new GetRequest(indexName, documentId)).get(); + if (response.isExists() == false) { + mismatchDescription.appendText("Document does not exists"); + return false; + } + } catch (InterruptedException | ExecutionException e) { + log.error("Cannot verify if cluster contains document '{}' in index '{}'.", documentId, indexName, e); + mismatchDescription.appendText("Exception occured during verification if cluster contain document").appendValue(e); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Cluster contain document in index ").appendValue(indexName).appendText(" with id ").appendValue(documentId); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentWithFieldValueMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentWithFieldValueMatcher.java index df67a1669a..21f062fabf 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentWithFieldValueMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsDocumentWithFieldValueMatcher.java @@ -25,56 +25,61 @@ class ClusterContainsDocumentWithFieldValueMatcher extends TypeSafeDiagnosingMatcher { - private static final Logger log = LogManager.getLogger(ClusterContainsDocumentWithFieldValueMatcher.class); + private static final Logger log = LogManager.getLogger(ClusterContainsDocumentWithFieldValueMatcher.class); - private final String indexName; - private final String documentId; + private final String indexName; + private final String documentId; - private final String fieldName; + private final String fieldName; - private final Object fieldValue; + private final Object fieldValue; - ClusterContainsDocumentWithFieldValueMatcher(String indexName, String documentId, String fieldName, Object fieldValue) { - this.indexName = requireNonNull(indexName, "Index name is required."); - this.documentId = requireNonNull(documentId, "Document id is required."); - this.fieldName = requireNonNull(fieldName, "Field name is required."); - this.fieldValue = requireNonNull(fieldValue, "Field value is required."); - } + ClusterContainsDocumentWithFieldValueMatcher(String indexName, String documentId, String fieldName, Object fieldValue) { + this.indexName = requireNonNull(indexName, "Index name is required."); + this.documentId = requireNonNull(documentId, "Document id is required."); + this.fieldName = requireNonNull(fieldName, "Field name is required."); + this.fieldValue = requireNonNull(fieldValue, "Field value is required."); + } - @Override - protected boolean matchesSafely(Client client, Description mismatchDescription) { - try { - GetResponse response = client.get(new GetRequest(indexName, documentId)).get(); - if(response.isExists() == false) { - mismatchDescription.appendText("Document does not exists"); - return false; - } - Map source = response.getSource(); - if(source == null) { - mismatchDescription.appendText("Cannot retrieve document source"); - return false; - } - if(source.containsKey(fieldName) == false) { - mismatchDescription.appendText("document does not contain field ").appendValue(fieldName); - return false; - } - Object actualFieldValue = source.get(fieldName); - if(fieldValue.equals(actualFieldValue) == false) { - mismatchDescription.appendText(" document contain incorrect field value ").appendValue(actualFieldValue); - return false; - } - } catch (InterruptedException | ExecutionException e) { - log.error("Cannot verify if cluster contains document '{}' in index '{}'.", documentId, indexName, e); - mismatchDescription.appendText("Exception occured during verification if cluster contain document").appendValue(e); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + try { + GetResponse response = client.get(new GetRequest(indexName, documentId)).get(); + if (response.isExists() == false) { + mismatchDescription.appendText("Document does not exists"); + return false; + } + Map source = response.getSource(); + if (source == null) { + mismatchDescription.appendText("Cannot retrieve document source"); + return false; + } + if (source.containsKey(fieldName) == false) { + mismatchDescription.appendText("document does not contain field ").appendValue(fieldName); + return false; + } + Object actualFieldValue = source.get(fieldName); + if (fieldValue.equals(actualFieldValue) == false) { + mismatchDescription.appendText(" document contain incorrect field value ").appendValue(actualFieldValue); + return false; + } + } catch (InterruptedException | ExecutionException e) { + log.error("Cannot verify if cluster contains document '{}' in index '{}'.", documentId, indexName, e); + mismatchDescription.appendText("Exception occured during verification if cluster contain document").appendValue(e); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Cluster contain document in index ").appendValue(indexName).appendText(" with id ") - .appendValue(documentId).appendText(" with field ").appendValue(fieldName).appendText(" which is equal to ") - .appendValue(fieldValue); - } + @Override + public void describeTo(Description description) { + description.appendText("Cluster contain document in index ") + .appendValue(indexName) + .appendText(" with id ") + .appendValue(documentId) + .appendText(" with field ") + .appendValue(fieldName) + .appendText(" which is equal to ") + .appendValue(fieldValue); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsSnapshotRepositoryMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsSnapshotRepositoryMatcher.java index 8b7dc777e4..fcff8bd6cf 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsSnapshotRepositoryMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterContainsSnapshotRepositoryMatcher.java @@ -26,41 +26,42 @@ class ClusterContainsSnapshotRepositoryMatcher extends TypeSafeDiagnosingMatcher { - private final String repositoryName; + private final String repositoryName; - public ClusterContainsSnapshotRepositoryMatcher(String repositoryName) { - this.repositoryName = requireNonNull(repositoryName, "Repository name is required."); - } + public ClusterContainsSnapshotRepositoryMatcher(String repositoryName) { + this.repositoryName = requireNonNull(repositoryName, "Repository name is required."); + } - @Override - protected boolean matchesSafely(Client client, Description mismatchDescription) { - try { - ClusterAdminClient adminClient = client.admin().cluster(); - GetRepositoriesRequest request = new GetRepositoriesRequest(new String[]{"*"}); - GetRepositoriesResponse response = adminClient.getRepositories(request).actionGet(); - if(response == null) { - mismatchDescription.appendText("Cannot check if cluster contain repository"); - return false; - } - Set actualRepositoryNames = response.repositories() - .stream() - .map(metadata -> metadata.name()) - .collect(Collectors.toSet()); - if(actualRepositoryNames.contains(repositoryName) == false) { - mismatchDescription.appendText("Cluster does not contain snapshot repository ").appendValue(repositoryName) - .appendText(", but the following repositories are defined in the cluster ") - .appendValue(actualRepositoryNames.stream().collect(joining(", "))); - return false; - } - } catch (RepositoryMissingException e) { - mismatchDescription.appendText(" cluster does not contain any repository."); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + try { + ClusterAdminClient adminClient = client.admin().cluster(); + GetRepositoriesRequest request = new GetRepositoriesRequest(new String[] { "*" }); + GetRepositoriesResponse response = adminClient.getRepositories(request).actionGet(); + if (response == null) { + mismatchDescription.appendText("Cannot check if cluster contain repository"); + return false; + } + Set actualRepositoryNames = response.repositories() + .stream() + .map(metadata -> metadata.name()) + .collect(Collectors.toSet()); + if (actualRepositoryNames.contains(repositoryName) == false) { + mismatchDescription.appendText("Cluster does not contain snapshot repository ") + .appendValue(repositoryName) + .appendText(", but the following repositories are defined in the cluster ") + .appendValue(actualRepositoryNames.stream().collect(joining(", "))); + return false; + } + } catch (RepositoryMissingException e) { + mismatchDescription.appendText(" cluster does not contain any repository."); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Cluster contain snapshot repository with name ").appendValue(repositoryName); - } + @Override + public void describeTo(Description description) { + description.appendText("Cluster contain snapshot repository with name ").appendValue(repositoryName); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java index eff1f4c803..3829436d74 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ClusterMatchers.java @@ -20,55 +20,60 @@ public class ClusterMatchers { - private ClusterMatchers() { - - } - - public static Matcher clusterContainsDocument(String indexName, String documentId) { - return new ClusterContainsDocumentMatcher(indexName, documentId); - } - - public static Matcher clusterContainsDocumentWithFieldValue(String indexName, String documentId, String fieldName, Object fieldValue) { - return new ClusterContainsDocumentWithFieldValueMatcher(indexName, documentId, fieldName, fieldValue); - } - - public static Matcher clusterContainTemplate(String templateName) { - return new ClusterContainTemplateMatcher(templateName); - } - - public static Matcher clusterContainTemplateWithAlias(String templateName, String aliasName) { - return new ClusterContainTemplateWithAliasMatcher(templateName, aliasName); - } - - public static Matcher clusterContainsSnapshotRepository(String repositoryName) { - return new ClusterContainsSnapshotRepositoryMatcher(repositoryName); - } - - public static Matcher clusterContainSuccessSnapshot(String repositoryName, String snapshotName) { - return new ClusterContainSuccessSnapshotMatcher(repositoryName, snapshotName); - } - - public static Matcher snapshotInClusterDoesNotExists(String repositoryName, String snapshotName) { - return new SnapshotInClusterDoesNotExist(repositoryName, snapshotName); - } - - public static Matcher aliasExists(String aliasName) { - return new AliasExistsMatcher(aliasName); - } - - public static Matcher indexExists(String expectedIndexName) { - return new IndexExistsMatcher(expectedIndexName); - } - - public static Matcher indexStateIsEqualTo(String expectedIndexName, IndexMetadata.State expectedState) { - return new IndexStateIsEqualToMatcher(expectedIndexName, expectedState); - } - - public static Matcher indexSettingsContainValues(String expectedIndexName, Settings expectedSettings) { - return new IndexSettingsContainValuesMatcher(expectedIndexName, expectedSettings); - } - - public static Matcher indexMappingIsEqualTo(String expectedIndexName, Map expectedMapping) { - return new IndexMappingIsEqualToMatcher(expectedIndexName, expectedMapping); - } + private ClusterMatchers() { + + } + + public static Matcher clusterContainsDocument(String indexName, String documentId) { + return new ClusterContainsDocumentMatcher(indexName, documentId); + } + + public static Matcher clusterContainsDocumentWithFieldValue( + String indexName, + String documentId, + String fieldName, + Object fieldValue + ) { + return new ClusterContainsDocumentWithFieldValueMatcher(indexName, documentId, fieldName, fieldValue); + } + + public static Matcher clusterContainTemplate(String templateName) { + return new ClusterContainTemplateMatcher(templateName); + } + + public static Matcher clusterContainTemplateWithAlias(String templateName, String aliasName) { + return new ClusterContainTemplateWithAliasMatcher(templateName, aliasName); + } + + public static Matcher clusterContainsSnapshotRepository(String repositoryName) { + return new ClusterContainsSnapshotRepositoryMatcher(repositoryName); + } + + public static Matcher clusterContainSuccessSnapshot(String repositoryName, String snapshotName) { + return new ClusterContainSuccessSnapshotMatcher(repositoryName, snapshotName); + } + + public static Matcher snapshotInClusterDoesNotExists(String repositoryName, String snapshotName) { + return new SnapshotInClusterDoesNotExist(repositoryName, snapshotName); + } + + public static Matcher aliasExists(String aliasName) { + return new AliasExistsMatcher(aliasName); + } + + public static Matcher indexExists(String expectedIndexName) { + return new IndexExistsMatcher(expectedIndexName); + } + + public static Matcher indexStateIsEqualTo(String expectedIndexName, IndexMetadata.State expectedState) { + return new IndexStateIsEqualToMatcher(expectedIndexName, expectedState); + } + + public static Matcher indexSettingsContainValues(String expectedIndexName, Settings expectedSettings) { + return new IndexSettingsContainValuesMatcher(expectedIndexName, expectedSettings); + } + + public static Matcher indexMappingIsEqualTo(String expectedIndexName, Map expectedMapping) { + return new IndexMappingIsEqualToMatcher(expectedIndexName, expectedMapping); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainNotEmptyScrollingIdMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainNotEmptyScrollingIdMatcher.java index 094b26f33f..03b9b6bab8 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainNotEmptyScrollingIdMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainNotEmptyScrollingIdMatcher.java @@ -17,18 +17,18 @@ class ContainNotEmptyScrollingIdMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { - String scrollId = searchResponse.getScrollId(); - if(StringUtils.isEmpty(scrollId)) { - mismatchDescription.appendText("scrolling id is null or empty"); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + String scrollId = searchResponse.getScrollId(); + if (StringUtils.isEmpty(scrollId)) { + mismatchDescription.appendText("scrolling id is null or empty"); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Search response should contain scrolling id."); - } + @Override + public void describeTo(Description description) { + description.appendText("Search response should contain scrolling id."); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsAggregationWithNameAndTypeMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsAggregationWithNameAndTypeMatcher.java index 9f8fbd6dba..b1ef21c922 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsAggregationWithNameAndTypeMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsAggregationWithNameAndTypeMatcher.java @@ -20,36 +20,38 @@ class ContainsAggregationWithNameAndTypeMatcher extends TypeSafeDiagnosingMatcher { - private final String expectedAggregationName; - private final String expectedAggregationType; - - public ContainsAggregationWithNameAndTypeMatcher(String expectedAggregationName, String expectedAggregationType) { - this.expectedAggregationName = requireNonNull(expectedAggregationName, "Aggregation name is required"); - this.expectedAggregationType = requireNonNull(expectedAggregationType, "Expected aggregation type is required."); - } - - @Override - protected boolean matchesSafely(SearchResponse response, Description mismatchDescription) { - Aggregations aggregations = response.getAggregations(); - if(aggregations == null) { - mismatchDescription.appendText("search response does not contain aggregations"); - return false; - } - Aggregation aggregation = aggregations.get(expectedAggregationName); - if(aggregation == null) { - mismatchDescription.appendText("Response does not contain aggregation with name ").appendValue(expectedAggregationName); - return false; - } - if(expectedAggregationType.equals(aggregation.getType()) == false) { - mismatchDescription.appendText("Aggregation contain incorrect type which is ").appendValue(aggregation.getType()); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Search response should contains aggregation results with name ").appendValue(expectedAggregationName) - .appendText(" and type ").appendValue(expectedAggregationType); - } + private final String expectedAggregationName; + private final String expectedAggregationType; + + public ContainsAggregationWithNameAndTypeMatcher(String expectedAggregationName, String expectedAggregationType) { + this.expectedAggregationName = requireNonNull(expectedAggregationName, "Aggregation name is required"); + this.expectedAggregationType = requireNonNull(expectedAggregationType, "Expected aggregation type is required."); + } + + @Override + protected boolean matchesSafely(SearchResponse response, Description mismatchDescription) { + Aggregations aggregations = response.getAggregations(); + if (aggregations == null) { + mismatchDescription.appendText("search response does not contain aggregations"); + return false; + } + Aggregation aggregation = aggregations.get(expectedAggregationName); + if (aggregation == null) { + mismatchDescription.appendText("Response does not contain aggregation with name ").appendValue(expectedAggregationName); + return false; + } + if (expectedAggregationType.equals(aggregation.getType()) == false) { + mismatchDescription.appendText("Aggregation contain incorrect type which is ").appendValue(aggregation.getType()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Search response should contains aggregation results with name ") + .appendValue(expectedAggregationName) + .appendText(" and type ") + .appendValue(expectedAggregationType); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsExactlyIndicesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsExactlyIndicesMatcher.java index 551a1823bf..9b597ad1c8 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsExactlyIndicesMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsExactlyIndicesMatcher.java @@ -20,27 +20,27 @@ class ContainsExactlyIndicesMatcher extends TypeSafeDiagnosingMatcher { - private final Set expectedIndices; - - ContainsExactlyIndicesMatcher(String... expectedIndices) { - if (isNull(expectedIndices) || expectedIndices.length == 0) { - throw new IllegalArgumentException("expectedIndices cannot be null or empty"); - } - this.expectedIndices = Set.of(expectedIndices); - } - - @Override - protected boolean matchesSafely(FieldCapabilitiesResponse response, Description mismatchDescription) { - Set actualIndices = Set.of(response.getIndices()); - if (!expectedIndices.equals(actualIndices)) { - mismatchDescription.appendText("Actual indices: ").appendValue(actualIndices); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Response contains indices: ").appendValue(expectedIndices); - } + private final Set expectedIndices; + + ContainsExactlyIndicesMatcher(String... expectedIndices) { + if (isNull(expectedIndices) || expectedIndices.length == 0) { + throw new IllegalArgumentException("expectedIndices cannot be null or empty"); + } + this.expectedIndices = Set.of(expectedIndices); + } + + @Override + protected boolean matchesSafely(FieldCapabilitiesResponse response, Description mismatchDescription) { + Set actualIndices = Set.of(response.getIndices()); + if (!expectedIndices.equals(actualIndices)) { + mismatchDescription.appendText("Actual indices: ").appendValue(actualIndices); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response contains indices: ").appendValue(expectedIndices); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsFieldWithTypeMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsFieldWithTypeMatcher.java index 41db8a9ed5..cd6b4b05cc 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsFieldWithTypeMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ContainsFieldWithTypeMatcher.java @@ -21,31 +21,35 @@ class ContainsFieldWithTypeMatcher extends TypeSafeDiagnosingMatcher { - private final String expectedFieldName; - private final String expectedFieldType; - - ContainsFieldWithTypeMatcher(String expectedFieldName, String expectedFieldType) { - this.expectedFieldName = requireNonNull(expectedFieldName, "Field name is required");; - this.expectedFieldType = requireNonNull(expectedFieldType, "Field type is required");; - } - - @Override - protected boolean matchesSafely(FieldCapabilitiesResponse response, Description mismatchDescription) { - Map> fieldCapabilitiesMap = response.get(); - if (!fieldCapabilitiesMap.containsKey(expectedFieldName)) { - mismatchDescription.appendText("Response does not contain field with name ").appendText(expectedFieldName); - return false; - } - if (!fieldCapabilitiesMap.get(expectedFieldName).containsKey(expectedFieldType)) { - mismatchDescription.appendText("Field type does not match ").appendText(expectedFieldType); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Response contains field with name ").appendValue(expectedFieldName) - .appendText(" and type ").appendValue(expectedFieldType); - } + private final String expectedFieldName; + private final String expectedFieldType; + + ContainsFieldWithTypeMatcher(String expectedFieldName, String expectedFieldType) { + this.expectedFieldName = requireNonNull(expectedFieldName, "Field name is required"); + ; + this.expectedFieldType = requireNonNull(expectedFieldType, "Field type is required"); + ; + } + + @Override + protected boolean matchesSafely(FieldCapabilitiesResponse response, Description mismatchDescription) { + Map> fieldCapabilitiesMap = response.get(); + if (!fieldCapabilitiesMap.containsKey(expectedFieldName)) { + mismatchDescription.appendText("Response does not contain field with name ").appendText(expectedFieldName); + return false; + } + if (!fieldCapabilitiesMap.get(expectedFieldName).containsKey(expectedFieldType)) { + mismatchDescription.appendText("Field type does not match ").appendText(expectedFieldType); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response contains field with name ") + .appendValue(expectedFieldName) + .appendText(" and type ") + .appendValue(expectedFieldType); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/DeletePitContainsExactlyIdsResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/DeletePitContainsExactlyIdsResponseMatcher.java index fd921d0d72..788d023447 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/DeletePitContainsExactlyIdsResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/DeletePitContainsExactlyIdsResponseMatcher.java @@ -22,27 +22,27 @@ class DeletePitContainsExactlyIdsResponseMatcher extends TypeSafeDiagnosingMatcher { - private final Set expectedPitIds; - - DeletePitContainsExactlyIdsResponseMatcher(String[] expectedPitIds) { - if (isNull(expectedPitIds) || 0 == expectedPitIds.length) { - throw new IllegalArgumentException("expectedPitIds cannot be null or empty"); - } - this.expectedPitIds = Set.of(expectedPitIds); - } - - @Override - protected boolean matchesSafely(DeletePitResponse response, Description mismatchDescription) { - Set actualPitIds = response.getDeletePitResults().stream().map(DeletePitInfo::getPitId).collect(Collectors.toSet()); - if (!actualPitIds.equals(expectedPitIds)) { - mismatchDescription.appendText("Actual pit ids: ").appendValue(actualPitIds); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Should contain exactly pit with ids: ").appendValue(expectedPitIds); - } + private final Set expectedPitIds; + + DeletePitContainsExactlyIdsResponseMatcher(String[] expectedPitIds) { + if (isNull(expectedPitIds) || 0 == expectedPitIds.length) { + throw new IllegalArgumentException("expectedPitIds cannot be null or empty"); + } + this.expectedPitIds = Set.of(expectedPitIds); + } + + @Override + protected boolean matchesSafely(DeletePitResponse response, Description mismatchDescription) { + Set actualPitIds = response.getDeletePitResults().stream().map(DeletePitInfo::getPitId).collect(Collectors.toSet()); + if (!actualPitIds.equals(expectedPitIds)) { + mismatchDescription.appendText("Actual pit ids: ").appendValue(actualPitIds); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Should contain exactly pit with ids: ").appendValue(expectedPitIds); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/DeleteResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/DeleteResponseMatchers.java index 435c2521c8..4112d0bab8 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/DeleteResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/DeleteResponseMatchers.java @@ -15,9 +15,9 @@ public class DeleteResponseMatchers { - private DeleteResponseMatchers() {} + private DeleteResponseMatchers() {} - public static Matcher isSuccessfulDeleteResponse() { - return new SuccessfulDeleteResponseMatcher(); - } + public static Matcher isSuccessfulDeleteResponse() { + return new SuccessfulDeleteResponseMatcher(); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExactNumberOfAuditsFulfillPredicateMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExactNumberOfAuditsFulfillPredicateMatcher.java index c7d918db2c..9bbe966e07 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExactNumberOfAuditsFulfillPredicateMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExactNumberOfAuditsFulfillPredicateMatcher.java @@ -18,26 +18,28 @@ class ExactNumberOfAuditsFulfillPredicateMatcher extends AuditsFulfillPredicateMatcher { - private final long exactNumberOfAuditMessages; - - public ExactNumberOfAuditsFulfillPredicateMatcher(long exactNumberOfAuditMessages, Predicate predicate) { - super(predicate); - this.exactNumberOfAuditMessages = exactNumberOfAuditMessages; - } - - @Override - protected boolean matchesSafely(List audits, Description mismatchDescription) { - long count = countAuditsWhichMatchPredicate(audits); - if(exactNumberOfAuditMessages != count) { - mismatchDescription.appendText(" only ").appendValue(count).appendText(" match predicate. Examined audit logs ") - .appendText(auditMessagesToString(audits)); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendValue(exactNumberOfAuditMessages).appendText(" audit records should match predicate ").appendValue(predicate); - } + private final long exactNumberOfAuditMessages; + + public ExactNumberOfAuditsFulfillPredicateMatcher(long exactNumberOfAuditMessages, Predicate predicate) { + super(predicate); + this.exactNumberOfAuditMessages = exactNumberOfAuditMessages; + } + + @Override + protected boolean matchesSafely(List audits, Description mismatchDescription) { + long count = countAuditsWhichMatchPredicate(audits); + if (exactNumberOfAuditMessages != count) { + mismatchDescription.appendText(" only ") + .appendValue(count) + .appendText(" match predicate. Examined audit logs ") + .appendText(auditMessagesToString(audits)); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendValue(exactNumberOfAuditMessages).appendText(" audit records should match predicate ").appendValue(predicate); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionErrorMessageMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionErrorMessageMatcher.java index e0e7b11be3..2e07bd0ec5 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionErrorMessageMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionErrorMessageMatcher.java @@ -17,25 +17,27 @@ class ExceptionErrorMessageMatcher extends TypeSafeDiagnosingMatcher { - private final Matcher errorMessageMatcher; - - public ExceptionErrorMessageMatcher(Matcher errorMessageMatcher) { - this.errorMessageMatcher = requireNonNull(errorMessageMatcher, "Error message matcher is required"); - } - - @Override - protected boolean matchesSafely(Throwable ex, Description mismatchDescription) { - boolean matches = errorMessageMatcher.matches(ex.getMessage()); - if(matches == false) { - mismatchDescription.appendText("Exception of class ").appendValue(ex.getClass().getCanonicalName()) - .appendText("contains unexpected error message which is ").appendValue(ex.getMessage()); - } - return matches; - - } - - @Override - public void describeTo(Description description) { - description.appendText("Error message in exception matches").appendValue(errorMessageMatcher); - } + private final Matcher errorMessageMatcher; + + public ExceptionErrorMessageMatcher(Matcher errorMessageMatcher) { + this.errorMessageMatcher = requireNonNull(errorMessageMatcher, "Error message matcher is required"); + } + + @Override + protected boolean matchesSafely(Throwable ex, Description mismatchDescription) { + boolean matches = errorMessageMatcher.matches(ex.getMessage()); + if (matches == false) { + mismatchDescription.appendText("Exception of class ") + .appendValue(ex.getClass().getCanonicalName()) + .appendText("contains unexpected error message which is ") + .appendValue(ex.getMessage()); + } + return matches; + + } + + @Override + public void describeTo(Description description) { + description.appendText("Error message in exception matches").appendValue(errorMessageMatcher); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionHasCauseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionHasCauseMatcher.java index 3a200d8849..ed1b203898 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionHasCauseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionHasCauseMatcher.java @@ -16,28 +16,28 @@ class ExceptionHasCauseMatcher extends TypeSafeDiagnosingMatcher { - private final Class expectedCauseType; + private final Class expectedCauseType; - public ExceptionHasCauseMatcher(Class expectedCauseType) { - this.expectedCauseType = Objects.requireNonNull(expectedCauseType, "Exception cause type is required"); - } + public ExceptionHasCauseMatcher(Class expectedCauseType) { + this.expectedCauseType = Objects.requireNonNull(expectedCauseType, "Exception cause type is required"); + } - @Override - protected boolean matchesSafely(Throwable throwable, Description mismatchDescription) { - Throwable cause = throwable.getCause(); - if(cause == null) { - mismatchDescription.appendText("exception cause is null"); - return false; - } - if(expectedCauseType.isInstance(cause) == false) { - mismatchDescription.appendText(" cause is instance of ").appendValue(cause.getClass()); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(Throwable throwable, Description mismatchDescription) { + Throwable cause = throwable.getCause(); + if (cause == null) { + mismatchDescription.appendText("exception cause is null"); + return false; + } + if (expectedCauseType.isInstance(cause) == false) { + mismatchDescription.appendText(" cause is instance of ").appendValue(cause.getClass()); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Exception cause is instance of ").appendValue(expectedCauseType); - } + @Override + public void describeTo(Description description) { + description.appendText("Exception cause is instance of ").appendValue(expectedCauseType); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionMatcherAssert.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionMatcherAssert.java index d46107b0fb..671f22b8e3 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionMatcherAssert.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/ExceptionMatcherAssert.java @@ -17,24 +17,24 @@ public class ExceptionMatcherAssert { - @FunctionalInterface - public interface ThrowingCallable { - void call() throws Exception; - } + @FunctionalInterface + public interface ThrowingCallable { + void call() throws Exception; + } - public static void assertThatThrownBy(ThrowingCallable throwingCallable, Matcher matcher) { - Throwable expectedException = catchThrowable(throwingCallable); - assertThat("Expected exception was not thrown", expectedException, notNullValue()); - assertThat(expectedException, matcher); - } + public static void assertThatThrownBy(ThrowingCallable throwingCallable, Matcher matcher) { + Throwable expectedException = catchThrowable(throwingCallable); + assertThat("Expected exception was not thrown", expectedException, notNullValue()); + assertThat(expectedException, matcher); + } - public static Throwable catchThrowable(ThrowingCallable throwingCallable) { - Throwable expectedException = null; - try { - requireNonNull(throwingCallable, "ThrowingCallable must not be null.").call(); - } catch (Throwable e) { - expectedException = e; - } - return expectedException; - } + public static Throwable catchThrowable(ThrowingCallable throwingCallable) { + Throwable expectedException = null; + try { + requireNonNull(throwingCallable, "ThrowingCallable must not be null.").call(); + } catch (Throwable e) { + expectedException = e; + } + return expectedException; + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/FailureBulkResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/FailureBulkResponseMatcher.java index a018c1c924..3d912e0283 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/FailureBulkResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/FailureBulkResponseMatcher.java @@ -16,17 +16,17 @@ class FailureBulkResponseMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(BulkResponse response, Description mismatchDescription) { - if(response.hasFailures() == false) { - mismatchDescription.appendText(" bulk operation was executed correctly what is not expected."); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(BulkResponse response, Description mismatchDescription) { + if (response.hasFailures() == false) { + mismatchDescription.appendText(" bulk operation was executed correctly what is not expected."); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("bulk operation failure"); - } + @Override + public void describeTo(Description description) { + description.appendText("bulk operation failure"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/FieldCapabilitiesResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/FieldCapabilitiesResponseMatchers.java index c5680f6055..2a78c7b71c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/FieldCapabilitiesResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/FieldCapabilitiesResponseMatchers.java @@ -15,18 +15,18 @@ public class FieldCapabilitiesResponseMatchers { - private FieldCapabilitiesResponseMatchers() {} + private FieldCapabilitiesResponseMatchers() {} - public static Matcher containsExactlyIndices(String... expectedIndices) { - return new ContainsExactlyIndicesMatcher(expectedIndices); - } + public static Matcher containsExactlyIndices(String... expectedIndices) { + return new ContainsExactlyIndicesMatcher(expectedIndices); + } - public static Matcher containsFieldWithNameAndType(String expectedFieldName, String expectedFieldType) { - return new ContainsFieldWithTypeMatcher(expectedFieldName, expectedFieldType); - } + public static Matcher containsFieldWithNameAndType(String expectedFieldName, String expectedFieldType) { + return new ContainsFieldWithTypeMatcher(expectedFieldName, expectedFieldType); + } - public static Matcher numberOfFieldsIsEqualTo(int expectedNumberOfFields) { - return new NumberOfFieldsIsEqualToMatcher(expectedNumberOfFields); - } + public static Matcher numberOfFieldsIsEqualTo(int expectedNumberOfFields) { + return new NumberOfFieldsIsEqualToMatcher(expectedNumberOfFields); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetAllPitsContainsExactlyIdsResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetAllPitsContainsExactlyIdsResponseMatcher.java index 447224345c..c568bfa9d7 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetAllPitsContainsExactlyIdsResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetAllPitsContainsExactlyIdsResponseMatcher.java @@ -22,27 +22,27 @@ class GetAllPitsContainsExactlyIdsResponseMatcher extends TypeSafeDiagnosingMatcher { - private final Set expectedPitIds; - - GetAllPitsContainsExactlyIdsResponseMatcher(String[] expectedPitIds) { - if (isNull(expectedPitIds) || 0 == expectedPitIds.length) { - throw new IllegalArgumentException("expectedPitIds cannot be null or empty"); - } - this.expectedPitIds = Set.of(expectedPitIds); - } - - @Override - protected boolean matchesSafely(GetAllPitNodesResponse response, Description mismatchDescription) { - Set actualPitIds = response.getPitInfos().stream().map(ListPitInfo::getPitId).collect(Collectors.toSet()); - if (!actualPitIds.equals(expectedPitIds)) { - mismatchDescription.appendText("Actual pit ids: ").appendValue(actualPitIds); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Should contain exactly pit with ids: ").appendValue(expectedPitIds); - } + private final Set expectedPitIds; + + GetAllPitsContainsExactlyIdsResponseMatcher(String[] expectedPitIds) { + if (isNull(expectedPitIds) || 0 == expectedPitIds.length) { + throw new IllegalArgumentException("expectedPitIds cannot be null or empty"); + } + this.expectedPitIds = Set.of(expectedPitIds); + } + + @Override + protected boolean matchesSafely(GetAllPitNodesResponse response, Description mismatchDescription) { + Set actualPitIds = response.getPitInfos().stream().map(ListPitInfo::getPitId).collect(Collectors.toSet()); + if (!actualPitIds.equals(expectedPitIds)) { + mismatchDescription.appendText("Actual pit ids: ").appendValue(actualPitIds); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Should contain exactly pit with ids: ").appendValue(expectedPitIds); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetIndexResponseContainsIndicesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetIndexResponseContainsIndicesMatcher.java index 43d1bce761..20f02b1319 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetIndexResponseContainsIndicesMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetIndexResponseContainsIndicesMatcher.java @@ -21,31 +21,29 @@ class GetIndexResponseContainsIndicesMatcher extends TypeSafeDiagnosingMatcher { - private final String[] expectedIndices; - - GetIndexResponseContainsIndicesMatcher(String[] expectedIndices) { - if (isNull(expectedIndices) || 0 == expectedIndices.length) { - throw new IllegalArgumentException("expectedIndices cannot be null or empty"); - } - this.expectedIndices = expectedIndices; - } - - @Override - protected boolean matchesSafely(GetIndexResponse response, Description mismatchDescription) { - List actual = Arrays.asList(response.getIndices()); - for (String index : expectedIndices) { - if (!actual.contains(index)) { - mismatchDescription - .appendText("Actual indices: ") - .appendValue(response.getIndices()); - return false; - } - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Response should contain indices: ").appendValue(expectedIndices); - } + private final String[] expectedIndices; + + GetIndexResponseContainsIndicesMatcher(String[] expectedIndices) { + if (isNull(expectedIndices) || 0 == expectedIndices.length) { + throw new IllegalArgumentException("expectedIndices cannot be null or empty"); + } + this.expectedIndices = expectedIndices; + } + + @Override + protected boolean matchesSafely(GetIndexResponse response, Description mismatchDescription) { + List actual = Arrays.asList(response.getIndices()); + for (String index : expectedIndices) { + if (!actual.contains(index)) { + mismatchDescription.appendText("Actual indices: ").appendValue(response.getIndices()); + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response should contain indices: ").appendValue(expectedIndices); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetMappingsResponseContainsIndicesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetMappingsResponseContainsIndicesMatcher.java index a246ec14b3..8e2b3da097 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetMappingsResponseContainsIndicesMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetMappingsResponseContainsIndicesMatcher.java @@ -21,31 +21,29 @@ class GetMappingsResponseContainsIndicesMatcher extends TypeSafeDiagnosingMatcher { - private final String[] expectedIndices; - - GetMappingsResponseContainsIndicesMatcher(String[] expectedIndices) { - if (isNull(expectedIndices) || 0 == expectedIndices.length) { - throw new IllegalArgumentException("expectedIndices cannot be null or empty"); - } - this.expectedIndices = expectedIndices; - } - - @Override - protected boolean matchesSafely(GetMappingsResponse response, Description mismatchDescription) { - Map indicesMappings = response.mappings(); - for (String index : expectedIndices) { - if (!indicesMappings.containsKey(index)) { - mismatchDescription - .appendText("Response contains mappings of indices: ") - .appendValue(indicesMappings.keySet()); - return false; - } - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Response should contain mappings of indices: ").appendValue(expectedIndices); - } + private final String[] expectedIndices; + + GetMappingsResponseContainsIndicesMatcher(String[] expectedIndices) { + if (isNull(expectedIndices) || 0 == expectedIndices.length) { + throw new IllegalArgumentException("expectedIndices cannot be null or empty"); + } + this.expectedIndices = expectedIndices; + } + + @Override + protected boolean matchesSafely(GetMappingsResponse response, Description mismatchDescription) { + Map indicesMappings = response.mappings(); + for (String index : expectedIndices) { + if (!indicesMappings.containsKey(index)) { + mismatchDescription.appendText("Response contains mappings of indices: ").appendValue(indicesMappings.keySet()); + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response should contain mappings of indices: ").appendValue(expectedIndices); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainOnlyDocumentIdMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainOnlyDocumentIdMatcher.java index b677d6e6e1..e6d7f02c6e 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainOnlyDocumentIdMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainOnlyDocumentIdMatcher.java @@ -18,34 +18,37 @@ class GetResponseContainOnlyDocumentIdMatcher extends TypeSafeDiagnosingMatcher { - private final String indexName; - private final String documentId; - - public GetResponseContainOnlyDocumentIdMatcher(String indexName, String documentId) { - this.indexName = requireNonNull(indexName, "Index name is required"); - this.documentId = requireNonNull(documentId, "Document id is required"); - } - - @Override - protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { - if(indexName.equals(response.getIndex()) == false ) { - mismatchDescription.appendText(" index name ").appendValue(response.getIndex()).appendText(" is incorrect "); - return false; - } - if(documentId.equals(response.getId()) == false) { - mismatchDescription.appendText(" id ").appendValue(response.getId()).appendText(" is incorrect "); - return false; - } - if(response.isExists()) { - mismatchDescription.appendText(" document exist what is not desired "); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Response should contain document id from index ").appendValue(indexName).appendText(" with id ") - .appendValue(documentId).appendText(" but document should not be present "); - } + private final String indexName; + private final String documentId; + + public GetResponseContainOnlyDocumentIdMatcher(String indexName, String documentId) { + this.indexName = requireNonNull(indexName, "Index name is required"); + this.documentId = requireNonNull(documentId, "Document id is required"); + } + + @Override + protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { + if (indexName.equals(response.getIndex()) == false) { + mismatchDescription.appendText(" index name ").appendValue(response.getIndex()).appendText(" is incorrect "); + return false; + } + if (documentId.equals(response.getId()) == false) { + mismatchDescription.appendText(" id ").appendValue(response.getId()).appendText(" is incorrect "); + return false; + } + if (response.isExists()) { + mismatchDescription.appendText(" document exist what is not desired "); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response should contain document id from index ") + .appendValue(indexName) + .appendText(" with id ") + .appendValue(documentId) + .appendText(" but document should not be present "); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainsDocumentWithIdMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainsDocumentWithIdMatcher.java index 5dd0c72576..aa9d702243 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainsDocumentWithIdMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseContainsDocumentWithIdMatcher.java @@ -18,38 +18,40 @@ class GetResponseContainsDocumentWithIdMatcher extends TypeSafeDiagnosingMatcher { - private final String indexName; - private final String documentId; - - public GetResponseContainsDocumentWithIdMatcher(String indexName, String documentId) { - this.indexName = requireNonNull(indexName, "Index name is required"); - this.documentId = requireNonNull(documentId, "Document id is required"); - } - - @Override - protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { - if(indexName.equals(response.getIndex()) == false ) { - mismatchDescription.appendText("Document should not belong to index ").appendValue(response.getIndex()); - return false; - } - if(documentId.equals(response.getId()) == false) { - mismatchDescription.appendText("Document contain incorrect id which is ").appendValue(response.getId()); - return false; - } - if(response.isExists() == false) { - mismatchDescription.appendText("Document does not exist or is inaccessible"); - return false; - } - if(response.isSourceEmpty()) { - mismatchDescription.appendText("Document source is empty"); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Response should contain document from index ").appendValue(indexName).appendText(" with id ") - .appendValue(documentId); - } + private final String indexName; + private final String documentId; + + public GetResponseContainsDocumentWithIdMatcher(String indexName, String documentId) { + this.indexName = requireNonNull(indexName, "Index name is required"); + this.documentId = requireNonNull(documentId, "Document id is required"); + } + + @Override + protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { + if (indexName.equals(response.getIndex()) == false) { + mismatchDescription.appendText("Document should not belong to index ").appendValue(response.getIndex()); + return false; + } + if (documentId.equals(response.getId()) == false) { + mismatchDescription.appendText("Document contain incorrect id which is ").appendValue(response.getId()); + return false; + } + if (response.isExists() == false) { + mismatchDescription.appendText("Document does not exist or is inaccessible"); + return false; + } + if (response.isSourceEmpty()) { + mismatchDescription.appendText("Document source is empty"); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response should contain document from index ") + .appendValue(indexName) + .appendText(" with id ") + .appendValue(documentId); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentContainsExactlyFieldsWithNamesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentContainsExactlyFieldsWithNamesMatcher.java index aa8daa0128..66bdc0a9b7 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentContainsExactlyFieldsWithNamesMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentContainsExactlyFieldsWithNamesMatcher.java @@ -21,29 +21,31 @@ class GetResponseDocumentContainsExactlyFieldsWithNamesMatcher extends TypeSafeDiagnosingMatcher { - private final Set expectedFieldsNames; - - GetResponseDocumentContainsExactlyFieldsWithNamesMatcher(String... expectedFieldsNames) { - if (isNull(expectedFieldsNames) || expectedFieldsNames.length == 0) { - throw new IllegalArgumentException("expectedFieldsNames cannot be null or empty"); - } - this.expectedFieldsNames = Set.of(expectedFieldsNames); - } - - @Override - protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { - Map sourceMap = response.getSourceAsMap(); - Set actualFieldsNames = sourceMap.keySet(); - if (!expectedFieldsNames.equals(actualFieldsNames)) { - mismatchDescription.appendValue("Document with id ").appendValue(response.getId()) - .appendText(" contains fields with names: ").appendValue(actualFieldsNames); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Document contain exactly fields with names: ").appendValue(expectedFieldsNames); - } + private final Set expectedFieldsNames; + + GetResponseDocumentContainsExactlyFieldsWithNamesMatcher(String... expectedFieldsNames) { + if (isNull(expectedFieldsNames) || expectedFieldsNames.length == 0) { + throw new IllegalArgumentException("expectedFieldsNames cannot be null or empty"); + } + this.expectedFieldsNames = Set.of(expectedFieldsNames); + } + + @Override + protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { + Map sourceMap = response.getSourceAsMap(); + Set actualFieldsNames = sourceMap.keySet(); + if (!expectedFieldsNames.equals(actualFieldsNames)) { + mismatchDescription.appendValue("Document with id ") + .appendValue(response.getId()) + .appendText(" contains fields with names: ") + .appendValue(actualFieldsNames); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Document contain exactly fields with names: ").appendValue(expectedFieldsNames); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentDoesNotContainFieldMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentDoesNotContainFieldMatcher.java index f8f81f0e95..508e5b8b61 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentDoesNotContainFieldMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentDoesNotContainFieldMatcher.java @@ -20,28 +20,28 @@ class GetResponseDocumentDoesNotContainFieldMatcher extends TypeSafeDiagnosingMatcher { - private final String fieldName; - - public GetResponseDocumentDoesNotContainFieldMatcher(String fieldName) { - this.fieldName = requireNonNull(fieldName, "Field name is required."); - } - - @Override - protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { - Map source = response.getSource(); - if(source == null) { - mismatchDescription.appendText("Source is not available in search results"); - return false; - } - if(source.containsKey(fieldName)) { - mismatchDescription.appendText("Document contains field ").appendValue(fieldName); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Document does not contain field ").appendValue(fieldName); - } + private final String fieldName; + + public GetResponseDocumentDoesNotContainFieldMatcher(String fieldName) { + this.fieldName = requireNonNull(fieldName, "Field name is required."); + } + + @Override + protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { + Map source = response.getSource(); + if (source == null) { + mismatchDescription.appendText("Source is not available in search results"); + return false; + } + if (source.containsKey(fieldName)) { + mismatchDescription.appendText("Document contains field ").appendValue(fieldName); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Document does not contain field ").appendValue(fieldName); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentFieldValueMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentFieldValueMatcher.java index 72418fe6e0..78bcae5494 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentFieldValueMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseDocumentFieldValueMatcher.java @@ -20,36 +20,38 @@ class GetResponseDocumentFieldValueMatcher extends TypeSafeDiagnosingMatcher { - private final String fieldName; - private final Object fieldValue; - - public GetResponseDocumentFieldValueMatcher(String fieldName, Object fieldValue) { - this.fieldName = requireNonNull(fieldName, "Field name is required."); - this.fieldValue = requireNonNull(fieldValue, "Field value is required."); - } - - @Override - protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { - Map source = response.getSource(); - if(source == null) { - mismatchDescription.appendText("Source is not available in search results"); - return false; - } - if(source.containsKey(fieldName) == false) { - mismatchDescription.appendText("Document does not contain field ").appendValue(fieldName); - return false; - } - Object actualFieldValue = source.get(fieldName); - if(fieldValue.equals(actualFieldValue) == false) { - mismatchDescription.appendText("Field ").appendValue(fieldName).appendText(" has incorrect value ") - .appendValue(actualFieldValue); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Document contain field ").appendValue(fieldName).appendText(" with value ").appendValue(fieldValue); - } + private final String fieldName; + private final Object fieldValue; + + public GetResponseDocumentFieldValueMatcher(String fieldName, Object fieldValue) { + this.fieldName = requireNonNull(fieldName, "Field name is required."); + this.fieldValue = requireNonNull(fieldValue, "Field value is required."); + } + + @Override + protected boolean matchesSafely(GetResponse response, Description mismatchDescription) { + Map source = response.getSource(); + if (source == null) { + mismatchDescription.appendText("Source is not available in search results"); + return false; + } + if (source.containsKey(fieldName) == false) { + mismatchDescription.appendText("Document does not contain field ").appendValue(fieldName); + return false; + } + Object actualFieldValue = source.get(fieldName); + if (fieldValue.equals(actualFieldValue) == false) { + mismatchDescription.appendText("Field ") + .appendValue(fieldName) + .appendText(" has incorrect value ") + .appendValue(actualFieldValue); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Document contain field ").appendValue(fieldName).appendText(" with value ").appendValue(fieldValue); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java index 6a494dc9cd..89c183fc34 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetResponseMatchers.java @@ -13,27 +13,27 @@ import org.opensearch.action.get.GetResponse; -public class GetResponseMatchers { +public class GetResponseMatchers { - private GetResponseMatchers() {} + private GetResponseMatchers() {} - public static Matcher containDocument(String indexName, String documentId) { - return new GetResponseContainsDocumentWithIdMatcher(indexName, documentId); - } + public static Matcher containDocument(String indexName, String documentId) { + return new GetResponseContainsDocumentWithIdMatcher(indexName, documentId); + } - public static Matcher containOnlyDocumentId(String indexName, String documentId) { - return new GetResponseContainOnlyDocumentIdMatcher(indexName, documentId); - } + public static Matcher containOnlyDocumentId(String indexName, String documentId) { + return new GetResponseContainOnlyDocumentIdMatcher(indexName, documentId); + } - public static Matcher documentContainField(String fieldName, Object fieldValue) { - return new GetResponseDocumentFieldValueMatcher(fieldName, fieldValue); - } + public static Matcher documentContainField(String fieldName, Object fieldValue) { + return new GetResponseDocumentFieldValueMatcher(fieldName, fieldValue); + } - public static Matcher documentDoesNotContainField(String fieldName) { - return new GetResponseDocumentDoesNotContainFieldMatcher(fieldName); - } + public static Matcher documentDoesNotContainField(String fieldName) { + return new GetResponseDocumentDoesNotContainFieldMatcher(fieldName); + } - public static Matcher documentContainsExactlyFieldsWithNames(String... expectedFieldsNames) { - return new GetResponseDocumentContainsExactlyFieldsWithNamesMatcher(expectedFieldsNames); - } + public static Matcher documentContainsExactlyFieldsWithNames(String... expectedFieldsNames) { + return new GetResponseDocumentContainsExactlyFieldsWithNamesMatcher(expectedFieldsNames); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java index e4ed4ac6c4..c96deef001 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java @@ -21,32 +21,30 @@ class GetSettingsResponseContainsIndicesMatcher extends TypeSafeDiagnosingMatcher { - private final String[] expectedIndices; - - GetSettingsResponseContainsIndicesMatcher(String[] expectedIndices) { - if (isNull(expectedIndices) || 0 == expectedIndices.length) { - throw new IllegalArgumentException("expectedIndices cannot be null or empty"); - } - this.expectedIndices = expectedIndices; - } - - @Override - protected boolean matchesSafely(GetSettingsResponse response, Description mismatchDescription) { - - final Map indexToSettings = response.getIndexToSettings(); - for (String index : expectedIndices) { - if (!indexToSettings.containsKey(index)) { - mismatchDescription - .appendText("Response contains settings of indices: ") - .appendValue(indexToSettings.keySet()); - return false; - } - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Response should contain settings of indices: ").appendValue(expectedIndices); - } + private final String[] expectedIndices; + + GetSettingsResponseContainsIndicesMatcher(String[] expectedIndices) { + if (isNull(expectedIndices) || 0 == expectedIndices.length) { + throw new IllegalArgumentException("expectedIndices cannot be null or empty"); + } + this.expectedIndices = expectedIndices; + } + + @Override + protected boolean matchesSafely(GetSettingsResponse response, Description mismatchDescription) { + + final Map indexToSettings = response.getIndexToSettings(); + for (String index : expectedIndices) { + if (!indexToSettings.containsKey(index)) { + mismatchDescription.appendText("Response contains settings of indices: ").appendValue(indexToSettings.keySet()); + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Response should contain settings of indices: ").appendValue(expectedIndices); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexExistsMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexExistsMatcher.java index 09e76c93e0..aab3d426d2 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexExistsMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexExistsMatcher.java @@ -21,26 +21,29 @@ class IndexExistsMatcher extends TypeSafeDiagnosingMatcher { - private final String expectedIndexName; - - IndexExistsMatcher(String expectedIndexName) { - this.expectedIndexName = requireNonNull(expectedIndexName); - } - - @Override - protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { - try(Client client = cluster.getInternalNodeClient()) { - IndicesExistsResponse indicesExistsResponse = client.admin().indices().exists(new IndicesExistsRequest(expectedIndexName)).actionGet(); - if (!indicesExistsResponse.isExists()) { - mismatchDescription.appendText("Index ").appendValue(expectedIndexName).appendValue(" does not exist"); - return false; - } - return true; - } - } - - @Override - public void describeTo(Description description) { - description.appendText("Index ").appendValue(expectedIndexName).appendText(" exists"); - } + private final String expectedIndexName; + + IndexExistsMatcher(String expectedIndexName) { + this.expectedIndexName = requireNonNull(expectedIndexName); + } + + @Override + protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { + try (Client client = cluster.getInternalNodeClient()) { + IndicesExistsResponse indicesExistsResponse = client.admin() + .indices() + .exists(new IndicesExistsRequest(expectedIndexName)) + .actionGet(); + if (!indicesExistsResponse.isExists()) { + mismatchDescription.appendText("Index ").appendValue(expectedIndexName).appendValue(" does not exist"); + return false; + } + return true; + } + } + + @Override + public void describeTo(Description description) { + description.appendText("Index ").appendValue(expectedIndexName).appendText(" exists"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexMappingIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexMappingIsEqualToMatcher.java index b90defef7d..ead0e9d1d7 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexMappingIsEqualToMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexMappingIsEqualToMatcher.java @@ -25,39 +25,43 @@ class IndexMappingIsEqualToMatcher extends TypeSafeDiagnosingMatcher { - private final String expectedIndexName; - private final Map expectedMapping; - - IndexMappingIsEqualToMatcher(String expectedIndexName, Map expectedMapping) { - this.expectedIndexName = requireNonNull(expectedIndexName); - if (isNull(expectedMapping) || expectedMapping.isEmpty()) { - throw new IllegalArgumentException("expectedMapping cannot be null or empty"); - } - this.expectedMapping = expectedMapping; - } - - @Override - protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { - try(Client client = cluster.getInternalNodeClient()) { - GetMappingsResponse response = client.admin().indices() - .getMappings(new GetMappingsRequest().indices(expectedIndexName)).actionGet(); - - Map actualIndexMapping = response.getMappings().get(expectedIndexName).sourceAsMap(); - - if (!expectedMapping.equals(actualIndexMapping)) { - mismatchDescription.appendText("Actual mapping ").appendValue(actualIndexMapping).appendText(" does not match expected"); - return false; - } - return true; - } catch (IndexNotFoundException e) { - mismatchDescription.appendText("Index: ").appendValue(expectedIndexName).appendText(" does not exist"); - return false; - } - } - - @Override - public void describeTo(Description description) { - description.appendText("Index ").appendValue(expectedIndexName) - .appendText(". Mapping should be equal to: ").appendValue(expectedMapping); - } + private final String expectedIndexName; + private final Map expectedMapping; + + IndexMappingIsEqualToMatcher(String expectedIndexName, Map expectedMapping) { + this.expectedIndexName = requireNonNull(expectedIndexName); + if (isNull(expectedMapping) || expectedMapping.isEmpty()) { + throw new IllegalArgumentException("expectedMapping cannot be null or empty"); + } + this.expectedMapping = expectedMapping; + } + + @Override + protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { + try (Client client = cluster.getInternalNodeClient()) { + GetMappingsResponse response = client.admin() + .indices() + .getMappings(new GetMappingsRequest().indices(expectedIndexName)) + .actionGet(); + + Map actualIndexMapping = response.getMappings().get(expectedIndexName).sourceAsMap(); + + if (!expectedMapping.equals(actualIndexMapping)) { + mismatchDescription.appendText("Actual mapping ").appendValue(actualIndexMapping).appendText(" does not match expected"); + return false; + } + return true; + } catch (IndexNotFoundException e) { + mismatchDescription.appendText("Index: ").appendValue(expectedIndexName).appendText(" does not exist"); + return false; + } + } + + @Override + public void describeTo(Description description) { + description.appendText("Index ") + .appendValue(expectedIndexName) + .appendText(". Mapping should be equal to: ") + .appendValue(expectedMapping); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexResponseMatchers.java index 80d90ecc6b..88f3ac099d 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexResponseMatchers.java @@ -22,36 +22,36 @@ public class IndexResponseMatchers { - public static Matcher isSuccessfulCreateIndexResponse(String expectedIndexName) { - return new SuccessfulCreateIndexResponseMatcher(expectedIndexName); - } + public static Matcher isSuccessfulCreateIndexResponse(String expectedIndexName) { + return new SuccessfulCreateIndexResponseMatcher(expectedIndexName); + } - public static Matcher getIndexResponseContainsIndices(String... expectedIndices) { - return new GetIndexResponseContainsIndicesMatcher(expectedIndices); - } + public static Matcher getIndexResponseContainsIndices(String... expectedIndices) { + return new GetIndexResponseContainsIndicesMatcher(expectedIndices); + } - public static Matcher isSuccessfulCloseIndexResponse() { - return new SuccessfulCloseIndexResponseMatcher(); - } + public static Matcher isSuccessfulCloseIndexResponse() { + return new SuccessfulCloseIndexResponseMatcher(); + } - public static Matcher isSuccessfulOpenIndexResponse() { - return new SuccessfulOpenIndexResponseMatcher(); - } + public static Matcher isSuccessfulOpenIndexResponse() { + return new SuccessfulOpenIndexResponseMatcher(); + } - public static Matcher isSuccessfulResizeResponse(String expectedIndexName) { - return new SuccessfulResizeResponseMatcher(expectedIndexName); - } + public static Matcher isSuccessfulResizeResponse(String expectedIndexName) { + return new SuccessfulResizeResponseMatcher(expectedIndexName); + } - public static Matcher getSettingsResponseContainsIndices(String... expectedIndices) { - return new GetSettingsResponseContainsIndicesMatcher(expectedIndices); - } + public static Matcher getSettingsResponseContainsIndices(String... expectedIndices) { + return new GetSettingsResponseContainsIndicesMatcher(expectedIndices); + } - public static Matcher isSuccessfulClearIndicesCacheResponse() { - return new SuccessfulClearIndicesCacheResponseMatcher(); - } + public static Matcher isSuccessfulClearIndicesCacheResponse() { + return new SuccessfulClearIndicesCacheResponseMatcher(); + } - public static Matcher getMappingsResponseContainsIndices(String... expectedIndices) { - return new GetMappingsResponseContainsIndicesMatcher(expectedIndices); - } + public static Matcher getMappingsResponseContainsIndices(String... expectedIndices) { + return new GetMappingsResponseContainsIndicesMatcher(expectedIndices); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexSettingsContainValuesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexSettingsContainValuesMatcher.java index 9af40fcd48..6bb89c6cae 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexSettingsContainValuesMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexSettingsContainValuesMatcher.java @@ -24,45 +24,52 @@ class IndexSettingsContainValuesMatcher extends TypeSafeDiagnosingMatcher { - private final String expectedIndexName; - private final Settings expectedSettings; + private final String expectedIndexName; + private final Settings expectedSettings; - IndexSettingsContainValuesMatcher(String expectedIndexName, Settings expectedSettings) { - this.expectedIndexName = requireNonNull(expectedIndexName); - if (isNull(expectedSettings) || expectedSettings.isEmpty()) { - throw new IllegalArgumentException("expectedSettings cannot be null or empty"); - } - this.expectedSettings = expectedSettings; - } + IndexSettingsContainValuesMatcher(String expectedIndexName, Settings expectedSettings) { + this.expectedIndexName = requireNonNull(expectedIndexName); + if (isNull(expectedSettings) || expectedSettings.isEmpty()) { + throw new IllegalArgumentException("expectedSettings cannot be null or empty"); + } + this.expectedSettings = expectedSettings; + } - @Override - protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { - try(Client client = cluster.getInternalNodeClient()) { - GetSettingsResponse response = client.admin().indices().getSettings(new GetSettingsRequest().indices(expectedIndexName)).actionGet(); + @Override + protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { + try (Client client = cluster.getInternalNodeClient()) { + GetSettingsResponse response = client.admin() + .indices() + .getSettings(new GetSettingsRequest().indices(expectedIndexName)) + .actionGet(); - Settings actualSettings = response.getIndexToSettings().get(expectedIndexName); + Settings actualSettings = response.getIndexToSettings().get(expectedIndexName); - for (String setting : expectedSettings.keySet()) { - if (isNull(actualSettings.get(setting))) { - mismatchDescription.appendValue("Value of ").appendValue(setting).appendText(" property is missing"); - return false; - } - if (!expectedSettings.get(setting).equals(actualSettings.get(setting))) { - mismatchDescription.appendText("Actual value of `").appendValue(setting) - .appendText("` property: ").appendValue(actualSettings.get(setting)); - return false; - } - } - return true; - } catch (IndexNotFoundException e) { - mismatchDescription.appendText("Index: ").appendValue(expectedIndexName).appendText(" does not exist"); - return false; - } - } + for (String setting : expectedSettings.keySet()) { + if (isNull(actualSettings.get(setting))) { + mismatchDescription.appendValue("Value of ").appendValue(setting).appendText(" property is missing"); + return false; + } + if (!expectedSettings.get(setting).equals(actualSettings.get(setting))) { + mismatchDescription.appendText("Actual value of `") + .appendValue(setting) + .appendText("` property: ") + .appendValue(actualSettings.get(setting)); + return false; + } + } + return true; + } catch (IndexNotFoundException e) { + mismatchDescription.appendText("Index: ").appendValue(expectedIndexName).appendText(" does not exist"); + return false; + } + } - @Override - public void describeTo(Description description) { - description.appendText("Settings of index ").appendValue(expectedIndexName) - .appendText(" should contain values: ").appendValue(expectedSettings); - } + @Override + public void describeTo(Description description) { + description.appendText("Settings of index ") + .appendValue(expectedIndexName) + .appendText(" should contain values: ") + .appendValue(expectedSettings); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexStateIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexStateIsEqualToMatcher.java index ecff3fe2df..87270b1388 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexStateIsEqualToMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/IndexStateIsEqualToMatcher.java @@ -24,36 +24,38 @@ class IndexStateIsEqualToMatcher extends TypeSafeDiagnosingMatcher { - private final String expectedIndexName; - private final IndexMetadata.State expectedState; - - IndexStateIsEqualToMatcher(String expectedIndexName, IndexMetadata.State expectedState) { - this.expectedIndexName = requireNonNull(expectedIndexName); - this.expectedState = requireNonNull(expectedState); - } - - @Override - protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { - try(Client client = cluster.getInternalNodeClient()) { - ClusterStateRequest clusterStateRequest = new ClusterStateRequest().indices(expectedIndexName); - ClusterStateResponse clusterStateResponse = client.admin().cluster().state(clusterStateRequest).actionGet(); - - Map indicesMetadata = clusterStateResponse.getState().getMetadata().indices(); - if (!indicesMetadata.containsKey(expectedIndexName)) { - mismatchDescription.appendValue("Index does not exist"); - } - IndexMetadata indexMetadata = indicesMetadata.get(expectedIndexName); - if (expectedState != indexMetadata.getState()) { - mismatchDescription.appendValue("Actual index state is equal to ").appendValue(indexMetadata.getState().name()); - return false; - } - return true; - } - } - - @Override - public void describeTo(Description description) { - description.appendText("Index: ").appendValue(expectedIndexName) - .appendText(" . State should be equal to ").appendValue(expectedState.name()); - } + private final String expectedIndexName; + private final IndexMetadata.State expectedState; + + IndexStateIsEqualToMatcher(String expectedIndexName, IndexMetadata.State expectedState) { + this.expectedIndexName = requireNonNull(expectedIndexName); + this.expectedState = requireNonNull(expectedState); + } + + @Override + protected boolean matchesSafely(LocalCluster cluster, Description mismatchDescription) { + try (Client client = cluster.getInternalNodeClient()) { + ClusterStateRequest clusterStateRequest = new ClusterStateRequest().indices(expectedIndexName); + ClusterStateResponse clusterStateResponse = client.admin().cluster().state(clusterStateRequest).actionGet(); + + Map indicesMetadata = clusterStateResponse.getState().getMetadata().indices(); + if (!indicesMetadata.containsKey(expectedIndexName)) { + mismatchDescription.appendValue("Index does not exist"); + } + IndexMetadata indexMetadata = indicesMetadata.get(expectedIndexName); + if (expectedState != indexMetadata.getState()) { + mismatchDescription.appendValue("Actual index state is equal to ").appendValue(indexMetadata.getState().name()); + return false; + } + return true; + } + } + + @Override + public void describeTo(Description description) { + description.appendText("Index: ") + .appendValue(expectedIndexName) + .appendText(" . State should be equal to ") + .appendValue(expectedState.name()); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiGetResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiGetResponseMatchers.java index 30b3037752..c2e86b1310 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiGetResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiGetResponseMatchers.java @@ -15,15 +15,14 @@ public class MultiGetResponseMatchers { - private MultiGetResponseMatchers() { - } + private MultiGetResponseMatchers() {} - public static Matcher isSuccessfulMultiGetResponse() { - return new SuccessfulMultiGetResponseMatcher(); - } + public static Matcher isSuccessfulMultiGetResponse() { + return new SuccessfulMultiGetResponseMatcher(); + } - public static Matcher numberOfGetItemResponsesIsEqualTo(int expectedNumberOfResponses) { - return new NumberOfGetItemResponsesIsEqualToMatcher(expectedNumberOfResponses); - } + public static Matcher numberOfGetItemResponsesIsEqualTo(int expectedNumberOfResponses) { + return new NumberOfGetItemResponsesIsEqualToMatcher(expectedNumberOfResponses); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiSearchResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiSearchResponseMatchers.java index 39a2645ce4..9709249d11 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiSearchResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/MultiSearchResponseMatchers.java @@ -15,15 +15,14 @@ public class MultiSearchResponseMatchers { - private MultiSearchResponseMatchers() { - } + private MultiSearchResponseMatchers() {} - public static Matcher isSuccessfulMultiSearchResponse() { - return new SuccessfulMultiSearchResponseMatcher(); - } + public static Matcher isSuccessfulMultiSearchResponse() { + return new SuccessfulMultiSearchResponseMatcher(); + } - public static Matcher numberOfSearchItemResponsesIsEqualTo(int expectedNumberOfResponses) { - return new NumberOfSearchItemResponsesIsEqualToMatcher(expectedNumberOfResponses); - } + public static Matcher numberOfSearchItemResponsesIsEqualTo(int expectedNumberOfResponses) { + return new NumberOfSearchItemResponsesIsEqualToMatcher(expectedNumberOfResponses); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfFieldsIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfFieldsIsEqualToMatcher.java index a11d360b98..ad8e9725c3 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfFieldsIsEqualToMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfFieldsIsEqualToMatcher.java @@ -16,24 +16,23 @@ class NumberOfFieldsIsEqualToMatcher extends TypeSafeDiagnosingMatcher { - private final int expectedNumberOfFields; + private final int expectedNumberOfFields; - NumberOfFieldsIsEqualToMatcher(int expectedNumberOfFields) { - this.expectedNumberOfFields = expectedNumberOfFields; - } + NumberOfFieldsIsEqualToMatcher(int expectedNumberOfFields) { + this.expectedNumberOfFields = expectedNumberOfFields; + } - @Override - protected boolean matchesSafely(FieldCapabilitiesResponse response, Description mismatchDescription) { - if (expectedNumberOfFields != response.get().size()) { - mismatchDescription.appendText("Actual number of fields: ").appendValue(response.get().size()); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(FieldCapabilitiesResponse response, Description mismatchDescription) { + if (expectedNumberOfFields != response.get().size()) { + mismatchDescription.appendText("Actual number of fields: ").appendValue(response.get().size()); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Response contains information about ") - .appendValue(expectedNumberOfFields).appendText(" fields"); - } + @Override + public void describeTo(Description description) { + description.appendText("Response contains information about ").appendValue(expectedNumberOfFields).appendText(" fields"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfGetItemResponsesIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfGetItemResponsesIsEqualToMatcher.java index 141b235f2f..38cfaeb130 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfGetItemResponsesIsEqualToMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfGetItemResponsesIsEqualToMatcher.java @@ -16,25 +16,23 @@ class NumberOfGetItemResponsesIsEqualToMatcher extends TypeSafeDiagnosingMatcher { - private final int expectedNumberOfResponses; - - NumberOfGetItemResponsesIsEqualToMatcher(int expectedNumberOfResponses) { - this.expectedNumberOfResponses = expectedNumberOfResponses; - } - - - @Override - protected boolean matchesSafely(MultiGetResponse response, Description mismatchDescription) { - if (expectedNumberOfResponses != response.getResponses().length) { - mismatchDescription.appendText("Actual number of responses: ").appendValue(response.getResponses().length); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Multi get response contains: ").appendValue(expectedNumberOfResponses) - .appendText(" item responses"); - } + private final int expectedNumberOfResponses; + + NumberOfGetItemResponsesIsEqualToMatcher(int expectedNumberOfResponses) { + this.expectedNumberOfResponses = expectedNumberOfResponses; + } + + @Override + protected boolean matchesSafely(MultiGetResponse response, Description mismatchDescription) { + if (expectedNumberOfResponses != response.getResponses().length) { + mismatchDescription.appendText("Actual number of responses: ").appendValue(response.getResponses().length); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Multi get response contains: ").appendValue(expectedNumberOfResponses).appendText(" item responses"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfHitsInPageIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfHitsInPageIsEqualToMatcher.java index b8671bb885..8a25a336f3 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfHitsInPageIsEqualToMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfHitsInPageIsEqualToMatcher.java @@ -17,29 +17,29 @@ class NumberOfHitsInPageIsEqualToMatcher extends TypeSafeDiagnosingMatcher { - private final int expectedNumberOfHits; + private final int expectedNumberOfHits; - public NumberOfHitsInPageIsEqualToMatcher(int expectedNumberOfHits) { - this.expectedNumberOfHits = expectedNumberOfHits; - } + public NumberOfHitsInPageIsEqualToMatcher(int expectedNumberOfHits) { + this.expectedNumberOfHits = expectedNumberOfHits; + } - @Override - protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { - SearchHits hits = searchResponse.getHits(); - if((hits == null) || (hits.getHits() == null)) { - mismatchDescription.appendText("contains null hits"); - return false; - } - int actualNumberOfHits = hits.getHits().length; - if(expectedNumberOfHits != actualNumberOfHits) { - mismatchDescription.appendText("actual number of hits is equal to ").appendValue(actualNumberOfHits); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + SearchHits hits = searchResponse.getHits(); + if ((hits == null) || (hits.getHits() == null)) { + mismatchDescription.appendText("contains null hits"); + return false; + } + int actualNumberOfHits = hits.getHits().length; + if (expectedNumberOfHits != actualNumberOfHits) { + mismatchDescription.appendText("actual number of hits is equal to ").appendValue(actualNumberOfHits); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Number of hits on current page should be equal to ").appendValue(expectedNumberOfHits); - } + @Override + public void describeTo(Description description) { + description.appendText("Number of hits on current page should be equal to ").appendValue(expectedNumberOfHits); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfSearchItemResponsesIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfSearchItemResponsesIsEqualToMatcher.java index 971473148c..54bb83cba7 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfSearchItemResponsesIsEqualToMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfSearchItemResponsesIsEqualToMatcher.java @@ -16,26 +16,24 @@ class NumberOfSearchItemResponsesIsEqualToMatcher extends TypeSafeDiagnosingMatcher { - private final int expectedNumberOfResponses; - - NumberOfSearchItemResponsesIsEqualToMatcher(int expectedNumberOfResponses) { - this.expectedNumberOfResponses = expectedNumberOfResponses; - } - - - @Override - protected boolean matchesSafely(MultiSearchResponse response, Description mismatchDescription) { - if (expectedNumberOfResponses != response.getResponses().length) { - mismatchDescription.appendText("Actual number of responses: ").appendValue(response.getResponses().length); - return false; - } - - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Multi search response contains: ").appendValue(expectedNumberOfResponses) - .appendText(" item responses"); - } + private final int expectedNumberOfResponses; + + NumberOfSearchItemResponsesIsEqualToMatcher(int expectedNumberOfResponses) { + this.expectedNumberOfResponses = expectedNumberOfResponses; + } + + @Override + protected boolean matchesSafely(MultiSearchResponse response, Description mismatchDescription) { + if (expectedNumberOfResponses != response.getResponses().length) { + mismatchDescription.appendText("Actual number of responses: ").appendValue(response.getResponses().length); + return false; + } + + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Multi search response contains: ").appendValue(expectedNumberOfResponses).appendText(" item responses"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfTotalHitsIsEqualToMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfTotalHitsIsEqualToMatcher.java index 3045da00b1..3f2b379498 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfTotalHitsIsEqualToMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/NumberOfTotalHitsIsEqualToMatcher.java @@ -21,37 +21,39 @@ class NumberOfTotalHitsIsEqualToMatcher extends TypeSafeDiagnosingMatcher { - private final int expectedNumberOfHits; - - NumberOfTotalHitsIsEqualToMatcher(int expectedNumberOfHits) { - this.expectedNumberOfHits = expectedNumberOfHits; - } - - @Override - protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { - SearchHits hits = searchResponse.getHits(); - if(hits == null) { - mismatchDescription.appendText("contains null hits"); - return false; - } - TotalHits totalHits = hits.getTotalHits(); - if(totalHits == null) { - mismatchDescription.appendText("Total hits number is null."); - return false; - } - if(expectedNumberOfHits != totalHits.value) { - String documentIds = Arrays.stream(searchResponse.getHits().getHits()) - .map(hit -> hit.getIndex() + "/" + hit.getId()) - .collect(Collectors.joining(",")); - mismatchDescription.appendText( "contains ").appendValue(hits.getHits().length).appendText(" hits, found document ids ") - .appendValue(documentIds); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Search response should contains ").appendValue(expectedNumberOfHits).appendText(" hits"); - } + private final int expectedNumberOfHits; + + NumberOfTotalHitsIsEqualToMatcher(int expectedNumberOfHits) { + this.expectedNumberOfHits = expectedNumberOfHits; + } + + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + SearchHits hits = searchResponse.getHits(); + if (hits == null) { + mismatchDescription.appendText("contains null hits"); + return false; + } + TotalHits totalHits = hits.getTotalHits(); + if (totalHits == null) { + mismatchDescription.appendText("Total hits number is null."); + return false; + } + if (expectedNumberOfHits != totalHits.value) { + String documentIds = Arrays.stream(searchResponse.getHits().getHits()) + .map(hit -> hit.getIndex() + "/" + hit.getId()) + .collect(Collectors.joining(",")); + mismatchDescription.appendText("contains ") + .appendValue(hits.getHits().length) + .appendText(" hits, found document ids ") + .appendValue(documentIds); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Search response should contains ").appendValue(expectedNumberOfHits).appendText(" hits"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchExceptionMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchExceptionMatchers.java index 008922b4e8..8627e4bda2 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchExceptionMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchExceptionMatchers.java @@ -17,21 +17,21 @@ public class OpenSearchExceptionMatchers { - private OpenSearchExceptionMatchers() {} + private OpenSearchExceptionMatchers() {} - public static Matcher statusException(RestStatus expectedRestStatus) { - return new OpenSearchStatusExceptionMatcher(expectedRestStatus); - } + public static Matcher statusException(RestStatus expectedRestStatus) { + return new OpenSearchStatusExceptionMatcher(expectedRestStatus); + } - public static Matcher errorMessage(Matcher errorMessageMatcher) { - return new ExceptionErrorMessageMatcher(errorMessageMatcher); - } + public static Matcher errorMessage(Matcher errorMessageMatcher) { + return new ExceptionErrorMessageMatcher(errorMessageMatcher); + } - public static Matcher errorMessageContain(String errorMessage) { - return errorMessage(containsString(errorMessage)); - } + public static Matcher errorMessageContain(String errorMessage) { + return errorMessage(containsString(errorMessage)); + } - public static Matcher hasCause(Class clazz) { - return new ExceptionHasCauseMatcher(clazz); - } + public static Matcher hasCause(Class clazz) { + return new ExceptionHasCauseMatcher(clazz); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchStatusExceptionMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchStatusExceptionMatcher.java index 863f1e52a1..118251ec04 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchStatusExceptionMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/OpenSearchStatusExceptionMatcher.java @@ -19,30 +19,34 @@ class OpenSearchStatusExceptionMatcher extends TypeSafeDiagnosingMatcher { - private final RestStatus expectedRestStatus; - - public OpenSearchStatusExceptionMatcher(RestStatus expectedRestStatus) { - this.expectedRestStatus = requireNonNull(expectedRestStatus, "Expected rest status is required."); - } - - @Override - protected boolean matchesSafely(Throwable throwable, Description mismatchDescription) { - if((throwable instanceof OpenSearchException) == false) { - mismatchDescription.appendText("actual exception type is ").appendValue(throwable.getClass().getCanonicalName()) - .appendText(", error message ").appendValue(throwable.getMessage()); - return false; - } - OpenSearchException openSearchException = (OpenSearchException) throwable; - if(expectedRestStatus.equals(openSearchException.status()) == false) { - mismatchDescription.appendText("actual status code is ").appendValue(openSearchException.status()) - .appendText(", error message ").appendValue(throwable.getMessage()); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("OpenSearchException with status code ").appendValue(expectedRestStatus); - } + private final RestStatus expectedRestStatus; + + public OpenSearchStatusExceptionMatcher(RestStatus expectedRestStatus) { + this.expectedRestStatus = requireNonNull(expectedRestStatus, "Expected rest status is required."); + } + + @Override + protected boolean matchesSafely(Throwable throwable, Description mismatchDescription) { + if ((throwable instanceof OpenSearchException) == false) { + mismatchDescription.appendText("actual exception type is ") + .appendValue(throwable.getClass().getCanonicalName()) + .appendText(", error message ") + .appendValue(throwable.getMessage()); + return false; + } + OpenSearchException openSearchException = (OpenSearchException) throwable; + if (expectedRestStatus.equals(openSearchException.status()) == false) { + mismatchDescription.appendText("actual status code is ") + .appendValue(openSearchException.status()) + .appendText(", error message ") + .appendValue(throwable.getMessage()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("OpenSearchException with status code ").appendValue(expectedRestStatus); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/PitResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/PitResponseMatchers.java index 3e72866092..84ab459210 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/PitResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/PitResponseMatchers.java @@ -17,22 +17,21 @@ public class PitResponseMatchers { - private PitResponseMatchers() { - } + private PitResponseMatchers() {} - public static Matcher isSuccessfulCreatePitResponse() { - return new SuccessfulCreatePitResponseMatcher(); - } + public static Matcher isSuccessfulCreatePitResponse() { + return new SuccessfulCreatePitResponseMatcher(); + } - public static Matcher getAllResponseContainsExactlyPitWithIds(String... expectedPitIds) { - return new GetAllPitsContainsExactlyIdsResponseMatcher(expectedPitIds); - } + public static Matcher getAllResponseContainsExactlyPitWithIds(String... expectedPitIds) { + return new GetAllPitsContainsExactlyIdsResponseMatcher(expectedPitIds); + } - public static Matcher isSuccessfulDeletePitResponse() { - return new SuccessfulDeletePitResponseMatcher(); - } + public static Matcher isSuccessfulDeletePitResponse() { + return new SuccessfulDeletePitResponseMatcher(); + } - public static Matcher deleteResponseContainsExactlyPitWithIds(String... expectedPitIds) { - return new DeletePitContainsExactlyIdsResponseMatcher(expectedPitIds); - } + public static Matcher deleteResponseContainsExactlyPitWithIds(String... expectedPitIds) { + return new DeletePitContainsExactlyIdsResponseMatcher(expectedPitIds); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitContainsFieldWithValueMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitContainsFieldWithValueMatcher.java index aa9cfe6864..c92924ebfe 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitContainsFieldWithValueMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitContainsFieldWithValueMatcher.java @@ -21,49 +21,54 @@ class SearchHitContainsFieldWithValueMatcher extends TypeSafeDiagnosingMatcher { - private final int hitIndex; + private final int hitIndex; - private final String fieldName; + private final String fieldName; - private final T expectedValue; + private final T expectedValue; - SearchHitContainsFieldWithValueMatcher(int hitIndex, String fieldName, T expectedValue) { - this.hitIndex = hitIndex; - this.fieldName = fieldName; - this.expectedValue = expectedValue; - } + SearchHitContainsFieldWithValueMatcher(int hitIndex, String fieldName, T expectedValue) { + this.hitIndex = hitIndex; + this.fieldName = fieldName; + this.expectedValue = expectedValue; + } - @Override protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { - Long numberOfHits = readTotalHits(searchResponse); - if(numberOfHits == null) { - mismatchDescription.appendText("Total number of hits is unknown."); - return false; - } - if(hitIndex >= numberOfHits) { - mismatchDescription.appendText("Search result contain only ").appendValue(numberOfHits).appendText(" hits"); - return false; - } - SearchHit searchHit = searchResponse.getHits().getAt(hitIndex); - Map source = searchHit.getSourceAsMap(); - if(source == null){ - mismatchDescription.appendText("Source document is null, is fetch source option set to true?"); - return false; - } - if(source.containsKey(fieldName) == false) { - mismatchDescription.appendText("Document does not contain field ").appendValue(fieldName); - return false; - } - Object actualValue = source.get(fieldName); - if(!expectedValue.equals(actualValue)) { - mismatchDescription.appendText("Field value is equal to ").appendValue(actualValue); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + Long numberOfHits = readTotalHits(searchResponse); + if (numberOfHits == null) { + mismatchDescription.appendText("Total number of hits is unknown."); + return false; + } + if (hitIndex >= numberOfHits) { + mismatchDescription.appendText("Search result contain only ").appendValue(numberOfHits).appendText(" hits"); + return false; + } + SearchHit searchHit = searchResponse.getHits().getAt(hitIndex); + Map source = searchHit.getSourceAsMap(); + if (source == null) { + mismatchDescription.appendText("Source document is null, is fetch source option set to true?"); + return false; + } + if (source.containsKey(fieldName) == false) { + mismatchDescription.appendText("Document does not contain field ").appendValue(fieldName); + return false; + } + Object actualValue = source.get(fieldName); + if (!expectedValue.equals(actualValue)) { + mismatchDescription.appendText("Field value is equal to ").appendValue(actualValue); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Search hit with index ").appendValue(hitIndex).appendText(" should contain field ").appendValue(fieldName) - .appendValue(" with value equal to ").appendValue(expectedValue); - } + @Override + public void describeTo(Description description) { + description.appendText("Search hit with index ") + .appendValue(hitIndex) + .appendText(" should contain field ") + .appendValue(fieldName) + .appendValue(" with value equal to ") + .appendValue(expectedValue); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitDoesNotContainFieldMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitDoesNotContainFieldMatcher.java index 106f2ee943..0562acdcbb 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitDoesNotContainFieldMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitDoesNotContainFieldMatcher.java @@ -22,42 +22,44 @@ class SearchHitDoesNotContainFieldMatcher extends TypeSafeDiagnosingMatcher { - private final int hitIndex; - - private final String fieldName; - - public SearchHitDoesNotContainFieldMatcher(int hitIndex, String fieldName) { - this.hitIndex = hitIndex; - this.fieldName = requireNonNull(fieldName, "Field name is required."); - } - - @Override - protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { - Long numberOfHits = readTotalHits(searchResponse); - if(numberOfHits == null) { - mismatchDescription.appendText("Total number of hits is unknown."); - return false; - } - if(hitIndex >= numberOfHits) { - mismatchDescription.appendText("Search result contain only ").appendValue(numberOfHits).appendText(" hits"); - return false; - } - SearchHit searchHit = searchResponse.getHits().getAt(hitIndex); - Map source = searchHit.getSourceAsMap(); - if(source == null){ - mismatchDescription.appendText("Source document is null, is fetch source option set to true?"); - return false; - } - if(source.containsKey(fieldName)) { - mismatchDescription.appendText(" document contains field ").appendValue(fieldName); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("search hit with index ").appendValue(hitIndex).appendText(" does not contain field ") - .appendValue(fieldName); - } + private final int hitIndex; + + private final String fieldName; + + public SearchHitDoesNotContainFieldMatcher(int hitIndex, String fieldName) { + this.hitIndex = hitIndex; + this.fieldName = requireNonNull(fieldName, "Field name is required."); + } + + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + Long numberOfHits = readTotalHits(searchResponse); + if (numberOfHits == null) { + mismatchDescription.appendText("Total number of hits is unknown."); + return false; + } + if (hitIndex >= numberOfHits) { + mismatchDescription.appendText("Search result contain only ").appendValue(numberOfHits).appendText(" hits"); + return false; + } + SearchHit searchHit = searchResponse.getHits().getAt(hitIndex); + Map source = searchHit.getSourceAsMap(); + if (source == null) { + mismatchDescription.appendText("Source document is null, is fetch source option set to true?"); + return false; + } + if (source.containsKey(fieldName)) { + mismatchDescription.appendText(" document contains field ").appendValue(fieldName); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("search hit with index ") + .appendValue(hitIndex) + .appendText(" does not contain field ") + .appendValue(fieldName); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentWithIdMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentWithIdMatcher.java index c3a7528432..d21df7a578 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentWithIdMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentWithIdMatcher.java @@ -19,42 +19,46 @@ class SearchHitsContainDocumentWithIdMatcher extends TypeSafeDiagnosingMatcher { - private final int hitIndex; - private final String indexName; - private final String id; - - public SearchHitsContainDocumentWithIdMatcher(int hitIndex, String indexName, String id) { - this.hitIndex = hitIndex; - this.indexName = indexName; - this.id = id; - } - - @Override - protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { - Long numberOfHits = readTotalHits(searchResponse); - if(numberOfHits == null) { - mismatchDescription.appendText("Number of total hits is unknown."); - return false; - } - if(hitIndex >= numberOfHits) { - mismatchDescription.appendText("Search result contain only ").appendValue(numberOfHits).appendText(" hits"); - return false; - } - SearchHit searchHit = searchResponse.getHits().getAt(hitIndex); - if(indexName.equals(searchHit.getIndex()) == false) { - mismatchDescription.appendText("document is part of another index ").appendValue(indexName); - return false; - } - if(id.equals(searchHit.getId()) == false) { - mismatchDescription.appendText("Document has another id which is ").appendValue(searchHit.getId()); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Search hit with index ").appendValue(hitIndex).appendText(" should contains document which is part of index ") - .appendValue(indexName).appendValue(" and has id ").appendValue(id); - } + private final int hitIndex; + private final String indexName; + private final String id; + + public SearchHitsContainDocumentWithIdMatcher(int hitIndex, String indexName, String id) { + this.hitIndex = hitIndex; + this.indexName = indexName; + this.id = id; + } + + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + Long numberOfHits = readTotalHits(searchResponse); + if (numberOfHits == null) { + mismatchDescription.appendText("Number of total hits is unknown."); + return false; + } + if (hitIndex >= numberOfHits) { + mismatchDescription.appendText("Search result contain only ").appendValue(numberOfHits).appendText(" hits"); + return false; + } + SearchHit searchHit = searchResponse.getHits().getAt(hitIndex); + if (indexName.equals(searchHit.getIndex()) == false) { + mismatchDescription.appendText("document is part of another index ").appendValue(indexName); + return false; + } + if (id.equals(searchHit.getId()) == false) { + mismatchDescription.appendText("Document has another id which is ").appendValue(searchHit.getId()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Search hit with index ") + .appendValue(hitIndex) + .appendText(" should contains document which is part of index ") + .appendValue(indexName) + .appendValue(" and has id ") + .appendValue(id); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentsInAnyOrderMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentsInAnyOrderMatcher.java index 78fd20557e..28bf13e321 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentsInAnyOrderMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchHitsContainDocumentsInAnyOrderMatcher.java @@ -26,49 +26,51 @@ class SearchHitsContainDocumentsInAnyOrderMatcher extends TypeSafeDiagnosingMatcher { - /** - * Pair contain index name and document id - */ - private final List> documentIds; + /** + * Pair contain index name and document id + */ + private final List> documentIds; - /** - * - * @param documentIds Pair contain index name and document id - */ - public SearchHitsContainDocumentsInAnyOrderMatcher(List> documentIds) { - this.documentIds = requireNonNull(documentIds, "Document ids are required."); - } + /** + * + * @param documentIds Pair contain index name and document id + */ + public SearchHitsContainDocumentsInAnyOrderMatcher(List> documentIds) { + this.documentIds = requireNonNull(documentIds, "Document ids are required."); + } - @Override - protected boolean matchesSafely(SearchResponse response, Description mismatchDescription) { - SearchHits hits = response.getHits(); - if(hits == null) { - mismatchDescription.appendText("Search response does not contains hits (null)."); - return false; - } - SearchHit[] hitsArray = hits.getHits(); - if(hitsArray == null) { - mismatchDescription.appendText("Search hits array is null"); - return false; - } - Set> actualDocumentIds = Arrays.stream(hitsArray) - .map(result -> Pair.of(result.getIndex(), result.getId())) - .collect(Collectors.toSet()); - for(Pair desiredDocumentId : documentIds) { - if(actualDocumentIds.contains(desiredDocumentId) == false) { - mismatchDescription.appendText("search result does not contain document with id ") - .appendValue(desiredDocumentId.getKey()).appendText("/").appendValue(desiredDocumentId.getValue()); - return false; - } - } - return true; - } + @Override + protected boolean matchesSafely(SearchResponse response, Description mismatchDescription) { + SearchHits hits = response.getHits(); + if (hits == null) { + mismatchDescription.appendText("Search response does not contains hits (null)."); + return false; + } + SearchHit[] hitsArray = hits.getHits(); + if (hitsArray == null) { + mismatchDescription.appendText("Search hits array is null"); + return false; + } + Set> actualDocumentIds = Arrays.stream(hitsArray) + .map(result -> Pair.of(result.getIndex(), result.getId())) + .collect(Collectors.toSet()); + for (Pair desiredDocumentId : documentIds) { + if (actualDocumentIds.contains(desiredDocumentId) == false) { + mismatchDescription.appendText("search result does not contain document with id ") + .appendValue(desiredDocumentId.getKey()) + .appendText("/") + .appendValue(desiredDocumentId.getValue()); + return false; + } + } + return true; + } - @Override - public void describeTo(Description description) { - String documentIdsString = documentIds.stream() - .map(pair -> pair.getKey() + "/" + pair.getValue()) - .collect(Collectors.joining(", ")); - description.appendText("Search response should contains following documents ").appendValue(documentIdsString); - } + @Override + public void describeTo(Description description) { + String documentIdsString = documentIds.stream() + .map(pair -> pair.getKey() + "/" + pair.getValue()) + .collect(Collectors.joining(", ")); + description.appendText("Search response should contains following documents ").appendValue(documentIdsString); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java index acb669a85e..c0a484b47c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java @@ -22,64 +22,66 @@ public class SearchResponseMatchers { - private SearchResponseMatchers() {} - - public static Matcher isSuccessfulSearchResponse() { - return new SuccessfulSearchResponseMatcher(); - } - - public static Matcher numberOfTotalHitsIsEqualTo(int expectedNumberOfHits) { - return new NumberOfTotalHitsIsEqualToMatcher(expectedNumberOfHits); - } - - public static Matcher numberOfHitsInPageIsEqualTo(int expectedNumberOfHits) { - return new NumberOfHitsInPageIsEqualToMatcher(expectedNumberOfHits); - } - - public static Matcher searchHitContainsFieldWithValue(int hitIndex, String fieldName, T expectedValue) { - return new SearchHitContainsFieldWithValueMatcher<>(hitIndex, fieldName, expectedValue); - } - - public static Matcher searchHitDoesNotContainField(int hitIndex, String fieldName) { - return new SearchHitDoesNotContainFieldMatcher(hitIndex, fieldName); - } - - - public static Matcher searchHitsContainDocumentWithId(int hitIndex, String indexName, String documentId) { - return new SearchHitsContainDocumentWithIdMatcher(hitIndex, indexName, documentId); - } - - public static Matcher restStatusIs(RestStatus expectedRestStatus) { - return new SearchResponseWithStatusCodeMatcher(expectedRestStatus); - } - - public static Matcher containNotEmptyScrollingId() { - return new ContainNotEmptyScrollingIdMatcher(); - } - - public static Matcher containAggregationWithNameAndType(String expectedAggregationName, String expectedAggregationType) { - return new ContainsAggregationWithNameAndTypeMatcher(expectedAggregationName, expectedAggregationType); - } - - /** - * Matcher checks if search result contains all expected documents - * - * @param documentIds Pair contain index name and document id - * @return matcher - */ - public static Matcher searchHitsContainDocumentsInAnyOrder(List> documentIds) { - return new SearchHitsContainDocumentsInAnyOrderMatcher(documentIds); - } - - public static Matcher searchHitsContainDocumentsInAnyOrder(Pair...documentIds) { - return new SearchHitsContainDocumentsInAnyOrderMatcher(Arrays.asList(documentIds)); - } - - static Long readTotalHits(SearchResponse searchResponse) { - return Optional.ofNullable(searchResponse) - .map(SearchResponse::getHits) - .map(SearchHits::getTotalHits) - .map(totalHits -> totalHits.value) - .orElse(null); - } + private SearchResponseMatchers() {} + + public static Matcher isSuccessfulSearchResponse() { + return new SuccessfulSearchResponseMatcher(); + } + + public static Matcher numberOfTotalHitsIsEqualTo(int expectedNumberOfHits) { + return new NumberOfTotalHitsIsEqualToMatcher(expectedNumberOfHits); + } + + public static Matcher numberOfHitsInPageIsEqualTo(int expectedNumberOfHits) { + return new NumberOfHitsInPageIsEqualToMatcher(expectedNumberOfHits); + } + + public static Matcher searchHitContainsFieldWithValue(int hitIndex, String fieldName, T expectedValue) { + return new SearchHitContainsFieldWithValueMatcher<>(hitIndex, fieldName, expectedValue); + } + + public static Matcher searchHitDoesNotContainField(int hitIndex, String fieldName) { + return new SearchHitDoesNotContainFieldMatcher(hitIndex, fieldName); + } + + public static Matcher searchHitsContainDocumentWithId(int hitIndex, String indexName, String documentId) { + return new SearchHitsContainDocumentWithIdMatcher(hitIndex, indexName, documentId); + } + + public static Matcher restStatusIs(RestStatus expectedRestStatus) { + return new SearchResponseWithStatusCodeMatcher(expectedRestStatus); + } + + public static Matcher containNotEmptyScrollingId() { + return new ContainNotEmptyScrollingIdMatcher(); + } + + public static Matcher containAggregationWithNameAndType( + String expectedAggregationName, + String expectedAggregationType + ) { + return new ContainsAggregationWithNameAndTypeMatcher(expectedAggregationName, expectedAggregationType); + } + + /** + * Matcher checks if search result contains all expected documents + * + * @param documentIds Pair contain index name and document id + * @return matcher + */ + public static Matcher searchHitsContainDocumentsInAnyOrder(List> documentIds) { + return new SearchHitsContainDocumentsInAnyOrderMatcher(documentIds); + } + + public static Matcher searchHitsContainDocumentsInAnyOrder(Pair... documentIds) { + return new SearchHitsContainDocumentsInAnyOrderMatcher(Arrays.asList(documentIds)); + } + + static Long readTotalHits(SearchResponse searchResponse) { + return Optional.ofNullable(searchResponse) + .map(SearchResponse::getHits) + .map(SearchHits::getTotalHits) + .map(totalHits -> totalHits.value) + .orElse(null); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseWithStatusCodeMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseWithStatusCodeMatcher.java index 8316cc3425..ede13bcb56 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseWithStatusCodeMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseWithStatusCodeMatcher.java @@ -17,23 +17,23 @@ class SearchResponseWithStatusCodeMatcher extends TypeSafeDiagnosingMatcher { - private final RestStatus expectedRestStatus; + private final RestStatus expectedRestStatus; - public SearchResponseWithStatusCodeMatcher(RestStatus expectedRestStatus) { - this.expectedRestStatus = expectedRestStatus; - } + public SearchResponseWithStatusCodeMatcher(RestStatus expectedRestStatus) { + this.expectedRestStatus = expectedRestStatus; + } - @Override - protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { - if(expectedRestStatus.equals(searchResponse.status()) == false) { - mismatchDescription.appendText("actual response status is ").appendValue(searchResponse.status()); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + if (expectedRestStatus.equals(searchResponse.status()) == false) { + mismatchDescription.appendText("actual response status is ").appendValue(searchResponse.status()); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Expected response status is ").appendValue(expectedRestStatus); - } + @Override + public void describeTo(Description description) { + description.appendText("Expected response status is ").appendValue(expectedRestStatus); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SnapshotInClusterDoesNotExist.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SnapshotInClusterDoesNotExist.java index c2626669be..36e50143f0 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SnapshotInClusterDoesNotExist.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SnapshotInClusterDoesNotExist.java @@ -19,29 +19,31 @@ import static java.util.Objects.requireNonNull; class SnapshotInClusterDoesNotExist extends TypeSafeDiagnosingMatcher { - private final String repositoryName; - private final String snapshotName; + private final String repositoryName; + private final String snapshotName; - public SnapshotInClusterDoesNotExist(String repositoryName, String snapshotName) { - this.repositoryName = requireNonNull(repositoryName, "Snapshot repository name is required."); - this.snapshotName = requireNonNull(snapshotName, "Snapshot name is required."); - } + public SnapshotInClusterDoesNotExist(String repositoryName, String snapshotName) { + this.repositoryName = requireNonNull(repositoryName, "Snapshot repository name is required."); + this.snapshotName = requireNonNull(snapshotName, "Snapshot name is required."); + } - @Override - protected boolean matchesSafely(Client client, Description mismatchDescription) { - try { - GetSnapshotsRequest request = new GetSnapshotsRequest(repositoryName, new String[] { snapshotName }); - client.admin().cluster().getSnapshots(request).actionGet(); - mismatchDescription.appendText("snapshot exists"); - return false; - }catch (SnapshotMissingException e) { - return true; - } - } + @Override + protected boolean matchesSafely(Client client, Description mismatchDescription) { + try { + GetSnapshotsRequest request = new GetSnapshotsRequest(repositoryName, new String[] { snapshotName }); + client.admin().cluster().getSnapshots(request).actionGet(); + mismatchDescription.appendText("snapshot exists"); + return false; + } catch (SnapshotMissingException e) { + return true; + } + } - @Override - public void describeTo(Description description) { - description.appendText("Snapshot ").appendValue(snapshotName).appendText(" does not exist in repository ") - .appendValue(repositoryName); - } + @Override + public void describeTo(Description description) { + description.appendText("Snapshot ") + .appendValue(snapshotName) + .appendText(" does not exist in repository ") + .appendValue(repositoryName); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessBulkResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessBulkResponseMatcher.java index 72cc83491e..7bf4993783 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessBulkResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessBulkResponseMatcher.java @@ -21,27 +21,27 @@ class SuccessBulkResponseMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(BulkResponse response, Description mismatchDescription) { - RestStatus status = response.status(); - if(RestStatus.OK.equals(status) == false){ - mismatchDescription.appendText("incorrect response status ").appendValue(status); - return false; - } - if(response.hasFailures()) { - String failureDescription = Arrays.stream(response.getItems()) - .filter(BulkItemResponse::isFailed) - .map(BulkItemResponse::getFailure) - .map(Object::toString) - .collect(Collectors.joining(",\n")); - mismatchDescription.appendText("bulk response contains failures ").appendValue(failureDescription); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(BulkResponse response, Description mismatchDescription) { + RestStatus status = response.status(); + if (RestStatus.OK.equals(status) == false) { + mismatchDescription.appendText("incorrect response status ").appendValue(status); + return false; + } + if (response.hasFailures()) { + String failureDescription = Arrays.stream(response.getItems()) + .filter(BulkItemResponse::isFailed) + .map(BulkItemResponse::getFailure) + .map(Object::toString) + .collect(Collectors.joining(",\n")); + mismatchDescription.appendText("bulk response contains failures ").appendValue(failureDescription); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("success bulk response"); - } + @Override + public void describeTo(Description description) { + description.appendText("success bulk response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulClearIndicesCacheResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulClearIndicesCacheResponseMatcher.java index cc2a11d82b..e27ee17ef6 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulClearIndicesCacheResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulClearIndicesCacheResponseMatcher.java @@ -17,21 +17,21 @@ class SuccessfulClearIndicesCacheResponseMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(ClearIndicesCacheResponse response, Description mismatchDescription) { - if(!RestStatus.OK.equals(response.getStatus())) { - mismatchDescription.appendText("Status is equal to ").appendValue(response.getStatus()); - return false; - } - if(response.getShardFailures().length != 0) { - mismatchDescription.appendText("Contains ").appendValue(response.getShardFailures().length).appendText(" shard failures"); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(ClearIndicesCacheResponse response, Description mismatchDescription) { + if (!RestStatus.OK.equals(response.getStatus())) { + mismatchDescription.appendText("Status is equal to ").appendValue(response.getStatus()); + return false; + } + if (response.getShardFailures().length != 0) { + mismatchDescription.appendText("Contains ").appendValue(response.getShardFailures().length).appendText(" shard failures"); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Successful clear index cache response"); - } + @Override + public void describeTo(Description description) { + description.appendText("Successful clear index cache response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCloseIndexResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCloseIndexResponseMatcher.java index beda676540..480b7845e9 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCloseIndexResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCloseIndexResponseMatcher.java @@ -16,21 +16,21 @@ class SuccessfulCloseIndexResponseMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(CloseIndexResponse response, Description mismatchDescription) { - if (!response.isShardsAcknowledged()) { - mismatchDescription.appendText("shardsAcknowledged is equal to ").appendValue(response.isShardsAcknowledged()); - return false; - } - if (!response.isAcknowledged()) { - mismatchDescription.appendText("acknowledged is equal to ").appendValue(response.isShardsAcknowledged()); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(CloseIndexResponse response, Description mismatchDescription) { + if (!response.isShardsAcknowledged()) { + mismatchDescription.appendText("shardsAcknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + if (!response.isAcknowledged()) { + mismatchDescription.appendText("acknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Successful close index response"); - } + @Override + public void describeTo(Description description) { + description.appendText("Successful close index response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreateIndexResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreateIndexResponseMatcher.java index 2a017e0ae0..810c93e034 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreateIndexResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreateIndexResponseMatcher.java @@ -18,32 +18,34 @@ class SuccessfulCreateIndexResponseMatcher extends TypeSafeDiagnosingMatcher { - private final String expectedIndexName; - - SuccessfulCreateIndexResponseMatcher(String expectedIndexName) { - this.expectedIndexName = requireNonNull(expectedIndexName); - } - - @Override - protected boolean matchesSafely(CreateIndexResponse response, Description mismatchDescription) { - if (!expectedIndexName.equals(response.index())) { - mismatchDescription.appendText("Index name ").appendValue(response.index()) - .appendText(" does not match expected index name ").appendValue(expectedIndexName); - return false; - } - if (!response.isShardsAcknowledged()) { - mismatchDescription.appendText("shardsAcknowledged is equal to ").appendValue(response.isShardsAcknowledged()); - return false; - } - if (!response.isAcknowledged()) { - mismatchDescription.appendText("acknowledged is equal to ").appendValue(response.isShardsAcknowledged()); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Successful create index response"); - } + private final String expectedIndexName; + + SuccessfulCreateIndexResponseMatcher(String expectedIndexName) { + this.expectedIndexName = requireNonNull(expectedIndexName); + } + + @Override + protected boolean matchesSafely(CreateIndexResponse response, Description mismatchDescription) { + if (!expectedIndexName.equals(response.index())) { + mismatchDescription.appendText("Index name ") + .appendValue(response.index()) + .appendText(" does not match expected index name ") + .appendValue(expectedIndexName); + return false; + } + if (!response.isShardsAcknowledged()) { + mismatchDescription.appendText("shardsAcknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + if (!response.isAcknowledged()) { + mismatchDescription.appendText("acknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful create index response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreatePitResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreatePitResponseMatcher.java index 29670334cc..7f9a68064c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreatePitResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulCreatePitResponseMatcher.java @@ -17,21 +17,21 @@ class SuccessfulCreatePitResponseMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(CreatePitResponse response, Description mismatchDescription) { - if(!RestStatus.OK.equals(response.status())) { - mismatchDescription.appendText("has status ").appendValue(response.status()).appendText(" which denotes failure."); - return false; - } - if(response.getShardFailures().length != 0) { - mismatchDescription.appendText("contains ").appendValue(response.getShardFailures().length).appendText(" shard failures"); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(CreatePitResponse response, Description mismatchDescription) { + if (!RestStatus.OK.equals(response.status())) { + mismatchDescription.appendText("has status ").appendValue(response.status()).appendText(" which denotes failure."); + return false; + } + if (response.getShardFailures().length != 0) { + mismatchDescription.appendText("contains ").appendValue(response.getShardFailures().length).appendText(" shard failures"); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Successful create pit response"); - } + @Override + public void describeTo(Description description) { + description.appendText("Successful create pit response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeletePitResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeletePitResponseMatcher.java index 0b0c0af09c..aec2e7d61a 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeletePitResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeletePitResponseMatcher.java @@ -18,23 +18,25 @@ class SuccessfulDeletePitResponseMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(DeletePitResponse response, Description mismatchDescription) { - if(!RestStatus.OK.equals(response.status())) { - mismatchDescription.appendText("has status ").appendValue(response.status()).appendText(" which denotes failure."); - return false; - } - for(DeletePitInfo deletePitInfo : response.getDeletePitResults()) { - if (!deletePitInfo.isSuccessful()) { - mismatchDescription.appendValue("Pit: ").appendValue(deletePitInfo.getPitId()).appendText(" - delete result was not successful"); - return false; - } - } - return true; - } + @Override + protected boolean matchesSafely(DeletePitResponse response, Description mismatchDescription) { + if (!RestStatus.OK.equals(response.status())) { + mismatchDescription.appendText("has status ").appendValue(response.status()).appendText(" which denotes failure."); + return false; + } + for (DeletePitInfo deletePitInfo : response.getDeletePitResults()) { + if (!deletePitInfo.isSuccessful()) { + mismatchDescription.appendValue("Pit: ") + .appendValue(deletePitInfo.getPitId()) + .appendText(" - delete result was not successful"); + return false; + } + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Successful delete pit response"); - } + @Override + public void describeTo(Description description) { + description.appendText("Successful delete pit response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeleteResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeleteResponseMatcher.java index 8d1df13ea9..eb4b1fc064 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeleteResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulDeleteResponseMatcher.java @@ -17,21 +17,23 @@ class SuccessfulDeleteResponseMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(DeleteResponse response, Description mismatchDescription) { - if(!RestStatus.OK.equals(response.status())) { - mismatchDescription.appendText("has status ").appendValue(response.status()).appendText(" which denotes failure."); - return false; - } - if(response.getShardInfo().getFailures().length != 0) { - mismatchDescription.appendText("contains ").appendValue(response.getShardInfo().getFailures().length).appendText(" shard failures"); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(DeleteResponse response, Description mismatchDescription) { + if (!RestStatus.OK.equals(response.status())) { + mismatchDescription.appendText("has status ").appendValue(response.status()).appendText(" which denotes failure."); + return false; + } + if (response.getShardInfo().getFailures().length != 0) { + mismatchDescription.appendText("contains ") + .appendValue(response.getShardInfo().getFailures().length) + .appendText(" shard failures"); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Successful delete response"); - } + @Override + public void describeTo(Description description) { + description.appendText("Successful delete response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiGetResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiGetResponseMatcher.java index 3aedb0174b..fce5d1201c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiGetResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiGetResponseMatcher.java @@ -17,21 +17,23 @@ class SuccessfulMultiGetResponseMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(MultiGetResponse response, Description mismatchDescription) { - for (MultiGetItemResponse getItemResponse : response.getResponses()) { - if (getItemResponse.isFailed()) { - mismatchDescription.appendValue("Get an item from index: ").appendValue(getItemResponse.getIndex()) - .appendText(" failed: ").appendValue(getItemResponse.getFailure().getMessage()); - return false; - } - } + @Override + protected boolean matchesSafely(MultiGetResponse response, Description mismatchDescription) { + for (MultiGetItemResponse getItemResponse : response.getResponses()) { + if (getItemResponse.isFailed()) { + mismatchDescription.appendValue("Get an item from index: ") + .appendValue(getItemResponse.getIndex()) + .appendText(" failed: ") + .appendValue(getItemResponse.getFailure().getMessage()); + return false; + } + } - return true; - } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Successful multi get response"); - } + @Override + public void describeTo(Description description) { + description.appendText("Successful multi get response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiSearchResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiSearchResponseMatcher.java index b1dbd64c33..e601f16e8e 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiSearchResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulMultiSearchResponseMatcher.java @@ -16,20 +16,20 @@ class SuccessfulMultiSearchResponseMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(MultiSearchResponse response, Description mismatchDescription) { - for (MultiSearchResponse.Item itemResponse : response.getResponses()) { - if (itemResponse.isFailure()) { - mismatchDescription.appendValue("Get an item failed: ").appendValue(itemResponse.getFailureMessage()); - return false; - } - } + @Override + protected boolean matchesSafely(MultiSearchResponse response, Description mismatchDescription) { + for (MultiSearchResponse.Item itemResponse : response.getResponses()) { + if (itemResponse.isFailure()) { + mismatchDescription.appendValue("Get an item failed: ").appendValue(itemResponse.getFailureMessage()); + return false; + } + } - return true; - } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Successful multi search response"); - } + @Override + public void describeTo(Description description) { + description.appendText("Successful multi search response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulOpenIndexResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulOpenIndexResponseMatcher.java index 3147195474..68389979b1 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulOpenIndexResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulOpenIndexResponseMatcher.java @@ -16,21 +16,21 @@ class SuccessfulOpenIndexResponseMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(OpenIndexResponse response, Description mismatchDescription) { - if (!response.isShardsAcknowledged()) { - mismatchDescription.appendText("shardsAcknowledged is equal to ").appendValue(response.isShardsAcknowledged()); - return false; - } - if (!response.isAcknowledged()) { - mismatchDescription.appendText("acknowledged is equal to ").appendValue(response.isShardsAcknowledged()); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(OpenIndexResponse response, Description mismatchDescription) { + if (!response.isShardsAcknowledged()) { + mismatchDescription.appendText("shardsAcknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + if (!response.isAcknowledged()) { + mismatchDescription.appendText("acknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Successful open index response"); - } + @Override + public void describeTo(Description description) { + description.appendText("Successful open index response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulResizeResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulResizeResponseMatcher.java index c9b5d92b94..915a0f39bb 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulResizeResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulResizeResponseMatcher.java @@ -18,32 +18,34 @@ class SuccessfulResizeResponseMatcher extends TypeSafeDiagnosingMatcher { - private final String expectedIndexName; - - SuccessfulResizeResponseMatcher(String expectedIndexName) { - this.expectedIndexName = requireNonNull(expectedIndexName); - } - - @Override - protected boolean matchesSafely(ResizeResponse response, Description mismatchDescription) { - if (!expectedIndexName.equals(response.index())) { - mismatchDescription.appendText("Index name ").appendValue(response.index()) - .appendText(" does not match expected index name ").appendValue(expectedIndexName); - return false; - } - if (!response.isShardsAcknowledged()) { - mismatchDescription.appendText("shardsAcknowledged is equal to ").appendValue(response.isShardsAcknowledged()); - return false; - } - if (!response.isAcknowledged()) { - mismatchDescription.appendText("acknowledged is equal to ").appendValue(response.isShardsAcknowledged()); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Successful create index response"); - } + private final String expectedIndexName; + + SuccessfulResizeResponseMatcher(String expectedIndexName) { + this.expectedIndexName = requireNonNull(expectedIndexName); + } + + @Override + protected boolean matchesSafely(ResizeResponse response, Description mismatchDescription) { + if (!expectedIndexName.equals(response.index())) { + mismatchDescription.appendText("Index name ") + .appendValue(response.index()) + .appendText(" does not match expected index name ") + .appendValue(expectedIndexName); + return false; + } + if (!response.isShardsAcknowledged()) { + mismatchDescription.appendText("shardsAcknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + if (!response.isAcknowledged()) { + mismatchDescription.appendText("acknowledged is equal to ").appendValue(response.isShardsAcknowledged()); + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("Successful create index response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulSearchResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulSearchResponseMatcher.java index 6d59780798..fe2acff9b9 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulSearchResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulSearchResponseMatcher.java @@ -17,21 +17,21 @@ class SuccessfulSearchResponseMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { - if(RestStatus.OK.equals(searchResponse.status()) == false) { - mismatchDescription.appendText("has status ").appendValue(searchResponse.status()).appendText(" which denotes failure."); - return false; - } - if(searchResponse.getShardFailures().length != 0) { - mismatchDescription.appendText("contains ").appendValue(searchResponse.getShardFailures().length).appendText(" shard failures"); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) { + if (RestStatus.OK.equals(searchResponse.status()) == false) { + mismatchDescription.appendText("has status ").appendValue(searchResponse.status()).appendText(" which denotes failure."); + return false; + } + if (searchResponse.getShardFailures().length != 0) { + mismatchDescription.appendText("contains ").appendValue(searchResponse.getShardFailures().length).appendText(" shard failures"); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Successful search response"); - } + @Override + public void describeTo(Description description) { + description.appendText("Successful search response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulUpdateResponseMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulUpdateResponseMatcher.java index 35a9851055..dcff052f01 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulUpdateResponseMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/SuccessfulUpdateResponseMatcher.java @@ -17,21 +17,23 @@ class SuccessfulUpdateResponseMatcher extends TypeSafeDiagnosingMatcher { - @Override - protected boolean matchesSafely(UpdateResponse response, Description mismatchDescription) { - if(!RestStatus.OK.equals(response.status())) { - mismatchDescription.appendText("has status ").appendValue(response.status()).appendText(" which denotes failure."); - return false; - } - if(response.getShardInfo().getFailures().length != 0) { - mismatchDescription.appendText("contains ").appendValue(response.getShardInfo().getFailures().length).appendText(" shard failures"); - return false; - } - return true; - } + @Override + protected boolean matchesSafely(UpdateResponse response, Description mismatchDescription) { + if (!RestStatus.OK.equals(response.status())) { + mismatchDescription.appendText("has status ").appendValue(response.status()).appendText(" which denotes failure."); + return false; + } + if (response.getShardInfo().getFailures().length != 0) { + mismatchDescription.appendText("contains ") + .appendValue(response.getShardInfo().getFailures().length) + .appendText(" shard failures"); + return false; + } + return true; + } - @Override - public void describeTo(Description description) { - description.appendText("Successful update response"); - } + @Override + public void describeTo(Description description) { + description.appendText("Successful update response"); + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/UpdateResponseMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/UpdateResponseMatchers.java index 9062d9bb68..ee01fabced 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/UpdateResponseMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/UpdateResponseMatchers.java @@ -15,9 +15,9 @@ public class UpdateResponseMatchers { - private UpdateResponseMatchers() {} + private UpdateResponseMatchers() {} - public static Matcher isSuccessfulUpdateResponse() { - return new SuccessfulUpdateResponseMatcher(); - } + public static Matcher isSuccessfulUpdateResponse() { + return new SuccessfulUpdateResponseMatcher(); + } } diff --git a/src/test/java/org/opensearch/bootstrap/JarHell.java b/src/test/java/org/opensearch/bootstrap/JarHell.java index 11978a780c..3024a169bc 100644 --- a/src/test/java/org/opensearch/bootstrap/JarHell.java +++ b/src/test/java/org/opensearch/bootstrap/JarHell.java @@ -24,9 +24,16 @@ */ public class JarHell { private JarHell() {} + public static void checkJarHell(Consumer output) throws IOException, Exception {} + public static void checkJarHell(Set urls, Consumer output) throws URISyntaxException, IOException {} + public static void checkVersionFormat(String targetVersion) {} + public static void checkJavaVersion(String resource, String targetVersion) {} - public static Set parseClassPath() {return new HashSet();} + + public static Set parseClassPath() { + return new HashSet(); + } } diff --git a/src/test/java/org/opensearch/node/PluginAwareNode.java b/src/test/java/org/opensearch/node/PluginAwareNode.java index 35dcd8b699..4cd7ff9247 100644 --- a/src/test/java/org/opensearch/node/PluginAwareNode.java +++ b/src/test/java/org/opensearch/node/PluginAwareNode.java @@ -38,11 +38,14 @@ public class PluginAwareNode extends Node { @SafeVarargs public PluginAwareNode(boolean clusterManagerEligible, final Settings preparedSettings, final Class... plugins) { - super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, Collections.emptyMap(), null, () -> System.getenv("HOSTNAME")), Arrays.asList(plugins), true); + super( + InternalSettingsPreparer.prepareEnvironment(preparedSettings, Collections.emptyMap(), null, () -> System.getenv("HOSTNAME")), + Arrays.asList(plugins), + true + ); this.clusterManagerEligible = clusterManagerEligible; } - public boolean isClusterManagerEligible() { return clusterManagerEligible; } From 0bc71586204ddb2feee991d96583ee0e46fa90ea Mon Sep 17 00:00:00 2001 From: Bhavana Ramaram Date: Tue, 13 Jun 2023 17:00:34 -0700 Subject: [PATCH 203/369] add search model group permission to ml_read_access role (#2855) * add search model group permission to ml_read_access role Signed-off-by: Bhavana Ramaram --- config/roles.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/roles.yml b/config/roles.yml index d03b47ab28..4603093834 100644 --- a/config/roles.yml +++ b/config/roles.yml @@ -258,6 +258,7 @@ ml_read_access: reserved: true cluster_permissions: - 'cluster:admin/opensearch/ml/stats/nodes' + - 'cluster:admin/opensearch/ml/model_groups/search' - 'cluster:admin/opensearch/ml/models/get' - 'cluster:admin/opensearch/ml/models/search' - 'cluster:admin/opensearch/ml/tasks/get' From 83573b84ce88e943c8d353015e09730cd24f015a Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Thu, 15 Jun 2023 11:57:25 -0400 Subject: [PATCH 204/369] IntegrationTest spotless (#2863) Signed-off-by: Stephen Crawford --- build.gradle | 2 + .../logging/NodeAndClusterIdConverter.java | 10 +- .../org/opensearch/node/PluginAwareNode.java | 27 +- .../security/ConfigurationFiles.java | 71 +- .../security/CrossClusterSearchTests.java | 748 +-- .../security/DefaultConfigurationTests.java | 72 +- .../security/DlsIntegrationTests.java | 867 +-- .../security/DoNotFailOnForbiddenTests.java | 623 +-- .../security/FlsAndFieldMaskingTests.java | 1375 ++--- .../security/IndexOperationsHelper.java | 58 +- .../IpBruteForceAttacksPreventionTests.java | 246 +- .../security/PointInTimeOperationTest.java | 687 +-- .../security/SearchOperationTest.java | 4664 +++++++++-------- .../security/SecurityAdminLauncher.java | 46 +- .../security/SecurityConfigurationTests.java | 353 +- .../security/SecurityRolesTests.java | 51 +- .../opensearch/security/SnapshotSteps.java | 96 +- .../java/org/opensearch/security/Song.java | 167 +- .../org/opensearch/security/SslOnlyTests.java | 54 +- .../org/opensearch/security/TlsTests.java | 104 +- .../UserBruteForceAttacksPreventionTests.java | 182 +- .../http/AnonymousAuthenticationTest.java | 183 +- .../opensearch/security/http/AuthInfo.java | 16 +- .../security/http/BasicAuthTests.java | 212 +- .../http/BasicAuthWithoutChallengeTests.java | 41 +- .../http/CertificateAuthenticationTest.java | 207 +- .../http/CommonProxyAuthenticationTests.java | 445 +- .../http/DirectoryInformationTrees.java | 205 +- .../security/http/DisabledBasicAuthTests.java | 33 +- .../http/ExtendedProxyAuthenticationTest.java | 405 +- .../security/http/JwtAuthenticationTests.java | 395 +- .../http/JwtAuthorizationHeaderFactory.java | 224 +- .../security/http/LdapAuthenticationTest.java | 122 +- .../http/LdapStartTlsAuthenticationTest.java | 101 +- .../http/LdapTlsAuthenticationTest.java | 627 +-- .../http/ProxyAuthenticationTest.java | 177 +- .../UntrustedLdapServerCertificateTest.java | 79 +- .../privileges/PrivilegesEvaluatorTest.java | 50 +- 38 files changed, 7336 insertions(+), 6689 deletions(-) diff --git a/build.gradle b/build.gradle index 15eaad0803..59d9107d1d 100644 --- a/build.gradle +++ b/build.gradle @@ -79,6 +79,7 @@ spotless { target '**/com/amazon/dlic/**/*.java' target '**/com/amazon/security/**/*.java' target '**/test/**/*.java' + target '**/integrationTest/**/*.java' removeUnusedImports() eclipse().configFile rootProject.file('formatter/formatterConfig.xml') @@ -113,6 +114,7 @@ spotless { targetExclude '**/com/amazon/dlic/**/*.java' targetExclude '**/com/amazon/security/**/*.java' targetExclude '**/test/**/*.java' + targetExclude '**/integrationTest/**/*.java' trimTrailingWhitespace() endWithNewline(); diff --git a/src/integrationTest/java/org/opensearch/common/logging/NodeAndClusterIdConverter.java b/src/integrationTest/java/org/opensearch/common/logging/NodeAndClusterIdConverter.java index 94242ecc28..4aba6c976b 100644 --- a/src/integrationTest/java/org/opensearch/common/logging/NodeAndClusterIdConverter.java +++ b/src/integrationTest/java/org/opensearch/common/logging/NodeAndClusterIdConverter.java @@ -21,13 +21,9 @@ class NodeAndClusterIdConverter { + public NodeAndClusterIdConverter() {} - public NodeAndClusterIdConverter() { - } + public static void setNodeIdAndClusterId(String nodeId, String clusterUUID) {} - public static void setNodeIdAndClusterId(String nodeId, String clusterUUID) { - } - - public void format(LogEvent event, StringBuilder toAppendTo) { - } + public void format(LogEvent event, StringBuilder toAppendTo) {} } diff --git a/src/integrationTest/java/org/opensearch/node/PluginAwareNode.java b/src/integrationTest/java/org/opensearch/node/PluginAwareNode.java index 62b0199824..53e44496ca 100644 --- a/src/integrationTest/java/org/opensearch/node/PluginAwareNode.java +++ b/src/integrationTest/java/org/opensearch/node/PluginAwareNode.java @@ -34,16 +34,19 @@ public class PluginAwareNode extends Node { - private final boolean clusterManagerEligible; - - @SafeVarargs - public PluginAwareNode(boolean clusterManagerEligible, final Settings preparedSettings, final Class... plugins) { - super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, Collections.emptyMap(), null, () -> System.getenv("HOSTNAME")), Arrays.asList(plugins), true); - this.clusterManagerEligible = clusterManagerEligible; - } - - - public boolean isClusterManagerEligible() { - return clusterManagerEligible; - } + private final boolean clusterManagerEligible; + + @SafeVarargs + public PluginAwareNode(boolean clusterManagerEligible, final Settings preparedSettings, final Class... plugins) { + super( + InternalSettingsPreparer.prepareEnvironment(preparedSettings, Collections.emptyMap(), null, () -> System.getenv("HOSTNAME")), + Arrays.asList(plugins), + true + ); + this.clusterManagerEligible = clusterManagerEligible; + } + + public boolean isClusterManagerEligible() { + return clusterManagerEligible; + } } diff --git a/src/integrationTest/java/org/opensearch/security/ConfigurationFiles.java b/src/integrationTest/java/org/opensearch/security/ConfigurationFiles.java index e77d6a9f73..287bc139b1 100644 --- a/src/integrationTest/java/org/opensearch/security/ConfigurationFiles.java +++ b/src/integrationTest/java/org/opensearch/security/ConfigurationFiles.java @@ -20,42 +20,41 @@ class ConfigurationFiles { - public static void createRoleMappingFile(File destination) { - String resource = "roles_mapping.yml"; - copyResourceToFile(resource, destination); - } + public static void createRoleMappingFile(File destination) { + String resource = "roles_mapping.yml"; + copyResourceToFile(resource, destination); + } - public static Path createConfigurationDirectory() { - try { - Path tempDirectory = Files.createTempDirectory("test-security-config"); - String[] configurationFiles = { - "config.yml", - "action_groups.yml", - "config.yml", - "internal_users.yml", - "roles.yml", - "roles_mapping.yml", - "security_tenants.yml", - "tenants.yml" - }; - for (String fileName : configurationFiles) { - Path configFileDestination = tempDirectory.resolve(fileName); - copyResourceToFile(fileName, configFileDestination.toFile()); - } - return tempDirectory.toAbsolutePath(); - } catch (IOException ex) { - throw new RuntimeException("Cannot create directory with security plugin configuration.", ex); - } - } + public static Path createConfigurationDirectory() { + try { + Path tempDirectory = Files.createTempDirectory("test-security-config"); + String[] configurationFiles = { + "config.yml", + "action_groups.yml", + "config.yml", + "internal_users.yml", + "roles.yml", + "roles_mapping.yml", + "security_tenants.yml", + "tenants.yml" }; + for (String fileName : configurationFiles) { + Path configFileDestination = tempDirectory.resolve(fileName); + copyResourceToFile(fileName, configFileDestination.toFile()); + } + return tempDirectory.toAbsolutePath(); + } catch (IOException ex) { + throw new RuntimeException("Cannot create directory with security plugin configuration.", ex); + } + } - private static void copyResourceToFile(String resource, File destination) { - try(InputStream input = ConfigurationFiles.class.getClassLoader().getResourceAsStream(resource)) { - Objects.requireNonNull(input, "Cannot find source resource " + resource); - try(OutputStream output = new FileOutputStream(destination)) { - input.transferTo(output); - } - } catch (IOException e) { - throw new RuntimeException("Cannot create file with security plugin configuration", e); - } - } + private static void copyResourceToFile(String resource, File destination) { + try (InputStream input = ConfigurationFiles.class.getClassLoader().getResourceAsStream(resource)) { + Objects.requireNonNull(input, "Cannot find source resource " + resource); + try (OutputStream output = new FileOutputStream(destination)) { + input.transferTo(output); + } + } catch (IOException e) { + throw new RuntimeException("Cannot create file with security plugin configuration", e); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/CrossClusterSearchTests.java b/src/integrationTest/java/org/opensearch/security/CrossClusterSearchTests.java index 6762274133..ee03913c55 100644 --- a/src/integrationTest/java/org/opensearch/security/CrossClusterSearchTests.java +++ b/src/integrationTest/java/org/opensearch/security/CrossClusterSearchTests.java @@ -68,368 +68,388 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class CrossClusterSearchTests { - private static final String SONG_INDEX_NAME = "song_lyrics"; - - private static final String PROHIBITED_SONG_INDEX_NAME = "prohibited_song_lyrics"; - - public static final String REMOTE_CLUSTER_NAME = "ccsRemote"; - public static final String REMOTE_SONG_INDEX = REMOTE_CLUSTER_NAME + ":" + SONG_INDEX_NAME; - - public static final String SONG_ID_1R = "remote-00001"; - public static final String SONG_ID_2L = "local-00002"; - public static final String SONG_ID_3R = "remote-00003"; - public static final String SONG_ID_4L = "local-00004"; - public static final String SONG_ID_5R = "remote-00005"; - public static final String SONG_ID_6R = "remote-00006"; - - private static final Role LIMITED_ROLE = new Role("limited_role") - .indexPermissions("indices:data/read/search", "indices:admin/shards/search_shards") - .on(SONG_INDEX_NAME, "user-${user.name}-${attr.internal.type}"); - - private static final Role DLS_ROLE_ROCK = new Role("dls_role_rock") - .indexPermissions("indices:data/read/search", "indices:data/read/get", "indices:admin/shards/search_shards") - .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_GENRE, GENRE_ROCK)) - .on(SONG_INDEX_NAME); - - private static final Role DLS_ROLE_JAZZ = new Role("dls_role_jazz") - .indexPermissions("indices:data/read/search", "indices:data/read/get", "indices:admin/shards/search_shards") - .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_GENRE, GENRE_JAZZ)) - .on(SONG_INDEX_NAME); - - private static final Role FLS_EXCLUDE_LYRICS_ROLE = new Role("fls_exclude_lyrics_role") - .indexPermissions("indices:data/read/search", "indices:data/read/get", "indices:admin/shards/search_shards") - .fls("~" + FIELD_LYRICS) - .on(SONG_INDEX_NAME); - - private static final Role FLS_INCLUDE_TITLE_ROLE = new Role("fls_include_title_role") - .indexPermissions("indices:data/read/search", "indices:data/read/get", "indices:admin/shards/search_shards") - .fls(FIELD_TITLE) - .on(SONG_INDEX_NAME); - - public static final String TYPE_ATTRIBUTE = "type"; - - private static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS).attr(TYPE_ATTRIBUTE, "administrative"); - private static final User LIMITED_USER = new User("limited_user").attr(TYPE_ATTRIBUTE, "personal"); - - private static final User FLS_INCLUDE_TITLE_USER = new User("fls_include_title_user"); - - private static final User FLS_EXCLUDE_LYRICS_USER = new User("fls_exclude_lyrics_user"); - - private static final User DLS_USER_ROCK = new User("dls-user-rock"); - - private static final User DLS_USER_JAZZ = new User("dls-user-jazz"); - - public static final String LIMITED_USER_INDEX_NAME = "user-" + LIMITED_USER.getName() + "-" + LIMITED_USER.getAttribute(TYPE_ATTRIBUTE); - public static final String ADMIN_USER_INDEX_NAME = "user-" + ADMIN_USER.getName() + "-" + ADMIN_USER.getAttribute(TYPE_ATTRIBUTE); - - private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); - - private final boolean ccsMinimizeRoundtrips; - - public static final String PLUGINS_SECURITY_RESTAPI_ROLES_ENABLED = "plugins.security.restapi.roles_enabled"; - @ClassRule - public static final LocalCluster remoteCluster = new LocalCluster.Builder().certificates(TEST_CERTIFICATES) - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false).clusterName(REMOTE_CLUSTER_NAME) - .authc(AUTHC_HTTPBASIC_INTERNAL) - .roles(LIMITED_ROLE, DLS_ROLE_ROCK, DLS_ROLE_JAZZ, FLS_EXCLUDE_LYRICS_ROLE, FLS_INCLUDE_TITLE_ROLE).users(ADMIN_USER) - .build(); - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder().certificates(TEST_CERTIFICATES) - .clusterManager(ClusterManager.SINGLE_REMOTE_CLIENT).anonymousAuth(false).clusterName("ccsLocal") - .nodeSettings(Map.of(PLUGINS_SECURITY_RESTAPI_ROLES_ENABLED, List.of("user_" + ADMIN_USER.getName() +"__" + ALL_ACCESS.getName()))) - .remote(REMOTE_CLUSTER_NAME, remoteCluster) - .roles(LIMITED_ROLE, DLS_ROLE_ROCK, DLS_ROLE_JAZZ, FLS_EXCLUDE_LYRICS_ROLE, FLS_INCLUDE_TITLE_ROLE).authc(AUTHC_HTTPBASIC_INTERNAL) - .users(ADMIN_USER, LIMITED_USER, DLS_USER_ROCK, DLS_USER_JAZZ, FLS_INCLUDE_TITLE_USER, FLS_EXCLUDE_LYRICS_USER) - .build(); - - @ParametersFactory(shuffle = false) - public static Iterable parameters() { - return List.of(new Object[] { true }, new Object[] { false }); - } - - public CrossClusterSearchTests(Boolean ccsMinimizeRoundtrips) { - this.ccsMinimizeRoundtrips = ccsMinimizeRoundtrips; - } - - @BeforeClass - public static void createTestData() { - try(Client client = remoteCluster.getInternalNodeClient()){ - client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_1R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); - client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_6R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[5].asMap()).get(); - client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(SONG_ID_3R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1].asMap()).get(); - client.prepareIndex(LIMITED_USER_INDEX_NAME).setId(SONG_ID_5R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[4].asMap()).get(); - } - try(Client client = cluster.getInternalNodeClient()){ - client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_2L).setRefreshPolicy(IMMEDIATE).setSource(SONGS[2].asMap()).get(); - client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(SONG_ID_4L).setRefreshPolicy(IMMEDIATE).setSource(SONGS[3].asMap()).get(); - } - try(TestRestClient client = cluster.getRestClient(ADMIN_USER)) { - client.assignRoleToUser(LIMITED_USER.getName(), LIMITED_ROLE.getName()).assertStatusCode(200); - client.assignRoleToUser(DLS_USER_ROCK.getName(), DLS_ROLE_ROCK.getName()).assertStatusCode(200); - client.assignRoleToUser(DLS_USER_JAZZ.getName(), DLS_ROLE_JAZZ.getName()).assertStatusCode(200); - client.assignRoleToUser(FLS_INCLUDE_TITLE_USER.getName(), FLS_INCLUDE_TITLE_ROLE.getName()).assertStatusCode(200); - client.assignRoleToUser(FLS_EXCLUDE_LYRICS_USER.getName(), FLS_EXCLUDE_LYRICS_ROLE.getName()).assertStatusCode(200); - } - } - - @Test - public void shouldFindDocumentOnRemoteCluster_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = searchAll(REMOTE_SONG_INDEX); - - SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(response, isSuccessfulSearchResponse()); - assertThat(response, numberOfTotalHitsIsEqualTo(2)); - assertThat(response, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_1R)); - assertThat(response, searchHitsContainDocumentWithId(1, SONG_INDEX_NAME, SONG_ID_6R)); - } - } - - private SearchRequest searchAll(String indexName) { - SearchRequest searchRequest = SearchRequestFactory.searchAll(indexName); - searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); - return searchRequest; - } - - @Test - public void shouldFindDocumentOnRemoteCluster_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":" + PROHIBITED_SONG_INDEX_NAME); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldSearchForDocumentOnRemoteClustersWhenStarIsUsedAsClusterName_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = searchAll("*" + ":" + SONG_INDEX_NAME); - - SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); - - //only remote documents are found - assertThat(response, isSuccessfulSearchResponse()); - assertThat(response, numberOfTotalHitsIsEqualTo(2)); - assertThat(response, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_1R)); - assertThat(response, searchHitsContainDocumentWithId(1, SONG_INDEX_NAME, SONG_ID_6R)); - } - } - - @Test - public void shouldSearchForDocumentOnRemoteClustersWhenStarIsUsedAsClusterName_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = searchAll("*" + ":" + PROHIBITED_SONG_INDEX_NAME); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldSearchForDocumentOnBothClustersWhenIndexOnBothClusterArePointedOut_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = SearchRequestFactory.searchAll(REMOTE_SONG_INDEX, SONG_INDEX_NAME); - searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); - - SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(response, isSuccessfulSearchResponse()); - assertThat(response, numberOfTotalHitsIsEqualTo(3)); - assertThat(response, searchHitsContainDocumentsInAnyOrder( - Pair.of(SONG_INDEX_NAME, SONG_ID_1R), - Pair.of(SONG_INDEX_NAME, SONG_ID_2L), - Pair.of(SONG_INDEX_NAME, SONG_ID_6R)) - ); - } - } - - @Test - public void shouldSearchForDocumentOnBothClustersWhenIndexOnBothClusterArePointedOut_negativeLackOfLocalAccess() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - var searchRequest = SearchRequestFactory.searchAll(REMOTE_SONG_INDEX, PROHIBITED_SONG_INDEX_NAME); - searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldSearchForDocumentOnBothClustersWhenIndexOnBothClusterArePointedOut_negativeLackOfRemoteAccess() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - String remoteIndex = REMOTE_CLUSTER_NAME + ":" + PROHIBITED_SONG_INDEX_NAME; - SearchRequest searchRequest = SearchRequestFactory.searchAll(remoteIndex, SONG_INDEX_NAME); - searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldSearchViaAllAliasOnRemoteCluster_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { - SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":_all"); - - SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(response, isSuccessfulSearchResponse()); - assertThat(response, numberOfTotalHitsIsEqualTo(4)); - assertThat(response, searchHitsContainDocumentsInAnyOrder( - Pair.of(SONG_INDEX_NAME, SONG_ID_1R), - Pair.of(SONG_INDEX_NAME, SONG_ID_6R), - Pair.of(PROHIBITED_SONG_INDEX_NAME, SONG_ID_3R), - Pair.of(LIMITED_USER_INDEX_NAME, SONG_ID_5R)) - ); - } - } - - @Test - public void shouldSearchViaAllAliasOnRemoteCluster_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":_all"); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldSearchAllIndexOnRemoteClusterWhenStarIsUsedAsIndexName_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { - SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":*"); - - SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(response, isSuccessfulSearchResponse()); - assertThat(response, numberOfTotalHitsIsEqualTo(4)); - assertThat(response, searchHitsContainDocumentsInAnyOrder( - Pair.of(SONG_INDEX_NAME, SONG_ID_1R), - Pair.of(SONG_INDEX_NAME, SONG_ID_6R), - Pair.of(PROHIBITED_SONG_INDEX_NAME, SONG_ID_3R), - Pair.of(LIMITED_USER_INDEX_NAME, SONG_ID_5R)) - ); - } - } - - @Test - public void shouldSearchAllIndexOnRemoteClusterWhenStarIsUsedAsIndexName_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":*"); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldResolveUserNameExpressionInRoleIndexPattern_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":" + LIMITED_USER_INDEX_NAME); - - SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(response, numberOfTotalHitsIsEqualTo(1)); - } - } - - @Test - public void shouldResolveUserNameExpressionInRoleIndexPattern_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":" + ADMIN_USER_INDEX_NAME); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldSearchInIndexWithPrefix_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":song*"); - - SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(response, isSuccessfulSearchResponse()); - assertThat(response, numberOfTotalHitsIsEqualTo(2)); - assertThat(response, searchHitsContainDocumentsInAnyOrder( - Pair.of(SONG_INDEX_NAME, SONG_ID_1R), - Pair.of(SONG_INDEX_NAME, SONG_ID_6R) - )); - } - } - - @Test - public void shouldSearchInIndexWithPrefix_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":prohibited*"); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldEvaluateDocumentLevelSecurityRulesOnRemoteClusterOnSearchRequest_caseRock() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DLS_USER_ROCK)) { - SearchRequest searchRequest = searchAll(REMOTE_SONG_INDEX); - - SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); - - //searching for all documents, so is it important that result contain only one document with id SONG_ID_1 - // and document with SONG_ID_6 is excluded from result set by DLS - assertThat(response, isSuccessfulSearchResponse()); - assertThat(response, numberOfTotalHitsIsEqualTo(1)); - assertThat(response, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_1R)); - } - } - - @Test - public void shouldEvaluateDocumentLevelSecurityRulesOnRemoteClusterOnSearchRequest_caseJazz() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DLS_USER_JAZZ)) { - SearchRequest searchRequest = searchAll(REMOTE_SONG_INDEX); - - SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); - - //searching for all documents, so is it important that result contain only one document with id SONG_ID_6 - // and document with SONG_ID_1 is excluded from result set by DLS - assertThat(response, isSuccessfulSearchResponse()); - assertThat(response, numberOfTotalHitsIsEqualTo(1)); - assertThat(response, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_6R)); - } - } - - @Test - public void shouldHaveAccessOnlyToSpecificField() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(FLS_INCLUDE_TITLE_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(REMOTE_SONG_INDEX, QUERY_TITLE_MAGNUM_OPUS); - searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); - - SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(response, isSuccessfulSearchResponse()); - assertThat(response, numberOfTotalHitsIsEqualTo(1)); - //document should contain only title field - assertThat(response, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); - assertThat(response, searchHitDoesNotContainField(0, FIELD_ARTIST)); - assertThat(response, searchHitDoesNotContainField(0, FIELD_LYRICS)); - assertThat(response, searchHitDoesNotContainField(0, FIELD_STARS)); - assertThat(response, searchHitDoesNotContainField(0, FIELD_GENRE)); - } - } - - @Test - public void shouldLackAccessToSpecificField() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(FLS_EXCLUDE_LYRICS_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(REMOTE_SONG_INDEX, QUERY_TITLE_MAGNUM_OPUS); - searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); - - SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(response, isSuccessfulSearchResponse()); - assertThat(response, numberOfTotalHitsIsEqualTo(1)); - //document should not contain lyrics field - assertThat(response, searchHitDoesNotContainField(0, FIELD_LYRICS)); - - assertThat(response, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); - assertThat(response, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); - assertThat(response, searchHitContainsFieldWithValue(0, FIELD_STARS, 1)); - assertThat(response, searchHitContainsFieldWithValue(0, FIELD_GENRE, GENRE_ROCK)); - } - } + private static final String SONG_INDEX_NAME = "song_lyrics"; + + private static final String PROHIBITED_SONG_INDEX_NAME = "prohibited_song_lyrics"; + + public static final String REMOTE_CLUSTER_NAME = "ccsRemote"; + public static final String REMOTE_SONG_INDEX = REMOTE_CLUSTER_NAME + ":" + SONG_INDEX_NAME; + + public static final String SONG_ID_1R = "remote-00001"; + public static final String SONG_ID_2L = "local-00002"; + public static final String SONG_ID_3R = "remote-00003"; + public static final String SONG_ID_4L = "local-00004"; + public static final String SONG_ID_5R = "remote-00005"; + public static final String SONG_ID_6R = "remote-00006"; + + private static final Role LIMITED_ROLE = new Role("limited_role").indexPermissions( + "indices:data/read/search", + "indices:admin/shards/search_shards" + ).on(SONG_INDEX_NAME, "user-${user.name}-${attr.internal.type}"); + + private static final Role DLS_ROLE_ROCK = new Role("dls_role_rock").indexPermissions( + "indices:data/read/search", + "indices:data/read/get", + "indices:admin/shards/search_shards" + ).dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_GENRE, GENRE_ROCK)).on(SONG_INDEX_NAME); + + private static final Role DLS_ROLE_JAZZ = new Role("dls_role_jazz").indexPermissions( + "indices:data/read/search", + "indices:data/read/get", + "indices:admin/shards/search_shards" + ).dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_GENRE, GENRE_JAZZ)).on(SONG_INDEX_NAME); + + private static final Role FLS_EXCLUDE_LYRICS_ROLE = new Role("fls_exclude_lyrics_role").indexPermissions( + "indices:data/read/search", + "indices:data/read/get", + "indices:admin/shards/search_shards" + ).fls("~" + FIELD_LYRICS).on(SONG_INDEX_NAME); + + private static final Role FLS_INCLUDE_TITLE_ROLE = new Role("fls_include_title_role").indexPermissions( + "indices:data/read/search", + "indices:data/read/get", + "indices:admin/shards/search_shards" + ).fls(FIELD_TITLE).on(SONG_INDEX_NAME); + + public static final String TYPE_ATTRIBUTE = "type"; + + private static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS).attr(TYPE_ATTRIBUTE, "administrative"); + private static final User LIMITED_USER = new User("limited_user").attr(TYPE_ATTRIBUTE, "personal"); + + private static final User FLS_INCLUDE_TITLE_USER = new User("fls_include_title_user"); + + private static final User FLS_EXCLUDE_LYRICS_USER = new User("fls_exclude_lyrics_user"); + + private static final User DLS_USER_ROCK = new User("dls-user-rock"); + + private static final User DLS_USER_JAZZ = new User("dls-user-jazz"); + + public static final String LIMITED_USER_INDEX_NAME = "user-" + LIMITED_USER.getName() + "-" + LIMITED_USER.getAttribute(TYPE_ATTRIBUTE); + public static final String ADMIN_USER_INDEX_NAME = "user-" + ADMIN_USER.getName() + "-" + ADMIN_USER.getAttribute(TYPE_ATTRIBUTE); + + private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); + + private final boolean ccsMinimizeRoundtrips; + + public static final String PLUGINS_SECURITY_RESTAPI_ROLES_ENABLED = "plugins.security.restapi.roles_enabled"; + @ClassRule + public static final LocalCluster remoteCluster = new LocalCluster.Builder().certificates(TEST_CERTIFICATES) + .clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .clusterName(REMOTE_CLUSTER_NAME) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .roles(LIMITED_ROLE, DLS_ROLE_ROCK, DLS_ROLE_JAZZ, FLS_EXCLUDE_LYRICS_ROLE, FLS_INCLUDE_TITLE_ROLE) + .users(ADMIN_USER) + .build(); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().certificates(TEST_CERTIFICATES) + .clusterManager(ClusterManager.SINGLE_REMOTE_CLIENT) + .anonymousAuth(false) + .clusterName("ccsLocal") + .nodeSettings(Map.of(PLUGINS_SECURITY_RESTAPI_ROLES_ENABLED, List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName()))) + .remote(REMOTE_CLUSTER_NAME, remoteCluster) + .roles(LIMITED_ROLE, DLS_ROLE_ROCK, DLS_ROLE_JAZZ, FLS_EXCLUDE_LYRICS_ROLE, FLS_INCLUDE_TITLE_ROLE) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER, LIMITED_USER, DLS_USER_ROCK, DLS_USER_JAZZ, FLS_INCLUDE_TITLE_USER, FLS_EXCLUDE_LYRICS_USER) + .build(); + + @ParametersFactory(shuffle = false) + public static Iterable parameters() { + return List.of(new Object[] { true }, new Object[] { false }); + } + + public CrossClusterSearchTests(Boolean ccsMinimizeRoundtrips) { + this.ccsMinimizeRoundtrips = ccsMinimizeRoundtrips; + } + + @BeforeClass + public static void createTestData() { + try (Client client = remoteCluster.getInternalNodeClient()) { + client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_1R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); + client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_6R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[5].asMap()).get(); + client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(SONG_ID_3R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1].asMap()).get(); + client.prepareIndex(LIMITED_USER_INDEX_NAME).setId(SONG_ID_5R).setRefreshPolicy(IMMEDIATE).setSource(SONGS[4].asMap()).get(); + } + try (Client client = cluster.getInternalNodeClient()) { + client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_2L).setRefreshPolicy(IMMEDIATE).setSource(SONGS[2].asMap()).get(); + client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(SONG_ID_4L).setRefreshPolicy(IMMEDIATE).setSource(SONGS[3].asMap()).get(); + } + try (TestRestClient client = cluster.getRestClient(ADMIN_USER)) { + client.assignRoleToUser(LIMITED_USER.getName(), LIMITED_ROLE.getName()).assertStatusCode(200); + client.assignRoleToUser(DLS_USER_ROCK.getName(), DLS_ROLE_ROCK.getName()).assertStatusCode(200); + client.assignRoleToUser(DLS_USER_JAZZ.getName(), DLS_ROLE_JAZZ.getName()).assertStatusCode(200); + client.assignRoleToUser(FLS_INCLUDE_TITLE_USER.getName(), FLS_INCLUDE_TITLE_ROLE.getName()).assertStatusCode(200); + client.assignRoleToUser(FLS_EXCLUDE_LYRICS_USER.getName(), FLS_EXCLUDE_LYRICS_ROLE.getName()).assertStatusCode(200); + } + } + + @Test + public void shouldFindDocumentOnRemoteCluster_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_SONG_INDEX); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(2)); + assertThat(response, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_1R)); + assertThat(response, searchHitsContainDocumentWithId(1, SONG_INDEX_NAME, SONG_ID_6R)); + } + } + + private SearchRequest searchAll(String indexName) { + SearchRequest searchRequest = SearchRequestFactory.searchAll(indexName); + searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); + return searchRequest; + } + + @Test + public void shouldFindDocumentOnRemoteCluster_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":" + PROHIBITED_SONG_INDEX_NAME); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentOnRemoteClustersWhenStarIsUsedAsClusterName_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll("*" + ":" + SONG_INDEX_NAME); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + // only remote documents are found + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(2)); + assertThat(response, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_1R)); + assertThat(response, searchHitsContainDocumentWithId(1, SONG_INDEX_NAME, SONG_ID_6R)); + } + } + + @Test + public void shouldSearchForDocumentOnRemoteClustersWhenStarIsUsedAsClusterName_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll("*" + ":" + PROHIBITED_SONG_INDEX_NAME); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentOnBothClustersWhenIndexOnBothClusterArePointedOut_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = SearchRequestFactory.searchAll(REMOTE_SONG_INDEX, SONG_INDEX_NAME); + searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(3)); + assertThat( + response, + searchHitsContainDocumentsInAnyOrder( + Pair.of(SONG_INDEX_NAME, SONG_ID_1R), + Pair.of(SONG_INDEX_NAME, SONG_ID_2L), + Pair.of(SONG_INDEX_NAME, SONG_ID_6R) + ) + ); + } + } + + @Test + public void shouldSearchForDocumentOnBothClustersWhenIndexOnBothClusterArePointedOut_negativeLackOfLocalAccess() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + var searchRequest = SearchRequestFactory.searchAll(REMOTE_SONG_INDEX, PROHIBITED_SONG_INDEX_NAME); + searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentOnBothClustersWhenIndexOnBothClusterArePointedOut_negativeLackOfRemoteAccess() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + String remoteIndex = REMOTE_CLUSTER_NAME + ":" + PROHIBITED_SONG_INDEX_NAME; + SearchRequest searchRequest = SearchRequestFactory.searchAll(remoteIndex, SONG_INDEX_NAME); + searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchViaAllAliasOnRemoteCluster_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":_all"); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(4)); + assertThat( + response, + searchHitsContainDocumentsInAnyOrder( + Pair.of(SONG_INDEX_NAME, SONG_ID_1R), + Pair.of(SONG_INDEX_NAME, SONG_ID_6R), + Pair.of(PROHIBITED_SONG_INDEX_NAME, SONG_ID_3R), + Pair.of(LIMITED_USER_INDEX_NAME, SONG_ID_5R) + ) + ); + } + } + + @Test + public void shouldSearchViaAllAliasOnRemoteCluster_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":_all"); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchAllIndexOnRemoteClusterWhenStarIsUsedAsIndexName_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":*"); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(4)); + assertThat( + response, + searchHitsContainDocumentsInAnyOrder( + Pair.of(SONG_INDEX_NAME, SONG_ID_1R), + Pair.of(SONG_INDEX_NAME, SONG_ID_6R), + Pair.of(PROHIBITED_SONG_INDEX_NAME, SONG_ID_3R), + Pair.of(LIMITED_USER_INDEX_NAME, SONG_ID_5R) + ) + ); + } + } + + @Test + public void shouldSearchAllIndexOnRemoteClusterWhenStarIsUsedAsIndexName_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":*"); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldResolveUserNameExpressionInRoleIndexPattern_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":" + LIMITED_USER_INDEX_NAME); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, numberOfTotalHitsIsEqualTo(1)); + } + } + + @Test + public void shouldResolveUserNameExpressionInRoleIndexPattern_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":" + ADMIN_USER_INDEX_NAME); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchInIndexWithPrefix_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":song*"); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(2)); + assertThat( + response, + searchHitsContainDocumentsInAnyOrder(Pair.of(SONG_INDEX_NAME, SONG_ID_1R), Pair.of(SONG_INDEX_NAME, SONG_ID_6R)) + ); + } + } + + @Test + public void shouldSearchInIndexWithPrefix_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchAll(REMOTE_CLUSTER_NAME + ":prohibited*"); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldEvaluateDocumentLevelSecurityRulesOnRemoteClusterOnSearchRequest_caseRock() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DLS_USER_ROCK)) { + SearchRequest searchRequest = searchAll(REMOTE_SONG_INDEX); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + // searching for all documents, so is it important that result contain only one document with id SONG_ID_1 + // and document with SONG_ID_6 is excluded from result set by DLS + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(1)); + assertThat(response, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_1R)); + } + } + + @Test + public void shouldEvaluateDocumentLevelSecurityRulesOnRemoteClusterOnSearchRequest_caseJazz() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DLS_USER_JAZZ)) { + SearchRequest searchRequest = searchAll(REMOTE_SONG_INDEX); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + // searching for all documents, so is it important that result contain only one document with id SONG_ID_6 + // and document with SONG_ID_1 is excluded from result set by DLS + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(1)); + assertThat(response, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_6R)); + } + } + + @Test + public void shouldHaveAccessOnlyToSpecificField() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(FLS_INCLUDE_TITLE_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(REMOTE_SONG_INDEX, QUERY_TITLE_MAGNUM_OPUS); + searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(1)); + // document should contain only title field + assertThat(response, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat(response, searchHitDoesNotContainField(0, FIELD_ARTIST)); + assertThat(response, searchHitDoesNotContainField(0, FIELD_LYRICS)); + assertThat(response, searchHitDoesNotContainField(0, FIELD_STARS)); + assertThat(response, searchHitDoesNotContainField(0, FIELD_GENRE)); + } + } + + @Test + public void shouldLackAccessToSpecificField() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(FLS_EXCLUDE_LYRICS_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(REMOTE_SONG_INDEX, QUERY_TITLE_MAGNUM_OPUS); + searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips); + + SearchResponse response = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(1)); + // document should not contain lyrics field + assertThat(response, searchHitDoesNotContainField(0, FIELD_LYRICS)); + + assertThat(response, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat(response, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + assertThat(response, searchHitContainsFieldWithValue(0, FIELD_STARS, 1)); + assertThat(response, searchHitContainsFieldWithValue(0, FIELD_GENRE, GENRE_ROCK)); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/DefaultConfigurationTests.java b/src/integrationTest/java/org/opensearch/security/DefaultConfigurationTests.java index 589fe798d5..a9f6cf9b1e 100644 --- a/src/integrationTest/java/org/opensearch/security/DefaultConfigurationTests.java +++ b/src/integrationTest/java/org/opensearch/security/DefaultConfigurationTests.java @@ -37,44 +37,42 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class DefaultConfigurationTests { - private final static Path configurationFolder = ConfigurationFiles.createConfigurationDirectory(); - public static final String ADMIN_USER_NAME = "admin"; - public static final String DEFAULT_PASSWORD = "secret"; - public static final String NEW_USER = "new-user"; - public static final String LIMITED_USER = "limited-user"; + private final static Path configurationFolder = ConfigurationFiles.createConfigurationDirectory(); + public static final String ADMIN_USER_NAME = "admin"; + public static final String DEFAULT_PASSWORD = "secret"; + public static final String NEW_USER = "new-user"; + public static final String LIMITED_USER = "limited-user"; - @ClassRule - public static LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.SINGLENODE) - .nodeSettings(Map.of( - "plugins.security.allow_default_init_securityindex", true, - "plugins.security.restapi.roles_enabled", List.of("user_admin__all_access") - )) - .defaultConfigurationInitDirectory(configurationFolder.toString()) - .loadConfigurationIntoIndex(false) - .build(); + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .nodeSettings( + Map.of( + "plugins.security.allow_default_init_securityindex", + true, + "plugins.security.restapi.roles_enabled", + List.of("user_admin__all_access") + ) + ) + .defaultConfigurationInitDirectory(configurationFolder.toString()) + .loadConfigurationIntoIndex(false) + .build(); - @AfterClass - public static void cleanConfigurationDirectory() throws IOException { - FileUtils.deleteDirectory(configurationFolder.toFile()); - } + @AfterClass + public static void cleanConfigurationDirectory() throws IOException { + FileUtils.deleteDirectory(configurationFolder.toFile()); + } - @Test - public void shouldLoadDefaultConfiguration() { - try(TestRestClient client = cluster.getRestClient(NEW_USER, DEFAULT_PASSWORD)) { - Awaitility.await().alias("Load default configuration") - .until(() -> client.getAuthInfo().getStatusCode(), equalTo(200)); - } - try(TestRestClient client = cluster.getRestClient(ADMIN_USER_NAME, DEFAULT_PASSWORD)){ - client.assertCorrectCredentials(ADMIN_USER_NAME); - HttpResponse response = client.get("/_plugins/_security/api/internalusers"); - response.assertStatusCode(200); - Map users = response.getBodyAs(Map.class); - assertThat(users, allOf( - aMapWithSize(3), - hasKey(ADMIN_USER_NAME), - hasKey(NEW_USER), - hasKey(LIMITED_USER))); - } - } + @Test + public void shouldLoadDefaultConfiguration() { + try (TestRestClient client = cluster.getRestClient(NEW_USER, DEFAULT_PASSWORD)) { + Awaitility.await().alias("Load default configuration").until(() -> client.getAuthInfo().getStatusCode(), equalTo(200)); + } + try (TestRestClient client = cluster.getRestClient(ADMIN_USER_NAME, DEFAULT_PASSWORD)) { + client.assertCorrectCredentials(ADMIN_USER_NAME); + HttpResponse response = client.get("/_plugins/_security/api/internalusers"); + response.assertStatusCode(200); + Map users = response.getBodyAs(Map.class); + assertThat(users, allOf(aMapWithSize(3), hasKey(ADMIN_USER_NAME), hasKey(NEW_USER), hasKey(LIMITED_USER))); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/DlsIntegrationTests.java b/src/integrationTest/java/org/opensearch/security/DlsIntegrationTests.java index a99d584a50..d1957e50a6 100644 --- a/src/integrationTest/java/org/opensearch/security/DlsIntegrationTests.java +++ b/src/integrationTest/java/org/opensearch/security/DlsIntegrationTests.java @@ -61,419 +61,456 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class DlsIntegrationTests { - static final String FIRST_INDEX_ID_SONG_1 = "INDEX_1_S1"; - static final String FIRST_INDEX_ID_SONG_2 = "INDEX_1_S2"; - static final String FIRST_INDEX_ID_SONG_3 = "INDEX_1_S3"; - static final String FIRST_INDEX_ID_SONG_4 = "INDEX_1_S4"; - static final String FIRST_INDEX_ID_SONG_5 = "INDEX_1_S5"; - static final String FIRST_INDEX_ID_SONG_6 = "INDEX_1_S6"; - static final String SECOND_INDEX_ID_SONG_1 = "INDEX_2_S1"; - static final String SECOND_INDEX_ID_SONG_2 = "INDEX_2_S2"; - static final String SECOND_INDEX_ID_SONG_3 = "INDEX_2_S3"; - static final String SECOND_INDEX_ID_SONG_4 = "INDEX_2_S4"; - - static final String INDEX_NAME_SUFFIX = "-test-index"; - static final String FIRST_INDEX_NAME = "first".concat(INDEX_NAME_SUFFIX); - static final String SECOND_INDEX_NAME = "second".concat(INDEX_NAME_SUFFIX); - static final String FIRST_INDEX_ALIAS = FIRST_INDEX_NAME.concat("-alias"); - static final String SECOND_INDEX_ALIAS = SECOND_INDEX_NAME.concat("-alias"); - static final String FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE = FIRST_INDEX_NAME.concat("-filtered-by-next-song-title"); - static final String FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST = FIRST_INDEX_NAME.concat("-filtered-by-twins-artist"); - static final String FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST = FIRST_INDEX_NAME.concat("-filtered-by-first-artist"); - static final String ALL_INDICES_ALIAS = "_all"; - - - static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); - - /** - * User who is allowed to read all indices. - */ - static final TestSecurityConfig.User READ_ALL_USER = new TestSecurityConfig.User("read_all_user") - .roles( - new TestSecurityConfig.Role("read_all_user") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .on("*") - ); - - /** - * User who is allowed to see all fields on indices {@link #FIRST_INDEX_NAME} and {@link #SECOND_INDEX_NAME}. - */ - static final TestSecurityConfig.User READ_FIRST_AND_SECOND_USER = new TestSecurityConfig.User("read_first_and_second_user") - .roles( - new TestSecurityConfig.Role("first_index_reader") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .on(FIRST_INDEX_NAME), - new TestSecurityConfig.Role("second_index_reader") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .on(SECOND_INDEX_NAME) - ); - - /** - * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_STRING}. - */ - static final TestSecurityConfig.User READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_STRING = new TestSecurityConfig.User("read_where_field_artist_matches_artist_string") - .roles( - new TestSecurityConfig.Role("read_where_field_artist_matches_artist_string") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_STRING)) - .on("*") - ); - - /** - * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_TWINS} OR {@link Song#FIELD_STARS} is greater than five. - */ - static final TestSecurityConfig.User READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_FIELD_STARS_GREATER_THAN_FIVE = new TestSecurityConfig.User("read_where_field_artist_matches_artist_twins_or_field_stars_greater_than_five") - .roles( - new TestSecurityConfig.Role("read_where_field_artist_matches_artist_twins") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_TWINS)) - .on("*"), - new TestSecurityConfig.Role("read_where_field_stars_greater_than_five") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .dls(String.format("{\"range\":{\"%s\":{\"gt\":%d}}}", FIELD_STARS, 5)) - .on("*") - ); - - - /** - * User who is allowed to see documents on indices where value of {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_TWINS} or {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_FIRST}: - */ - static final TestSecurityConfig.User READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_MATCHES_ARTIST_FIRST = new TestSecurityConfig.User("read_where_field_artist_matches_artist_twins_or_artist_first") - .roles( - new TestSecurityConfig.Role("read_where_field_artist_matches_artist_twins") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_TWINS)) - .on(FIRST_INDEX_NAME), - new TestSecurityConfig.Role("read_where_field_artist_matches_artist_first") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_FIRST)) - .on(SECOND_INDEX_NAME) - ); - - /** - * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_STARS} is less than three. - */ - static final TestSecurityConfig.User READ_WHERE_STARS_LESS_THAN_THREE = new TestSecurityConfig.User("read_where_stars_less_than_three") - .roles( - new TestSecurityConfig.Role("read_where_stars_less_than_three") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .dls(String.format("{\"range\":{\"%s\":{\"lt\":%d}}}", FIELD_STARS, 3)) - .on("*") - ); - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) - .nodeSettings(Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() +"__" + ALL_ACCESS.getName()))) - .authc(AUTHC_HTTPBASIC_INTERNAL) - .users( - ADMIN_USER, READ_ALL_USER, READ_FIRST_AND_SECOND_USER, READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_STRING, - READ_WHERE_STARS_LESS_THAN_THREE, READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_FIELD_STARS_GREATER_THAN_FIVE, - READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_MATCHES_ARTIST_FIRST - ) - .build(); - - /** - * Function that returns id assigned to song with title equal to given title or throws {@link RuntimeException} - * when no song matches. - */ - static final BiFunction, String, String> FIND_ID_OF_SONG_WITH_TITLE = (map, title) -> map.entrySet() - .stream().filter(entry -> title.equals(entry.getValue().getTitle())) - .findAny() - .map(Map.Entry::getKey) - .orElseThrow(() -> new RuntimeException("Cannot find id of song with title: " + title)); - - /** - * Function that returns id assigned to song with artist equal to given artist or throws {@link RuntimeException} - * when no song matches. - */ - static final BiFunction, String, String> FIND_ID_OF_SONG_WITH_ARTIST = (map, artist) -> map.entrySet() - .stream().filter(entry -> artist.equals(entry.getValue().getArtist())) - .findAny() - .map(Map.Entry::getKey) - .orElseThrow(() -> new RuntimeException("Cannot find id of song with artist: " + artist)); - - static final TreeMap FIRST_INDEX_SONGS_BY_ID = new TreeMap<>() {{ // SONG = (String artist, String title, String lyrics, Integer stars, String genre) - put(FIRST_INDEX_ID_SONG_1, SONGS[0]); // (ARTIST_FIRST, TITLE_MAGNUM_OPUS ,LYRICS_1, 1, GENRE_ROCK) - put(FIRST_INDEX_ID_SONG_2, SONGS[1]); // (ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES), - put(FIRST_INDEX_ID_SONG_3, SONGS[2]); // (ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ), - put(FIRST_INDEX_ID_SONG_4, SONGS[3]); // (ARTIST_NO, TITLE_POISON, LYRICS_4, 4, GENRE_ROCK), - put(FIRST_INDEX_ID_SONG_5, SONGS[4]); // (ARTIST_YES, TITLE_AFFIRMATIVE,LYRICS_5, 5, GENRE_BLUES), - put(FIRST_INDEX_ID_SONG_6, SONGS[5]); // (ARTIST_UNKNOWN, TITLE_CONFIDENTIAL, LYRICS_6, 6, GENRE_JAZZ) - }}; - - static final TreeMap SECOND_INDEX_SONGS_BY_ID = new TreeMap<>() {{ - put(SECOND_INDEX_ID_SONG_1, SONGS[3]); // (ARTIST_NO, TITLE_POISON, LYRICS_4, 4, GENRE_ROCK), - put(SECOND_INDEX_ID_SONG_2, SONGS[2]); // (ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ), - put(SECOND_INDEX_ID_SONG_3, SONGS[1]); // (ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES), - put(SECOND_INDEX_ID_SONG_4, SONGS[0]); // (ARTIST_FIRST, TITLE_MAGNUM_OPUS ,LYRICS_1, 1, GENRE_ROCK) - }}; - - @BeforeClass - public static void createTestData() { - try(Client client = cluster.getInternalNodeClient()){ - FIRST_INDEX_SONGS_BY_ID.forEach((id, song) -> { - client.prepareIndex(FIRST_INDEX_NAME).setId(id).setRefreshPolicy(IMMEDIATE).setSource(song.asMap()).get(); - }); - - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) - .indices(FIRST_INDEX_NAME) - .alias(FIRST_INDEX_ALIAS) - )).actionGet(); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) - .index(FIRST_INDEX_NAME) - .alias(FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE) - .filter(QueryBuilders.queryStringQuery(QUERY_TITLE_NEXT_SONG)) - )).actionGet(); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) - .index(FIRST_INDEX_NAME) - .alias(FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST) - .filter(QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, ARTIST_TWINS))) - )).actionGet(); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) - .index(FIRST_INDEX_NAME) - .alias(FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST) - .filter(QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, ARTIST_FIRST))) - )).actionGet(); - - SECOND_INDEX_SONGS_BY_ID.forEach((id, song) -> { - client.prepareIndex(SECOND_INDEX_NAME).setId(id).setRefreshPolicy(IMMEDIATE).setSource(song.asMap()).get(); - }); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) - .indices(SECOND_INDEX_NAME) - .alias(SECOND_INDEX_ALIAS) - )).actionGet(); - } - } - - @Test - public void testShouldSearchAll() throws IOException { - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_ALL_USER)) { - SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(6)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); - assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); - assertThat(searchResponse, searchHitContainsFieldWithValue(2, FIELD_ARTIST, ARTIST_TWINS)); - assertThat(searchResponse, searchHitContainsFieldWithValue(3, FIELD_ARTIST, ARTIST_NO)); - assertThat(searchResponse, searchHitContainsFieldWithValue(4, FIELD_ARTIST, ARTIST_YES)); - assertThat(searchResponse, searchHitContainsFieldWithValue(5, FIELD_ARTIST, ARTIST_UNKNOWN)); - - searchRequest = new SearchRequest(SECOND_INDEX_NAME); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(4)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_NO)); - } - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_FIRST_AND_SECOND_USER)) { - SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(6)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); - assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); - assertThat(searchResponse, searchHitContainsFieldWithValue(2, FIELD_ARTIST, ARTIST_TWINS)); - assertThat(searchResponse, searchHitContainsFieldWithValue(3, FIELD_ARTIST, ARTIST_NO)); - assertThat(searchResponse, searchHitContainsFieldWithValue(4, FIELD_ARTIST, ARTIST_YES)); - assertThat(searchResponse, searchHitContainsFieldWithValue(5, FIELD_ARTIST, ARTIST_UNKNOWN)); - - searchRequest = new SearchRequest(SECOND_INDEX_NAME); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(4)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_NO)); - } - } - - @Test - public void testShouldSearchI1_S2I2_S3() throws IOException { - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_STRING)) { - SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_STRING)); - - searchRequest = new SearchRequest(SECOND_INDEX_NAME); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_STRING)); - } - } - - public void testShouldSearchI1_S3I1_S6I2_S2() throws IOException { - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_FIELD_STARS_GREATER_THAN_FIVE)) { - SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); - assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_UNKNOWN)); - - searchRequest = new SearchRequest(SECOND_INDEX_NAME); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); - } - } - - public void testShouldSearchI1_S1I1_S3I2_S2I2_S4() throws IOException { - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_MATCHES_ARTIST_FIRST)) { - SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); - assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_FIRST)); - - searchRequest = new SearchRequest(SECOND_INDEX_NAME); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); - assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_FIRST)); - } - } - - public void testShouldSearchStarsLessThanThree() throws IOException { - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_STARS_LESS_THAN_THREE)) { - SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); - assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); - - searchRequest = new SearchRequest(SECOND_INDEX_NAME); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_STRING)); - assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_FIRST)); - } - } - - - @Test - public void testSearchForAllDocumentsWithIndexPattern() throws IOException { - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_ALL_USER)) { - SearchRequest searchRequest = new SearchRequest("*".concat(FIRST_INDEX_NAME)); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(6)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); - assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); - assertThat(searchResponse, searchHitContainsFieldWithValue(2, FIELD_ARTIST, ARTIST_TWINS)); - assertThat(searchResponse, searchHitContainsFieldWithValue(3, FIELD_ARTIST, ARTIST_NO)); - assertThat(searchResponse, searchHitContainsFieldWithValue(4, FIELD_ARTIST, ARTIST_YES)); - assertThat(searchResponse, searchHitContainsFieldWithValue(5, FIELD_ARTIST, ARTIST_UNKNOWN)); - - searchRequest = new SearchRequest("*".concat(SECOND_INDEX_NAME)); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(4)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_NO)); - } - } - - @Test - public void testSearchForAllDocumentsWithAlias() throws IOException { - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_ALL_USER)) { - SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_ALIAS); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(6)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); - assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); - assertThat(searchResponse, searchHitContainsFieldWithValue(2, FIELD_ARTIST, ARTIST_TWINS)); - assertThat(searchResponse, searchHitContainsFieldWithValue(3, FIELD_ARTIST, ARTIST_NO)); - assertThat(searchResponse, searchHitContainsFieldWithValue(4, FIELD_ARTIST, ARTIST_YES)); - assertThat(searchResponse, searchHitContainsFieldWithValue(5, FIELD_ARTIST, ARTIST_UNKNOWN)); - - searchRequest = new SearchRequest("*".concat(SECOND_INDEX_NAME)); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(4)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_NO)); - } - } - - @Test - public void testAggregateAndComputeStarRatings() throws IOException { - - //DLS - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_MATCHES_ARTIST_FIRST)) { - String aggregationName = "averageStars"; - Song song = FIRST_INDEX_SONGS_BY_ID.get(FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_TWINS)); - - SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); - Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); - assertThat(actualAggregation, instanceOf(ParsedAvg.class)); - assertThat(((ParsedAvg) actualAggregation).getValue(), is(song.getStars()*1.0)); - } - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_FIELD_STARS_GREATER_THAN_FIVE)) { - String aggregationName = "averageStars"; - SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); - Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); - assertThat(actualAggregation, instanceOf(ParsedAvg.class)); - assertThat(((ParsedAvg) actualAggregation).getValue(), is(4.5)); - } - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_STARS_LESS_THAN_THREE)) { - String aggregationName = "averageStars"; - SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); - Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); - assertThat(actualAggregation, instanceOf(ParsedAvg.class)); - assertThat(((ParsedAvg) actualAggregation).getValue(), is(1.5)); - } - } + static final String FIRST_INDEX_ID_SONG_1 = "INDEX_1_S1"; + static final String FIRST_INDEX_ID_SONG_2 = "INDEX_1_S2"; + static final String FIRST_INDEX_ID_SONG_3 = "INDEX_1_S3"; + static final String FIRST_INDEX_ID_SONG_4 = "INDEX_1_S4"; + static final String FIRST_INDEX_ID_SONG_5 = "INDEX_1_S5"; + static final String FIRST_INDEX_ID_SONG_6 = "INDEX_1_S6"; + static final String SECOND_INDEX_ID_SONG_1 = "INDEX_2_S1"; + static final String SECOND_INDEX_ID_SONG_2 = "INDEX_2_S2"; + static final String SECOND_INDEX_ID_SONG_3 = "INDEX_2_S3"; + static final String SECOND_INDEX_ID_SONG_4 = "INDEX_2_S4"; + + static final String INDEX_NAME_SUFFIX = "-test-index"; + static final String FIRST_INDEX_NAME = "first".concat(INDEX_NAME_SUFFIX); + static final String SECOND_INDEX_NAME = "second".concat(INDEX_NAME_SUFFIX); + static final String FIRST_INDEX_ALIAS = FIRST_INDEX_NAME.concat("-alias"); + static final String SECOND_INDEX_ALIAS = SECOND_INDEX_NAME.concat("-alias"); + static final String FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE = FIRST_INDEX_NAME.concat("-filtered-by-next-song-title"); + static final String FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST = FIRST_INDEX_NAME.concat("-filtered-by-twins-artist"); + static final String FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST = FIRST_INDEX_NAME.concat("-filtered-by-first-artist"); + static final String ALL_INDICES_ALIAS = "_all"; + + static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + /** + * User who is allowed to read all indices. + */ + static final TestSecurityConfig.User READ_ALL_USER = new TestSecurityConfig.User("read_all_user").roles( + new TestSecurityConfig.Role("read_all_user").clusterPermissions("cluster_composite_ops_ro").indexPermissions("read").on("*") + ); + + /** + * User who is allowed to see all fields on indices {@link #FIRST_INDEX_NAME} and {@link #SECOND_INDEX_NAME}. + */ + static final TestSecurityConfig.User READ_FIRST_AND_SECOND_USER = new TestSecurityConfig.User("read_first_and_second_user").roles( + new TestSecurityConfig.Role("first_index_reader").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .on(FIRST_INDEX_NAME), + new TestSecurityConfig.Role("second_index_reader").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .on(SECOND_INDEX_NAME) + ); + + /** + * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_STRING}. + */ + static final TestSecurityConfig.User READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_STRING = new TestSecurityConfig.User( + "read_where_field_artist_matches_artist_string" + ).roles( + new TestSecurityConfig.Role("read_where_field_artist_matches_artist_string").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_STRING)) + .on("*") + ); + + /** + * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_TWINS} OR {@link Song#FIELD_STARS} is greater than five. + */ + static final TestSecurityConfig.User READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_FIELD_STARS_GREATER_THAN_FIVE = + new TestSecurityConfig.User("read_where_field_artist_matches_artist_twins_or_field_stars_greater_than_five").roles( + new TestSecurityConfig.Role("read_where_field_artist_matches_artist_twins").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_TWINS)) + .on("*"), + new TestSecurityConfig.Role("read_where_field_stars_greater_than_five").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"range\":{\"%s\":{\"gt\":%d}}}", FIELD_STARS, 5)) + .on("*") + ); + + /** + * User who is allowed to see documents on indices where value of {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_TWINS} or {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_FIRST}: + */ + static final TestSecurityConfig.User READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_MATCHES_ARTIST_FIRST = new TestSecurityConfig.User( + "read_where_field_artist_matches_artist_twins_or_artist_first" + ).roles( + new TestSecurityConfig.Role("read_where_field_artist_matches_artist_twins").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_TWINS)) + .on(FIRST_INDEX_NAME), + new TestSecurityConfig.Role("read_where_field_artist_matches_artist_first").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_FIRST)) + .on(SECOND_INDEX_NAME) + ); + + /** + * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_STARS} is less than three. + */ + static final TestSecurityConfig.User READ_WHERE_STARS_LESS_THAN_THREE = new TestSecurityConfig.User("read_where_stars_less_than_three") + .roles( + new TestSecurityConfig.Role("read_where_stars_less_than_three").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"range\":{\"%s\":{\"lt\":%d}}}", FIELD_STARS, 3)) + .on("*") + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .anonymousAuth(false) + .nodeSettings( + Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName())) + ) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users( + ADMIN_USER, + READ_ALL_USER, + READ_FIRST_AND_SECOND_USER, + READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_STRING, + READ_WHERE_STARS_LESS_THAN_THREE, + READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_FIELD_STARS_GREATER_THAN_FIVE, + READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_MATCHES_ARTIST_FIRST + ) + .build(); + + /** + * Function that returns id assigned to song with title equal to given title or throws {@link RuntimeException} + * when no song matches. + */ + static final BiFunction, String, String> FIND_ID_OF_SONG_WITH_TITLE = (map, title) -> map.entrySet() + .stream() + .filter(entry -> title.equals(entry.getValue().getTitle())) + .findAny() + .map(Map.Entry::getKey) + .orElseThrow(() -> new RuntimeException("Cannot find id of song with title: " + title)); + + /** + * Function that returns id assigned to song with artist equal to given artist or throws {@link RuntimeException} + * when no song matches. + */ + static final BiFunction, String, String> FIND_ID_OF_SONG_WITH_ARTIST = (map, artist) -> map.entrySet() + .stream() + .filter(entry -> artist.equals(entry.getValue().getArtist())) + .findAny() + .map(Map.Entry::getKey) + .orElseThrow(() -> new RuntimeException("Cannot find id of song with artist: " + artist)); + + static final TreeMap FIRST_INDEX_SONGS_BY_ID = new TreeMap<>() { + { // SONG = (String artist, String title, String lyrics, Integer stars, String genre) + put(FIRST_INDEX_ID_SONG_1, SONGS[0]); // (ARTIST_FIRST, TITLE_MAGNUM_OPUS ,LYRICS_1, 1, GENRE_ROCK) + put(FIRST_INDEX_ID_SONG_2, SONGS[1]); // (ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES), + put(FIRST_INDEX_ID_SONG_3, SONGS[2]); // (ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ), + put(FIRST_INDEX_ID_SONG_4, SONGS[3]); // (ARTIST_NO, TITLE_POISON, LYRICS_4, 4, GENRE_ROCK), + put(FIRST_INDEX_ID_SONG_5, SONGS[4]); // (ARTIST_YES, TITLE_AFFIRMATIVE,LYRICS_5, 5, GENRE_BLUES), + put(FIRST_INDEX_ID_SONG_6, SONGS[5]); // (ARTIST_UNKNOWN, TITLE_CONFIDENTIAL, LYRICS_6, 6, GENRE_JAZZ) + } + }; + + static final TreeMap SECOND_INDEX_SONGS_BY_ID = new TreeMap<>() { + { + put(SECOND_INDEX_ID_SONG_1, SONGS[3]); // (ARTIST_NO, TITLE_POISON, LYRICS_4, 4, GENRE_ROCK), + put(SECOND_INDEX_ID_SONG_2, SONGS[2]); // (ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ), + put(SECOND_INDEX_ID_SONG_3, SONGS[1]); // (ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES), + put(SECOND_INDEX_ID_SONG_4, SONGS[0]); // (ARTIST_FIRST, TITLE_MAGNUM_OPUS ,LYRICS_1, 1, GENRE_ROCK) + } + }; + + @BeforeClass + public static void createTestData() { + try (Client client = cluster.getInternalNodeClient()) { + FIRST_INDEX_SONGS_BY_ID.forEach((id, song) -> { + client.prepareIndex(FIRST_INDEX_NAME).setId(id).setRefreshPolicy(IMMEDIATE).setSource(song.asMap()).get(); + }); + + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).indices(FIRST_INDEX_NAME).alias(FIRST_INDEX_ALIAS) + ) + ) + .actionGet(); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).index(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE) + .filter(QueryBuilders.queryStringQuery(QUERY_TITLE_NEXT_SONG)) + ) + ) + .actionGet(); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).index(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST) + .filter(QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, ARTIST_TWINS))) + ) + ) + .actionGet(); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).index(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST) + .filter(QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, ARTIST_FIRST))) + ) + ) + .actionGet(); + + SECOND_INDEX_SONGS_BY_ID.forEach((id, song) -> { + client.prepareIndex(SECOND_INDEX_NAME).setId(id).setRefreshPolicy(IMMEDIATE).setSource(song.asMap()).get(); + }); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).indices(SECOND_INDEX_NAME).alias(SECOND_INDEX_ALIAS) + ) + ) + .actionGet(); + } + } + + @Test + public void testShouldSearchAll() throws IOException { + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_ALL_USER)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(6)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); + assertThat(searchResponse, searchHitContainsFieldWithValue(2, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(3, FIELD_ARTIST, ARTIST_NO)); + assertThat(searchResponse, searchHitContainsFieldWithValue(4, FIELD_ARTIST, ARTIST_YES)); + assertThat(searchResponse, searchHitContainsFieldWithValue(5, FIELD_ARTIST, ARTIST_UNKNOWN)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(4)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_NO)); + } + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_FIRST_AND_SECOND_USER)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(6)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); + assertThat(searchResponse, searchHitContainsFieldWithValue(2, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(3, FIELD_ARTIST, ARTIST_NO)); + assertThat(searchResponse, searchHitContainsFieldWithValue(4, FIELD_ARTIST, ARTIST_YES)); + assertThat(searchResponse, searchHitContainsFieldWithValue(5, FIELD_ARTIST, ARTIST_UNKNOWN)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(4)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_NO)); + } + } + + @Test + public void testShouldSearchI1_S2I2_S3() throws IOException { + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_STRING)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_STRING)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_STRING)); + } + } + + public void testShouldSearchI1_S3I1_S6I2_S2() throws IOException { + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_FIELD_STARS_GREATER_THAN_FIVE + ) + ) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_UNKNOWN)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + } + } + + public void testShouldSearchI1_S1I1_S3I2_S2I2_S4() throws IOException { + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_MATCHES_ARTIST_FIRST + ) + ) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_FIRST)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_FIRST)); + } + } + + public void testShouldSearchStarsLessThanThree() throws IOException { + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_STARS_LESS_THAN_THREE)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_NAME); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); + + searchRequest = new SearchRequest(SECOND_INDEX_NAME); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(2)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_STRING)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_FIRST)); + } + } + + @Test + public void testSearchForAllDocumentsWithIndexPattern() throws IOException { + + // DLS + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_ALL_USER)) { + SearchRequest searchRequest = new SearchRequest("*".concat(FIRST_INDEX_NAME)); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(6)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); + assertThat(searchResponse, searchHitContainsFieldWithValue(2, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(3, FIELD_ARTIST, ARTIST_NO)); + assertThat(searchResponse, searchHitContainsFieldWithValue(4, FIELD_ARTIST, ARTIST_YES)); + assertThat(searchResponse, searchHitContainsFieldWithValue(5, FIELD_ARTIST, ARTIST_UNKNOWN)); + + searchRequest = new SearchRequest("*".concat(SECOND_INDEX_NAME)); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(4)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_NO)); + } + } + + @Test + public void testSearchForAllDocumentsWithAlias() throws IOException { + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_ALL_USER)) { + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_ALIAS); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(6)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_FIRST)); + assertThat(searchResponse, searchHitContainsFieldWithValue(1, FIELD_ARTIST, ARTIST_STRING)); + assertThat(searchResponse, searchHitContainsFieldWithValue(2, FIELD_ARTIST, ARTIST_TWINS)); + assertThat(searchResponse, searchHitContainsFieldWithValue(3, FIELD_ARTIST, ARTIST_NO)); + assertThat(searchResponse, searchHitContainsFieldWithValue(4, FIELD_ARTIST, ARTIST_YES)); + assertThat(searchResponse, searchHitContainsFieldWithValue(5, FIELD_ARTIST, ARTIST_UNKNOWN)); + + searchRequest = new SearchRequest("*".concat(SECOND_INDEX_NAME)); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(4)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, ARTIST_NO)); + } + } + + @Test + public void testAggregateAndComputeStarRatings() throws IOException { + + // DLS + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_MATCHES_ARTIST_FIRST + ) + ) { + String aggregationName = "averageStars"; + Song song = FIRST_INDEX_SONGS_BY_ID.get(FIND_ID_OF_SONG_WITH_ARTIST.apply(FIRST_INDEX_SONGS_BY_ID, ARTIST_TWINS)); + + SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); + assertThat(actualAggregation, instanceOf(ParsedAvg.class)); + assertThat(((ParsedAvg) actualAggregation).getValue(), is(song.getStars() * 1.0)); + } + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + READ_WHERE_FIELD_ARTIST_MATCHES_ARTIST_TWINS_OR_FIELD_STARS_GREATER_THAN_FIVE + ) + ) { + String aggregationName = "averageStars"; + SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); + assertThat(actualAggregation, instanceOf(ParsedAvg.class)); + assertThat(((ParsedAvg) actualAggregation).getValue(), is(4.5)); + } + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(READ_WHERE_STARS_LESS_THAN_THREE)) { + String aggregationName = "averageStars"; + SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); + assertThat(actualAggregation, instanceOf(ParsedAvg.class)); + assertThat(((ParsedAvg) actualAggregation).getValue(), is(1.5)); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java b/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java index 67d61b7d6f..f3b641f1e6 100644 --- a/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java +++ b/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java @@ -80,301 +80,332 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class DoNotFailOnForbiddenTests { - /** - * Songs accessible for {@link #LIMITED_USER} - */ - private static final String MARVELOUS_SONGS = "marvelous_songs"; - - /** - * Songs inaccessible for {@link #LIMITED_USER} - */ - private static final String HORRIBLE_SONGS = "horrible_songs"; - - private static final String BOTH_INDEX_PATTERN = "*songs"; - - private static final String ID_1 = "1"; - private static final String ID_2 = "2"; - private static final String ID_3 = "3"; - private static final String ID_4 = "4"; - - private static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS); - private static final User LIMITED_USER = new User("limited_user") - .roles(new TestSecurityConfig.Role("limited-role") - .clusterPermissions("indices:data/read/mget", "indices:data/read/msearch", "indices:data/read/scroll") - .indexPermissions("indices:data/read/search", "indices:data/read/mget*", "indices:data/read/field_caps", "indices:data/read/field_caps*", "indices:data/read/msearch", "indices:data/read/scroll") - .on(MARVELOUS_SONGS)); - - private static final String BOTH_INDEX_ALIAS = "both-indices"; - private static final String FORBIDDEN_INDEX_ALIAS = "forbidden-index"; - - @ClassRule - public static LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).authc(AUTHC_HTTPBASIC_INTERNAL) - .users(ADMIN_USER, LIMITED_USER).anonymousAuth(false).doNotFailOnForbidden(true).build(); - - @BeforeClass - public static void createTestData() { - try(Client client = cluster.getInternalNodeClient()) { - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_1).source(SONGS[0].asMap())).actionGet(); - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_2).source(SONGS[1].asMap())).actionGet(); - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_3).source(SONGS[2].asMap())).actionGet(); - - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(HORRIBLE_SONGS).id(ID_4).source(SONGS[3].asMap())).actionGet(); - - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD).indices( - MARVELOUS_SONGS, HORRIBLE_SONGS).alias(BOTH_INDEX_ALIAS))).actionGet(); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD).indices( - HORRIBLE_SONGS).alias(FORBIDDEN_INDEX_ALIAS))).actionGet(); - - } - } - - @Test - public void shouldPerformSimpleSearch_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(new String[]{MARVELOUS_SONGS, HORRIBLE_SONGS}, QUERY_TITLE_MAGNUM_OPUS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThatContainOneSong(searchResponse, ID_1, TITLE_MAGNUM_OPUS); - } - } - - private static void assertThatContainOneSong(SearchResponse searchResponse, String documentId, String title) { - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, MARVELOUS_SONGS, documentId)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, title)); - } - - @Test - public void shouldPerformSimpleSearch_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(HORRIBLE_SONGS, QUERY_TITLE_POISON); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldSearchForDocumentsViaIndexPattern_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(BOTH_INDEX_PATTERN, QUERY_TITLE_MAGNUM_OPUS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThatContainOneSong(searchResponse, ID_1, TITLE_MAGNUM_OPUS); - } - } - - @Test - public void shouldSearchForDocumentsViaIndexPattern_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(HORRIBLE_SONGS, QUERY_TITLE_POISON); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldSearchForDocumentsViaAlias_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(BOTH_INDEX_ALIAS, QUERY_TITLE_MAGNUM_OPUS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThatContainOneSong(searchResponse, ID_1, TITLE_MAGNUM_OPUS); - } - } - - @Test - public void shouldSearchForDocumentsViaAlias_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(FORBIDDEN_INDEX_ALIAS, QUERY_TITLE_POISON); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldSearchForDocumentsViaAll_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = queryStringQueryRequest("_all", QUERY_TITLE_MAGNUM_OPUS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThatContainOneSong(searchResponse, ID_1, TITLE_MAGNUM_OPUS); - } - } - - @Test - public void shouldSearchForDocumentsViaAll_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = queryStringQueryRequest("_all", QUERY_TITLE_POISON); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(0)); - } - } - - @Test - public void shouldMGetDocument_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - MultiGetRequest request = new MultiGetRequest() - .add(BOTH_INDEX_PATTERN, ID_1) - .add(BOTH_INDEX_PATTERN, ID_4); - - MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); - - MultiGetItemResponse[] responses = response.getResponses(); - assertThat(responses, arrayWithSize(2)); - MultiGetItemResponse firstResult = responses[0]; - MultiGetItemResponse secondResult = responses[1]; - assertThat(firstResult.getFailure(), nullValue()); - assertThat(secondResult.getFailure(), nullValue()); - assertThat(firstResult.getResponse(), allOf( - containDocument(MARVELOUS_SONGS, ID_1), - documentContainField(FIELD_TITLE, TITLE_MAGNUM_OPUS)) - ); - assertThat(secondResult.getResponse(), containOnlyDocumentId(MARVELOUS_SONGS, ID_4)); - } - } - - @Test - public void shouldMGetDocument_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - MultiGetRequest request = new MultiGetRequest().add(HORRIBLE_SONGS, ID_4); - - assertThatThrownBy(() -> restHighLevelClient.mget(request, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldMSearchDocument_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(queryStringQueryRequest(BOTH_INDEX_PATTERN, QUERY_TITLE_MAGNUM_OPUS)); - request.add(queryStringQueryRequest(BOTH_INDEX_PATTERN, QUERY_TITLE_NEXT_SONG)); - - MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); - - MultiSearchResponse.Item[] responses = response.getResponses(); - assertThat(responses, Matchers.arrayWithSize(2)); - assertThat(responses[0].getFailure(), nullValue()); - assertThat(responses[1].getFailure(), nullValue()); - - assertThat(responses[0].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); - assertThat(responses[0].getResponse(), searchHitsContainDocumentWithId(0, MARVELOUS_SONGS, ID_1)); - assertThat(responses[1].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); - assertThat(responses[1].getResponse(), searchHitsContainDocumentWithId(0, MARVELOUS_SONGS, ID_3)); - } - } - - @Test - public void shouldMSearchDocument_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(queryStringQueryRequest(FORBIDDEN_INDEX_ALIAS, QUERY_TITLE_POISON)); - - assertThatThrownBy(() -> restHighLevelClient.msearch(request, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldGetFieldCapabilities_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(MARVELOUS_SONGS, HORRIBLE_SONGS).fields(FIELD_TITLE); - - FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); - - assertThat(response.get(), aMapWithSize(1)); - assertThat(response.getIndices(), arrayWithSize(1)); - assertThat(response.getField(FIELD_TITLE), hasKey("text")); - assertThat(response.getIndices(), arrayContainingInAnyOrder(MARVELOUS_SONGS)); - } - } - - @Test - public void shouldGetFieldCapabilities_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(HORRIBLE_SONGS).fields(FIELD_TITLE); - - assertThatThrownBy(() -> restHighLevelClient.fieldCaps(request, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldScrollOverSearchResults_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = searchRequestWithScroll(BOTH_INDEX_PATTERN, 2); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containNotEmptyScrollingId()); - - SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); - - SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); - assertThat(scrollResponse, isSuccessfulSearchResponse()); - assertThat(scrollResponse, containNotEmptyScrollingId()); - assertThat(scrollResponse, numberOfTotalHitsIsEqualTo(3)); - assertThat(scrollResponse, numberOfHitsInPageIsEqualTo(1)); - } - } - - @Test - public void shouldScrollOverSearchResults_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - SearchRequest searchRequest = searchRequestWithScroll(HORRIBLE_SONGS, 2); - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldPerformAggregation_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - final String aggregationName = "averageStars"; - SearchRequest searchRequest = averageAggregationRequest(BOTH_INDEX_PATTERN, aggregationName, FIELD_STARS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); - } - } - - @Test - public void shouldPerformAggregation_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - final String aggregationName = "averageStars"; - SearchRequest searchRequest = averageAggregationRequest(HORRIBLE_SONGS, aggregationName, FIELD_STARS); - - assertThatThrownBy( () -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldPerformStatAggregation_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - final String aggregationName = "statsStars"; - SearchRequest searchRequest = statsAggregationRequest(BOTH_INDEX_ALIAS, aggregationName, FIELD_STARS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "stats")); - } - } - - @Test - public void shouldPerformStatAggregation_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { - final String aggregationName = "statsStars"; - SearchRequest searchRequest = statsAggregationRequest(HORRIBLE_SONGS, aggregationName, FIELD_STARS); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } + /** + * Songs accessible for {@link #LIMITED_USER} + */ + private static final String MARVELOUS_SONGS = "marvelous_songs"; + + /** + * Songs inaccessible for {@link #LIMITED_USER} + */ + private static final String HORRIBLE_SONGS = "horrible_songs"; + + private static final String BOTH_INDEX_PATTERN = "*songs"; + + private static final String ID_1 = "1"; + private static final String ID_2 = "2"; + private static final String ID_3 = "3"; + private static final String ID_4 = "4"; + + private static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS); + private static final User LIMITED_USER = new User("limited_user").roles( + new TestSecurityConfig.Role("limited-role").clusterPermissions( + "indices:data/read/mget", + "indices:data/read/msearch", + "indices:data/read/scroll" + ) + .indexPermissions( + "indices:data/read/search", + "indices:data/read/mget*", + "indices:data/read/field_caps", + "indices:data/read/field_caps*", + "indices:data/read/msearch", + "indices:data/read/scroll" + ) + .on(MARVELOUS_SONGS) + ); + + private static final String BOTH_INDEX_ALIAS = "both-indices"; + private static final String FORBIDDEN_INDEX_ALIAS = "forbidden-index"; + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER, LIMITED_USER) + .anonymousAuth(false) + .doNotFailOnForbidden(true) + .build(); + + @BeforeClass + public static void createTestData() { + try (Client client = cluster.getInternalNodeClient()) { + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_1).source(SONGS[0].asMap())) + .actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_2).source(SONGS[1].asMap())) + .actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(MARVELOUS_SONGS).id(ID_3).source(SONGS[2].asMap())) + .actionGet(); + + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(HORRIBLE_SONGS).id(ID_4).source(SONGS[3].asMap())) + .actionGet(); + + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).indices(MARVELOUS_SONGS, HORRIBLE_SONGS).alias(BOTH_INDEX_ALIAS) + ) + ) + .actionGet(); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).indices(HORRIBLE_SONGS).alias(FORBIDDEN_INDEX_ALIAS) + ) + ) + .actionGet(); + + } + } + + @Test + public void shouldPerformSimpleSearch_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest( + new String[] { MARVELOUS_SONGS, HORRIBLE_SONGS }, + QUERY_TITLE_MAGNUM_OPUS + ); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThatContainOneSong(searchResponse, ID_1, TITLE_MAGNUM_OPUS); + } + } + + private static void assertThatContainOneSong(SearchResponse searchResponse, String documentId, String title) { + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, MARVELOUS_SONGS, documentId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, title)); + } + + @Test + public void shouldPerformSimpleSearch_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(HORRIBLE_SONGS, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentsViaIndexPattern_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(BOTH_INDEX_PATTERN, QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThatContainOneSong(searchResponse, ID_1, TITLE_MAGNUM_OPUS); + } + } + + @Test + public void shouldSearchForDocumentsViaIndexPattern_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(HORRIBLE_SONGS, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentsViaAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(BOTH_INDEX_ALIAS, QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThatContainOneSong(searchResponse, ID_1, TITLE_MAGNUM_OPUS); + } + } + + @Test + public void shouldSearchForDocumentsViaAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(FORBIDDEN_INDEX_ALIAS, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldSearchForDocumentsViaAll_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("_all", QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThatContainOneSong(searchResponse, ID_1, TITLE_MAGNUM_OPUS); + } + } + + @Test + public void shouldSearchForDocumentsViaAll_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("_all", QUERY_TITLE_POISON); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(0)); + } + } + + @Test + public void shouldMGetDocument_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + MultiGetRequest request = new MultiGetRequest().add(BOTH_INDEX_PATTERN, ID_1).add(BOTH_INDEX_PATTERN, ID_4); + + MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); + + MultiGetItemResponse[] responses = response.getResponses(); + assertThat(responses, arrayWithSize(2)); + MultiGetItemResponse firstResult = responses[0]; + MultiGetItemResponse secondResult = responses[1]; + assertThat(firstResult.getFailure(), nullValue()); + assertThat(secondResult.getFailure(), nullValue()); + assertThat( + firstResult.getResponse(), + allOf(containDocument(MARVELOUS_SONGS, ID_1), documentContainField(FIELD_TITLE, TITLE_MAGNUM_OPUS)) + ); + assertThat(secondResult.getResponse(), containOnlyDocumentId(MARVELOUS_SONGS, ID_4)); + } + } + + @Test + public void shouldMGetDocument_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + MultiGetRequest request = new MultiGetRequest().add(HORRIBLE_SONGS, ID_4); + + assertThatThrownBy(() -> restHighLevelClient.mget(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldMSearchDocument_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + MultiSearchRequest request = new MultiSearchRequest(); + request.add(queryStringQueryRequest(BOTH_INDEX_PATTERN, QUERY_TITLE_MAGNUM_OPUS)); + request.add(queryStringQueryRequest(BOTH_INDEX_PATTERN, QUERY_TITLE_NEXT_SONG)); + + MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); + + MultiSearchResponse.Item[] responses = response.getResponses(); + assertThat(responses, Matchers.arrayWithSize(2)); + assertThat(responses[0].getFailure(), nullValue()); + assertThat(responses[1].getFailure(), nullValue()); + + assertThat(responses[0].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat(responses[0].getResponse(), searchHitsContainDocumentWithId(0, MARVELOUS_SONGS, ID_1)); + assertThat(responses[1].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); + assertThat(responses[1].getResponse(), searchHitsContainDocumentWithId(0, MARVELOUS_SONGS, ID_3)); + } + } + + @Test + public void shouldMSearchDocument_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + MultiSearchRequest request = new MultiSearchRequest(); + request.add(queryStringQueryRequest(FORBIDDEN_INDEX_ALIAS, QUERY_TITLE_POISON)); + + assertThatThrownBy(() -> restHighLevelClient.msearch(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldGetFieldCapabilities_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(MARVELOUS_SONGS, HORRIBLE_SONGS).fields(FIELD_TITLE); + + FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); + + assertThat(response.get(), aMapWithSize(1)); + assertThat(response.getIndices(), arrayWithSize(1)); + assertThat(response.getField(FIELD_TITLE), hasKey("text")); + assertThat(response.getIndices(), arrayContainingInAnyOrder(MARVELOUS_SONGS)); + } + } + + @Test + public void shouldGetFieldCapabilities_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(HORRIBLE_SONGS).fields(FIELD_TITLE); + + assertThatThrownBy(() -> restHighLevelClient.fieldCaps(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldScrollOverSearchResults_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchRequestWithScroll(BOTH_INDEX_PATTERN, 2); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containNotEmptyScrollingId()); + + SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); + + SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); + assertThat(scrollResponse, isSuccessfulSearchResponse()); + assertThat(scrollResponse, containNotEmptyScrollingId()); + assertThat(scrollResponse, numberOfTotalHitsIsEqualTo(3)); + assertThat(scrollResponse, numberOfHitsInPageIsEqualTo(1)); + } + } + + @Test + public void shouldScrollOverSearchResults_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + SearchRequest searchRequest = searchRequestWithScroll(HORRIBLE_SONGS, 2); + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldPerformAggregation_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + final String aggregationName = "averageStars"; + SearchRequest searchRequest = averageAggregationRequest(BOTH_INDEX_PATTERN, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + } + } + + @Test + public void shouldPerformAggregation_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + final String aggregationName = "averageStars"; + SearchRequest searchRequest = averageAggregationRequest(HORRIBLE_SONGS, aggregationName, FIELD_STARS); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldPerformStatAggregation_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + final String aggregationName = "statsStars"; + SearchRequest searchRequest = statsAggregationRequest(BOTH_INDEX_ALIAS, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "stats")); + } + } + + @Test + public void shouldPerformStatAggregation_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { + final String aggregationName = "statsStars"; + SearchRequest searchRequest = statsAggregationRequest(HORRIBLE_SONGS, aggregationName, FIELD_STARS); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/FlsAndFieldMaskingTests.java b/src/integrationTest/java/org/opensearch/security/FlsAndFieldMaskingTests.java index 692a3d8e4f..4a5460f329 100644 --- a/src/integrationTest/java/org/opensearch/security/FlsAndFieldMaskingTests.java +++ b/src/integrationTest/java/org/opensearch/security/FlsAndFieldMaskingTests.java @@ -101,671 +101,714 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class FlsAndFieldMaskingTests { - static final String FIRST_INDEX_ID_SONG_1 = "INDEX_1_S1"; - static final String FIRST_INDEX_ID_SONG_2 = "INDEX_1_S2"; - static final String FIRST_INDEX_ID_SONG_3 = "INDEX_1_S3"; - static final String FIRST_INDEX_ID_SONG_4 = "INDEX_1_S4"; - static final String SECOND_INDEX_ID_SONG_1 = "INDEX_2_S1"; - static final String SECOND_INDEX_ID_SONG_2 = "INDEX_2_S2"; - static final String SECOND_INDEX_ID_SONG_3 = "INDEX_2_S3"; - static final String SECOND_INDEX_ID_SONG_4 = "INDEX_2_S4"; - - static final String INDEX_NAME_SUFFIX = "-test-index"; - static final String FIRST_INDEX_NAME = "first".concat(INDEX_NAME_SUFFIX); - static final String SECOND_INDEX_NAME = "second".concat(INDEX_NAME_SUFFIX); - static final String FIRST_INDEX_ALIAS = FIRST_INDEX_NAME.concat("-alias"); - static final String SECOND_INDEX_ALIAS = SECOND_INDEX_NAME.concat("-alias"); - static final String FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE = FIRST_INDEX_NAME.concat("-filtered-by-next-song-title"); - static final String FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST = FIRST_INDEX_NAME.concat("-filtered-by-twins-artist"); - static final String FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST = FIRST_INDEX_NAME.concat("-filtered-by-first-artist"); - static final String ALL_INDICES_ALIAS = "_all"; - - static final String MASK_VALUE = "*"; - - static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); - - /** - * User who is allowed to see all fields on all indices. Values of the title and artist fields should be masked. - */ - static final TestSecurityConfig.User ALL_INDICES_MASKED_TITLE_ARTIST_READER = new TestSecurityConfig.User("masked_artist_title_reader") - .roles( - new TestSecurityConfig.Role("masked_artist_title_reader") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .maskedFields( - FIELD_TITLE.concat("::/(?<=.{1})./::").concat(MASK_VALUE), - FIELD_ARTIST.concat("::/(?<=.{1})./::").concat(MASK_VALUE) - ) - .on("*") - ); - - /** - * User who is allowed to see all fields on indices {@link #FIRST_INDEX_NAME} and {@link #SECOND_INDEX_NAME}. - *
    - *
  • values of the artist and lyrics fields should be masked on index {@link #FIRST_INDEX_NAME}
  • - *
  • values of the lyrics field should be masked on index {@link #SECOND_INDEX_NAME}
  • - *
- */ - static final TestSecurityConfig.User MASKED_ARTIST_LYRICS_READER = new TestSecurityConfig.User("masked_title_artist_lyrics_reader") - .roles( - new TestSecurityConfig.Role("masked_title_artist_lyrics_reader") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .maskedFields( - FIELD_ARTIST.concat("::/(?<=.{1})./::").concat(MASK_VALUE), - FIELD_LYRICS.concat("::/(?<=.{1})./::").concat(MASK_VALUE) - ) - .on(FIRST_INDEX_NAME), - new TestSecurityConfig.Role("masked_lyrics_reader") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .maskedFields(FIELD_LYRICS.concat("::/(?<=.{1})./::").concat(MASK_VALUE)) - .on(SECOND_INDEX_NAME) - ); - - /** - * Function that converts field value to value masked with {@link #MASK_VALUE} - */ - static final Function VALUE_TO_MASKED_VALUE = value -> value.substring(0, 1) - .concat(MASK_VALUE.repeat(value.length() - 1)); - - /** - * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_STRING}. - */ - static final TestSecurityConfig.User ALL_INDICES_STRING_ARTIST_READER = new TestSecurityConfig.User("string_artist_reader") - .roles( - new TestSecurityConfig.Role("string_artist_reader") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_STRING)) - .on("*") - ); - - /** - * User who is allowed to see documents on index: - *
    - *
  • {@link #FIRST_INDEX_NAME} where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_TWINS}
  • - *
  • {@link #SECOND_INDEX_NAME} where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_FIRST}
  • - *
- */ - static final TestSecurityConfig.User TWINS_FIRST_ARTIST_READER = new TestSecurityConfig.User("twins_first_artist_reader") - .roles( - new TestSecurityConfig.Role("twins_artist_reader") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_TWINS)) - .on(FIRST_INDEX_NAME), - new TestSecurityConfig.Role("first_artist_reader") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_FIRST)) - .on(SECOND_INDEX_NAME) - ); - - /** - * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_STARS} is less than zero. - */ - static final TestSecurityConfig.User ALL_INDICES_STARS_LESS_THAN_ZERO_READER = new TestSecurityConfig.User("stars_less_than_zero_reader") - .roles( - new TestSecurityConfig.Role("stars_less_than_zero_reader") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .dls(String.format("{\"range\":{\"%s\":{\"lt\":%d}}}", FIELD_STARS, 0)) - .on("*") - ); - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) - .nodeSettings(Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() +"__" + ALL_ACCESS.getName()))) - .authc(AUTHC_HTTPBASIC_INTERNAL) - .users( - ADMIN_USER, - ALL_INDICES_MASKED_TITLE_ARTIST_READER, MASKED_ARTIST_LYRICS_READER, - ALL_INDICES_STRING_ARTIST_READER, ALL_INDICES_STARS_LESS_THAN_ZERO_READER, TWINS_FIRST_ARTIST_READER - ) - .build(); - - /** - * Function that returns id assigned to song with title equal to given title or throws {@link RuntimeException} - * when no song matches. - */ - static final BiFunction, String, String> FIND_ID_OF_SONG_WITH_TITLE = (map, title) -> map.entrySet() - .stream().filter(entry -> title.equals(entry.getValue().getTitle())) - .findAny() - .map(Map.Entry::getKey) - .orElseThrow(() -> new RuntimeException("Cannot find id of song with title: " + title)); - - /** - * Function that returns id assigned to song with artist equal to given artist or throws {@link RuntimeException} - * when no song matches. - */ - static final BiFunction, String, String> FIND_ID_OF_SONG_WITH_ARTIST = (map, artist) -> map.entrySet() - .stream().filter(entry -> artist.equals(entry.getValue().getArtist())) - .findAny() - .map(Map.Entry::getKey) - .orElseThrow(() -> new RuntimeException("Cannot find id of song with artist: " + artist)); - - static final TreeMap FIRST_INDEX_SONGS_BY_ID = new TreeMap<>() {{ - put(FIRST_INDEX_ID_SONG_1, SONGS[0]); - put(FIRST_INDEX_ID_SONG_2, SONGS[1]); - put(FIRST_INDEX_ID_SONG_3, SONGS[2]); - put(FIRST_INDEX_ID_SONG_4, SONGS[3]); - }}; - - static final TreeMap SECOND_INDEX_SONGS_BY_ID = new TreeMap<>() {{ - put(SECOND_INDEX_ID_SONG_1, SONGS[3]); - put(SECOND_INDEX_ID_SONG_2, SONGS[2]); - put(SECOND_INDEX_ID_SONG_3, SONGS[1]); - put(SECOND_INDEX_ID_SONG_4, SONGS[0]); - }}; - - @BeforeClass - public static void createTestData() { - try(Client client = cluster.getInternalNodeClient()){ - FIRST_INDEX_SONGS_BY_ID.forEach((id, song) -> { - client.prepareIndex(FIRST_INDEX_NAME).setId(id).setRefreshPolicy(IMMEDIATE).setSource(song.asMap()).get(); - }); - - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) - .indices(FIRST_INDEX_NAME) - .alias(FIRST_INDEX_ALIAS) - )).actionGet(); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) - .index(FIRST_INDEX_NAME) - .alias(FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE) - .filter(QueryBuilders.queryStringQuery(QUERY_TITLE_NEXT_SONG)) - )).actionGet(); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) - .index(FIRST_INDEX_NAME) - .alias(FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST) - .filter(QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, ARTIST_TWINS))) - )).actionGet(); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) - .index(FIRST_INDEX_NAME) - .alias(FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST) - .filter(QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, ARTIST_FIRST))) - )).actionGet(); - - SECOND_INDEX_SONGS_BY_ID.forEach((id, song) -> { - client.prepareIndex(SECOND_INDEX_NAME).setId(id).setRefreshPolicy(IMMEDIATE).setSource(song.asMap()).get(); - }); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) - .indices(SECOND_INDEX_NAME) - .alias(SECOND_INDEX_ALIAS) - )).actionGet(); - } - } - - @Test - public void flsEnabledFieldsAreHiddenForNormalUsers() throws IOException { - String indexName = "fls_index"; - String indexAlias = "fls_index_alias"; - String indexFilteredAlias = "fls_index_filtered_alias"; - TestSecurityConfig.Role userRole = new TestSecurityConfig.Role("fls_exclude_stars_reader") - .clusterPermissions("cluster_composite_ops_ro") - .indexPermissions("read") - .fls("~".concat(FIELD_STARS)) - .on("*"); - TestSecurityConfig.User user = createUserWithRole("fls_user", userRole); - List docIds = createIndexWithDocs(indexName, SONGS[0], SONGS[1]); - addAliasToIndex(indexName, indexAlias); - addAliasToIndex(indexName, indexFilteredAlias, QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, SONGS[0].getArtist()))); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(user)) { - //search - SearchResponse searchResponse = restHighLevelClient.search(new SearchRequest(indexName), DEFAULT); - - assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); - - //search with index pattern - searchResponse = restHighLevelClient.search(new SearchRequest("*".concat(indexName)), DEFAULT); - - assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); - - //search via alias - searchResponse = restHighLevelClient.search(new SearchRequest(indexAlias), DEFAULT); - - assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); - - //search via filtered alias - searchResponse = restHighLevelClient.search(new SearchRequest(indexFilteredAlias), DEFAULT); - - assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); - - //search via all indices alias - searchResponse = restHighLevelClient.search(new SearchRequest(ALL_INDICES_ALIAS), DEFAULT); - - assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); - - //scroll - searchResponse = restHighLevelClient.search(searchRequestWithScroll(indexName, 1), DEFAULT); - - assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); - - SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); - SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); - - assertSearchHitsDoNotContainField(scrollResponse, FIELD_STARS); - - //aggregate data and compute avg - String aggregationName = "averageStars"; - searchResponse = restHighLevelClient.search(averageAggregationRequest(indexName, aggregationName, FIELD_STARS), DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); - Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); - assertThat(actualAggregation, instanceOf(ParsedAvg.class)); - assertThat(((ParsedAvg) actualAggregation).getValue(), is(Double.POSITIVE_INFINITY)); //user cannot see the STARS field - - //get document - GetResponse getResponse = restHighLevelClient.get(new GetRequest(indexName, docIds.get(0)), DEFAULT); - - assertThat(getResponse, documentDoesNotContainField(FIELD_STARS)); - - //multi get - for (String index: List.of(indexName, indexAlias)) { - MultiGetRequest multiGetRequest = new MultiGetRequest(); - docIds.forEach(id -> multiGetRequest.add(new MultiGetRequest.Item(index, id))); - - MultiGetResponse multiGetResponse = restHighLevelClient.mget(multiGetRequest, DEFAULT); - - List getResponses = Arrays.stream(multiGetResponse.getResponses()) - .map(MultiGetItemResponse::getResponse) - .collect(Collectors.toList()); - assertThat(getResponses, everyItem(documentDoesNotContainField(FIELD_STARS))); - } - - //multi search - for (String index: List.of(indexName, indexAlias)) { - MultiSearchRequest multiSearchRequest = new MultiSearchRequest(); - docIds.forEach(id -> multiSearchRequest.add(queryByIdsRequest(index, id))); - MultiSearchResponse multiSearchResponse = restHighLevelClient.msearch(multiSearchRequest, DEFAULT); - - assertThat(multiSearchResponse, isSuccessfulMultiSearchResponse()); - List itemResponses = List.of(multiSearchResponse.getResponses()); - itemResponses.forEach(item -> assertSearchHitsDoNotContainField(item.getResponse(), FIELD_STARS)); - } - - //field capabilities - FieldCapabilitiesResponse fieldCapsResponse = restHighLevelClient.fieldCaps( - new FieldCapabilitiesRequest().indices(indexName).fields(FIELD_TITLE, FIELD_STARS), DEFAULT - ); - assertThat(fieldCapsResponse.getField(FIELD_STARS), nullValue()); - } - } - - private static List createIndexWithDocs(String indexName, Song... songs) { - try(Client client = cluster.getInternalNodeClient()){ - return Stream.of(songs).map(song -> { - IndexResponse response = client.index( - new IndexRequest(indexName).setRefreshPolicy(IMMEDIATE).source(song.asMap()) - ).actionGet(); - return response.getId(); - }).collect(Collectors.toList()); - } - } - - private static void addAliasToIndex(String indexName, String alias) { - addAliasToIndex(indexName, alias, QueryBuilders.matchAllQuery()); - } - - private static void addAliasToIndex(String indexName, String alias, QueryBuilder filterQuery) { - try(Client client = cluster.getInternalNodeClient()){ - client.admin().indices() - .aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD) - .indices(indexName) - .alias(alias) - .filter(filterQuery) - )).actionGet(); - } - } - - private static TestSecurityConfig.User createUserWithRole(String userName, TestSecurityConfig.Role role) { - TestSecurityConfig.User user = new TestSecurityConfig.User(userName); - try(TestRestClient client = cluster.getRestClient(ADMIN_USER)) { - client.createRole(role.getName(), role).assertStatusCode(201); - client.createUser(user.getName(), user).assertStatusCode(201); - client.assignRoleToUser(user.getName(), role.getName()).assertStatusCode(200); - } - return user; - } - - private static void assertSearchHitsDoNotContainField(SearchResponse response, String excludedField) { - assertThat(response, isSuccessfulSearchResponse()); - assertThat(response.getHits().getHits().length, greaterThan(0)); - IntStream.range(0, response.getHits().getHits().length).boxed() - .forEach(index -> assertThat(response, searchHitDoesNotContainField(index, excludedField))); - } - - @Test - public void searchForDocuments() throws IOException { - //FIELD MASKING - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { - String songId = FIRST_INDEX_ID_SONG_1; - Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); - - SearchRequest searchRequest = queryByIdsRequest(FIRST_INDEX_NAME, songId); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); - - songId = SECOND_INDEX_ID_SONG_2; - song = SECOND_INDEX_SONGS_BY_ID.get(songId); - - searchRequest = queryByIdsRequest(SECOND_INDEX_NAME, songId); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, songId)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, song.getArtist())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); - } - } - - @Test - public void searchForDocumentsWithIndexPattern() throws IOException { - //FIELD MASKING - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { - String songId = FIRST_INDEX_ID_SONG_2; - Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); - - SearchRequest searchRequest = queryByIdsRequest("*".concat(FIRST_INDEX_NAME), songId); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); - - - songId = SECOND_INDEX_ID_SONG_3; - song = SECOND_INDEX_SONGS_BY_ID.get(songId); - - searchRequest = queryByIdsRequest("*".concat(SECOND_INDEX_NAME), songId); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, songId)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, song.getArtist())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); - } - } - - @Test - public void searchForDocumentsViaAlias() throws IOException { - //FIELD MASKING - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { - String songId = FIRST_INDEX_ID_SONG_3; - Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); - - SearchRequest searchRequest = queryByIdsRequest(FIRST_INDEX_ALIAS, songId); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); - - songId = SECOND_INDEX_ID_SONG_4; - song = SECOND_INDEX_SONGS_BY_ID.get(songId); - - searchRequest = queryByIdsRequest("*".concat(SECOND_INDEX_ALIAS), songId); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, songId)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, song.getArtist())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); - } - } - - @Test - public void searchForDocumentsViaFilteredAlias() throws IOException { - //FIELD MASKING - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { - String songId = FIND_ID_OF_SONG_WITH_TITLE.apply(FIRST_INDEX_SONGS_BY_ID, TITLE_NEXT_SONG); - Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); - - SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); - } - } - - @Test - public void searchForDocumentsViaAllIndicesAlias() throws IOException { - //FIELD MASKING - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ALL_INDICES_MASKED_TITLE_ARTIST_READER)) { - String songId = FIRST_INDEX_ID_SONG_4; - Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); - - SearchRequest searchRequest = queryByIdsRequest(ALL_INDICES_ALIAS, songId); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, VALUE_TO_MASKED_VALUE.apply(song.getTitle()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, song.getLyrics())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); - - - songId = SECOND_INDEX_ID_SONG_1; - song = SECOND_INDEX_SONGS_BY_ID.get(songId); - - searchRequest = queryByIdsRequest(ALL_INDICES_ALIAS, songId); - searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, songId)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, VALUE_TO_MASKED_VALUE.apply(song.getTitle()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, song.getLyrics())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); - } - } - - @Test - public void scrollOverSearchResults() throws IOException { - //FIELD MASKING - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { - String songId = FIRST_INDEX_SONGS_BY_ID.firstKey(); - Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); - - SearchRequest searchRequest = searchRequestWithScroll(FIRST_INDEX_NAME, 1); - searchRequest.source().sort("_id", SortOrder.ASC); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containNotEmptyScrollingId()); - - SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); - - SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); - assertThat(scrollResponse, isSuccessfulSearchResponse()); - assertThat(scrollResponse, containNotEmptyScrollingId()); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); - } - } - - @Test - public void aggregateDataAndComputeAverage() throws IOException { - //FIELD MASKING - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { - String aggregationName = "averageStars"; - Double expectedValue = FIRST_INDEX_SONGS_BY_ID.values() - .stream() - .mapToDouble(Song::getStars) - .average().orElseThrow(() -> new RuntimeException("Cannot compute average stars - list of docs is empty")); - SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); - Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); - assertThat(actualAggregation, instanceOf(ParsedAvg.class)); - assertThat(((ParsedAvg) actualAggregation).getValue(), is(expectedValue)); - } - } - - @Test - public void getDocument() throws IOException { - //FIELD MASKING - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { - String songId = FIRST_INDEX_ID_SONG_4; - Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); - GetResponse response = restHighLevelClient.get(new GetRequest(FIRST_INDEX_NAME, songId), DEFAULT); - - assertThat(response, containDocument(FIRST_INDEX_NAME, songId)); - assertThat(response, documentContainField(FIELD_TITLE, song.getTitle())); - assertThat(response, documentContainField(FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); - assertThat(response, documentContainField(FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); - assertThat(response, documentContainField(FIELD_STARS, song.getStars())); - - songId = SECOND_INDEX_ID_SONG_1; - song = SECOND_INDEX_SONGS_BY_ID.get(songId); - response = restHighLevelClient.get(new GetRequest(SECOND_INDEX_NAME, songId), DEFAULT); - - assertThat(response, containDocument(SECOND_INDEX_NAME, songId)); - assertThat(response, documentContainField(FIELD_TITLE, song.getTitle())); - assertThat(response, documentContainField(FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); - assertThat(response, documentContainField(FIELD_ARTIST, song.getArtist())); - assertThat(response, documentContainField(FIELD_STARS, song.getStars())); - } - } - - @Test - public void multiGetDocuments() throws IOException { - //FIELD MASKING - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { - List> indicesToCheck = List.of( - List.of(FIRST_INDEX_NAME, SECOND_INDEX_NAME), - List.of(FIRST_INDEX_ALIAS, SECOND_INDEX_ALIAS) - ); - String firstSongId = FIRST_INDEX_ID_SONG_1; - Song firstSong = FIRST_INDEX_SONGS_BY_ID.get(firstSongId); - String secondSongId = SECOND_INDEX_ID_SONG_2; - Song secondSong = SECOND_INDEX_SONGS_BY_ID.get(secondSongId); - - for (List indices : indicesToCheck) { - MultiGetRequest request = new MultiGetRequest(); - request.add(new MultiGetRequest.Item(indices.get(0), firstSongId)); - request.add(new MultiGetRequest.Item(indices.get(1), secondSongId)); - MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); - - assertThat(response, isSuccessfulMultiGetResponse()); - assertThat(response, numberOfGetItemResponsesIsEqualTo(2)); - - MultiGetItemResponse[] responses = response.getResponses(); - assertThat(responses[0].getResponse(), allOf( - containDocument(FIRST_INDEX_NAME, FIRST_INDEX_ID_SONG_1), - documentContainField(FIELD_TITLE, firstSong.getTitle()), - documentContainField(FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(firstSong.getLyrics())), - documentContainField(FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(firstSong.getArtist())), - documentContainField(FIELD_STARS, firstSong.getStars()) - )); - assertThat(responses[1].getResponse(), allOf( - containDocument(SECOND_INDEX_NAME, secondSongId), - documentContainField(FIELD_TITLE, secondSong.getTitle()), - documentContainField(FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(secondSong.getLyrics())), - documentContainField(FIELD_ARTIST, secondSong.getArtist()), - documentContainField(FIELD_STARS, secondSong.getStars()) - )); - } - } - } - - @Test - public void multiSearchDocuments() throws IOException { - //FIELD MASKING - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { - List> indicesToCheck = List.of( - List.of(FIRST_INDEX_NAME, SECOND_INDEX_NAME), - List.of(FIRST_INDEX_ALIAS, SECOND_INDEX_ALIAS) - ); - String firstSongId = FIRST_INDEX_ID_SONG_3; - Song firstSong = FIRST_INDEX_SONGS_BY_ID.get(firstSongId); - String secondSongId = SECOND_INDEX_ID_SONG_4; - Song secondSong = SECOND_INDEX_SONGS_BY_ID.get(secondSongId); - - for (List indices : indicesToCheck) { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(queryByIdsRequest(indices.get(0), firstSongId)); - request.add(queryByIdsRequest(indices.get(1), secondSongId)); - MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); - - assertThat(response, isSuccessfulMultiSearchResponse()); - assertThat(response, numberOfSearchItemResponsesIsEqualTo(2)); - - MultiSearchResponse.Item[] responses = response.getResponses(); - - assertThat(responses[0].getResponse(), allOf( - searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, firstSongId), - searchHitContainsFieldWithValue(0, FIELD_TITLE, firstSong.getTitle()), - searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(firstSong.getLyrics())), - searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(firstSong.getArtist())), - searchHitContainsFieldWithValue(0, FIELD_STARS, firstSong.getStars()) - )); - assertThat(responses[1].getResponse(), allOf( - searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, secondSongId), - searchHitContainsFieldWithValue(0, FIELD_TITLE, secondSong.getTitle()), - searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(secondSong.getLyrics())), - searchHitContainsFieldWithValue(0, FIELD_ARTIST, secondSong.getArtist()), - searchHitContainsFieldWithValue(0, FIELD_STARS, secondSong.getStars()) - )); - } - } - } - - @Test - public void getFieldCapabilities() throws IOException { - //FIELD MASKING - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { - FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(FIRST_INDEX_NAME).fields(FIELD_ARTIST, FIELD_TITLE, FIELD_LYRICS); - FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); - - assertThat(response, containsExactlyIndices(FIRST_INDEX_NAME)); - assertThat(response, numberOfFieldsIsEqualTo(3)); - assertThat(response, containsFieldWithNameAndType(FIELD_ARTIST, "text")); - assertThat(response, containsFieldWithNameAndType(FIELD_TITLE, "text")); - assertThat(response, containsFieldWithNameAndType(FIELD_LYRICS, "text")); - } - } + static final String FIRST_INDEX_ID_SONG_1 = "INDEX_1_S1"; + static final String FIRST_INDEX_ID_SONG_2 = "INDEX_1_S2"; + static final String FIRST_INDEX_ID_SONG_3 = "INDEX_1_S3"; + static final String FIRST_INDEX_ID_SONG_4 = "INDEX_1_S4"; + static final String SECOND_INDEX_ID_SONG_1 = "INDEX_2_S1"; + static final String SECOND_INDEX_ID_SONG_2 = "INDEX_2_S2"; + static final String SECOND_INDEX_ID_SONG_3 = "INDEX_2_S3"; + static final String SECOND_INDEX_ID_SONG_4 = "INDEX_2_S4"; + + static final String INDEX_NAME_SUFFIX = "-test-index"; + static final String FIRST_INDEX_NAME = "first".concat(INDEX_NAME_SUFFIX); + static final String SECOND_INDEX_NAME = "second".concat(INDEX_NAME_SUFFIX); + static final String FIRST_INDEX_ALIAS = FIRST_INDEX_NAME.concat("-alias"); + static final String SECOND_INDEX_ALIAS = SECOND_INDEX_NAME.concat("-alias"); + static final String FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE = FIRST_INDEX_NAME.concat("-filtered-by-next-song-title"); + static final String FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST = FIRST_INDEX_NAME.concat("-filtered-by-twins-artist"); + static final String FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST = FIRST_INDEX_NAME.concat("-filtered-by-first-artist"); + static final String ALL_INDICES_ALIAS = "_all"; + + static final String MASK_VALUE = "*"; + + static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + /** + * User who is allowed to see all fields on all indices. Values of the title and artist fields should be masked. + */ + static final TestSecurityConfig.User ALL_INDICES_MASKED_TITLE_ARTIST_READER = new TestSecurityConfig.User("masked_artist_title_reader") + .roles( + new TestSecurityConfig.Role("masked_artist_title_reader").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .maskedFields( + FIELD_TITLE.concat("::/(?<=.{1})./::").concat(MASK_VALUE), + FIELD_ARTIST.concat("::/(?<=.{1})./::").concat(MASK_VALUE) + ) + .on("*") + ); + + /** + * User who is allowed to see all fields on indices {@link #FIRST_INDEX_NAME} and {@link #SECOND_INDEX_NAME}. + *
    + *
  • values of the artist and lyrics fields should be masked on index {@link #FIRST_INDEX_NAME}
  • + *
  • values of the lyrics field should be masked on index {@link #SECOND_INDEX_NAME}
  • + *
+ */ + static final TestSecurityConfig.User MASKED_ARTIST_LYRICS_READER = new TestSecurityConfig.User("masked_title_artist_lyrics_reader") + .roles( + new TestSecurityConfig.Role("masked_title_artist_lyrics_reader").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .maskedFields( + FIELD_ARTIST.concat("::/(?<=.{1})./::").concat(MASK_VALUE), + FIELD_LYRICS.concat("::/(?<=.{1})./::").concat(MASK_VALUE) + ) + .on(FIRST_INDEX_NAME), + new TestSecurityConfig.Role("masked_lyrics_reader").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .maskedFields(FIELD_LYRICS.concat("::/(?<=.{1})./::").concat(MASK_VALUE)) + .on(SECOND_INDEX_NAME) + ); + + /** + * Function that converts field value to value masked with {@link #MASK_VALUE} + */ + static final Function VALUE_TO_MASKED_VALUE = value -> value.substring(0, 1) + .concat(MASK_VALUE.repeat(value.length() - 1)); + + /** + * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_STRING}. + */ + static final TestSecurityConfig.User ALL_INDICES_STRING_ARTIST_READER = new TestSecurityConfig.User("string_artist_reader").roles( + new TestSecurityConfig.Role("string_artist_reader").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_STRING)) + .on("*") + ); + + /** + * User who is allowed to see documents on index: + *
    + *
  • {@link #FIRST_INDEX_NAME} where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_TWINS}
  • + *
  • {@link #SECOND_INDEX_NAME} where value of the {@link Song#FIELD_ARTIST} field matches {@link Song#ARTIST_FIRST}
  • + *
+ */ + static final TestSecurityConfig.User TWINS_FIRST_ARTIST_READER = new TestSecurityConfig.User("twins_first_artist_reader").roles( + new TestSecurityConfig.Role("twins_artist_reader").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_TWINS)) + .on(FIRST_INDEX_NAME), + new TestSecurityConfig.Role("first_artist_reader").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"match\":{\"%s\":\"%s\"}}", FIELD_ARTIST, ARTIST_FIRST)) + .on(SECOND_INDEX_NAME) + ); + + /** + * User who is allowed to see documents on all indices where value of the {@link Song#FIELD_STARS} is less than zero. + */ + static final TestSecurityConfig.User ALL_INDICES_STARS_LESS_THAN_ZERO_READER = new TestSecurityConfig.User( + "stars_less_than_zero_reader" + ).roles( + new TestSecurityConfig.Role("stars_less_than_zero_reader").clusterPermissions("cluster_composite_ops_ro") + .indexPermissions("read") + .dls(String.format("{\"range\":{\"%s\":{\"lt\":%d}}}", FIELD_STARS, 0)) + .on("*") + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .anonymousAuth(false) + .nodeSettings( + Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName())) + ) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users( + ADMIN_USER, + ALL_INDICES_MASKED_TITLE_ARTIST_READER, + MASKED_ARTIST_LYRICS_READER, + ALL_INDICES_STRING_ARTIST_READER, + ALL_INDICES_STARS_LESS_THAN_ZERO_READER, + TWINS_FIRST_ARTIST_READER + ) + .build(); + + /** + * Function that returns id assigned to song with title equal to given title or throws {@link RuntimeException} + * when no song matches. + */ + static final BiFunction, String, String> FIND_ID_OF_SONG_WITH_TITLE = (map, title) -> map.entrySet() + .stream() + .filter(entry -> title.equals(entry.getValue().getTitle())) + .findAny() + .map(Map.Entry::getKey) + .orElseThrow(() -> new RuntimeException("Cannot find id of song with title: " + title)); + + /** + * Function that returns id assigned to song with artist equal to given artist or throws {@link RuntimeException} + * when no song matches. + */ + static final BiFunction, String, String> FIND_ID_OF_SONG_WITH_ARTIST = (map, artist) -> map.entrySet() + .stream() + .filter(entry -> artist.equals(entry.getValue().getArtist())) + .findAny() + .map(Map.Entry::getKey) + .orElseThrow(() -> new RuntimeException("Cannot find id of song with artist: " + artist)); + + static final TreeMap FIRST_INDEX_SONGS_BY_ID = new TreeMap<>() { + { + put(FIRST_INDEX_ID_SONG_1, SONGS[0]); + put(FIRST_INDEX_ID_SONG_2, SONGS[1]); + put(FIRST_INDEX_ID_SONG_3, SONGS[2]); + put(FIRST_INDEX_ID_SONG_4, SONGS[3]); + } + }; + + static final TreeMap SECOND_INDEX_SONGS_BY_ID = new TreeMap<>() { + { + put(SECOND_INDEX_ID_SONG_1, SONGS[3]); + put(SECOND_INDEX_ID_SONG_2, SONGS[2]); + put(SECOND_INDEX_ID_SONG_3, SONGS[1]); + put(SECOND_INDEX_ID_SONG_4, SONGS[0]); + } + }; + + @BeforeClass + public static void createTestData() { + try (Client client = cluster.getInternalNodeClient()) { + FIRST_INDEX_SONGS_BY_ID.forEach((id, song) -> { + client.prepareIndex(FIRST_INDEX_NAME).setId(id).setRefreshPolicy(IMMEDIATE).setSource(song.asMap()).get(); + }); + + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).indices(FIRST_INDEX_NAME).alias(FIRST_INDEX_ALIAS) + ) + ) + .actionGet(); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).index(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE) + .filter(QueryBuilders.queryStringQuery(QUERY_TITLE_NEXT_SONG)) + ) + ) + .actionGet(); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).index(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS_FILTERED_BY_TWINS_ARTIST) + .filter(QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, ARTIST_TWINS))) + ) + ) + .actionGet(); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).index(FIRST_INDEX_NAME) + .alias(FIRST_INDEX_ALIAS_FILTERED_BY_FIRST_ARTIST) + .filter(QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, ARTIST_FIRST))) + ) + ) + .actionGet(); + + SECOND_INDEX_SONGS_BY_ID.forEach((id, song) -> { + client.prepareIndex(SECOND_INDEX_NAME).setId(id).setRefreshPolicy(IMMEDIATE).setSource(song.asMap()).get(); + }); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).indices(SECOND_INDEX_NAME).alias(SECOND_INDEX_ALIAS) + ) + ) + .actionGet(); + } + } + + @Test + public void flsEnabledFieldsAreHiddenForNormalUsers() throws IOException { + String indexName = "fls_index"; + String indexAlias = "fls_index_alias"; + String indexFilteredAlias = "fls_index_filtered_alias"; + TestSecurityConfig.Role userRole = new TestSecurityConfig.Role("fls_exclude_stars_reader").clusterPermissions( + "cluster_composite_ops_ro" + ).indexPermissions("read").fls("~".concat(FIELD_STARS)).on("*"); + TestSecurityConfig.User user = createUserWithRole("fls_user", userRole); + List docIds = createIndexWithDocs(indexName, SONGS[0], SONGS[1]); + addAliasToIndex(indexName, indexAlias); + addAliasToIndex( + indexName, + indexFilteredAlias, + QueryBuilders.queryStringQuery(String.format("%s:%s", FIELD_ARTIST, SONGS[0].getArtist())) + ); + + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(user)) { + // search + SearchResponse searchResponse = restHighLevelClient.search(new SearchRequest(indexName), DEFAULT); + + assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); + + // search with index pattern + searchResponse = restHighLevelClient.search(new SearchRequest("*".concat(indexName)), DEFAULT); + + assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); + + // search via alias + searchResponse = restHighLevelClient.search(new SearchRequest(indexAlias), DEFAULT); + + assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); + + // search via filtered alias + searchResponse = restHighLevelClient.search(new SearchRequest(indexFilteredAlias), DEFAULT); + + assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); + + // search via all indices alias + searchResponse = restHighLevelClient.search(new SearchRequest(ALL_INDICES_ALIAS), DEFAULT); + + assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); + + // scroll + searchResponse = restHighLevelClient.search(searchRequestWithScroll(indexName, 1), DEFAULT); + + assertSearchHitsDoNotContainField(searchResponse, FIELD_STARS); + + SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); + SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); + + assertSearchHitsDoNotContainField(scrollResponse, FIELD_STARS); + + // aggregate data and compute avg + String aggregationName = "averageStars"; + searchResponse = restHighLevelClient.search(averageAggregationRequest(indexName, aggregationName, FIELD_STARS), DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); + assertThat(actualAggregation, instanceOf(ParsedAvg.class)); + assertThat(((ParsedAvg) actualAggregation).getValue(), is(Double.POSITIVE_INFINITY)); // user cannot see the STARS field + + // get document + GetResponse getResponse = restHighLevelClient.get(new GetRequest(indexName, docIds.get(0)), DEFAULT); + + assertThat(getResponse, documentDoesNotContainField(FIELD_STARS)); + + // multi get + for (String index : List.of(indexName, indexAlias)) { + MultiGetRequest multiGetRequest = new MultiGetRequest(); + docIds.forEach(id -> multiGetRequest.add(new MultiGetRequest.Item(index, id))); + + MultiGetResponse multiGetResponse = restHighLevelClient.mget(multiGetRequest, DEFAULT); + + List getResponses = Arrays.stream(multiGetResponse.getResponses()) + .map(MultiGetItemResponse::getResponse) + .collect(Collectors.toList()); + assertThat(getResponses, everyItem(documentDoesNotContainField(FIELD_STARS))); + } + + // multi search + for (String index : List.of(indexName, indexAlias)) { + MultiSearchRequest multiSearchRequest = new MultiSearchRequest(); + docIds.forEach(id -> multiSearchRequest.add(queryByIdsRequest(index, id))); + MultiSearchResponse multiSearchResponse = restHighLevelClient.msearch(multiSearchRequest, DEFAULT); + + assertThat(multiSearchResponse, isSuccessfulMultiSearchResponse()); + List itemResponses = List.of(multiSearchResponse.getResponses()); + itemResponses.forEach(item -> assertSearchHitsDoNotContainField(item.getResponse(), FIELD_STARS)); + } + + // field capabilities + FieldCapabilitiesResponse fieldCapsResponse = restHighLevelClient.fieldCaps( + new FieldCapabilitiesRequest().indices(indexName).fields(FIELD_TITLE, FIELD_STARS), + DEFAULT + ); + assertThat(fieldCapsResponse.getField(FIELD_STARS), nullValue()); + } + } + + private static List createIndexWithDocs(String indexName, Song... songs) { + try (Client client = cluster.getInternalNodeClient()) { + return Stream.of(songs).map(song -> { + IndexResponse response = client.index(new IndexRequest(indexName).setRefreshPolicy(IMMEDIATE).source(song.asMap())) + .actionGet(); + return response.getId(); + }).collect(Collectors.toList()); + } + } + + private static void addAliasToIndex(String indexName, String alias) { + addAliasToIndex(indexName, alias, QueryBuilders.matchAllQuery()); + } + + private static void addAliasToIndex(String indexName, String alias, QueryBuilder filterQuery) { + try (Client client = cluster.getInternalNodeClient()) { + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).indices(indexName).alias(alias).filter(filterQuery) + ) + ) + .actionGet(); + } + } + + private static TestSecurityConfig.User createUserWithRole(String userName, TestSecurityConfig.Role role) { + TestSecurityConfig.User user = new TestSecurityConfig.User(userName); + try (TestRestClient client = cluster.getRestClient(ADMIN_USER)) { + client.createRole(role.getName(), role).assertStatusCode(201); + client.createUser(user.getName(), user).assertStatusCode(201); + client.assignRoleToUser(user.getName(), role.getName()).assertStatusCode(200); + } + return user; + } + + private static void assertSearchHitsDoNotContainField(SearchResponse response, String excludedField) { + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response.getHits().getHits().length, greaterThan(0)); + IntStream.range(0, response.getHits().getHits().length) + .boxed() + .forEach(index -> assertThat(response, searchHitDoesNotContainField(index, excludedField))); + } + + @Test + public void searchForDocuments() throws IOException { + // FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String songId = FIRST_INDEX_ID_SONG_1; + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + + SearchRequest searchRequest = queryByIdsRequest(FIRST_INDEX_NAME, songId); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + + songId = SECOND_INDEX_ID_SONG_2; + song = SECOND_INDEX_SONGS_BY_ID.get(songId); + + searchRequest = queryByIdsRequest(SECOND_INDEX_NAME, songId); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, song.getArtist())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + } + } + + @Test + public void searchForDocumentsWithIndexPattern() throws IOException { + // FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String songId = FIRST_INDEX_ID_SONG_2; + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + + SearchRequest searchRequest = queryByIdsRequest("*".concat(FIRST_INDEX_NAME), songId); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + + songId = SECOND_INDEX_ID_SONG_3; + song = SECOND_INDEX_SONGS_BY_ID.get(songId); + + searchRequest = queryByIdsRequest("*".concat(SECOND_INDEX_NAME), songId); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, song.getArtist())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + } + } + + @Test + public void searchForDocumentsViaAlias() throws IOException { + // FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String songId = FIRST_INDEX_ID_SONG_3; + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + + SearchRequest searchRequest = queryByIdsRequest(FIRST_INDEX_ALIAS, songId); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + + songId = SECOND_INDEX_ID_SONG_4; + song = SECOND_INDEX_SONGS_BY_ID.get(songId); + + searchRequest = queryByIdsRequest("*".concat(SECOND_INDEX_ALIAS), songId); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, song.getArtist())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + } + } + + @Test + public void searchForDocumentsViaFilteredAlias() throws IOException { + // FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String songId = FIND_ID_OF_SONG_WITH_TITLE.apply(FIRST_INDEX_SONGS_BY_ID, TITLE_NEXT_SONG); + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + + SearchRequest searchRequest = new SearchRequest(FIRST_INDEX_ALIAS_FILTERED_BY_NEXT_SONG_TITLE); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + } + } + + @Test + public void searchForDocumentsViaAllIndicesAlias() throws IOException { + // FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ALL_INDICES_MASKED_TITLE_ARTIST_READER)) { + String songId = FIRST_INDEX_ID_SONG_4; + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + + SearchRequest searchRequest = queryByIdsRequest(ALL_INDICES_ALIAS, songId); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, VALUE_TO_MASKED_VALUE.apply(song.getTitle()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, song.getLyrics())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + + songId = SECOND_INDEX_ID_SONG_1; + song = SECOND_INDEX_SONGS_BY_ID.get(songId); + + searchRequest = queryByIdsRequest(ALL_INDICES_ALIAS, songId); + searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, VALUE_TO_MASKED_VALUE.apply(song.getTitle()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, song.getLyrics())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + } + } + + @Test + public void scrollOverSearchResults() throws IOException { + // FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String songId = FIRST_INDEX_SONGS_BY_ID.firstKey(); + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + + SearchRequest searchRequest = searchRequestWithScroll(FIRST_INDEX_NAME, 1); + searchRequest.source().sort("_id", SortOrder.ASC); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containNotEmptyScrollingId()); + + SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); + + SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); + assertThat(scrollResponse, isSuccessfulSearchResponse()); + assertThat(scrollResponse, containNotEmptyScrollingId()); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, songId)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, song.getTitle())); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_STARS, song.getStars())); + } + } + + @Test + public void aggregateDataAndComputeAverage() throws IOException { + // FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String aggregationName = "averageStars"; + Double expectedValue = FIRST_INDEX_SONGS_BY_ID.values() + .stream() + .mapToDouble(Song::getStars) + .average() + .orElseThrow(() -> new RuntimeException("Cannot compute average stars - list of docs is empty")); + SearchRequest searchRequest = averageAggregationRequest(FIRST_INDEX_NAME, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + Aggregation actualAggregation = searchResponse.getAggregations().get(aggregationName); + assertThat(actualAggregation, instanceOf(ParsedAvg.class)); + assertThat(((ParsedAvg) actualAggregation).getValue(), is(expectedValue)); + } + } + + @Test + public void getDocument() throws IOException { + // FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + String songId = FIRST_INDEX_ID_SONG_4; + Song song = FIRST_INDEX_SONGS_BY_ID.get(songId); + GetResponse response = restHighLevelClient.get(new GetRequest(FIRST_INDEX_NAME, songId), DEFAULT); + + assertThat(response, containDocument(FIRST_INDEX_NAME, songId)); + assertThat(response, documentContainField(FIELD_TITLE, song.getTitle())); + assertThat(response, documentContainField(FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(response, documentContainField(FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(song.getArtist()))); + assertThat(response, documentContainField(FIELD_STARS, song.getStars())); + + songId = SECOND_INDEX_ID_SONG_1; + song = SECOND_INDEX_SONGS_BY_ID.get(songId); + response = restHighLevelClient.get(new GetRequest(SECOND_INDEX_NAME, songId), DEFAULT); + + assertThat(response, containDocument(SECOND_INDEX_NAME, songId)); + assertThat(response, documentContainField(FIELD_TITLE, song.getTitle())); + assertThat(response, documentContainField(FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(song.getLyrics()))); + assertThat(response, documentContainField(FIELD_ARTIST, song.getArtist())); + assertThat(response, documentContainField(FIELD_STARS, song.getStars())); + } + } + + @Test + public void multiGetDocuments() throws IOException { + // FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + List> indicesToCheck = List.of( + List.of(FIRST_INDEX_NAME, SECOND_INDEX_NAME), + List.of(FIRST_INDEX_ALIAS, SECOND_INDEX_ALIAS) + ); + String firstSongId = FIRST_INDEX_ID_SONG_1; + Song firstSong = FIRST_INDEX_SONGS_BY_ID.get(firstSongId); + String secondSongId = SECOND_INDEX_ID_SONG_2; + Song secondSong = SECOND_INDEX_SONGS_BY_ID.get(secondSongId); + + for (List indices : indicesToCheck) { + MultiGetRequest request = new MultiGetRequest(); + request.add(new MultiGetRequest.Item(indices.get(0), firstSongId)); + request.add(new MultiGetRequest.Item(indices.get(1), secondSongId)); + MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); + + assertThat(response, isSuccessfulMultiGetResponse()); + assertThat(response, numberOfGetItemResponsesIsEqualTo(2)); + + MultiGetItemResponse[] responses = response.getResponses(); + assertThat( + responses[0].getResponse(), + allOf( + containDocument(FIRST_INDEX_NAME, FIRST_INDEX_ID_SONG_1), + documentContainField(FIELD_TITLE, firstSong.getTitle()), + documentContainField(FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(firstSong.getLyrics())), + documentContainField(FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(firstSong.getArtist())), + documentContainField(FIELD_STARS, firstSong.getStars()) + ) + ); + assertThat( + responses[1].getResponse(), + allOf( + containDocument(SECOND_INDEX_NAME, secondSongId), + documentContainField(FIELD_TITLE, secondSong.getTitle()), + documentContainField(FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(secondSong.getLyrics())), + documentContainField(FIELD_ARTIST, secondSong.getArtist()), + documentContainField(FIELD_STARS, secondSong.getStars()) + ) + ); + } + } + } + + @Test + public void multiSearchDocuments() throws IOException { + // FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + List> indicesToCheck = List.of( + List.of(FIRST_INDEX_NAME, SECOND_INDEX_NAME), + List.of(FIRST_INDEX_ALIAS, SECOND_INDEX_ALIAS) + ); + String firstSongId = FIRST_INDEX_ID_SONG_3; + Song firstSong = FIRST_INDEX_SONGS_BY_ID.get(firstSongId); + String secondSongId = SECOND_INDEX_ID_SONG_4; + Song secondSong = SECOND_INDEX_SONGS_BY_ID.get(secondSongId); + + for (List indices : indicesToCheck) { + MultiSearchRequest request = new MultiSearchRequest(); + request.add(queryByIdsRequest(indices.get(0), firstSongId)); + request.add(queryByIdsRequest(indices.get(1), secondSongId)); + MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); + + assertThat(response, isSuccessfulMultiSearchResponse()); + assertThat(response, numberOfSearchItemResponsesIsEqualTo(2)); + + MultiSearchResponse.Item[] responses = response.getResponses(); + + assertThat( + responses[0].getResponse(), + allOf( + searchHitsContainDocumentWithId(0, FIRST_INDEX_NAME, firstSongId), + searchHitContainsFieldWithValue(0, FIELD_TITLE, firstSong.getTitle()), + searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(firstSong.getLyrics())), + searchHitContainsFieldWithValue(0, FIELD_ARTIST, VALUE_TO_MASKED_VALUE.apply(firstSong.getArtist())), + searchHitContainsFieldWithValue(0, FIELD_STARS, firstSong.getStars()) + ) + ); + assertThat( + responses[1].getResponse(), + allOf( + searchHitsContainDocumentWithId(0, SECOND_INDEX_NAME, secondSongId), + searchHitContainsFieldWithValue(0, FIELD_TITLE, secondSong.getTitle()), + searchHitContainsFieldWithValue(0, FIELD_LYRICS, VALUE_TO_MASKED_VALUE.apply(secondSong.getLyrics())), + searchHitContainsFieldWithValue(0, FIELD_ARTIST, secondSong.getArtist()), + searchHitContainsFieldWithValue(0, FIELD_STARS, secondSong.getStars()) + ) + ); + } + } + } + + @Test + public void getFieldCapabilities() throws IOException { + // FIELD MASKING + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(MASKED_ARTIST_LYRICS_READER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(FIRST_INDEX_NAME) + .fields(FIELD_ARTIST, FIELD_TITLE, FIELD_LYRICS); + FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); + + assertThat(response, containsExactlyIndices(FIRST_INDEX_NAME)); + assertThat(response, numberOfFieldsIsEqualTo(3)); + assertThat(response, containsFieldWithNameAndType(FIELD_ARTIST, "text")); + assertThat(response, containsFieldWithNameAndType(FIELD_TITLE, "text")); + assertThat(response, containsFieldWithNameAndType(FIELD_LYRICS, "text")); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/IndexOperationsHelper.java b/src/integrationTest/java/org/opensearch/security/IndexOperationsHelper.java index c1001e5fc5..7482558c5b 100644 --- a/src/integrationTest/java/org/opensearch/security/IndexOperationsHelper.java +++ b/src/integrationTest/java/org/opensearch/security/IndexOperationsHelper.java @@ -28,39 +28,39 @@ public class IndexOperationsHelper { - public static void createIndex(LocalCluster cluster, String indexName) { - createIndex(cluster, indexName, Settings.EMPTY); - } + public static void createIndex(LocalCluster cluster, String indexName) { + createIndex(cluster, indexName, Settings.EMPTY); + } - public static void createIndex(LocalCluster cluster, String indexName, Settings settings) { - try(Client client = cluster.getInternalNodeClient()) { - CreateIndexResponse createIndexResponse = client.admin().indices() - .create(new CreateIndexRequest(indexName).settings(settings)) - .actionGet(); + public static void createIndex(LocalCluster cluster, String indexName, Settings settings) { + try (Client client = cluster.getInternalNodeClient()) { + CreateIndexResponse createIndexResponse = client.admin() + .indices() + .create(new CreateIndexRequest(indexName).settings(settings)) + .actionGet(); - assertThat(createIndexResponse.isAcknowledged(), is(true)); - assertThat(createIndexResponse.isShardsAcknowledged(), is(true)); - assertThat(cluster, indexExists(indexName)); - } - } + assertThat(createIndexResponse.isAcknowledged(), is(true)); + assertThat(createIndexResponse.isShardsAcknowledged(), is(true)); + assertThat(cluster, indexExists(indexName)); + } + } - public static void closeIndex(LocalCluster cluster, String indexName) { - try(Client client = cluster.getInternalNodeClient()) { - CloseIndexRequest closeIndexRequest = new CloseIndexRequest(indexName); - CloseIndexResponse response = client.admin().indices().close(closeIndexRequest).actionGet(); + public static void closeIndex(LocalCluster cluster, String indexName) { + try (Client client = cluster.getInternalNodeClient()) { + CloseIndexRequest closeIndexRequest = new CloseIndexRequest(indexName); + CloseIndexResponse response = client.admin().indices().close(closeIndexRequest).actionGet(); - assertThat(response.isAcknowledged(), is(true)); - assertThat(response.isShardsAcknowledged(), is(true)); - assertThat(cluster, indexStateIsEqualTo(indexName, IndexMetadata.State.CLOSE)); - } - } + assertThat(response.isAcknowledged(), is(true)); + assertThat(response.isShardsAcknowledged(), is(true)); + assertThat(cluster, indexStateIsEqualTo(indexName, IndexMetadata.State.CLOSE)); + } + } - public static void createMapping(LocalCluster cluster, String indexName, Map indexMapping) { - try(Client client = cluster.getInternalNodeClient()) { - var response = client.admin().indices() - .putMapping(new PutMappingRequest(indexName).source(indexMapping)).actionGet(); + public static void createMapping(LocalCluster cluster, String indexName, Map indexMapping) { + try (Client client = cluster.getInternalNodeClient()) { + var response = client.admin().indices().putMapping(new PutMappingRequest(indexName).source(indexMapping)).actionGet(); - assertThat(response.isAcknowledged(), is(true)); - } - } + assertThat(response.isAcknowledged(), is(true)); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java b/src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java index f1f9cdf3f8..bb16e0be1b 100644 --- a/src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java +++ b/src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java @@ -36,123 +36,131 @@ @RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class IpBruteForceAttacksPreventionTests { - private static final User USER_1 = new User("simple-user-1").roles(ALL_ACCESS); - private static final User USER_2 = new User("simple-user-2").roles(ALL_ACCESS); - - public static final int ALLOWED_TRIES = 3; - public static final int TIME_WINDOW_SECONDS = 3; - - public static final String CLIENT_IP_2 = "127.0.0.2"; - public static final String CLIENT_IP_3 = "127.0.0.3"; - public static final String CLIENT_IP_4 = "127.0.0.4"; - public static final String CLIENT_IP_5 = "127.0.0.5"; - public static final String CLIENT_IP_6 = "127.0.0.6"; - public static final String CLIENT_IP_7 = "127.0.0.7"; - public static final String CLIENT_IP_8 = "127.0.0.8"; - public static final String CLIENT_IP_9 = "127.0.0.9"; - - private static final AuthFailureListeners listener = new AuthFailureListeners() - .addRateLimit(new RateLimiting("internal_authentication_backend_limiting").type("ip") - .allowedTries(ALLOWED_TRIES).timeWindowSeconds(TIME_WINDOW_SECONDS).blockExpirySeconds(2).maxBlockedClients(500) - .maxTrackedClients(500)); - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false).authFailureListeners(listener) - .authc(AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE).users(USER_1, USER_2).build(); - - @Rule - public LogsRule logsRule = new LogsRule("org.opensearch.security.auth.BackendRegistry"); - - @Test - public void shouldAuthenticateUserWhenBlockadeIsNotActive() { - try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_2))) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_OK); - } - } - - @Test - public void shouldBlockIpAddress() { - authenticateUserWithIncorrectPassword(CLIENT_IP_3, USER_2, ALLOWED_TRIES); - try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_2, CLIENT_IP_3))) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_UNAUTHORIZED); - logsRule.assertThatContain("Rejecting REST request because of blocked address: /" + CLIENT_IP_3); - } - } - - @Test - public void shouldBlockUsersWhoUseTheSameIpAddress() { - authenticateUserWithIncorrectPassword(CLIENT_IP_4, USER_1, ALLOWED_TRIES); - try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_2, CLIENT_IP_4))) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_UNAUTHORIZED); - logsRule.assertThatContain("Rejecting REST request because of blocked address: /" + CLIENT_IP_4); - } - } - - @Test - public void testUserShouldBeAbleToAuthenticateFromAnotherNotBlockedIpAddress() { - authenticateUserWithIncorrectPassword(CLIENT_IP_5, USER_1, ALLOWED_TRIES); - try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_6))) { - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_OK); - } - } - - @Test - public void shouldNotBlockIpWhenFailureAuthenticationCountIsLessThanAllowedTries() { - authenticateUserWithIncorrectPassword(CLIENT_IP_7, USER_1, ALLOWED_TRIES - 1); - try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_7))) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_OK); - } - } - - @Test - public void shouldBlockIpWhenFailureAuthenticationCountIsGreaterThanAllowedTries() { - authenticateUserWithIncorrectPassword(CLIENT_IP_8, USER_1, ALLOWED_TRIES * 2); - try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_8))) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_UNAUTHORIZED); - logsRule.assertThatContain("Rejecting REST request because of blocked address: /" + CLIENT_IP_8); - } - } - - @Test - public void shouldReleaseIpAddressLock() throws InterruptedException { - authenticateUserWithIncorrectPassword(CLIENT_IP_9, USER_1, ALLOWED_TRIES * 2); - TimeUnit.SECONDS.sleep(TIME_WINDOW_SECONDS); - try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_9))) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_OK); - logsRule.assertThatContain("Rejecting REST request because of blocked address: /" + CLIENT_IP_9); - } - } - - private static void authenticateUserWithIncorrectPassword(String sourceIpAddress, User user, int numberOfRequests) { - var clientConfiguration = new TestRestClientConfiguration().username(user.getName()) - .password("incorrect password").sourceInetAddress(sourceIpAddress); - try(TestRestClient client = cluster.createGenericClientRestClient(clientConfiguration)) { - for(int i = 0; i < numberOfRequests; ++i) { - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_UNAUTHORIZED); - } - } - } + private static final User USER_1 = new User("simple-user-1").roles(ALL_ACCESS); + private static final User USER_2 = new User("simple-user-2").roles(ALL_ACCESS); + + public static final int ALLOWED_TRIES = 3; + public static final int TIME_WINDOW_SECONDS = 3; + + public static final String CLIENT_IP_2 = "127.0.0.2"; + public static final String CLIENT_IP_3 = "127.0.0.3"; + public static final String CLIENT_IP_4 = "127.0.0.4"; + public static final String CLIENT_IP_5 = "127.0.0.5"; + public static final String CLIENT_IP_6 = "127.0.0.6"; + public static final String CLIENT_IP_7 = "127.0.0.7"; + public static final String CLIENT_IP_8 = "127.0.0.8"; + public static final String CLIENT_IP_9 = "127.0.0.9"; + + private static final AuthFailureListeners listener = new AuthFailureListeners().addRateLimit( + new RateLimiting("internal_authentication_backend_limiting").type("ip") + .allowedTries(ALLOWED_TRIES) + .timeWindowSeconds(TIME_WINDOW_SECONDS) + .blockExpirySeconds(2) + .maxBlockedClients(500) + .maxTrackedClients(500) + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .authFailureListeners(listener) + .authc(AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE) + .users(USER_1, USER_2) + .build(); + + @Rule + public LogsRule logsRule = new LogsRule("org.opensearch.security.auth.BackendRegistry"); + + @Test + public void shouldAuthenticateUserWhenBlockadeIsNotActive() { + try (TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_2))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + } + + @Test + public void shouldBlockIpAddress() { + authenticateUserWithIncorrectPassword(CLIENT_IP_3, USER_2, ALLOWED_TRIES); + try (TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_2, CLIENT_IP_3))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + logsRule.assertThatContain("Rejecting REST request because of blocked address: /" + CLIENT_IP_3); + } + } + + @Test + public void shouldBlockUsersWhoUseTheSameIpAddress() { + authenticateUserWithIncorrectPassword(CLIENT_IP_4, USER_1, ALLOWED_TRIES); + try (TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_2, CLIENT_IP_4))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + logsRule.assertThatContain("Rejecting REST request because of blocked address: /" + CLIENT_IP_4); + } + } + + @Test + public void testUserShouldBeAbleToAuthenticateFromAnotherNotBlockedIpAddress() { + authenticateUserWithIncorrectPassword(CLIENT_IP_5, USER_1, ALLOWED_TRIES); + try (TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_6))) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + } + + @Test + public void shouldNotBlockIpWhenFailureAuthenticationCountIsLessThanAllowedTries() { + authenticateUserWithIncorrectPassword(CLIENT_IP_7, USER_1, ALLOWED_TRIES - 1); + try (TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_7))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + } + + @Test + public void shouldBlockIpWhenFailureAuthenticationCountIsGreaterThanAllowedTries() { + authenticateUserWithIncorrectPassword(CLIENT_IP_8, USER_1, ALLOWED_TRIES * 2); + try (TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_8))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + logsRule.assertThatContain("Rejecting REST request because of blocked address: /" + CLIENT_IP_8); + } + } + + @Test + public void shouldReleaseIpAddressLock() throws InterruptedException { + authenticateUserWithIncorrectPassword(CLIENT_IP_9, USER_1, ALLOWED_TRIES * 2); + TimeUnit.SECONDS.sleep(TIME_WINDOW_SECONDS); + try (TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_9))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + logsRule.assertThatContain("Rejecting REST request because of blocked address: /" + CLIENT_IP_9); + } + } + + private static void authenticateUserWithIncorrectPassword(String sourceIpAddress, User user, int numberOfRequests) { + var clientConfiguration = new TestRestClientConfiguration().username(user.getName()) + .password("incorrect password") + .sourceInetAddress(sourceIpAddress); + try (TestRestClient client = cluster.createGenericClientRestClient(clientConfiguration)) { + for (int i = 0; i < numberOfRequests; ++i) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + } + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/PointInTimeOperationTest.java b/src/integrationTest/java/org/opensearch/security/PointInTimeOperationTest.java index eda561185d..54b9aa4bc2 100644 --- a/src/integrationTest/java/org/opensearch/security/PointInTimeOperationTest.java +++ b/src/integrationTest/java/org/opensearch/security/PointInTimeOperationTest.java @@ -63,333 +63,364 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class PointInTimeOperationTest { - private static final String FIRST_SONG_INDEX = "song-index-1"; - private static final String FIRST_INDEX_ALIAS = "song-index-1-alias"; - private static final String SECOND_SONG_INDEX = "song-index-2"; - private static final String SECOND_INDEX_ALIAS = "song-index-2-alias"; - - private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); - - /** - * User who is allowed to perform PIT operations only on the {@link #FIRST_SONG_INDEX} - */ - private static final TestSecurityConfig.User LIMITED_POINT_IN_TIME_USER = new TestSecurityConfig.User("limited_point_in_time_user") - .roles(new TestSecurityConfig.Role("limited_point_in_time_user") - .indexPermissions( - "indices:data/read/point_in_time/create", - "indices:data/read/point_in_time/delete", - "indices:data/read/search", - "indices:data/read/point_in_time/readall", //anyway user needs the all indexes permission (*) to find all pits - "indices:monitor/point_in_time/segments" //anyway user needs the all indexes permission (*) to list all pits segments - ) - .on(FIRST_SONG_INDEX)); - /** - * User who is allowed to perform PIT operations on all indices - */ - private static final TestSecurityConfig.User POINT_IN_TIME_USER = new TestSecurityConfig.User("point_in_time_user") - .roles(new TestSecurityConfig.Role("point_in_time_user") - .indexPermissions( - "indices:data/read/point_in_time/create", - "indices:data/read/point_in_time/delete", - "indices:data/read/search", - "indices:data/read/point_in_time/readall", - "indices:monitor/point_in_time/segments" - ) - .on("*")); - - private static final String ID_1 = "1"; - private static final String ID_2 = "2"; - private static final String ID_3 = "3"; - private static final String ID_4 = "4"; - - @BeforeClass - public static void createTestData() { - try(Client client = cluster.getInternalNodeClient()) { - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(FIRST_SONG_INDEX).id(ID_1).source(SONGS[0].asMap())).actionGet(); - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(FIRST_SONG_INDEX).id(ID_2).source(SONGS[1].asMap())).actionGet(); - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(FIRST_SONG_INDEX).id(ID_3).source(SONGS[2].asMap())).actionGet(); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD).indices( - FIRST_SONG_INDEX).alias(FIRST_INDEX_ALIAS))).actionGet(); - - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SECOND_SONG_INDEX).id(ID_4).source(SONGS[3].asMap())).actionGet(); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new IndicesAliasesRequest.AliasActions(ADD).indices( - SECOND_SONG_INDEX).alias(SECOND_INDEX_ALIAS))).actionGet(); - } - } - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) - .authc(AUTHC_HTTPBASIC_INTERNAL) - .users(ADMIN_USER, LIMITED_POINT_IN_TIME_USER, POINT_IN_TIME_USER) - .build(); - - @Test - public void createPit_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, FIRST_SONG_INDEX); - - CreatePitResponse createPitResponse = restHighLevelClient.createPit(createPitRequest, DEFAULT); - - assertThat(createPitResponse, isSuccessfulCreatePitResponse()); - } - } - - @Test - public void createPitWithIndexAlias_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, FIRST_INDEX_ALIAS); - - CreatePitResponse createPitResponse = restHighLevelClient.createPit(createPitRequest, DEFAULT); - - assertThat(createPitResponse, isSuccessfulCreatePitResponse()); - } - } - - @Test - public void createPit_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, SECOND_SONG_INDEX); - - assertThatThrownBy(() -> restHighLevelClient.createPit(createPitRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void createPitWithIndexAlias_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, SECOND_INDEX_ALIAS); - - assertThatThrownBy(() -> restHighLevelClient.createPit(createPitRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void listAllPits_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(POINT_IN_TIME_USER)) { - cleanUpPits(); - String firstIndexPit = createPitForIndices(FIRST_SONG_INDEX); - String secondIndexPit = createPitForIndices(SECOND_SONG_INDEX); - - GetAllPitNodesResponse getAllPitsResponse = restHighLevelClient.getAllPits(DEFAULT); - - assertThat(getAllPitsResponse, getAllResponseContainsExactlyPitWithIds(firstIndexPit, secondIndexPit)); - } - } - - @Test - public void listAllPits_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - assertThatThrownBy(() -> restHighLevelClient.getAllPits(DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void deletePit_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - String existingPitId = createPitForIndices(FIRST_SONG_INDEX); - - DeletePitResponse deletePitResponse = restHighLevelClient.deletePit(new DeletePitRequest(existingPitId), DEFAULT); - assertThat(deletePitResponse, isSuccessfulDeletePitResponse()); - assertThat(deletePitResponse, deleteResponseContainsExactlyPitWithIds(existingPitId)); - } - } - - @Test - public void deletePitCreatedWithIndexAlias_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - String existingPitId = createPitForIndices(FIRST_INDEX_ALIAS); - - DeletePitResponse deletePitResponse = restHighLevelClient.deletePit(new DeletePitRequest(existingPitId), DEFAULT); - assertThat(deletePitResponse, isSuccessfulDeletePitResponse()); - assertThat(deletePitResponse, deleteResponseContainsExactlyPitWithIds(existingPitId)); - } - } - - @Test - public void deletePit_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - String existingPitId = createPitForIndices(SECOND_SONG_INDEX); - - assertThatThrownBy(() -> restHighLevelClient.deletePit(new DeletePitRequest(existingPitId), DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void deletePitCreatedWithIndexAlias_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - String existingPitId = createPitForIndices(SECOND_INDEX_ALIAS); - - assertThatThrownBy(() -> restHighLevelClient.deletePit(new DeletePitRequest(existingPitId), DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void deleteAllPits_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(POINT_IN_TIME_USER)) { - cleanUpPits(); - String firstIndexPit = createPitForIndices(FIRST_SONG_INDEX); - String secondIndexPit = createPitForIndices(SECOND_SONG_INDEX); - - DeletePitResponse deletePitResponse = restHighLevelClient.deleteAllPits(DEFAULT); - assertThat(deletePitResponse, isSuccessfulDeletePitResponse()); - assertThat(deletePitResponse, deleteResponseContainsExactlyPitWithIds(firstIndexPit, secondIndexPit)); - } - } - - @Test - public void deleteAllPits_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - assertThatThrownBy(() -> restHighLevelClient.deleteAllPits(DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void searchWithPit_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - String existingPitId = createPitForIndices(FIRST_SONG_INDEX); - - SearchRequest searchRequest = new SearchRequest(); - searchRequest.source(new SearchSourceBuilder().pointInTimeBuilder(new PointInTimeBuilder(existingPitId))); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, searchHitsContainDocumentsInAnyOrder( - Pair.of(FIRST_SONG_INDEX, ID_1), Pair.of(FIRST_SONG_INDEX, ID_2), Pair.of(FIRST_SONG_INDEX, ID_3) - )); - } - } - - @Test - public void searchWithPitCreatedWithIndexAlias_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - String existingPitId = createPitForIndices(FIRST_INDEX_ALIAS); - - SearchRequest searchRequest = new SearchRequest(); - searchRequest.source(new SearchSourceBuilder().pointInTimeBuilder(new PointInTimeBuilder(existingPitId))); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, searchHitsContainDocumentsInAnyOrder( - Pair.of(FIRST_SONG_INDEX, ID_1), Pair.of(FIRST_SONG_INDEX, ID_2), Pair.of(FIRST_SONG_INDEX, ID_3) - )); - } - } - - @Test - public void searchWithPit_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - String existingPitId = createPitForIndices(SECOND_SONG_INDEX); - - SearchRequest searchRequest = new SearchRequest(); - searchRequest.source(new SearchSourceBuilder().pointInTimeBuilder(new PointInTimeBuilder(existingPitId))); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void searchWithPitCreatedWithIndexAlias_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { - String existingPitId = createPitForIndices(SECOND_INDEX_ALIAS); - - SearchRequest searchRequest = new SearchRequest(); - searchRequest.source(new SearchSourceBuilder().pointInTimeBuilder(new PointInTimeBuilder(existingPitId))); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void listPitSegments_positive() throws IOException { - try (TestRestClient restClient = cluster.getRestClient(LIMITED_POINT_IN_TIME_USER)) { - String existingPitId = createPitForIndices(FIRST_SONG_INDEX); - String body = String.format("{\"pit_id\":[\"%s\"]}", existingPitId); - HttpResponse response = restClient.getWithJsonBody("/_cat/pit_segments", body); - - response.assertStatusCode(OK.getStatus()); - } - } - - @Test - public void listPitSegmentsCreatedWithIndexAlias_positive() throws IOException { - try (TestRestClient restClient = cluster.getRestClient(POINT_IN_TIME_USER)) { - String existingPitId = createPitForIndices(FIRST_INDEX_ALIAS); - String body = String.format("{\"pit_id\":[\"%s\"]}", existingPitId); - HttpResponse response = restClient.getWithJsonBody("/_cat/pit_segments", body); - - response.assertStatusCode(OK.getStatus()); - } - } - - @Test - public void listPitSegments_negative() throws IOException { - try (TestRestClient restClient = cluster.getRestClient(LIMITED_POINT_IN_TIME_USER)) { - String existingPitId = createPitForIndices(SECOND_SONG_INDEX); - String body = String.format("{\"pit_id\":[\"%s\"]}", existingPitId); - HttpResponse response = restClient.getWithJsonBody("/_cat/pit_segments", body); - - response.assertStatusCode(FORBIDDEN.getStatus()); - } - } - - @Test - public void listPitSegmentsCreatedWithIndexAlias_negative() throws IOException { - try (TestRestClient restClient = cluster.getRestClient(LIMITED_POINT_IN_TIME_USER)) { - String existingPitId = createPitForIndices(SECOND_INDEX_ALIAS); - String body = String.format("{\"pit_id\":[\"%s\"]}", existingPitId); - HttpResponse response = restClient.getWithJsonBody("/_cat/pit_segments", body); - - response.assertStatusCode(FORBIDDEN.getStatus()); - } - } - - @Test - public void listAllPitSegments_positive() { - try (TestRestClient restClient = cluster.getRestClient(POINT_IN_TIME_USER)) { - HttpResponse response = restClient.get("/_cat/pit_segments/_all"); - - response.assertStatusCode(OK.getStatus()); - } - } - - @Test - public void listAllPitSegments_negative() { - try (TestRestClient restClient = cluster.getRestClient(LIMITED_POINT_IN_TIME_USER)) { - HttpResponse response = restClient.get("/_cat/pit_segments/_all"); - - response.assertStatusCode(FORBIDDEN.getStatus()); - } - } - - /** - * Creates PIT for given indices. Returns PIT id. - */ - private String createPitForIndices(String... indices) throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { - CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, indices); - - CreatePitResponse createPitResponse = restHighLevelClient.createPit(createPitRequest, DEFAULT); - - assertThat(createPitResponse, isSuccessfulCreatePitResponse()); - return createPitResponse.getId(); - } - } - - /** - * Deletes all PITs. - */ - public void cleanUpPits() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { - try { - restHighLevelClient.deleteAllPits(DEFAULT); - } catch (OpenSearchStatusException ex) { - if (ex.status() != RestStatus.NOT_FOUND) { - throw ex; - } - //tried to remove pits but no pit exists - } - } - } + private static final String FIRST_SONG_INDEX = "song-index-1"; + private static final String FIRST_INDEX_ALIAS = "song-index-1-alias"; + private static final String SECOND_SONG_INDEX = "song-index-2"; + private static final String SECOND_INDEX_ALIAS = "song-index-2-alias"; + + private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + /** + * User who is allowed to perform PIT operations only on the {@link #FIRST_SONG_INDEX} + */ + private static final TestSecurityConfig.User LIMITED_POINT_IN_TIME_USER = new TestSecurityConfig.User("limited_point_in_time_user") + .roles( + new TestSecurityConfig.Role("limited_point_in_time_user").indexPermissions( + "indices:data/read/point_in_time/create", + "indices:data/read/point_in_time/delete", + "indices:data/read/search", + "indices:data/read/point_in_time/readall", // anyway user needs the all indexes permission (*) to find all pits + "indices:monitor/point_in_time/segments" // anyway user needs the all indexes permission (*) to list all pits segments + ).on(FIRST_SONG_INDEX) + ); + /** + * User who is allowed to perform PIT operations on all indices + */ + private static final TestSecurityConfig.User POINT_IN_TIME_USER = new TestSecurityConfig.User("point_in_time_user").roles( + new TestSecurityConfig.Role("point_in_time_user").indexPermissions( + "indices:data/read/point_in_time/create", + "indices:data/read/point_in_time/delete", + "indices:data/read/search", + "indices:data/read/point_in_time/readall", + "indices:monitor/point_in_time/segments" + ).on("*") + ); + + private static final String ID_1 = "1"; + private static final String ID_2 = "2"; + private static final String ID_3 = "3"; + private static final String ID_4 = "4"; + + @BeforeClass + public static void createTestData() { + try (Client client = cluster.getInternalNodeClient()) { + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(FIRST_SONG_INDEX).id(ID_1).source(SONGS[0].asMap())) + .actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(FIRST_SONG_INDEX).id(ID_2).source(SONGS[1].asMap())) + .actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(FIRST_SONG_INDEX).id(ID_3).source(SONGS[2].asMap())) + .actionGet(); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).indices(FIRST_SONG_INDEX).alias(FIRST_INDEX_ALIAS) + ) + ) + .actionGet(); + + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SECOND_SONG_INDEX).id(ID_4).source(SONGS[3].asMap())) + .actionGet(); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new IndicesAliasesRequest.AliasActions(ADD).indices(SECOND_SONG_INDEX).alias(SECOND_INDEX_ALIAS) + ) + ) + .actionGet(); + } + } + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .anonymousAuth(false) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER, LIMITED_POINT_IN_TIME_USER, POINT_IN_TIME_USER) + .build(); + + @Test + public void createPit_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, FIRST_SONG_INDEX); + + CreatePitResponse createPitResponse = restHighLevelClient.createPit(createPitRequest, DEFAULT); + + assertThat(createPitResponse, isSuccessfulCreatePitResponse()); + } + } + + @Test + public void createPitWithIndexAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, FIRST_INDEX_ALIAS); + + CreatePitResponse createPitResponse = restHighLevelClient.createPit(createPitRequest, DEFAULT); + + assertThat(createPitResponse, isSuccessfulCreatePitResponse()); + } + } + + @Test + public void createPit_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, SECOND_SONG_INDEX); + + assertThatThrownBy(() -> restHighLevelClient.createPit(createPitRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void createPitWithIndexAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, SECOND_INDEX_ALIAS); + + assertThatThrownBy(() -> restHighLevelClient.createPit(createPitRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void listAllPits_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(POINT_IN_TIME_USER)) { + cleanUpPits(); + String firstIndexPit = createPitForIndices(FIRST_SONG_INDEX); + String secondIndexPit = createPitForIndices(SECOND_SONG_INDEX); + + GetAllPitNodesResponse getAllPitsResponse = restHighLevelClient.getAllPits(DEFAULT); + + assertThat(getAllPitsResponse, getAllResponseContainsExactlyPitWithIds(firstIndexPit, secondIndexPit)); + } + } + + @Test + public void listAllPits_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + assertThatThrownBy(() -> restHighLevelClient.getAllPits(DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void deletePit_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(FIRST_SONG_INDEX); + + DeletePitResponse deletePitResponse = restHighLevelClient.deletePit(new DeletePitRequest(existingPitId), DEFAULT); + assertThat(deletePitResponse, isSuccessfulDeletePitResponse()); + assertThat(deletePitResponse, deleteResponseContainsExactlyPitWithIds(existingPitId)); + } + } + + @Test + public void deletePitCreatedWithIndexAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(FIRST_INDEX_ALIAS); + + DeletePitResponse deletePitResponse = restHighLevelClient.deletePit(new DeletePitRequest(existingPitId), DEFAULT); + assertThat(deletePitResponse, isSuccessfulDeletePitResponse()); + assertThat(deletePitResponse, deleteResponseContainsExactlyPitWithIds(existingPitId)); + } + } + + @Test + public void deletePit_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(SECOND_SONG_INDEX); + + assertThatThrownBy( + () -> restHighLevelClient.deletePit(new DeletePitRequest(existingPitId), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + public void deletePitCreatedWithIndexAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(SECOND_INDEX_ALIAS); + + assertThatThrownBy( + () -> restHighLevelClient.deletePit(new DeletePitRequest(existingPitId), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + public void deleteAllPits_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(POINT_IN_TIME_USER)) { + cleanUpPits(); + String firstIndexPit = createPitForIndices(FIRST_SONG_INDEX); + String secondIndexPit = createPitForIndices(SECOND_SONG_INDEX); + + DeletePitResponse deletePitResponse = restHighLevelClient.deleteAllPits(DEFAULT); + assertThat(deletePitResponse, isSuccessfulDeletePitResponse()); + assertThat(deletePitResponse, deleteResponseContainsExactlyPitWithIds(firstIndexPit, secondIndexPit)); + } + } + + @Test + public void deleteAllPits_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + assertThatThrownBy(() -> restHighLevelClient.deleteAllPits(DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void searchWithPit_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(FIRST_SONG_INDEX); + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.source(new SearchSourceBuilder().pointInTimeBuilder(new PointInTimeBuilder(existingPitId))); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat( + searchResponse, + searchHitsContainDocumentsInAnyOrder( + Pair.of(FIRST_SONG_INDEX, ID_1), + Pair.of(FIRST_SONG_INDEX, ID_2), + Pair.of(FIRST_SONG_INDEX, ID_3) + ) + ); + } + } + + @Test + public void searchWithPitCreatedWithIndexAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(FIRST_INDEX_ALIAS); + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.source(new SearchSourceBuilder().pointInTimeBuilder(new PointInTimeBuilder(existingPitId))); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat( + searchResponse, + searchHitsContainDocumentsInAnyOrder( + Pair.of(FIRST_SONG_INDEX, ID_1), + Pair.of(FIRST_SONG_INDEX, ID_2), + Pair.of(FIRST_SONG_INDEX, ID_3) + ) + ); + } + } + + @Test + public void searchWithPit_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(SECOND_SONG_INDEX); + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.source(new SearchSourceBuilder().pointInTimeBuilder(new PointInTimeBuilder(existingPitId))); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void searchWithPitCreatedWithIndexAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(SECOND_INDEX_ALIAS); + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.source(new SearchSourceBuilder().pointInTimeBuilder(new PointInTimeBuilder(existingPitId))); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void listPitSegments_positive() throws IOException { + try (TestRestClient restClient = cluster.getRestClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(FIRST_SONG_INDEX); + String body = String.format("{\"pit_id\":[\"%s\"]}", existingPitId); + HttpResponse response = restClient.getWithJsonBody("/_cat/pit_segments", body); + + response.assertStatusCode(OK.getStatus()); + } + } + + @Test + public void listPitSegmentsCreatedWithIndexAlias_positive() throws IOException { + try (TestRestClient restClient = cluster.getRestClient(POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(FIRST_INDEX_ALIAS); + String body = String.format("{\"pit_id\":[\"%s\"]}", existingPitId); + HttpResponse response = restClient.getWithJsonBody("/_cat/pit_segments", body); + + response.assertStatusCode(OK.getStatus()); + } + } + + @Test + public void listPitSegments_negative() throws IOException { + try (TestRestClient restClient = cluster.getRestClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(SECOND_SONG_INDEX); + String body = String.format("{\"pit_id\":[\"%s\"]}", existingPitId); + HttpResponse response = restClient.getWithJsonBody("/_cat/pit_segments", body); + + response.assertStatusCode(FORBIDDEN.getStatus()); + } + } + + @Test + public void listPitSegmentsCreatedWithIndexAlias_negative() throws IOException { + try (TestRestClient restClient = cluster.getRestClient(LIMITED_POINT_IN_TIME_USER)) { + String existingPitId = createPitForIndices(SECOND_INDEX_ALIAS); + String body = String.format("{\"pit_id\":[\"%s\"]}", existingPitId); + HttpResponse response = restClient.getWithJsonBody("/_cat/pit_segments", body); + + response.assertStatusCode(FORBIDDEN.getStatus()); + } + } + + @Test + public void listAllPitSegments_positive() { + try (TestRestClient restClient = cluster.getRestClient(POINT_IN_TIME_USER)) { + HttpResponse response = restClient.get("/_cat/pit_segments/_all"); + + response.assertStatusCode(OK.getStatus()); + } + } + + @Test + public void listAllPitSegments_negative() { + try (TestRestClient restClient = cluster.getRestClient(LIMITED_POINT_IN_TIME_USER)) { + HttpResponse response = restClient.get("/_cat/pit_segments/_all"); + + response.assertStatusCode(FORBIDDEN.getStatus()); + } + } + + /** + * Creates PIT for given indices. Returns PIT id. + */ + private String createPitForIndices(String... indices) throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + CreatePitRequest createPitRequest = new CreatePitRequest(TimeValue.timeValueMinutes(30), false, indices); + + CreatePitResponse createPitResponse = restHighLevelClient.createPit(createPitRequest, DEFAULT); + + assertThat(createPitResponse, isSuccessfulCreatePitResponse()); + return createPitResponse.getId(); + } + } + + /** + * Deletes all PITs. + */ + public void cleanUpPits() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + try { + restHighLevelClient.deleteAllPits(DEFAULT); + } catch (OpenSearchStatusException ex) { + if (ex.status() != RestStatus.NOT_FOUND) { + throw ex; + } + // tried to remove pits but no pit exists + } + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java index 08e8394ec8..eccd9f5380 100644 --- a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java +++ b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java @@ -197,2149 +197,2523 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class SearchOperationTest { - private static final Logger log = LogManager.getLogger(SearchOperationTest.class); - - public static final String SONG_INDEX_NAME = "song_lyrics"; - public static final String PROHIBITED_SONG_INDEX_NAME = "prohibited_song_lyrics"; - public static final String WRITE_SONG_INDEX_NAME = "write_song_index"; - - public static final String SONG_LYRICS_ALIAS = "song_lyrics_index_alias"; - public static final String PROHIBITED_SONG_ALIAS = "prohibited_song_lyrics_index_alias"; - private static final String COLLECTIVE_INDEX_ALIAS = "collective-index-alias"; - private static final String TEMPLATE_INDEX_PREFIX = "song-transcription*"; - public static final String TEMPORARY_ALIAS_NAME = "temporary-alias"; - public static final String ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001 = "alias-used-in-musical-index-template-0001"; - public static final String ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002 = "alias-used-in-musical-index-template-0002"; - public static final String ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003 = "alias-used-in-musical-index-template-0003"; - public static final String INDEX_NAME_SONG_TRANSCRIPTION_JAZZ = "song-transcription-jazz"; - - public static final String MUSICAL_INDEX_TEMPLATE = "musical-index-template"; - public static final String ALIAS_CREATE_INDEX_WITH_ALIAS_POSITIVE = "alias_create_index_with_alias_positive"; - public static final String ALIAS_CREATE_INDEX_WITH_ALIAS_NEGATIVE = "alias_create_index_with_alias_negative"; - - public static final String UNDELETABLE_TEMPLATE_NAME = "undeletable-template-name"; - - public static final String ALIAS_FROM_UNDELETABLE_TEMPLATE = "alias-from-undeletable-template"; - - public static final String TEST_SNAPSHOT_REPOSITORY_NAME = "test-snapshot-repository"; - - public static final String UNUSED_SNAPSHOT_REPOSITORY_NAME = "unused-snapshot-repository"; - - public static final String RESTORED_SONG_INDEX_NAME = "restored_" + WRITE_SONG_INDEX_NAME; - - public static final String UPDATE_DELETE_OPERATION_INDEX_NAME = "update_delete_index"; - - public static final String DOCUMENT_TO_UPDATE_ID = "doc_to_update"; - - private static final String ID_P4 = "4"; - private static final String ID_S3 = "3"; - private static final String ID_S2 = "2"; - private static final String ID_S1 = "1"; - - static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS); - - /** - * All user read permissions are related to {@link #SONG_INDEX_NAME} index - */ - static final User LIMITED_READ_USER = new User("limited_read_user") - .roles(new Role("limited-song-reader") - .clusterPermissions("indices:data/read/mget", "indices:data/read/msearch", "indices:data/read/scroll") - .indexPermissions("indices:data/read/search", "indices:data/read/get", "indices:data/read/mget*", "indices:admin/aliases", "indices:data/read/field_caps", "indices:data/read/field_caps*") - .on(SONG_INDEX_NAME)); - - static final User LIMITED_WRITE_USER = new User("limited_write_user") - .roles(new Role("limited-write-role") - .clusterPermissions("indices:data/write/bulk", "indices:admin/template/put", "indices:admin/template/delete", "cluster:admin/repository/put", "cluster:admin/repository/delete", "cluster:admin/snapshot/create", "cluster:admin/snapshot/status", "cluster:admin/snapshot/status[nodes]", "cluster:admin/snapshot/delete", "cluster:admin/snapshot/get", "cluster:admin/snapshot/restore") - .indexPermissions("indices:data/write/index", "indices:data/write/bulk[s]", "indices:admin/create", "indices:admin/mapping/put", - "indices:data/write/update", "indices:data/write/bulk[s]", "indices:data/write/delete", "indices:data/write/bulk[s]") - .on(WRITE_SONG_INDEX_NAME), - new Role("transcription-role") - .indexPermissions("indices:data/write/index", "indices:admin/create", "indices:data/write/bulk[s]", "indices:admin/mapping/put") - .on(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ), - new Role("limited-write-index-restore-role") - .indexPermissions("indices:data/write/index", "indices:admin/create", "indices:data/read/search") - .on(RESTORED_SONG_INDEX_NAME)); - - - /** - * User who is allowed read both index {@link #SONG_INDEX_NAME} and {@link #PROHIBITED_SONG_INDEX_NAME} - */ - static final User DOUBLE_READER_USER = new User("double_read_user") - .roles(new Role("full-song-reader").indexPermissions("indices:data/read/search") - .on(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME)); - - static final User REINDEXING_USER = new User("reindexing_user") - .roles(new Role("song-reindexing-target-write") - .clusterPermissions("indices:data/write/reindex", "indices:data/write/bulk") - .indexPermissions("indices:admin/create", "indices:data/write/index", "indices:data/write/bulk[s]", "indices:admin/mapping/put") - .on(WRITE_SONG_INDEX_NAME), - new Role("song-reindexing-source-read") - .clusterPermissions("indices:data/read/scroll") - .indexPermissions("indices:data/read/search") - .on(SONG_INDEX_NAME)); - - private Client internalClient; - /** - * User who is allowed to update and delete documents on index {@link #UPDATE_DELETE_OPERATION_INDEX_NAME} - */ - static final User UPDATE_DELETE_USER = new User("update_delete_user") - .roles(new Role("document-updater") - .clusterPermissions("indices:data/write/bulk") - .indexPermissions("indices:data/write/update","indices:data/write/index", "indices:data/write/bulk[s]", "indices:admin/mapping/put") - .on(UPDATE_DELETE_OPERATION_INDEX_NAME), - new Role("document-remover") - .indexPermissions("indices:data/write/delete") - .on(UPDATE_DELETE_OPERATION_INDEX_NAME)) - ; - - static final String INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX = "index_operations_"; - - /** - * User who is allowed to perform index-related operations on - * indices with names prefixed by the {@link #INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX} - */ - static final User USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES = new User("index-operation-tester") - .roles(new Role("index-manager") - .indexPermissions( - "indices:admin/create", "indices:admin/get", "indices:admin/delete", "indices:admin/close", - "indices:admin/close*", "indices:admin/open", "indices:admin/resize", "indices:monitor/stats", - "indices:monitor/settings/get", "indices:admin/settings/update", "indices:admin/mapping/put", - "indices:admin/mappings/get", "indices:admin/cache/clear", "indices:admin/aliases" - ) - .on(INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("*"))); - - private static User USER_ALLOWED_TO_CREATE_INDEX = new User("user-allowed-to-create-index") - .roles(new Role("create-index-role").indexPermissions("indices:admin/create").on("*")); - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) - .authc(AUTHC_HTTPBASIC_INTERNAL) - .users(ADMIN_USER, LIMITED_READ_USER, LIMITED_WRITE_USER, DOUBLE_READER_USER, REINDEXING_USER, UPDATE_DELETE_USER, - USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES, USER_ALLOWED_TO_CREATE_INDEX) - .audit(new AuditConfiguration(true) - .compliance(new AuditCompliance().enabled(true)) - .filters(new AuditFilters().enabledRest(true).enabledTransport(true)) - ).build(); - - @Rule - public AuditLogsRule auditLogsRule = new AuditLogsRule(); - - @BeforeClass - public static void createTestData() { - try(Client client = cluster.getInternalNodeClient()) { - client.prepareIndex(SONG_INDEX_NAME).setId(ID_S1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); - client.prepareIndex(UPDATE_DELETE_OPERATION_INDEX_NAME).setId(DOCUMENT_TO_UPDATE_ID).setRefreshPolicy(IMMEDIATE).setSource("field", "value").get(); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new AliasActions(ADD).indices(SONG_INDEX_NAME).alias(SONG_LYRICS_ALIAS))).actionGet(); - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SONG_INDEX_NAME).id(ID_S2).source(SONGS[1].asMap())).actionGet(); - client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SONG_INDEX_NAME).id(ID_S3).source(SONGS[2].asMap())).actionGet(); - - client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(ID_P4).setSource(SONGS[3].asMap()).setRefreshPolicy(IMMEDIATE).get(); - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new AliasActions(ADD).indices(PROHIBITED_SONG_INDEX_NAME).alias(PROHIBITED_SONG_ALIAS))).actionGet(); - - client.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(new AliasActions(ADD).indices(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME).alias(COLLECTIVE_INDEX_ALIAS))).actionGet(); - var createTemplateRequest = new org.opensearch.action.admin.indices.template.put.PutIndexTemplateRequest(UNDELETABLE_TEMPLATE_NAME); - createTemplateRequest.patterns(List.of("pattern-does-not-match-to-any-index")); - createTemplateRequest.alias(new Alias(ALIAS_FROM_UNDELETABLE_TEMPLATE)); - client.admin().indices().putTemplate(createTemplateRequest).actionGet(); - - client.admin().cluster().putRepository(new PutRepositoryRequest(UNUSED_SNAPSHOT_REPOSITORY_NAME).type("fs").settings(Map.of("location", cluster.getSnapshotDirPath()))).actionGet(); - } - } - - @Before - public void retrieveClusterClient() { - this.internalClient = cluster.getInternalNodeClient(); - } - - @After - public void cleanData() throws ExecutionException, InterruptedException { - Stopwatch stopwatch = Stopwatch.createStarted(); - IndicesAdminClient indices = internalClient.admin().indices(); - List indicesToBeDeleted = List.of( - WRITE_SONG_INDEX_NAME, INDEX_NAME_SONG_TRANSCRIPTION_JAZZ, RESTORED_SONG_INDEX_NAME, - INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("*") - ); - for(String indexToBeDeleted : indicesToBeDeleted) { - IndicesExistsRequest indicesExistsRequest = new IndicesExistsRequest(indexToBeDeleted); - var indicesExistsResponse = indices.exists(indicesExistsRequest).get(); - if (indicesExistsResponse.isExists()) { - DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexToBeDeleted); - indices.delete(deleteIndexRequest).actionGet(); - Awaitility.await().ignoreExceptions().until(() -> indices.exists(indicesExistsRequest).get().isExists() == false); - } - } - - List aliasesToBeDeleted = List.of(TEMPORARY_ALIAS_NAME, ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, - ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002, ALIAS_CREATE_INDEX_WITH_ALIAS_POSITIVE, ALIAS_CREATE_INDEX_WITH_ALIAS_NEGATIVE); - for(String aliasToBeDeleted : aliasesToBeDeleted) { - if(indices.exists(new IndicesExistsRequest(aliasToBeDeleted)).get().isExists()) { - AliasActions aliasAction = new AliasActions(AliasActions.Type.REMOVE).indices(SONG_INDEX_NAME).alias(aliasToBeDeleted); - internalClient.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(aliasAction)).get(); - } - } - - GetIndexTemplatesResponse response = indices.getTemplates(new GetIndexTemplatesRequest(MUSICAL_INDEX_TEMPLATE)).get(); - for(IndexTemplateMetadata metadata : response.getIndexTemplates()) { - indices.deleteTemplate(new DeleteIndexTemplateRequest(metadata.getName())).get(); - } - - ClusterAdminClient clusterClient = internalClient.admin().cluster(); - try { - clusterClient.deleteRepository(new DeleteRepositoryRequest(TEST_SNAPSHOT_REPOSITORY_NAME)).actionGet(); - } catch (RepositoryMissingException e) { - log.debug("Repository '{}' does not exist. This is expected in most of test cases", TEST_SNAPSHOT_REPOSITORY_NAME, e); - } - internalClient.close(); - log.debug("Cleaning data after test took {}", stopwatch.stop()); - } - - @Test - public void shouldSearchForDocuments_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - - @Test - public void shouldSearchForDocuments_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(PROHIBITED_SONG_INDEX_NAME, QUERY_TITLE_POISON); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics/_search")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - - @Test - public void shouldSearchForDocumentsViaAlias_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(SONG_LYRICS_ALIAS, QUERY_TITLE_MAGNUM_OPUS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics_index_alias/_search")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - - @Test - public void shouldSearchForDocumentsViaAlias_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(PROHIBITED_SONG_ALIAS, QUERY_TITLE_POISON); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics_index_alias/_search")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - - @Test - public void shouldBeAbleToSearchSongViaMultiIndexAlias_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(COLLECTIVE_INDEX_ALIAS, QUERY_TITLE_NEXT_SONG); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S3)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/collective-index-alias/_search")); - auditLogsRule.assertExactlyOne(grantedPrivilege(DOUBLE_READER_USER, "SearchRequest")); - } - - @Test - public void shouldBeAbleToSearchSongViaMultiIndexAlias_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(COLLECTIVE_INDEX_ALIAS, QUERY_TITLE_POISON); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/collective-index-alias/_search")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - - @Test - public void shouldBeAbleToSearchAllIndexes_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(QUERY_TITLE_MAGNUM_OPUS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(ADMIN_USER).withRestRequest(POST, "/_search")); - auditLogsRule.assertExactlyOne(grantedPrivilege(ADMIN_USER, "SearchRequest")); - } - - @Test - public void shouldBeAbleToSearchAllIndexes_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = queryStringQueryRequest(QUERY_TITLE_MAGNUM_OPUS); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_search")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - @Test - public void shouldBeAbleToSearchSongIndexesWithAsterisk_prohibitedSongIndex_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { - SearchRequest searchRequest = queryStringQueryRequest("*" + SONG_INDEX_NAME, QUERY_TITLE_POISON); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, PROHIBITED_SONG_INDEX_NAME, ID_P4)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_POISON)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/*song_lyrics/_search")); - auditLogsRule.assertExactlyOne(grantedPrivilege(DOUBLE_READER_USER, "SearchRequest")); - } - - @Test - public void shouldBeAbleToSearchSongIndexesWithAsterisk_singIndex_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { - SearchRequest searchRequest = queryStringQueryRequest("*" + SONG_INDEX_NAME, QUERY_TITLE_NEXT_SONG); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S3)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/*song_lyrics/_search")); - auditLogsRule.assertExactlyOne(grantedPrivilege(DOUBLE_READER_USER, "SearchRequest")); - } - - @Test - public void shouldBeAbleToSearchSongIndexesWithAsterisk_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = queryStringQueryRequest("*" + SONG_INDEX_NAME, QUERY_TITLE_NEXT_SONG); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/*song_lyrics/_search")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - - @Test - public void shouldFindSongUsingDslQuery_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = new SearchRequest(SONG_INDEX_NAME); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); - boolQueryBuilder.filter(QueryBuilders.regexpQuery(FIELD_ARTIST, "f.+")); - boolQueryBuilder.filter(new MatchQueryBuilder(FIELD_TITLE, TITLE_MAGNUM_OPUS)); - searchSourceBuilder.query(boolQueryBuilder); - searchRequest.source(searchSourceBuilder); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - - @Test - public void shouldFindSongUsingDslQuery_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = new SearchRequest(PROHIBITED_SONG_INDEX_NAME); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); - boolQueryBuilder.filter(QueryBuilders.regexpQuery(FIELD_ARTIST, "n.+")); - boolQueryBuilder.filter(new MatchQueryBuilder(FIELD_TITLE, TITLE_POISON)); - searchSourceBuilder.query(boolQueryBuilder); - searchRequest.source(searchSourceBuilder); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics/_search")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - - @Test - public void shouldPerformSearchWithAllIndexAlias_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { - SearchRequest searchRequest = queryStringQueryRequest("_all", QUERY_TITLE_MAGNUM_OPUS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); - assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(ADMIN_USER).withRestRequest(POST, "/_all/_search")); - auditLogsRule.assertExactlyOne(grantedPrivilege(ADMIN_USER, "SearchRequest")); - } - - @Test - public void shouldPerformSearchWithAllIndexAlias_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = queryStringQueryRequest("_all", QUERY_TITLE_MAGNUM_OPUS); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_all/_search")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - - @Test - public void shouldScrollOverSearchResults_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = searchRequestWithScroll(SONG_INDEX_NAME, 2); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containNotEmptyScrollingId()); - - SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); - - SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); - assertThat(scrollResponse, isSuccessfulSearchResponse()); - assertThat(scrollResponse, containNotEmptyScrollingId()); - assertThat(scrollResponse, numberOfTotalHitsIsEqualTo(3)); - assertThat(scrollResponse, numberOfHitsInPageIsEqualTo(1)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_search/scroll")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchScrollRequest")); - } - - @Test - public void shouldScrollOverSearchResults_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { - SearchRequest searchRequest = searchRequestWithScroll(SONG_INDEX_NAME, 2); - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containNotEmptyScrollingId()); - - SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); - - assertThatThrownBy(() -> restHighLevelClient.scroll(scrollRequest, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/song_lyrics/_search")); - auditLogsRule.assertExactlyOne(grantedPrivilege(DOUBLE_READER_USER, "SearchRequest")); - auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/_search/scroll")); - auditLogsRule.assertExactlyOne(missingPrivilege(DOUBLE_READER_USER, "SearchScrollRequest")); - } - - @Test - public void shouldGetDocument_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - GetResponse response = restHighLevelClient.get(new GetRequest(SONG_INDEX_NAME, ID_S1), DEFAULT); - - assertThat(response, containDocument(SONG_INDEX_NAME, ID_S1)); - assertThat(response, documentContainField(FIELD_TITLE, TITLE_MAGNUM_OPUS)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/song_lyrics/_doc/1")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "GetRequest")); - } - - @Test - public void shouldGetDocument_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - GetRequest getRequest = new GetRequest(PROHIBITED_SONG_INDEX_NAME, ID_P4); - assertThatThrownBy(() -> restHighLevelClient.get(getRequest, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/prohibited_song_lyrics/_doc/4")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "GetRequest")); - } - - @Test - public void shouldPerformMultiGetDocuments_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - MultiGetRequest request = new MultiGetRequest(); - request.add(new Item(SONG_INDEX_NAME, ID_S1)); - request.add(new Item(SONG_INDEX_NAME, ID_S2)); - - MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); - - assertThat(response, is(notNullValue())); - assertThat(response, isSuccessfulMultiGetResponse()); - assertThat(response, numberOfGetItemResponsesIsEqualTo(2)); - - MultiGetItemResponse[] responses = response.getResponses(); - assertThat(responses[0].getResponse(), allOf( - containDocument(SONG_INDEX_NAME, ID_S1), - documentContainField(FIELD_TITLE, TITLE_MAGNUM_OPUS)) - ); - assertThat(responses[1].getResponse(), allOf( - containDocument(SONG_INDEX_NAME, ID_S2), - documentContainField(FIELD_TITLE, TITLE_SONG_1_PLUS_1)) - ); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_mget")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiGetRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiGetShardRequest")); - } - - @Test - public void shouldPerformMultiGetDocuments_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { - MultiGetRequest request = new MultiGetRequest(); - request.add(new Item(SONG_INDEX_NAME, ID_S1)); - - assertThatThrownBy(() -> restHighLevelClient.mget(request, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/_mget")); - auditLogsRule.assertExactlyOne(missingPrivilege(DOUBLE_READER_USER, "MultiGetRequest")); - } - - @Test - public void shouldPerformMultiGetDocuments_partiallyPositive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - MultiGetRequest request = new MultiGetRequest(); - request.add(new Item(SONG_INDEX_NAME, ID_S1)); - request.add(new Item(PROHIBITED_SONG_INDEX_NAME, ID_P4)); - - MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); - - assertThat(request, notNullValue()); - assertThat(response, not(isSuccessfulMultiGetResponse())); - assertThat(response, numberOfGetItemResponsesIsEqualTo(2)); - - MultiGetItemResponse[] responses = response.getResponses(); - assertThat(responses, arrayContaining( - hasProperty("failure", nullValue()), - hasProperty("failure", notNullValue()) - )); - assertThat(responses[1].getFailure().getFailure(), statusException(INTERNAL_SERVER_ERROR)); - assertThat(responses[1].getFailure().getFailure(), errorMessageContain("security_exception")); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_mget")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiGetRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiGetShardRequest").withIndex(SONG_INDEX_NAME)); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "MultiGetShardRequest").withIndex(PROHIBITED_SONG_INDEX_NAME)); - } - - @Test - public void shouldBeAllowedToPerformMulitSearch_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS)); - request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_NEXT_SONG)); - - MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); - - assertThat(response, notNullValue()); - assertThat(response, isSuccessfulMultiSearchResponse()); - assertThat(response, numberOfSearchItemResponsesIsEqualTo(2)); - - MultiSearchResponse.Item[] responses = response.getResponses(); - - assertThat(responses[0].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); - assertThat(responses[0].getResponse(), searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); - assertThat(responses[1].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); - assertThat(responses[1].getResponse(), searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S3)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_msearch")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiSearchRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - - @Test - public void shouldBeAllowedToPerformMulitSearch_partiallyPositive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS)); - request.add(queryStringQueryRequest(PROHIBITED_SONG_INDEX_NAME, QUERY_TITLE_POISON)); - - MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); - - assertThat(response, notNullValue()); - assertThat(response, not(isSuccessfulMultiSearchResponse())); - assertThat(response, numberOfSearchItemResponsesIsEqualTo(2)); - - MultiSearchResponse.Item[] responses = response.getResponses(); - assertThat(responses[0].getFailure(), nullValue()); - assertThat(responses[1].getFailure(), statusException(INTERNAL_SERVER_ERROR)); - assertThat(responses[1].getFailure(), errorMessageContain("security_exception")); - assertThat(responses[1].getResponse(), nullValue()); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_msearch")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiSearchRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest").withIndex(SONG_INDEX_NAME)); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest").withIndex(PROHIBITED_SONG_INDEX_NAME)); - } - - @Test - public void shouldBeAllowedToPerformMulitSearch_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS)); - request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_NEXT_SONG)); - - assertThatThrownBy(() -> restHighLevelClient.msearch(request, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/_msearch")); - auditLogsRule.assertExactlyOne(missingPrivilege(DOUBLE_READER_USER, "MultiSearchRequest")); - } - - @Test - public void shouldAggregateDataAndComputeAverage_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - final String aggregationName = "averageStars"; - SearchRequest searchRequest = averageAggregationRequest(SONG_INDEX_NAME, aggregationName, FIELD_STARS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest").withIndex(SONG_INDEX_NAME)); - } - - @Test - public void shouldAggregateDataAndComputeAverage_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = averageAggregationRequest(PROHIBITED_SONG_INDEX_NAME, "averageStars", FIELD_STARS); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics/_search")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest").withIndex(PROHIBITED_SONG_INDEX_NAME)); - } - - @Test - public void shouldPerformStatAggregation_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - final String aggregationName = "statsStars"; - SearchRequest searchRequest = statsAggregationRequest(SONG_INDEX_NAME, aggregationName, FIELD_STARS); - - SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "stats")); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - - @Test - public void shouldPerformStatAggregation_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SearchRequest searchRequest = statsAggregationRequest(PROHIBITED_SONG_INDEX_NAME, "statsStars", FIELD_STARS); - - assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics/_search")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); - } - - @Test - public void shouldIndexDocumentInBulkRequest_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); - bulkRequest.setRefreshPolicy(IMMEDIATE); - - BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); - - assertThat(response, successBulkResponse()); - assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, "one")); - assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, "two")); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "one", FIELD_TITLE, TITLE_MAGNUM_OPUS)); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); - auditLogsRule.assertAtLeast(4, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER));//sometimes 4 or 6 - auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest"));//sometimes 2 or 4 - } - - @Test - public void shouldIndexDocumentInBulkRequest_partiallyPositive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); - bulkRequest.setRefreshPolicy(IMMEDIATE); - - BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); - - assertThat(response, bulkResponseContainExceptions(0, allOf( - statusException(INTERNAL_SERVER_ERROR), - errorMessageContain("security_exception") - ))); - assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, "two")); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); - auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); - auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest").withIndex(SONG_INDEX_NAME)); - } - - @Test - public void shouldIndexDocumentInBulkRequest_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); - bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); - bulkRequest.setRefreshPolicy(IMMEDIATE); - - BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); - - assertThat(response, allOf( - failureBulkResponse(), - bulkResponseContainExceptions(statusException(INTERNAL_SERVER_ERROR)), - bulkResponseContainExceptions(errorMessageContain("security_exception")) - )); - assertThat(internalClient, not(clusterContainsDocument(SONG_INDEX_NAME, "one"))); - assertThat(internalClient, not(clusterContainsDocument(SONG_INDEX_NAME, "two"))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest").withIndex(SONG_INDEX_NAME)); - } - - @Test - public void shouldUpdateDocumentsInBulk_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - final String titleOne = "shape of my mind"; - final String titleTwo = "forgiven"; - BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); - restHighLevelClient.bulk(bulkRequest, DEFAULT); - bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new UpdateRequest(WRITE_SONG_INDEX_NAME, "one").doc(Map.of(FIELD_TITLE, titleOne))); - bulkRequest.add(new UpdateRequest(WRITE_SONG_INDEX_NAME, "two").doc(Map.of(FIELD_TITLE, titleTwo))); - - BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); - - assertThat(response, successBulkResponse()); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "one", FIELD_TITLE, titleOne)); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, titleTwo)); - } - auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); - auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); - auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); - - } - - @Test - public void shouldUpdateDocumentsInBulk_partiallyPositive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - final String titleOne = "shape of my mind"; - BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); - restHighLevelClient.bulk(bulkRequest, DEFAULT); - bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new UpdateRequest(WRITE_SONG_INDEX_NAME, "one").doc(Map.of(FIELD_TITLE, titleOne))); - bulkRequest.add(new UpdateRequest(SONG_INDEX_NAME, ID_S2).doc(Map.of(FIELD_TITLE, "forgiven"))); - - BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); - - assertThat(response, bulkResponseContainExceptions(1, allOf( - statusException(INTERNAL_SERVER_ERROR), - errorMessageContain("security_exception") - ))); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "one", FIELD_TITLE, titleOne)); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S2, FIELD_TITLE, TITLE_SONG_1_PLUS_1)); - } - auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); - auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); - auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest").withIndex(SONG_INDEX_NAME)); - } - - @Test - public void shouldUpdateDocumentsInBulk_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new UpdateRequest(SONG_INDEX_NAME, ID_S1).doc(Map.of(FIELD_TITLE, "shape of my mind"))); - bulkRequest.add(new UpdateRequest(SONG_INDEX_NAME, ID_S2).doc(Map.of(FIELD_TITLE, "forgiven"))); - - BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); - - assertThat(response, allOf( - failureBulkResponse(), - bulkResponseContainExceptions(statusException(INTERNAL_SERVER_ERROR)), - bulkResponseContainExceptions(errorMessageContain("security_exception")) - )); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S1, FIELD_TITLE, TITLE_MAGNUM_OPUS)); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S2, FIELD_TITLE, TITLE_SONG_1_PLUS_1)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest")); - } - - @Test - public void shouldDeleteDocumentInBulk_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("three").source(SONGS[2].asMap())); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("four").source(SONGS[3].asMap())); - assertThat(restHighLevelClient.bulk(bulkRequest, DEFAULT), successBulkResponse()); - bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "one")); - bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "three")); - - BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); - - assertThat(response, successBulkResponse()); - assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, "one"))); - assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, "three"))); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "four", FIELD_TITLE, TITLE_POISON)); - } - auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); - auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); - auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); - } - - @Test - public void shouldDeleteDocumentInBulk_partiallyPositive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); - assertThat(restHighLevelClient.bulk(bulkRequest, DEFAULT), successBulkResponse()); - bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "one")); - bulkRequest.add(new DeleteRequest(SONG_INDEX_NAME, ID_S3)); - - BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); - assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, "one"))); - - assertThat(response, bulkResponseContainExceptions(1, allOf( - statusException(INTERNAL_SERVER_ERROR), - errorMessageContain("security_exception") - ))); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S3, FIELD_TITLE, TITLE_NEXT_SONG)); - } - auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); - auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); - auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest")); - } - - @Test - public void shouldDeleteDocumentInBulk_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); - bulkRequest.add(new DeleteRequest(SONG_INDEX_NAME, ID_S1)); - bulkRequest.add(new DeleteRequest(SONG_INDEX_NAME, ID_S3)); - - BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); - - assertThat(response, allOf( - failureBulkResponse(), - bulkResponseContainExceptions(statusException(INTERNAL_SERVER_ERROR)), - bulkResponseContainExceptions(errorMessageContain("security_exception")) - )); - assertThat(internalClient, clusterContainsDocument(SONG_INDEX_NAME, ID_S1)); - assertThat(internalClient, clusterContainsDocument(SONG_INDEX_NAME, ID_S3)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest")); - - } - - @Test - public void shouldReindexDocuments_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(REINDEXING_USER)) { - ReindexRequest reindexRequest = new ReindexRequest().setSourceIndices(SONG_INDEX_NAME).setDestIndex(WRITE_SONG_INDEX_NAME); - - BulkByScrollResponse response = restHighLevelClient.reindex(reindexRequest, DEFAULT); - - assertThat(response, notNullValue()); - assertThat(response.getBulkFailures(), empty()); - assertThat(response.getSearchFailures(), empty()); - assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_S1)); - assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_S2)); - assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_S3)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(REINDEXING_USER).withRestRequest(POST, "/_reindex")); - auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "ReindexRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "SearchRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "BulkRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(REINDEXING_USER, "CreateIndexRequest")); - auditLogsRule.assertExactly(4, grantedPrivilege(REINDEXING_USER, "PutMappingRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "SearchScrollRequest")); - auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(REINDEXING_USER)); - auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "ClearScrollRequest")); - } - - @Test - public void shouldReindexDocuments_negativeSource() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(REINDEXING_USER)) { - ReindexRequest reindexRequest = new ReindexRequest().setSourceIndices(PROHIBITED_SONG_INDEX_NAME).setDestIndex(WRITE_SONG_INDEX_NAME); - - assertThatThrownBy(() -> restHighLevelClient.reindex(reindexRequest, DEFAULT), statusException(FORBIDDEN)); - assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_P4))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(REINDEXING_USER).withRestRequest(POST, "/_reindex")); - auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "ReindexRequest")); - auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "SearchRequest")); - } - - @Test - public void shouldReindexDocuments_negativeDestination() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(REINDEXING_USER)) { - ReindexRequest reindexRequest = new ReindexRequest().setSourceIndices(SONG_INDEX_NAME).setDestIndex(PROHIBITED_SONG_INDEX_NAME); - - assertThatThrownBy(() -> restHighLevelClient.reindex(reindexRequest, DEFAULT), statusException(FORBIDDEN)); - assertThat(internalClient, not(clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_S1))); - assertThat(internalClient, not(clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_S2))); - assertThat(internalClient, not(clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_S3))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(REINDEXING_USER).withRestRequest(POST, "/_reindex")); - auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "ReindexRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "SearchRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "BulkRequest")); - auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "BulkShardRequest")); - auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "ClearScrollRequest")); - } - - @Test - public void shouldReindexDocuments_negativeSourceAndDestination() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(REINDEXING_USER)) { - ReindexRequest reindexRequest = new ReindexRequest().setSourceIndices(PROHIBITED_SONG_INDEX_NAME).setDestIndex(SONG_INDEX_NAME); - - assertThatThrownBy(() -> restHighLevelClient.reindex(reindexRequest, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(REINDEXING_USER).withRestRequest(POST, "/_reindex")); - auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "ReindexRequest")); - auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "SearchRequest")); - } - - @Test - public void shouldUpdateDocument_positive() throws IOException { - String newField = "newField"; - String newValue = "newValue"; - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(UPDATE_DELETE_USER)) { - UpdateRequest updateRequest = new UpdateRequest(UPDATE_DELETE_OPERATION_INDEX_NAME, DOCUMENT_TO_UPDATE_ID) - .doc(newField, newValue).setRefreshPolicy(IMMEDIATE); - - UpdateResponse response = restHighLevelClient.update(updateRequest, DEFAULT); - - assertThat(response, isSuccessfulUpdateResponse()); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(UPDATE_DELETE_OPERATION_INDEX_NAME, DOCUMENT_TO_UPDATE_ID, newField, newValue)); - } - } - @Test - public void shouldUpdateDocument_negative() throws IOException { - String newField = "newField"; - String newValue = "newValue"; - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(UPDATE_DELETE_USER)) { - UpdateRequest updateRequest = new UpdateRequest(PROHIBITED_SONG_INDEX_NAME, DOCUMENT_TO_UPDATE_ID).doc(newField, newValue).setRefreshPolicy(IMMEDIATE); - - assertThatThrownBy(() -> restHighLevelClient.update(updateRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldDeleteDocument_positive() throws IOException { - String docId = "shouldDeleteDocument_positive"; - try(Client client = cluster.getInternalNodeClient()){ - client.index(new IndexRequest(UPDATE_DELETE_OPERATION_INDEX_NAME).id(docId).source("field", "value").setRefreshPolicy(IMMEDIATE)).actionGet(); - assertThat(internalClient, clusterContainsDocument(UPDATE_DELETE_OPERATION_INDEX_NAME, docId)); - } - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(UPDATE_DELETE_USER)) { - DeleteRequest deleteRequest = new DeleteRequest(UPDATE_DELETE_OPERATION_INDEX_NAME, docId).setRefreshPolicy(IMMEDIATE); - - DeleteResponse response = restHighLevelClient.delete(deleteRequest, DEFAULT); - - assertThat(response, isSuccessfulDeleteResponse()); - assertThat(internalClient, not(clusterContainsDocument(UPDATE_DELETE_OPERATION_INDEX_NAME, docId))); - } - } - @Test - public void shouldDeleteDocument_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(UPDATE_DELETE_USER)) { - DeleteRequest deleteRequest = new DeleteRequest(PROHIBITED_SONG_INDEX_NAME, ID_S1).setRefreshPolicy(IMMEDIATE); - - assertThatThrownBy(() -> restHighLevelClient.delete(deleteRequest, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldCreateAlias_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - AliasActions aliasAction = new AliasActions(ADD).indices(SONG_INDEX_NAME).alias(TEMPORARY_ALIAS_NAME); - IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); - - var response = restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT); - - assertThat(response, notNullValue()); - assertThat(response.isAcknowledged(), equalTo(true)); - assertThat(internalClient, clusterContainsDocument(TEMPORARY_ALIAS_NAME, ID_S1)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_aliases")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_READ_USER, "IndicesAliasesRequest")); - auditLogsRule.assertExactly(2, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_READ_USER)); - } - - @Test - public void shouldCreateAlias_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - AliasActions aliasAction = new AliasActions(ADD).indices(PROHIBITED_SONG_INDEX_NAME).alias(TEMPORARY_ALIAS_NAME); - IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); - - assertThatThrownBy(() -> restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT), statusException(FORBIDDEN)); - - assertThat(internalClient, not(clusterContainsDocument(TEMPORARY_ALIAS_NAME, ID_P4))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_aliases")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "IndicesAliasesRequest")); - } - - @Test - public void shouldDeleteAlias_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - AliasActions aliasAction = new AliasActions(ADD).indices(SONG_INDEX_NAME).alias(TEMPORARY_ALIAS_NAME); - IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); - restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT); - aliasAction = new AliasActions(REMOVE).indices(SONG_INDEX_NAME).alias(TEMPORARY_ALIAS_NAME); - indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); - - var response = restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT); - - assertThat(response, notNullValue()); - assertThat(response.isAcknowledged(), equalTo(true)); - assertThat(internalClient, not(clusterContainsDocument(TEMPORARY_ALIAS_NAME, ID_S1))); - } - auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_aliases")); - auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_READ_USER, "IndicesAliasesRequest")); - auditLogsRule.assertExactly(4, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_READ_USER)); - } - - @Test - public void shouldDeleteAlias_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - AliasActions aliasAction = new AliasActions(REMOVE).indices(PROHIBITED_SONG_INDEX_NAME).alias(PROHIBITED_SONG_ALIAS); - IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); - - assertThatThrownBy(() -> restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT), statusException(FORBIDDEN)); - - assertThat(internalClient, clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_P4)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_aliases")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "IndicesAliasesRequest")); - } - - @Test - public void shouldCreateIndexTemplate_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - PutIndexTemplateRequest request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE) - .patterns(List.of(TEMPLATE_INDEX_PREFIX)) - .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001)) - .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002)); - - var response = restHighLevelClient.indices().putTemplate(request, DEFAULT); - - assertThat(response, notNullValue()); - assertThat(response.isAcknowledged(), equalTo(true)); - assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE )); - String documentId = "0001"; - IndexRequest indexRequest = new IndexRequest(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ).id(documentId).source(SONGS[0].asMap()) - .setRefreshPolicy(IMMEDIATE); - restHighLevelClient.index(indexRequest, DEFAULT); - assertThat(internalClient, clusterContainsDocument(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ, documentId)); - assertThat(internalClient, clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, documentId)); - assertThat(internalClient, clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002, documentId)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_template/musical-index-template")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/song-transcription-jazz/_doc/0001")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutIndexTemplateRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "IndexRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); - auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); - auditLogsRule.assertExactly(8, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); - } - - @Test - public void shouldCreateIndexTemplate_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - PutIndexTemplateRequest request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE) - .patterns(List.of(TEMPLATE_INDEX_PREFIX)) - .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001)) - .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002)); - - assertThatThrownBy(() -> restHighLevelClient.indices().putTemplate(request, DEFAULT), statusException(FORBIDDEN)); - assertThat(internalClient, not(clusterContainTemplate(MUSICAL_INDEX_TEMPLATE ))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(PUT, "/_template/musical-index-template")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "PutIndexTemplateRequest")); - } - - @Test - public void shouldDeleteTemplate_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - PutIndexTemplateRequest request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE) - .patterns(List.of(TEMPLATE_INDEX_PREFIX)); - restHighLevelClient.indices().putTemplate(request, DEFAULT); - assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE)); - DeleteIndexTemplateRequest deleteRequest = new DeleteIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE); - - var response = restHighLevelClient.indices().deleteTemplate(deleteRequest, DEFAULT); - - assertThat(response, notNullValue()); - assertThat(response.isAcknowledged(), equalTo(true)); - assertThat(internalClient, not(clusterContainTemplate(MUSICAL_INDEX_TEMPLATE))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_template/musical-index-template")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(DELETE, "/_template/musical-index-template")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutIndexTemplateRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "DeleteIndexTemplateRequest")); - auditLogsRule.assertExactly(4, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); - } - - @Test - public void shouldDeleteTemplate_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - DeleteIndexTemplateRequest deleteRequest = new DeleteIndexTemplateRequest(UNDELETABLE_TEMPLATE_NAME); - - assertThatThrownBy(() -> restHighLevelClient.indices().deleteTemplate(deleteRequest, DEFAULT), statusException(FORBIDDEN)); - - assertThat(internalClient, clusterContainTemplate(UNDELETABLE_TEMPLATE_NAME)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(DELETE, "/_template/undeletable-template-name")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "DeleteIndexTemplateRequest")); - } - - @Test - public void shouldUpdateTemplate_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - PutIndexTemplateRequest request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE) - .patterns(List.of(TEMPLATE_INDEX_PREFIX)) - .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001)) - .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002)); - restHighLevelClient.indices().putTemplate(request, DEFAULT); - assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE)); - request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE) - .patterns(List.of(TEMPLATE_INDEX_PREFIX)) - .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003)); - - var response = restHighLevelClient.indices().putTemplate(request, DEFAULT); - - assertThat(response, notNullValue()); - assertThat(response.isAcknowledged(), equalTo(true)); - String documentId = "000one"; - IndexRequest indexRequest = new IndexRequest(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ).id(documentId).source(SONGS[0].asMap()) - .setRefreshPolicy(IMMEDIATE); - restHighLevelClient.index(indexRequest, DEFAULT); - assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE)); - assertThat(internalClient, clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003, documentId)); - assertThat(internalClient, not(clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, documentId))); - assertThat(internalClient, not(clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002, documentId))); - } - auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_template/musical-index-template")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/song-transcription-jazz/_doc/000one")); - auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutIndexTemplateRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "IndexRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); - auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); - auditLogsRule.assertExactly(10, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); - } - @Test - public void shouldUpdateTemplate_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - PutIndexTemplateRequest request = new PutIndexTemplateRequest(UNDELETABLE_TEMPLATE_NAME) - .patterns(List.of(TEMPLATE_INDEX_PREFIX)) - .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003)); - - assertThatThrownBy(() -> restHighLevelClient.indices().putTemplate(request, DEFAULT), statusException(FORBIDDEN)); - assertThat(internalClient, clusterContainTemplateWithAlias(UNDELETABLE_TEMPLATE_NAME, ALIAS_FROM_UNDELETABLE_TEMPLATE)); - assertThat(internalClient, not(clusterContainTemplateWithAlias(UNDELETABLE_TEMPLATE_NAME, ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(PUT, "/_template/undeletable-template-name")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "PutIndexTemplateRequest")); - } - - @Test - public void shouldGetFieldCapabilitiesForAllIndexes_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { - FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().fields(FIELD_TITLE); - - FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); - - assertThat(response, notNullValue()); - assertThat(response, containsExactlyIndices(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME, UPDATE_DELETE_OPERATION_INDEX_NAME)); - assertThat(response, numberOfFieldsIsEqualTo(1)); - assertThat(response, containsFieldWithNameAndType(FIELD_TITLE, "text")); - } - auditLogsRule.assertExactlyOne(userAuthenticated(ADMIN_USER).withRestRequest(GET, "/_field_caps")); - auditLogsRule.assertExactlyOne(grantedPrivilege(ADMIN_USER, "FieldCapabilitiesRequest")); - auditLogsRule.assertExactly(3, grantedPrivilege(ADMIN_USER, "FieldCapabilitiesIndexRequest")); - } - - @Test - public void shouldGetFieldCapabilitiesForAllIndexes_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().fields(FIELD_TITLE); - - assertThatThrownBy(() -> restHighLevelClient.fieldCaps(request, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/_field_caps")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "FieldCapabilitiesRequest")); - } - - @Test - public void shouldGetFieldCapabilitiesForParticularIndex_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(SONG_INDEX_NAME).fields(FIELD_TITLE); - - FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); - - assertThat(response, notNullValue()); - assertThat(response, containsExactlyIndices(SONG_INDEX_NAME)); - assertThat(response, numberOfFieldsIsEqualTo(1)); - assertThat(response, containsFieldWithNameAndType(FIELD_TITLE, "text")); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/song_lyrics/_field_caps")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "FieldCapabilitiesRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "FieldCapabilitiesIndexRequest")); - } - - @Test - public void shouldGetFieldCapabilitiesForParticularIndex_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(PROHIBITED_SONG_INDEX_NAME).fields(FIELD_TITLE); - - assertThatThrownBy(() -> restHighLevelClient.fieldCaps(request, DEFAULT), statusException(FORBIDDEN)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/prohibited_song_lyrics/_field_caps")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "FieldCapabilitiesRequest")); - } - - @Test - public void shouldCreateSnapshotRepository_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - String snapshotDirPath = cluster.getSnapshotDirPath(); - - var response = steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotDirPath, "fs"); - - assertThat(response, notNullValue()); - assertThat(response.isAcknowledged(), equalTo(true)); - assertThat(internalClient, clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); - } - - @Test - public void shouldCreateSnapshotRepository_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - String snapshotDirPath = cluster.getSnapshotDirPath(); - - assertThatThrownBy(() -> steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotDirPath, "fs"), statusException(FORBIDDEN)); - assertThat(internalClient, not(clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "PutRepositoryRequest")); - } - - @Test - public void shouldDeleteSnapshotRepository_positive() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); - assertThat(internalClient, clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME)); - - var response = steps.deleteSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME); - - assertThat(response, notNullValue()); - assertThat(response.isAcknowledged(), equalTo(true)); - assertThat(internalClient, not(clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(DELETE, "/_snapshot/test-snapshot-repository")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "DeleteRepositoryRequest")); - } - - @Test - public void shouldDeleteSnapshotRepository_negative() throws IOException { - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - - assertThatThrownBy(() -> steps.deleteSnapshotRepository(UNUSED_SNAPSHOT_REPOSITORY_NAME), statusException(FORBIDDEN)); - assertThat(internalClient, clusterContainsSnapshotRepository(UNUSED_SNAPSHOT_REPOSITORY_NAME)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(DELETE, "/_snapshot/unused-snapshot-repository")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "DeleteRepositoryRequest")); - } - - @Test //Bug which can be reproduced with the below test: https://github.com/opensearch-project/security/issues/2169 - public void shouldCreateSnapshot_positive() throws IOException { - final String snapshotName = "snapshot-positive-test"; - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); - - CreateSnapshotResponse response = steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, SONG_INDEX_NAME); - - assertThat(response, notNullValue()); - assertThat(response.status(), equalTo(RestStatus.ACCEPTED)); - steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); - assertThat(internalClient, clusterContainSuccessSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/snapshot-positive-test")); - auditLogsRule.assertAtLeast(1, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/snapshot-positive-test")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); - auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); - } - - @Test - public void shouldCreateSnapshot_negative() throws IOException { - final String snapshotName = "snapshot-negative-test"; - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - - assertThatThrownBy(() -> steps.createSnapshot(UNUSED_SNAPSHOT_REPOSITORY_NAME, snapshotName, SONG_INDEX_NAME), statusException(FORBIDDEN)); - - assertThat(internalClient, snapshotInClusterDoesNotExists(UNUSED_SNAPSHOT_REPOSITORY_NAME, snapshotName)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(PUT, "/_snapshot/unused-snapshot-repository/snapshot-negative-test")); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "CreateSnapshotRequest")); - } - - @Test - public void shouldDeleteSnapshot_positive() throws IOException { - String snapshotName = "delete-snapshot-positive"; - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - restHighLevelClient.snapshot(); - steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); - steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, SONG_INDEX_NAME); - steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); - - var response = steps.deleteSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); - - assertThat(response.isAcknowledged(), equalTo(true)); - assertThat(internalClient, snapshotInClusterDoesNotExists(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/delete-snapshot-positive")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(DELETE, "/_snapshot/test-snapshot-repository/delete-snapshot-positive")); - auditLogsRule.assertAtLeast(1, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/delete-snapshot-positive")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "DeleteSnapshotRequest")); - auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); - } - - @Test - public void shouldDeleteSnapshot_negative() throws IOException { - String snapshotName = "delete-snapshot-negative"; - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); - steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, SONG_INDEX_NAME); - steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); - } - try(RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - assertThatThrownBy(() -> steps.deleteSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName), statusException(FORBIDDEN)); - - assertThat(internalClient, clusterContainSuccessSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/delete-snapshot-negative")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(DELETE, "/_snapshot/test-snapshot-repository/delete-snapshot-negative")); - auditLogsRule.assertAtLeast(1, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/delete-snapshot-negative")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); - auditLogsRule.assertExactly(1, missingPrivilege(LIMITED_READ_USER, "DeleteSnapshotRequest")); - auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); - } - - @Test - public void shouldRestoreSnapshot_positive() throws IOException { - final String snapshotName = "restore-snapshot-positive"; - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - // 1. create some documents - BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0].asMap())); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1].asMap())); - bulkRequest.setRefreshPolicy(IMMEDIATE); - restHighLevelClient.bulk(bulkRequest, DEFAULT); - - //2. create snapshot repository - steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); - - // 3. create snapshot - steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, WRITE_SONG_INDEX_NAME) ; - - // 4. wait till snapshot is ready - steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); - - // 5. introduce some changes - bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Drei").source(SONGS[2].asMap())); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Vier").source(SONGS[3].asMap())); - bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "Eins")); - bulkRequest.setRefreshPolicy(IMMEDIATE); - restHighLevelClient.bulk(bulkRequest, DEFAULT); - - // 6. restore the snapshot - var response = steps.restoreSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, "(.+)", "restored_$1"); - - assertThat(response, notNullValue()); - assertThat(response.status(), equalTo(ACCEPTED)); - - // 7. wait until snapshot is restored - CountRequest countRequest = new CountRequest(RESTORED_SONG_INDEX_NAME); - Awaitility.await().ignoreExceptions().alias("Index contains proper number of documents restored from snapshot.") - .until(() -> restHighLevelClient.count(countRequest, DEFAULT).getCount() == 2); - - //8. verify that document are present in restored index - assertThat(internalClient, clusterContainsDocumentWithFieldValue(RESTORED_SONG_INDEX_NAME, "Eins", FIELD_TITLE, TITLE_MAGNUM_OPUS)); - assertThat(internalClient, clusterContainsDocumentWithFieldValue(RESTORED_SONG_INDEX_NAME, "Zwei", FIELD_TITLE, TITLE_SONG_1_PLUS_1)); - assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Drei"))); - assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Vier"))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/restore-snapshot-positive")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_snapshot/test-snapshot-repository/restore-snapshot-positive/_restore")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/restored_write_song_index/_count")); - auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); - auditLogsRule.assertAtLeast(1, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/restore-snapshot-positive")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); - auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "RestoreSnapshotRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "SearchRequest")); - auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); - auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); - } - - @Test - public void shouldRestoreSnapshot_failureForbiddenIndex() throws IOException { - final String snapshotName = "restore-snapshot-negative-forbidden-index"; - String restoreToIndex = "forbidden_index"; - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - // 1. create some documents - BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0].asMap())); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1].asMap())); - bulkRequest.setRefreshPolicy(IMMEDIATE); - restHighLevelClient.bulk(bulkRequest, DEFAULT); - - //2. create snapshot repository - steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); - - // 3. create snapshot - steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, WRITE_SONG_INDEX_NAME); - - // 4. wait till snapshot is ready - steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); - - // 5. restore the snapshot - assertThatThrownBy(() -> steps.restoreSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, "(.+)", restoreToIndex), - statusException(FORBIDDEN)); - - - //6. verify that document are not present in restored index - assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Eins"))); - assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Zwei"))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-index")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-index/_restore")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); - auditLogsRule.assertAtLeast(1, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-index")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); - auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "RestoreSnapshotRequest")); - auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); - auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); - auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "RestoreSnapshotRequest")); - } - - @Test - public void shouldRestoreSnapshot_failureOperationForbidden() throws IOException { - String snapshotName = "restore-snapshot-negative-forbidden-operation"; - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - // 1. create some documents - BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0].asMap())); - bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1].asMap())); - bulkRequest.setRefreshPolicy(IMMEDIATE); - restHighLevelClient.bulk(bulkRequest, DEFAULT); - - //2. create snapshot repository - steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); - - // 3. create snapshot - steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, WRITE_SONG_INDEX_NAME); - - // 4. wait till snapshot is ready - steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); - } - // 5. restore the snapshot - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { - SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); - assertThatThrownBy( () -> steps.restoreSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, "(.+)", "restored_$1"), - statusException(FORBIDDEN)); - - // 6. verify that documents does not exist - assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Eins"))); - assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Zwei"))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-operation")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-operation/_restore")); - auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); - auditLogsRule.assertAtLeast(1, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-operation")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); - auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); - auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); - auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); - auditLogsRule.assertExactly(1, missingPrivilege(LIMITED_READ_USER, "RestoreSnapshotRequest")); - auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); - auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); - } - - @Test - //required permissions: "indices:admin/create" - public void createIndex_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("create_index_positive"); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName); - CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, DEFAULT); - - assertThat(createIndexResponse, isSuccessfulCreateIndexResponse(indexName)); - assertThat(cluster, indexExists(indexName)); - } - } - - @Test - public void createIndex_negative() throws IOException { - String indexName = "create_index_negative"; - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName); - - assertThatThrownBy(() -> restHighLevelClient.indices().create(createIndexRequest, DEFAULT), statusException(FORBIDDEN)); - assertThat(cluster, not(indexExists(indexName))); - } - } - - @Test - //required permissions: "indices:admin/get" - public void checkIfIndexExists_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("index_exists_positive"); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - boolean exists = restHighLevelClient.indices().exists(new GetIndexRequest(indexName), DEFAULT); - - assertThat(exists, is(false)); - } - } - - @Test - public void checkIfIndexExists_negative() throws IOException { - String indexThatUserHasNoAccessTo = "index_exists_negative"; - String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - assertThatThrownBy(() -> - restHighLevelClient.indices().exists(new GetIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().exists(new GetIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().exists(new GetIndexRequest("*"), DEFAULT), - statusException(FORBIDDEN) - ); - } - } - - @Test - //required permissions: "indices:admin/delete" - public void deleteIndex_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("delete_index_positive"); - IndexOperationsHelper.createIndex(cluster, indexName); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName); - var response = restHighLevelClient.indices().delete(deleteIndexRequest, DEFAULT); - - assertThat(response.isAcknowledged(), is(true)); - assertThat(cluster, not(indexExists(indexName))); - } - } - - @Test - public void deleteIndex_negative() throws IOException { - String indexThatUserHasNoAccessTo = "delete_index_negative"; - String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - - assertThatThrownBy(() -> - restHighLevelClient.indices().delete(new DeleteIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().delete(new DeleteIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().delete(new DeleteIndexRequest("*"), DEFAULT), statusException(FORBIDDEN) - ); - } - } - - @Test - //required permissions: indices:admin/aliases, indices:admin/delete - public void shouldDeleteIndexByAliasRequest_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("delete_index_by_alias_request_positive"); - IndexOperationsHelper.createIndex(cluster, indexName); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - IndicesAliasesRequest request = new IndicesAliasesRequest().addAliasAction(new AliasActions(REMOVE_INDEX).indices(indexName)); - - var response = restHighLevelClient.indices().updateAliases(request, DEFAULT); - - assertThat(response.isAcknowledged(), is(true)); - assertThat(cluster, not(indexExists(indexName))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES).withRestRequest(POST, "/_aliases")); - auditLogsRule.assertExactly(2, grantedPrivilege(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES, "IndicesAliasesRequest")); - auditLogsRule.assertExactly(2, auditPredicate(INDEX_EVENT).withEffectiveUser(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)); - } - - @Test - public void shouldDeleteIndexByAliasRequest_negative() throws IOException { - String indexName = "delete_index_by_alias_request_negative"; - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - IndicesAliasesRequest request = new IndicesAliasesRequest().addAliasAction(new AliasActions(REMOVE_INDEX).indices(indexName)); - - assertThatThrownBy(() -> restHighLevelClient.indices().updateAliases(request, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - //required permissions: "indices:admin/get" - public void getIndex_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("get_index_positive"); - IndexOperationsHelper.createIndex(cluster, indexName); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - GetIndexRequest getIndexRequest = new GetIndexRequest(indexName); - GetIndexResponse response = restHighLevelClient.indices().get(getIndexRequest, DEFAULT); - - assertThat(response, getIndexResponseContainsIndices(indexName)); - } - } - - @Test - public void getIndex_negative() throws IOException { - String indexThatUserHasNoAccessTo = "get_index_negative"; - String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - - assertThatThrownBy(() -> - restHighLevelClient.indices().get(new GetIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().get(new GetIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().get(new GetIndexRequest("*"), DEFAULT), - statusException(FORBIDDEN) - ); - } - } - - @Test - //required permissions: "indices:admin/close", "indices:admin/close*" - public void closeIndex_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("close_index_positive"); - IndexOperationsHelper.createIndex(cluster, indexName); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - CloseIndexRequest closeIndexRequest = new CloseIndexRequest(indexName); - CloseIndexResponse response = restHighLevelClient.indices().close(closeIndexRequest, DEFAULT); - - assertThat(response, isSuccessfulCloseIndexResponse()); - assertThat(cluster, indexStateIsEqualTo(indexName, IndexMetadata.State.CLOSE)); - } - } - - @Test - public void closeIndex_negative() throws IOException { - String indexThatUserHasNoAccessTo = "close_index_negative"; - String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - - assertThatThrownBy(() -> - restHighLevelClient.indices().close(new CloseIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().close(new CloseIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().close(new CloseIndexRequest("*"), DEFAULT), - statusException(FORBIDDEN) - ); - } - } - - @Test - //required permissions: "indices:admin/open" - public void openIndex_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("open_index_positive"); - IndexOperationsHelper.createIndex(cluster, indexName); - IndexOperationsHelper.closeIndex(cluster, indexName); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - OpenIndexRequest closeIndexRequest = new OpenIndexRequest(indexName); - OpenIndexResponse response = restHighLevelClient.indices().open(closeIndexRequest, DEFAULT); - - assertThat(response, isSuccessfulOpenIndexResponse()); - assertThat(cluster, indexStateIsEqualTo(indexName, IndexMetadata.State.OPEN)); - } - } - - @Test - public void openIndex_negative() throws IOException { - String indexThatUserHasNoAccessTo = "open_index_negative"; - String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - - assertThatThrownBy(() -> - restHighLevelClient.indices().open(new OpenIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().open(new OpenIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().open(new OpenIndexRequest("*"), DEFAULT), - statusException(FORBIDDEN) - ); - } - } - - @Test - @Ignore - //required permissions: "indices:admin/resize", "indices:monitor/stats - // todo even when I assign the `indices:admin/resize` and `indices:monitor/stats` permissions to test user, this test fails. - // Issue: https://github.com/opensearch-project/security/issues/2141 - public void shrinkIndex_positive() throws IOException { - String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("shrink_index_positive_source"); - Settings sourceIndexSettings = Settings.builder() - .put("index.blocks.write", true) - .put("index.number_of_shards", 2) - .build(); - String targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("shrink_index_positive_target"); - IndexOperationsHelper.createIndex(cluster, sourceIndexName, sourceIndexSettings); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); - ResizeResponse response = restHighLevelClient.indices().shrink(resizeRequest, DEFAULT); - - assertThat(response, isSuccessfulResizeResponse(targetIndexName)); - assertThat(cluster, indexExists(targetIndexName)); - } - } - - @Test - public void shrinkIndex_negative() throws IOException { - //user cannot access target index - String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("shrink_index_negative_source"); - String targetIndexName = "shrink_index_negative_target"; - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); - - assertThatThrownBy(() -> restHighLevelClient.indices().shrink(resizeRequest, DEFAULT), statusException(FORBIDDEN)); - assertThat(cluster, not(indexExists(targetIndexName))); - } - - //user cannot access source index - sourceIndexName = "shrink_index_negative_source"; - targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("shrink_index_negative_target"); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); - - assertThatThrownBy(() -> restHighLevelClient.indices().shrink(resizeRequest, DEFAULT), statusException(FORBIDDEN)); - assertThat(cluster, not(indexExists(targetIndexName))); - } - } - - @Test - @Ignore - //required permissions: "indices:admin/resize", "indices:monitor/stats - // todo even when I assign the `indices:admin/resize` and `indices:monitor/stats` permissions to test user, this test fails. - // Issue: https://github.com/opensearch-project/security/issues/2141 - public void cloneIndex_positive() throws IOException { - String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clone_index_positive_source"); - Settings sourceIndexSettings = Settings.builder() - .put("index.blocks.write", true) - .build(); - String targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clone_index_positive_target"); - IndexOperationsHelper.createIndex(cluster, sourceIndexName, sourceIndexSettings); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); - ResizeResponse response = restHighLevelClient.indices().clone(resizeRequest, DEFAULT); - - assertThat(response, isSuccessfulResizeResponse(targetIndexName)); - assertThat(cluster, indexExists(targetIndexName)); - } - } - - @Test - public void cloneIndex_negative() throws IOException { - //user cannot access target index - String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clone_index_negative_source"); - String targetIndexName = "clone_index_negative_target"; - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); - - assertThatThrownBy(() -> restHighLevelClient.indices().clone(resizeRequest, DEFAULT), statusException(FORBIDDEN)); - assertThat(cluster, not(indexExists(targetIndexName))); - } - - //user cannot access source index - sourceIndexName = "clone_index_negative_source"; - targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clone_index_negative_target"); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); - - assertThatThrownBy(() -> restHighLevelClient.indices().clone(resizeRequest, DEFAULT), statusException(FORBIDDEN)); - assertThat(cluster, not(indexExists(targetIndexName))); - } - } - - @Test - @Ignore - //required permissions: "indices:admin/resize", "indices:monitor/stats - // todo even when I assign the `indices:admin/resize` and `indices:monitor/stats` permissions to test user, this test fails. - // Issue: https://github.com/opensearch-project/security/issues/2141 - public void splitIndex_positive() throws IOException { - String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("split_index_positive_source"); - Settings sourceIndexSettings = Settings.builder() - .put("index.blocks.write", true) - .build(); - String targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("split_index_positive_target"); - IndexOperationsHelper.createIndex(cluster, sourceIndexName, sourceIndexSettings); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); - resizeRequest.setSettings(Settings.builder().put("index.number_of_shards", 2).build()); - ResizeResponse response = restHighLevelClient.indices().split(resizeRequest, DEFAULT); - - assertThat(response, isSuccessfulResizeResponse(targetIndexName)); - assertThat(cluster, indexExists(targetIndexName)); - } - } - - @Test - public void splitIndex_negative() throws IOException { - //user cannot access target index - String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("split_index_negative_source"); - String targetIndexName = "split_index_negative_target"; - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); - resizeRequest.setSettings(Settings.builder().put("index.number_of_shards", 2).build()); - - assertThatThrownBy(() -> restHighLevelClient.indices().split(resizeRequest, DEFAULT), statusException(FORBIDDEN)); - assertThat(cluster, not(indexExists(targetIndexName))); - } - - //user cannot access source index - sourceIndexName = "split_index_negative_source"; - targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("split_index_negative_target"); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); - resizeRequest.setSettings(Settings.builder().put("index.number_of_shards", 2).build()); - - assertThatThrownBy(() -> restHighLevelClient.indices().split(resizeRequest, DEFAULT), statusException(FORBIDDEN)); - assertThat(cluster, not(indexExists(targetIndexName))); - } - } - - @Test - //required permissions: "indices:monitor/settings/get" - public void getIndexSettings_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("get_index_settings_positive"); - IndexOperationsHelper.createIndex(cluster, indexName); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - GetSettingsRequest getSettingsRequest = new GetSettingsRequest().indices(indexName); - GetSettingsResponse response = restHighLevelClient.indices().getSettings(getSettingsRequest, DEFAULT); - - assertThat(response, getSettingsResponseContainsIndices(indexName)); - } - } - - @Test - public void getIndexSettings_negative() throws IOException { - String indexThatUserHasNoAccessTo = "get_index_settings_negative"; - String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - assertThatThrownBy(() -> - restHighLevelClient.indices().getSettings(new GetSettingsRequest().indices(indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().getSettings(new GetSettingsRequest().indices(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().getSettings(new GetSettingsRequest().indices("*"), DEFAULT), - statusException(FORBIDDEN) - ); - } - } - - @Test - //required permissions: "indices:admin/settings/update" - public void updateIndexSettings_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("update_index_settings_positive"); - Settings initialSettings = Settings.builder().put("index.number_of_replicas", "2").build(); - Settings updatedSettings = Settings.builder().put("index.number_of_replicas", "4").build(); - IndexOperationsHelper.createIndex(cluster, indexName, initialSettings); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - UpdateSettingsRequest updateSettingsRequest = new UpdateSettingsRequest(indexName) - .settings(updatedSettings); - var response = restHighLevelClient.indices().putSettings(updateSettingsRequest, DEFAULT); - - assertThat(response.isAcknowledged(), is(true)); - assertThat(cluster, indexSettingsContainValues(indexName, updatedSettings)); - } - } - - @Test - public void updateIndexSettings_negative() throws IOException { - String indexThatUserHasNoAccessTo = "update_index_settings_negative"; - String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); - Settings settingsToUpdate = Settings.builder().put("index.number_of_replicas", 2).build(); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - - assertThatThrownBy(() -> - restHighLevelClient.indices().putSettings(new UpdateSettingsRequest(indexThatUserHasNoAccessTo).settings(settingsToUpdate), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().putSettings(new UpdateSettingsRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo).settings(settingsToUpdate), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().putSettings(new UpdateSettingsRequest("*").settings(settingsToUpdate), DEFAULT), - statusException(FORBIDDEN) - ); - } - } - - @Test - //required permissions: indices:admin/mapping/put - public void createIndexMappings_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("create_index_mappings_positive"); - Map indexMapping = Map.of("properties", Map.of("message", Map.of("type", "text"))); - IndexOperationsHelper.createIndex(cluster, indexName); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - PutMappingRequest putMappingRequest = new PutMappingRequest(indexName).source(indexMapping); - var response = restHighLevelClient.indices().putMapping(putMappingRequest, DEFAULT); - - assertThat(response.isAcknowledged(), is(true)); - assertThat(cluster, indexMappingIsEqualTo(indexName, indexMapping)); - } - } - - @Test - public void createIndexMappings_negative() throws IOException { - String indexThatUserHasNoAccessTo = "create_index_mappings_negative"; - String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); - Map indexMapping = Map.of("properties", Map.of("message", Map.of("type", "text"))); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - - assertThatThrownBy(() -> - restHighLevelClient.indices().putMapping(new PutMappingRequest(indexThatUserHasNoAccessTo).source(indexMapping), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().putMapping(new PutMappingRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo).source(indexMapping), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().putMapping(new PutMappingRequest("*").source(indexMapping), DEFAULT), - statusException(FORBIDDEN) - ); - } - } - - @Test - //required permissions: indices:admin/mappings/get - public void getIndexMappings_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("get_index_mappings_positive"); - Map indexMapping = Map.of("properties", Map.of("message", Map.of("type", "text"))); - IndexOperationsHelper.createIndex(cluster, indexName); - IndexOperationsHelper.createMapping(cluster, indexName, indexMapping); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - GetMappingsRequest getMappingsRequest = new GetMappingsRequest().indices(indexName); - GetMappingsResponse response = restHighLevelClient.indices().getMapping(getMappingsRequest, DEFAULT); - - assertThat(response, getMappingsResponseContainsIndices(indexName)); - } - } - - @Test - public void getIndexMappings_negative() throws IOException { - String indexThatUserHasNoAccessTo = "get_index_mappings_negative"; - String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - - assertThatThrownBy(() -> - restHighLevelClient.indices().getMapping(new GetMappingsRequest().indices(indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().getMapping(new GetMappingsRequest().indices(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().getMapping(new GetMappingsRequest().indices("*"), DEFAULT), - statusException(FORBIDDEN) - ); - } - } - - @Test - //required permissions: "indices:admin/cache/clear" - public void clearIndexCache_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clear_index_cache_positive"); - IndexOperationsHelper.createIndex(cluster, indexName); - - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - ClearIndicesCacheRequest clearIndicesCacheRequest = new ClearIndicesCacheRequest(indexName); - ClearIndicesCacheResponse response = restHighLevelClient.indices().clearCache(clearIndicesCacheRequest, DEFAULT); - - assertThat(response, isSuccessfulClearIndicesCacheResponse()); - } - } - - @Test - public void clearIndexCache_negative() throws IOException { - String indexThatUserHasNoAccessTo = "clear_index_cache_negative"; - String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - - assertThatThrownBy(() -> - restHighLevelClient.indices().clearCache(new ClearIndicesCacheRequest(indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().clearCache(new ClearIndicesCacheRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), - statusException(FORBIDDEN) - ); - assertThatThrownBy(() -> - restHighLevelClient.indices().clearCache(new ClearIndicesCacheRequest("*"), DEFAULT), - statusException(FORBIDDEN) - ); - } - } - - @Test - //required permissions: "indices:admin/create", "indices:admin/aliases" - public void shouldCreateIndexWithAlias_positive() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("create_index_with_alias_positive"); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)) { - CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName) - .alias(new Alias(ALIAS_CREATE_INDEX_WITH_ALIAS_POSITIVE)); - - CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, DEFAULT); - - assertThat(createIndexResponse, isSuccessfulCreateIndexResponse(indexName)); - assertThat(cluster, indexExists(indexName)); - assertThat(internalClient, aliasExists(ALIAS_CREATE_INDEX_WITH_ALIAS_POSITIVE)); - } - auditLogsRule.assertExactlyOne(userAuthenticated(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES).withRestRequest(PUT, "/index_operations_create_index_with_alias_positive")); - auditLogsRule.assertExactly(2, grantedPrivilege(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES, "CreateIndexRequest")); - auditLogsRule.assertExactly(2, auditPredicate(INDEX_EVENT).withEffectiveUser(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES)); - } - - @Test - public void shouldCreateIndexWithAlias_negative() throws IOException { - String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("create_index_with_alias_negative"); - try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_CREATE_INDEX)) { - CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName) - .alias(new Alias(ALIAS_CREATE_INDEX_WITH_ALIAS_NEGATIVE)); - - assertThatThrownBy(() -> restHighLevelClient.indices().create(createIndexRequest, DEFAULT), statusException(FORBIDDEN)); - - assertThat(internalClient, not(aliasExists(ALIAS_CREATE_INDEX_WITH_ALIAS_NEGATIVE))); - } - auditLogsRule.assertExactlyOne(userAuthenticated(USER_ALLOWED_TO_CREATE_INDEX).withRestRequest(PUT, "/index_operations_create_index_with_alias_negative")); - auditLogsRule.assertExactlyOne(missingPrivilege(USER_ALLOWED_TO_CREATE_INDEX, "CreateIndexRequest")); - } + private static final Logger log = LogManager.getLogger(SearchOperationTest.class); + + public static final String SONG_INDEX_NAME = "song_lyrics"; + public static final String PROHIBITED_SONG_INDEX_NAME = "prohibited_song_lyrics"; + public static final String WRITE_SONG_INDEX_NAME = "write_song_index"; + + public static final String SONG_LYRICS_ALIAS = "song_lyrics_index_alias"; + public static final String PROHIBITED_SONG_ALIAS = "prohibited_song_lyrics_index_alias"; + private static final String COLLECTIVE_INDEX_ALIAS = "collective-index-alias"; + private static final String TEMPLATE_INDEX_PREFIX = "song-transcription*"; + public static final String TEMPORARY_ALIAS_NAME = "temporary-alias"; + public static final String ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001 = "alias-used-in-musical-index-template-0001"; + public static final String ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002 = "alias-used-in-musical-index-template-0002"; + public static final String ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003 = "alias-used-in-musical-index-template-0003"; + public static final String INDEX_NAME_SONG_TRANSCRIPTION_JAZZ = "song-transcription-jazz"; + + public static final String MUSICAL_INDEX_TEMPLATE = "musical-index-template"; + public static final String ALIAS_CREATE_INDEX_WITH_ALIAS_POSITIVE = "alias_create_index_with_alias_positive"; + public static final String ALIAS_CREATE_INDEX_WITH_ALIAS_NEGATIVE = "alias_create_index_with_alias_negative"; + + public static final String UNDELETABLE_TEMPLATE_NAME = "undeletable-template-name"; + + public static final String ALIAS_FROM_UNDELETABLE_TEMPLATE = "alias-from-undeletable-template"; + + public static final String TEST_SNAPSHOT_REPOSITORY_NAME = "test-snapshot-repository"; + + public static final String UNUSED_SNAPSHOT_REPOSITORY_NAME = "unused-snapshot-repository"; + + public static final String RESTORED_SONG_INDEX_NAME = "restored_" + WRITE_SONG_INDEX_NAME; + + public static final String UPDATE_DELETE_OPERATION_INDEX_NAME = "update_delete_index"; + + public static final String DOCUMENT_TO_UPDATE_ID = "doc_to_update"; + + private static final String ID_P4 = "4"; + private static final String ID_S3 = "3"; + private static final String ID_S2 = "2"; + private static final String ID_S1 = "1"; + + static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS); + + /** + * All user read permissions are related to {@link #SONG_INDEX_NAME} index + */ + static final User LIMITED_READ_USER = new User("limited_read_user").roles( + new Role("limited-song-reader").clusterPermissions( + "indices:data/read/mget", + "indices:data/read/msearch", + "indices:data/read/scroll" + ) + .indexPermissions( + "indices:data/read/search", + "indices:data/read/get", + "indices:data/read/mget*", + "indices:admin/aliases", + "indices:data/read/field_caps", + "indices:data/read/field_caps*" + ) + .on(SONG_INDEX_NAME) + ); + + static final User LIMITED_WRITE_USER = new User("limited_write_user").roles( + new Role("limited-write-role").clusterPermissions( + "indices:data/write/bulk", + "indices:admin/template/put", + "indices:admin/template/delete", + "cluster:admin/repository/put", + "cluster:admin/repository/delete", + "cluster:admin/snapshot/create", + "cluster:admin/snapshot/status", + "cluster:admin/snapshot/status[nodes]", + "cluster:admin/snapshot/delete", + "cluster:admin/snapshot/get", + "cluster:admin/snapshot/restore" + ) + .indexPermissions( + "indices:data/write/index", + "indices:data/write/bulk[s]", + "indices:admin/create", + "indices:admin/mapping/put", + "indices:data/write/update", + "indices:data/write/bulk[s]", + "indices:data/write/delete", + "indices:data/write/bulk[s]" + ) + .on(WRITE_SONG_INDEX_NAME), + new Role("transcription-role").indexPermissions( + "indices:data/write/index", + "indices:admin/create", + "indices:data/write/bulk[s]", + "indices:admin/mapping/put" + ).on(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ), + new Role("limited-write-index-restore-role").indexPermissions( + "indices:data/write/index", + "indices:admin/create", + "indices:data/read/search" + ).on(RESTORED_SONG_INDEX_NAME) + ); + + /** + * User who is allowed read both index {@link #SONG_INDEX_NAME} and {@link #PROHIBITED_SONG_INDEX_NAME} + */ + static final User DOUBLE_READER_USER = new User("double_read_user").roles( + new Role("full-song-reader").indexPermissions("indices:data/read/search").on(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME) + ); + + static final User REINDEXING_USER = new User("reindexing_user").roles( + new Role("song-reindexing-target-write").clusterPermissions("indices:data/write/reindex", "indices:data/write/bulk") + .indexPermissions("indices:admin/create", "indices:data/write/index", "indices:data/write/bulk[s]", "indices:admin/mapping/put") + .on(WRITE_SONG_INDEX_NAME), + new Role("song-reindexing-source-read").clusterPermissions("indices:data/read/scroll") + .indexPermissions("indices:data/read/search") + .on(SONG_INDEX_NAME) + ); + + private Client internalClient; + /** + * User who is allowed to update and delete documents on index {@link #UPDATE_DELETE_OPERATION_INDEX_NAME} + */ + static final User UPDATE_DELETE_USER = new User("update_delete_user").roles( + new Role("document-updater").clusterPermissions("indices:data/write/bulk") + .indexPermissions( + "indices:data/write/update", + "indices:data/write/index", + "indices:data/write/bulk[s]", + "indices:admin/mapping/put" + ) + .on(UPDATE_DELETE_OPERATION_INDEX_NAME), + new Role("document-remover").indexPermissions("indices:data/write/delete").on(UPDATE_DELETE_OPERATION_INDEX_NAME) + ); + + static final String INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX = "index_operations_"; + + /** + * User who is allowed to perform index-related operations on + * indices with names prefixed by the {@link #INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX} + */ + static final User USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES = new User("index-operation-tester").roles( + new Role("index-manager").indexPermissions( + "indices:admin/create", + "indices:admin/get", + "indices:admin/delete", + "indices:admin/close", + "indices:admin/close*", + "indices:admin/open", + "indices:admin/resize", + "indices:monitor/stats", + "indices:monitor/settings/get", + "indices:admin/settings/update", + "indices:admin/mapping/put", + "indices:admin/mappings/get", + "indices:admin/cache/clear", + "indices:admin/aliases" + ).on(INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("*")) + ); + + private static User USER_ALLOWED_TO_CREATE_INDEX = new User("user-allowed-to-create-index").roles( + new Role("create-index-role").indexPermissions("indices:admin/create").on("*") + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .anonymousAuth(false) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users( + ADMIN_USER, + LIMITED_READ_USER, + LIMITED_WRITE_USER, + DOUBLE_READER_USER, + REINDEXING_USER, + UPDATE_DELETE_USER, + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES, + USER_ALLOWED_TO_CREATE_INDEX + ) + .audit( + new AuditConfiguration(true).compliance(new AuditCompliance().enabled(true)) + .filters(new AuditFilters().enabledRest(true).enabledTransport(true)) + ) + .build(); + + @Rule + public AuditLogsRule auditLogsRule = new AuditLogsRule(); + + @BeforeClass + public static void createTestData() { + try (Client client = cluster.getInternalNodeClient()) { + client.prepareIndex(SONG_INDEX_NAME).setId(ID_S1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); + client.prepareIndex(UPDATE_DELETE_OPERATION_INDEX_NAME) + .setId(DOCUMENT_TO_UPDATE_ID) + .setRefreshPolicy(IMMEDIATE) + .setSource("field", "value") + .get(); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction(new AliasActions(ADD).indices(SONG_INDEX_NAME).alias(SONG_LYRICS_ALIAS)) + ) + .actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SONG_INDEX_NAME).id(ID_S2).source(SONGS[1].asMap())) + .actionGet(); + client.index(new IndexRequest().setRefreshPolicy(IMMEDIATE).index(SONG_INDEX_NAME).id(ID_S3).source(SONGS[2].asMap())) + .actionGet(); + + client.prepareIndex(PROHIBITED_SONG_INDEX_NAME).setId(ID_P4).setSource(SONGS[3].asMap()).setRefreshPolicy(IMMEDIATE).get(); + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new AliasActions(ADD).indices(PROHIBITED_SONG_INDEX_NAME).alias(PROHIBITED_SONG_ALIAS) + ) + ) + .actionGet(); + + client.admin() + .indices() + .aliases( + new IndicesAliasesRequest().addAliasAction( + new AliasActions(ADD).indices(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME).alias(COLLECTIVE_INDEX_ALIAS) + ) + ) + .actionGet(); + var createTemplateRequest = new org.opensearch.action.admin.indices.template.put.PutIndexTemplateRequest( + UNDELETABLE_TEMPLATE_NAME + ); + createTemplateRequest.patterns(List.of("pattern-does-not-match-to-any-index")); + createTemplateRequest.alias(new Alias(ALIAS_FROM_UNDELETABLE_TEMPLATE)); + client.admin().indices().putTemplate(createTemplateRequest).actionGet(); + + client.admin() + .cluster() + .putRepository( + new PutRepositoryRequest(UNUSED_SNAPSHOT_REPOSITORY_NAME).type("fs") + .settings(Map.of("location", cluster.getSnapshotDirPath())) + ) + .actionGet(); + } + } + + @Before + public void retrieveClusterClient() { + this.internalClient = cluster.getInternalNodeClient(); + } + + @After + public void cleanData() throws ExecutionException, InterruptedException { + Stopwatch stopwatch = Stopwatch.createStarted(); + IndicesAdminClient indices = internalClient.admin().indices(); + List indicesToBeDeleted = List.of( + WRITE_SONG_INDEX_NAME, + INDEX_NAME_SONG_TRANSCRIPTION_JAZZ, + RESTORED_SONG_INDEX_NAME, + INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("*") + ); + for (String indexToBeDeleted : indicesToBeDeleted) { + IndicesExistsRequest indicesExistsRequest = new IndicesExistsRequest(indexToBeDeleted); + var indicesExistsResponse = indices.exists(indicesExistsRequest).get(); + if (indicesExistsResponse.isExists()) { + DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexToBeDeleted); + indices.delete(deleteIndexRequest).actionGet(); + Awaitility.await().ignoreExceptions().until(() -> indices.exists(indicesExistsRequest).get().isExists() == false); + } + } + + List aliasesToBeDeleted = List.of( + TEMPORARY_ALIAS_NAME, + ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, + ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002, + ALIAS_CREATE_INDEX_WITH_ALIAS_POSITIVE, + ALIAS_CREATE_INDEX_WITH_ALIAS_NEGATIVE + ); + for (String aliasToBeDeleted : aliasesToBeDeleted) { + if (indices.exists(new IndicesExistsRequest(aliasToBeDeleted)).get().isExists()) { + AliasActions aliasAction = new AliasActions(AliasActions.Type.REMOVE).indices(SONG_INDEX_NAME).alias(aliasToBeDeleted); + internalClient.admin().indices().aliases(new IndicesAliasesRequest().addAliasAction(aliasAction)).get(); + } + } + + GetIndexTemplatesResponse response = indices.getTemplates(new GetIndexTemplatesRequest(MUSICAL_INDEX_TEMPLATE)).get(); + for (IndexTemplateMetadata metadata : response.getIndexTemplates()) { + indices.deleteTemplate(new DeleteIndexTemplateRequest(metadata.getName())).get(); + } + + ClusterAdminClient clusterClient = internalClient.admin().cluster(); + try { + clusterClient.deleteRepository(new DeleteRepositoryRequest(TEST_SNAPSHOT_REPOSITORY_NAME)).actionGet(); + } catch (RepositoryMissingException e) { + log.debug("Repository '{}' does not exist. This is expected in most of test cases", TEST_SNAPSHOT_REPOSITORY_NAME, e); + } + internalClient.close(); + log.debug("Cleaning data after test took {}", stopwatch.stop()); + } + + @Test + public void shouldSearchForDocuments_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldSearchForDocuments_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(PROHIBITED_SONG_INDEX_NAME, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldSearchForDocumentsViaAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(SONG_LYRICS_ALIAS, QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics_index_alias/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldSearchForDocumentsViaAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(PROHIBITED_SONG_ALIAS, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics_index_alias/_search") + ); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldBeAbleToSearchSongViaMultiIndexAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(COLLECTIVE_INDEX_ALIAS, QUERY_TITLE_NEXT_SONG); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S3)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/collective-index-alias/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(DOUBLE_READER_USER, "SearchRequest")); + } + + @Test + public void shouldBeAbleToSearchSongViaMultiIndexAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(COLLECTIVE_INDEX_ALIAS, QUERY_TITLE_POISON); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/collective-index-alias/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldBeAbleToSearchAllIndexes_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(ADMIN_USER).withRestRequest(POST, "/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(ADMIN_USER, "SearchRequest")); + } + + @Test + public void shouldBeAbleToSearchAllIndexes_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest(QUERY_TITLE_MAGNUM_OPUS); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldBeAbleToSearchSongIndexesWithAsterisk_prohibitedSongIndex_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("*" + SONG_INDEX_NAME, QUERY_TITLE_POISON); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, PROHIBITED_SONG_INDEX_NAME, ID_P4)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_POISON)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/*song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(DOUBLE_READER_USER, "SearchRequest")); + } + + @Test + public void shouldBeAbleToSearchSongIndexesWithAsterisk_singIndex_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("*" + SONG_INDEX_NAME, QUERY_TITLE_NEXT_SONG); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S3)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/*song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(DOUBLE_READER_USER, "SearchRequest")); + } + + @Test + public void shouldBeAbleToSearchSongIndexesWithAsterisk_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("*" + SONG_INDEX_NAME, QUERY_TITLE_NEXT_SONG); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/*song_lyrics/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldFindSongUsingDslQuery_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = new SearchRequest(SONG_INDEX_NAME); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); + boolQueryBuilder.filter(QueryBuilders.regexpQuery(FIELD_ARTIST, "f.+")); + boolQueryBuilder.filter(new MatchQueryBuilder(FIELD_TITLE, TITLE_MAGNUM_OPUS)); + searchSourceBuilder.query(boolQueryBuilder); + searchRequest.source(searchSourceBuilder); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldFindSongUsingDslQuery_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = new SearchRequest(PROHIBITED_SONG_INDEX_NAME); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); + boolQueryBuilder.filter(QueryBuilders.regexpQuery(FIELD_ARTIST, "n.+")); + boolQueryBuilder.filter(new MatchQueryBuilder(FIELD_TITLE, TITLE_POISON)); + searchSourceBuilder.query(boolQueryBuilder); + searchRequest.source(searchSourceBuilder); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldPerformSearchWithAllIndexAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("_all", QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); + assertThat(searchResponse, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(ADMIN_USER).withRestRequest(POST, "/_all/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(ADMIN_USER, "SearchRequest")); + } + + @Test + public void shouldPerformSearchWithAllIndexAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = queryStringQueryRequest("_all", QUERY_TITLE_MAGNUM_OPUS); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_all/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldScrollOverSearchResults_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = searchRequestWithScroll(SONG_INDEX_NAME, 2); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containNotEmptyScrollingId()); + + SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); + + SearchResponse scrollResponse = restHighLevelClient.scroll(scrollRequest, DEFAULT); + assertThat(scrollResponse, isSuccessfulSearchResponse()); + assertThat(scrollResponse, containNotEmptyScrollingId()); + assertThat(scrollResponse, numberOfTotalHitsIsEqualTo(3)); + assertThat(scrollResponse, numberOfHitsInPageIsEqualTo(1)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_search/scroll")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchScrollRequest")); + } + + @Test + public void shouldScrollOverSearchResults_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { + SearchRequest searchRequest = searchRequestWithScroll(SONG_INDEX_NAME, 2); + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containNotEmptyScrollingId()); + + SearchScrollRequest scrollRequest = getSearchScrollRequest(searchResponse); + + assertThatThrownBy(() -> restHighLevelClient.scroll(scrollRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(DOUBLE_READER_USER, "SearchRequest")); + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/_search/scroll")); + auditLogsRule.assertExactlyOne(missingPrivilege(DOUBLE_READER_USER, "SearchScrollRequest")); + } + + @Test + public void shouldGetDocument_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + GetResponse response = restHighLevelClient.get(new GetRequest(SONG_INDEX_NAME, ID_S1), DEFAULT); + + assertThat(response, containDocument(SONG_INDEX_NAME, ID_S1)); + assertThat(response, documentContainField(FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/song_lyrics/_doc/1")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "GetRequest")); + } + + @Test + public void shouldGetDocument_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + GetRequest getRequest = new GetRequest(PROHIBITED_SONG_INDEX_NAME, ID_P4); + assertThatThrownBy(() -> restHighLevelClient.get(getRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/prohibited_song_lyrics/_doc/4")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "GetRequest")); + } + + @Test + public void shouldPerformMultiGetDocuments_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + MultiGetRequest request = new MultiGetRequest(); + request.add(new Item(SONG_INDEX_NAME, ID_S1)); + request.add(new Item(SONG_INDEX_NAME, ID_S2)); + + MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); + + assertThat(response, is(notNullValue())); + assertThat(response, isSuccessfulMultiGetResponse()); + assertThat(response, numberOfGetItemResponsesIsEqualTo(2)); + + MultiGetItemResponse[] responses = response.getResponses(); + assertThat( + responses[0].getResponse(), + allOf(containDocument(SONG_INDEX_NAME, ID_S1), documentContainField(FIELD_TITLE, TITLE_MAGNUM_OPUS)) + ); + assertThat( + responses[1].getResponse(), + allOf(containDocument(SONG_INDEX_NAME, ID_S2), documentContainField(FIELD_TITLE, TITLE_SONG_1_PLUS_1)) + ); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_mget")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiGetRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiGetShardRequest")); + } + + @Test + public void shouldPerformMultiGetDocuments_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { + MultiGetRequest request = new MultiGetRequest(); + request.add(new Item(SONG_INDEX_NAME, ID_S1)); + + assertThatThrownBy(() -> restHighLevelClient.mget(request, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/_mget")); + auditLogsRule.assertExactlyOne(missingPrivilege(DOUBLE_READER_USER, "MultiGetRequest")); + } + + @Test + public void shouldPerformMultiGetDocuments_partiallyPositive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + MultiGetRequest request = new MultiGetRequest(); + request.add(new Item(SONG_INDEX_NAME, ID_S1)); + request.add(new Item(PROHIBITED_SONG_INDEX_NAME, ID_P4)); + + MultiGetResponse response = restHighLevelClient.mget(request, DEFAULT); + + assertThat(request, notNullValue()); + assertThat(response, not(isSuccessfulMultiGetResponse())); + assertThat(response, numberOfGetItemResponsesIsEqualTo(2)); + + MultiGetItemResponse[] responses = response.getResponses(); + assertThat(responses, arrayContaining(hasProperty("failure", nullValue()), hasProperty("failure", notNullValue()))); + assertThat(responses[1].getFailure().getFailure(), statusException(INTERNAL_SERVER_ERROR)); + assertThat(responses[1].getFailure().getFailure(), errorMessageContain("security_exception")); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_mget")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiGetRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiGetShardRequest").withIndex(SONG_INDEX_NAME)); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "MultiGetShardRequest").withIndex(PROHIBITED_SONG_INDEX_NAME)); + } + + @Test + public void shouldBeAllowedToPerformMulitSearch_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + MultiSearchRequest request = new MultiSearchRequest(); + request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS)); + request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_NEXT_SONG)); + + MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response, isSuccessfulMultiSearchResponse()); + assertThat(response, numberOfSearchItemResponsesIsEqualTo(2)); + + MultiSearchResponse.Item[] responses = response.getResponses(); + + assertThat(responses[0].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat(responses[0].getResponse(), searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S1)); + assertThat(responses[1].getResponse(), searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_NEXT_SONG)); + assertThat(responses[1].getResponse(), searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, ID_S3)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_msearch")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiSearchRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldBeAllowedToPerformMulitSearch_partiallyPositive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + MultiSearchRequest request = new MultiSearchRequest(); + request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS)); + request.add(queryStringQueryRequest(PROHIBITED_SONG_INDEX_NAME, QUERY_TITLE_POISON)); + + MultiSearchResponse response = restHighLevelClient.msearch(request, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response, not(isSuccessfulMultiSearchResponse())); + assertThat(response, numberOfSearchItemResponsesIsEqualTo(2)); + + MultiSearchResponse.Item[] responses = response.getResponses(); + assertThat(responses[0].getFailure(), nullValue()); + assertThat(responses[1].getFailure(), statusException(INTERNAL_SERVER_ERROR)); + assertThat(responses[1].getFailure(), errorMessageContain("security_exception")); + assertThat(responses[1].getResponse(), nullValue()); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_msearch")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "MultiSearchRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest").withIndex(SONG_INDEX_NAME)); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest").withIndex(PROHIBITED_SONG_INDEX_NAME)); + } + + @Test + public void shouldBeAllowedToPerformMulitSearch_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(DOUBLE_READER_USER)) { + MultiSearchRequest request = new MultiSearchRequest(); + request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS)); + request.add(queryStringQueryRequest(SONG_INDEX_NAME, QUERY_TITLE_NEXT_SONG)); + + assertThatThrownBy(() -> restHighLevelClient.msearch(request, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(DOUBLE_READER_USER).withRestRequest(POST, "/_msearch")); + auditLogsRule.assertExactlyOne(missingPrivilege(DOUBLE_READER_USER, "MultiSearchRequest")); + } + + @Test + public void shouldAggregateDataAndComputeAverage_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + final String aggregationName = "averageStars"; + SearchRequest searchRequest = averageAggregationRequest(SONG_INDEX_NAME, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "avg")); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest").withIndex(SONG_INDEX_NAME)); + } + + @Test + public void shouldAggregateDataAndComputeAverage_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = averageAggregationRequest(PROHIBITED_SONG_INDEX_NAME, "averageStars", FIELD_STARS); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest").withIndex(PROHIBITED_SONG_INDEX_NAME)); + } + + @Test + public void shouldPerformStatAggregation_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + final String aggregationName = "statsStars"; + SearchRequest searchRequest = statsAggregationRequest(SONG_INDEX_NAME, aggregationName, FIELD_STARS); + + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, containAggregationWithNameAndType(aggregationName, "stats")); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/song_lyrics/_search")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldPerformStatAggregation_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SearchRequest searchRequest = statsAggregationRequest(PROHIBITED_SONG_INDEX_NAME, "statsStars", FIELD_STARS); + + assertThatThrownBy(() -> restHighLevelClient.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/prohibited_song_lyrics/_search")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "SearchRequest")); + } + + @Test + public void shouldIndexDocumentInBulkRequest_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); + bulkRequest.setRefreshPolicy(IMMEDIATE); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat(response, successBulkResponse()); + assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, "one")); + assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, "two")); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "one", FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat( + internalClient, + clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1) + ); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertAtLeast(4, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER));// sometimes 4 or 6 + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest"));// sometimes 2 or 4 + } + + @Test + public void shouldIndexDocumentInBulkRequest_partiallyPositive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); + bulkRequest.setRefreshPolicy(IMMEDIATE); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat( + response, + bulkResponseContainExceptions(0, allOf(statusException(INTERNAL_SERVER_ERROR), errorMessageContain("security_exception"))) + ); + assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, "two")); + assertThat( + internalClient, + clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1) + ); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest").withIndex(SONG_INDEX_NAME)); + } + + @Test + public void shouldIndexDocumentInBulkRequest_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); + bulkRequest.setRefreshPolicy(IMMEDIATE); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat( + response, + allOf( + failureBulkResponse(), + bulkResponseContainExceptions(statusException(INTERNAL_SERVER_ERROR)), + bulkResponseContainExceptions(errorMessageContain("security_exception")) + ) + ); + assertThat(internalClient, not(clusterContainsDocument(SONG_INDEX_NAME, "one"))); + assertThat(internalClient, not(clusterContainsDocument(SONG_INDEX_NAME, "two"))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest").withIndex(SONG_INDEX_NAME)); + } + + @Test + public void shouldUpdateDocumentsInBulk_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + final String titleOne = "shape of my mind"; + final String titleTwo = "forgiven"; + BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); + restHighLevelClient.bulk(bulkRequest, DEFAULT); + bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new UpdateRequest(WRITE_SONG_INDEX_NAME, "one").doc(Map.of(FIELD_TITLE, titleOne))); + bulkRequest.add(new UpdateRequest(WRITE_SONG_INDEX_NAME, "two").doc(Map.of(FIELD_TITLE, titleTwo))); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat(response, successBulkResponse()); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "one", FIELD_TITLE, titleOne)); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, titleTwo)); + } + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + + } + + @Test + public void shouldUpdateDocumentsInBulk_partiallyPositive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + final String titleOne = "shape of my mind"; + BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + restHighLevelClient.bulk(bulkRequest, DEFAULT); + bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new UpdateRequest(WRITE_SONG_INDEX_NAME, "one").doc(Map.of(FIELD_TITLE, titleOne))); + bulkRequest.add(new UpdateRequest(SONG_INDEX_NAME, ID_S2).doc(Map.of(FIELD_TITLE, "forgiven"))); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat( + response, + bulkResponseContainExceptions(1, allOf(statusException(INTERNAL_SERVER_ERROR), errorMessageContain("security_exception"))) + ); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "one", FIELD_TITLE, titleOne)); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S2, FIELD_TITLE, TITLE_SONG_1_PLUS_1)); + } + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest").withIndex(SONG_INDEX_NAME)); + } + + @Test + public void shouldUpdateDocumentsInBulk_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new UpdateRequest(SONG_INDEX_NAME, ID_S1).doc(Map.of(FIELD_TITLE, "shape of my mind"))); + bulkRequest.add(new UpdateRequest(SONG_INDEX_NAME, ID_S2).doc(Map.of(FIELD_TITLE, "forgiven"))); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat( + response, + allOf( + failureBulkResponse(), + bulkResponseContainExceptions(statusException(INTERNAL_SERVER_ERROR)), + bulkResponseContainExceptions(errorMessageContain("security_exception")) + ) + ); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S1, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S2, FIELD_TITLE, TITLE_SONG_1_PLUS_1)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest")); + } + + @Test + public void shouldDeleteDocumentInBulk_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("three").source(SONGS[2].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("four").source(SONGS[3].asMap())); + assertThat(restHighLevelClient.bulk(bulkRequest, DEFAULT), successBulkResponse()); + bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "one")); + bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "three")); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat(response, successBulkResponse()); + assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, "one"))); + assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, "three"))); + assertThat( + internalClient, + clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1) + ); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "four", FIELD_TITLE, TITLE_POISON)); + } + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + } + + @Test + public void shouldDeleteDocumentInBulk_partiallyPositive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("one").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("two").source(SONGS[1].asMap())); + assertThat(restHighLevelClient.bulk(bulkRequest, DEFAULT), successBulkResponse()); + bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "one")); + bulkRequest.add(new DeleteRequest(SONG_INDEX_NAME, ID_S3)); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, "one"))); + + assertThat( + response, + bulkResponseContainExceptions(1, allOf(statusException(INTERNAL_SERVER_ERROR), errorMessageContain("security_exception"))) + ); + assertThat( + internalClient, + clusterContainsDocumentWithFieldValue(WRITE_SONG_INDEX_NAME, "two", FIELD_TITLE, TITLE_SONG_1_PLUS_1) + ); + assertThat(internalClient, clusterContainsDocumentWithFieldValue(SONG_INDEX_NAME, ID_S3, FIELD_TITLE, TITLE_NEXT_SONG)); + } + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest")); + } + + @Test + public void shouldDeleteDocumentInBulk_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + BulkRequest bulkRequest = new BulkRequest().setRefreshPolicy(IMMEDIATE); + bulkRequest.add(new DeleteRequest(SONG_INDEX_NAME, ID_S1)); + bulkRequest.add(new DeleteRequest(SONG_INDEX_NAME, ID_S3)); + + BulkResponse response = restHighLevelClient.bulk(bulkRequest, DEFAULT); + + assertThat( + response, + allOf( + failureBulkResponse(), + bulkResponseContainExceptions(statusException(INTERNAL_SERVER_ERROR)), + bulkResponseContainExceptions(errorMessageContain("security_exception")) + ) + ); + assertThat(internalClient, clusterContainsDocument(SONG_INDEX_NAME, ID_S1)); + assertThat(internalClient, clusterContainsDocument(SONG_INDEX_NAME, ID_S3)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "BulkShardRequest")); + + } + + @Test + public void shouldReindexDocuments_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(REINDEXING_USER)) { + ReindexRequest reindexRequest = new ReindexRequest().setSourceIndices(SONG_INDEX_NAME).setDestIndex(WRITE_SONG_INDEX_NAME); + + BulkByScrollResponse response = restHighLevelClient.reindex(reindexRequest, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.getBulkFailures(), empty()); + assertThat(response.getSearchFailures(), empty()); + assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_S1)); + assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_S2)); + assertThat(internalClient, clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_S3)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(REINDEXING_USER).withRestRequest(POST, "/_reindex")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "ReindexRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "SearchRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(REINDEXING_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(REINDEXING_USER, "PutMappingRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "SearchScrollRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(REINDEXING_USER)); + auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "ClearScrollRequest")); + } + + @Test + public void shouldReindexDocuments_negativeSource() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(REINDEXING_USER)) { + ReindexRequest reindexRequest = new ReindexRequest().setSourceIndices(PROHIBITED_SONG_INDEX_NAME) + .setDestIndex(WRITE_SONG_INDEX_NAME); + + assertThatThrownBy(() -> restHighLevelClient.reindex(reindexRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(internalClient, not(clusterContainsDocument(WRITE_SONG_INDEX_NAME, ID_P4))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(REINDEXING_USER).withRestRequest(POST, "/_reindex")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "ReindexRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "SearchRequest")); + } + + @Test + public void shouldReindexDocuments_negativeDestination() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(REINDEXING_USER)) { + ReindexRequest reindexRequest = new ReindexRequest().setSourceIndices(SONG_INDEX_NAME).setDestIndex(PROHIBITED_SONG_INDEX_NAME); + + assertThatThrownBy(() -> restHighLevelClient.reindex(reindexRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(internalClient, not(clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_S1))); + assertThat(internalClient, not(clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_S2))); + assertThat(internalClient, not(clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_S3))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(REINDEXING_USER).withRestRequest(POST, "/_reindex")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "ReindexRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "SearchRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "BulkRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "BulkShardRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "ClearScrollRequest")); + } + + @Test + public void shouldReindexDocuments_negativeSourceAndDestination() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(REINDEXING_USER)) { + ReindexRequest reindexRequest = new ReindexRequest().setSourceIndices(PROHIBITED_SONG_INDEX_NAME).setDestIndex(SONG_INDEX_NAME); + + assertThatThrownBy(() -> restHighLevelClient.reindex(reindexRequest, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(REINDEXING_USER).withRestRequest(POST, "/_reindex")); + auditLogsRule.assertExactlyOne(grantedPrivilege(REINDEXING_USER, "ReindexRequest")); + auditLogsRule.assertExactlyOne(missingPrivilege(REINDEXING_USER, "SearchRequest")); + } + + @Test + public void shouldUpdateDocument_positive() throws IOException { + String newField = "newField"; + String newValue = "newValue"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(UPDATE_DELETE_USER)) { + UpdateRequest updateRequest = new UpdateRequest(UPDATE_DELETE_OPERATION_INDEX_NAME, DOCUMENT_TO_UPDATE_ID).doc( + newField, + newValue + ).setRefreshPolicy(IMMEDIATE); + + UpdateResponse response = restHighLevelClient.update(updateRequest, DEFAULT); + + assertThat(response, isSuccessfulUpdateResponse()); + assertThat( + internalClient, + clusterContainsDocumentWithFieldValue(UPDATE_DELETE_OPERATION_INDEX_NAME, DOCUMENT_TO_UPDATE_ID, newField, newValue) + ); + } + } + + @Test + public void shouldUpdateDocument_negative() throws IOException { + String newField = "newField"; + String newValue = "newValue"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(UPDATE_DELETE_USER)) { + UpdateRequest updateRequest = new UpdateRequest(PROHIBITED_SONG_INDEX_NAME, DOCUMENT_TO_UPDATE_ID).doc(newField, newValue) + .setRefreshPolicy(IMMEDIATE); + + assertThatThrownBy(() -> restHighLevelClient.update(updateRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldDeleteDocument_positive() throws IOException { + String docId = "shouldDeleteDocument_positive"; + try (Client client = cluster.getInternalNodeClient()) { + client.index( + new IndexRequest(UPDATE_DELETE_OPERATION_INDEX_NAME).id(docId).source("field", "value").setRefreshPolicy(IMMEDIATE) + ).actionGet(); + assertThat(internalClient, clusterContainsDocument(UPDATE_DELETE_OPERATION_INDEX_NAME, docId)); + } + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(UPDATE_DELETE_USER)) { + DeleteRequest deleteRequest = new DeleteRequest(UPDATE_DELETE_OPERATION_INDEX_NAME, docId).setRefreshPolicy(IMMEDIATE); + + DeleteResponse response = restHighLevelClient.delete(deleteRequest, DEFAULT); + + assertThat(response, isSuccessfulDeleteResponse()); + assertThat(internalClient, not(clusterContainsDocument(UPDATE_DELETE_OPERATION_INDEX_NAME, docId))); + } + } + + @Test + public void shouldDeleteDocument_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(UPDATE_DELETE_USER)) { + DeleteRequest deleteRequest = new DeleteRequest(PROHIBITED_SONG_INDEX_NAME, ID_S1).setRefreshPolicy(IMMEDIATE); + + assertThatThrownBy(() -> restHighLevelClient.delete(deleteRequest, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldCreateAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + AliasActions aliasAction = new AliasActions(ADD).indices(SONG_INDEX_NAME).alias(TEMPORARY_ALIAS_NAME); + IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); + + var response = restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, clusterContainsDocument(TEMPORARY_ALIAS_NAME, ID_S1)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_aliases")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_READ_USER, "IndicesAliasesRequest")); + auditLogsRule.assertExactly(2, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_READ_USER)); + } + + @Test + public void shouldCreateAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + AliasActions aliasAction = new AliasActions(ADD).indices(PROHIBITED_SONG_INDEX_NAME).alias(TEMPORARY_ALIAS_NAME); + IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); + + assertThatThrownBy( + () -> restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT), + statusException(FORBIDDEN) + ); + + assertThat(internalClient, not(clusterContainsDocument(TEMPORARY_ALIAS_NAME, ID_P4))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_aliases")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "IndicesAliasesRequest")); + } + + @Test + public void shouldDeleteAlias_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + AliasActions aliasAction = new AliasActions(ADD).indices(SONG_INDEX_NAME).alias(TEMPORARY_ALIAS_NAME); + IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); + restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT); + aliasAction = new AliasActions(REMOVE).indices(SONG_INDEX_NAME).alias(TEMPORARY_ALIAS_NAME); + indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); + + var response = restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, not(clusterContainsDocument(TEMPORARY_ALIAS_NAME, ID_S1))); + } + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_aliases")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_READ_USER, "IndicesAliasesRequest")); + auditLogsRule.assertExactly(4, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_READ_USER)); + } + + @Test + public void shouldDeleteAlias_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + AliasActions aliasAction = new AliasActions(REMOVE).indices(PROHIBITED_SONG_INDEX_NAME).alias(PROHIBITED_SONG_ALIAS); + IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest().addAliasAction(aliasAction); + + assertThatThrownBy( + () -> restHighLevelClient.indices().updateAliases(indicesAliasesRequest, DEFAULT), + statusException(FORBIDDEN) + ); + + assertThat(internalClient, clusterContainsDocument(PROHIBITED_SONG_INDEX_NAME, ID_P4)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(POST, "/_aliases")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "IndicesAliasesRequest")); + } + + @Test + public void shouldCreateIndexTemplate_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + PutIndexTemplateRequest request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE).patterns(List.of(TEMPLATE_INDEX_PREFIX)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002)); + + var response = restHighLevelClient.indices().putTemplate(request, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE)); + String documentId = "0001"; + IndexRequest indexRequest = new IndexRequest(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ).id(documentId) + .source(SONGS[0].asMap()) + .setRefreshPolicy(IMMEDIATE); + restHighLevelClient.index(indexRequest, DEFAULT); + assertThat(internalClient, clusterContainsDocument(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ, documentId)); + assertThat(internalClient, clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, documentId)); + assertThat(internalClient, clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002, documentId)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_template/musical-index-template")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/song-transcription-jazz/_doc/0001")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutIndexTemplateRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "IndexRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(8, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + } + + @Test + public void shouldCreateIndexTemplate_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + PutIndexTemplateRequest request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE).patterns(List.of(TEMPLATE_INDEX_PREFIX)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002)); + + assertThatThrownBy(() -> restHighLevelClient.indices().putTemplate(request, DEFAULT), statusException(FORBIDDEN)); + assertThat(internalClient, not(clusterContainTemplate(MUSICAL_INDEX_TEMPLATE))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(PUT, "/_template/musical-index-template")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "PutIndexTemplateRequest")); + } + + @Test + public void shouldDeleteTemplate_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + PutIndexTemplateRequest request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE).patterns(List.of(TEMPLATE_INDEX_PREFIX)); + restHighLevelClient.indices().putTemplate(request, DEFAULT); + assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE)); + DeleteIndexTemplateRequest deleteRequest = new DeleteIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE); + + var response = restHighLevelClient.indices().deleteTemplate(deleteRequest, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, not(clusterContainTemplate(MUSICAL_INDEX_TEMPLATE))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_template/musical-index-template")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(DELETE, "/_template/musical-index-template")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutIndexTemplateRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "DeleteIndexTemplateRequest")); + auditLogsRule.assertExactly(4, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + } + + @Test + public void shouldDeleteTemplate_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + DeleteIndexTemplateRequest deleteRequest = new DeleteIndexTemplateRequest(UNDELETABLE_TEMPLATE_NAME); + + assertThatThrownBy(() -> restHighLevelClient.indices().deleteTemplate(deleteRequest, DEFAULT), statusException(FORBIDDEN)); + + assertThat(internalClient, clusterContainTemplate(UNDELETABLE_TEMPLATE_NAME)); + } + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_READ_USER).withRestRequest(DELETE, "/_template/undeletable-template-name") + ); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "DeleteIndexTemplateRequest")); + } + + @Test + public void shouldUpdateTemplate_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + PutIndexTemplateRequest request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE).patterns(List.of(TEMPLATE_INDEX_PREFIX)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002)); + restHighLevelClient.indices().putTemplate(request, DEFAULT); + assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE)); + request = new PutIndexTemplateRequest(MUSICAL_INDEX_TEMPLATE).patterns(List.of(TEMPLATE_INDEX_PREFIX)) + .alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003)); + + var response = restHighLevelClient.indices().putTemplate(request, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + String documentId = "000one"; + IndexRequest indexRequest = new IndexRequest(INDEX_NAME_SONG_TRANSCRIPTION_JAZZ).id(documentId) + .source(SONGS[0].asMap()) + .setRefreshPolicy(IMMEDIATE); + restHighLevelClient.index(indexRequest, DEFAULT); + assertThat(internalClient, clusterContainTemplate(MUSICAL_INDEX_TEMPLATE)); + assertThat(internalClient, clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003, documentId)); + assertThat(internalClient, not(clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0001, documentId))); + assertThat(internalClient, not(clusterContainsDocument(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0002, documentId))); + } + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_template/musical-index-template")); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/song-transcription-jazz/_doc/000one")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutIndexTemplateRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "IndexRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(10, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + } + + @Test + public void shouldUpdateTemplate_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + PutIndexTemplateRequest request = new PutIndexTemplateRequest(UNDELETABLE_TEMPLATE_NAME).patterns( + List.of(TEMPLATE_INDEX_PREFIX) + ).alias(new Alias(ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003)); + + assertThatThrownBy(() -> restHighLevelClient.indices().putTemplate(request, DEFAULT), statusException(FORBIDDEN)); + assertThat(internalClient, clusterContainTemplateWithAlias(UNDELETABLE_TEMPLATE_NAME, ALIAS_FROM_UNDELETABLE_TEMPLATE)); + assertThat( + internalClient, + not(clusterContainTemplateWithAlias(UNDELETABLE_TEMPLATE_NAME, ALIAS_USED_IN_MUSICAL_INDEX_TEMPLATE_0003)) + ); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(PUT, "/_template/undeletable-template-name")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "PutIndexTemplateRequest")); + } + + @Test + public void shouldGetFieldCapabilitiesForAllIndexes_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().fields(FIELD_TITLE); + + FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response, containsExactlyIndices(SONG_INDEX_NAME, PROHIBITED_SONG_INDEX_NAME, UPDATE_DELETE_OPERATION_INDEX_NAME)); + assertThat(response, numberOfFieldsIsEqualTo(1)); + assertThat(response, containsFieldWithNameAndType(FIELD_TITLE, "text")); + } + auditLogsRule.assertExactlyOne(userAuthenticated(ADMIN_USER).withRestRequest(GET, "/_field_caps")); + auditLogsRule.assertExactlyOne(grantedPrivilege(ADMIN_USER, "FieldCapabilitiesRequest")); + auditLogsRule.assertExactly(3, grantedPrivilege(ADMIN_USER, "FieldCapabilitiesIndexRequest")); + } + + @Test + public void shouldGetFieldCapabilitiesForAllIndexes_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().fields(FIELD_TITLE); + + assertThatThrownBy(() -> restHighLevelClient.fieldCaps(request, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/_field_caps")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "FieldCapabilitiesRequest")); + } + + @Test + public void shouldGetFieldCapabilitiesForParticularIndex_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(SONG_INDEX_NAME).fields(FIELD_TITLE); + + FieldCapabilitiesResponse response = restHighLevelClient.fieldCaps(request, DEFAULT); + + assertThat(response, notNullValue()); + assertThat(response, containsExactlyIndices(SONG_INDEX_NAME)); + assertThat(response, numberOfFieldsIsEqualTo(1)); + assertThat(response, containsFieldWithNameAndType(FIELD_TITLE, "text")); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/song_lyrics/_field_caps")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "FieldCapabilitiesRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_READ_USER, "FieldCapabilitiesIndexRequest")); + } + + @Test + public void shouldGetFieldCapabilitiesForParticularIndex_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices(PROHIBITED_SONG_INDEX_NAME).fields(FIELD_TITLE); + + assertThatThrownBy(() -> restHighLevelClient.fieldCaps(request, DEFAULT), statusException(FORBIDDEN)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(GET, "/prohibited_song_lyrics/_field_caps")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "FieldCapabilitiesRequest")); + } + + @Test + public void shouldCreateSnapshotRepository_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + String snapshotDirPath = cluster.getSnapshotDirPath(); + + var response = steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotDirPath, "fs"); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + } + + @Test + public void shouldCreateSnapshotRepository_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + String snapshotDirPath = cluster.getSnapshotDirPath(); + + assertThatThrownBy( + () -> steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotDirPath, "fs"), + statusException(FORBIDDEN) + ); + assertThat(internalClient, not(clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_READ_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "PutRepositoryRequest")); + } + + @Test + public void shouldDeleteSnapshotRepository_positive() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + assertThat(internalClient, clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME)); + + var response = steps.deleteSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME); + + assertThat(response, notNullValue()); + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, not(clusterContainsSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_WRITE_USER).withRestRequest(DELETE, "/_snapshot/test-snapshot-repository") + ); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "DeleteRepositoryRequest")); + } + + @Test + public void shouldDeleteSnapshotRepository_negative() throws IOException { + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + + assertThatThrownBy(() -> steps.deleteSnapshotRepository(UNUSED_SNAPSHOT_REPOSITORY_NAME), statusException(FORBIDDEN)); + assertThat(internalClient, clusterContainsSnapshotRepository(UNUSED_SNAPSHOT_REPOSITORY_NAME)); + } + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_READ_USER).withRestRequest(DELETE, "/_snapshot/unused-snapshot-repository") + ); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "DeleteRepositoryRequest")); + } + + @Test // Bug which can be reproduced with the below test: https://github.com/opensearch-project/security/issues/2169 + public void shouldCreateSnapshot_positive() throws IOException { + final String snapshotName = "snapshot-positive-test"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + + CreateSnapshotResponse response = steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, SONG_INDEX_NAME); + + assertThat(response, notNullValue()); + assertThat(response.status(), equalTo(RestStatus.ACCEPTED)); + steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + assertThat(internalClient, clusterContainSuccessSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/snapshot-positive-test") + ); + auditLogsRule.assertAtLeast( + 1, + userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/snapshot-positive-test") + ); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); + } + + @Test + public void shouldCreateSnapshot_negative() throws IOException { + final String snapshotName = "snapshot-negative-test"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + + assertThatThrownBy( + () -> steps.createSnapshot(UNUSED_SNAPSHOT_REPOSITORY_NAME, snapshotName, SONG_INDEX_NAME), + statusException(FORBIDDEN) + ); + + assertThat(internalClient, snapshotInClusterDoesNotExists(UNUSED_SNAPSHOT_REPOSITORY_NAME, snapshotName)); + } + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_READ_USER).withRestRequest(PUT, "/_snapshot/unused-snapshot-repository/snapshot-negative-test") + ); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_READ_USER, "CreateSnapshotRequest")); + } + + @Test + public void shouldDeleteSnapshot_positive() throws IOException { + String snapshotName = "delete-snapshot-positive"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + restHighLevelClient.snapshot(); + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, SONG_INDEX_NAME); + steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + + var response = steps.deleteSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + + assertThat(response.isAcknowledged(), equalTo(true)); + assertThat(internalClient, snapshotInClusterDoesNotExists(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/delete-snapshot-positive") + ); + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_WRITE_USER).withRestRequest(DELETE, "/_snapshot/test-snapshot-repository/delete-snapshot-positive") + ); + auditLogsRule.assertAtLeast( + 1, + userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/delete-snapshot-positive") + ); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "DeleteSnapshotRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); + } + + @Test + public void shouldDeleteSnapshot_negative() throws IOException { + String snapshotName = "delete-snapshot-negative"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, SONG_INDEX_NAME); + steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + } + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + assertThatThrownBy(() -> steps.deleteSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName), statusException(FORBIDDEN)); + + assertThat(internalClient, clusterContainSuccessSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName)); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/delete-snapshot-negative") + ); + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_READ_USER).withRestRequest(DELETE, "/_snapshot/test-snapshot-repository/delete-snapshot-negative") + ); + auditLogsRule.assertAtLeast( + 1, + userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/delete-snapshot-negative") + ); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); + auditLogsRule.assertExactly(1, missingPrivilege(LIMITED_READ_USER, "DeleteSnapshotRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); + } + + @Test + public void shouldRestoreSnapshot_positive() throws IOException { + final String snapshotName = "restore-snapshot-positive"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + // 1. create some documents + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1].asMap())); + bulkRequest.setRefreshPolicy(IMMEDIATE); + restHighLevelClient.bulk(bulkRequest, DEFAULT); + + // 2. create snapshot repository + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + + // 3. create snapshot + steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, WRITE_SONG_INDEX_NAME); + + // 4. wait till snapshot is ready + steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + + // 5. introduce some changes + bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Drei").source(SONGS[2].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Vier").source(SONGS[3].asMap())); + bulkRequest.add(new DeleteRequest(WRITE_SONG_INDEX_NAME, "Eins")); + bulkRequest.setRefreshPolicy(IMMEDIATE); + restHighLevelClient.bulk(bulkRequest, DEFAULT); + + // 6. restore the snapshot + var response = steps.restoreSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, "(.+)", "restored_$1"); + + assertThat(response, notNullValue()); + assertThat(response.status(), equalTo(ACCEPTED)); + + // 7. wait until snapshot is restored + CountRequest countRequest = new CountRequest(RESTORED_SONG_INDEX_NAME); + Awaitility.await() + .ignoreExceptions() + .alias("Index contains proper number of documents restored from snapshot.") + .until(() -> restHighLevelClient.count(countRequest, DEFAULT).getCount() == 2); + + // 8. verify that document are present in restored index + assertThat( + internalClient, + clusterContainsDocumentWithFieldValue(RESTORED_SONG_INDEX_NAME, "Eins", FIELD_TITLE, TITLE_MAGNUM_OPUS) + ); + assertThat( + internalClient, + clusterContainsDocumentWithFieldValue(RESTORED_SONG_INDEX_NAME, "Zwei", FIELD_TITLE, TITLE_SONG_1_PLUS_1) + ); + assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Drei"))); + assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Vier"))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository/restore-snapshot-positive") + ); + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_WRITE_USER).withRestRequest( + POST, + "/_snapshot/test-snapshot-repository/restore-snapshot-positive/_restore" + ) + ); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/restored_write_song_index/_count")); + auditLogsRule.assertExactly(2, userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertAtLeast( + 1, + userAuthenticated(LIMITED_WRITE_USER).withRestRequest(GET, "/_snapshot/test-snapshot-repository/restore-snapshot-positive") + ); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "RestoreSnapshotRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "SearchRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + } + + @Test + public void shouldRestoreSnapshot_failureForbiddenIndex() throws IOException { + final String snapshotName = "restore-snapshot-negative-forbidden-index"; + String restoreToIndex = "forbidden_index"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + // 1. create some documents + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1].asMap())); + bulkRequest.setRefreshPolicy(IMMEDIATE); + restHighLevelClient.bulk(bulkRequest, DEFAULT); + + // 2. create snapshot repository + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + + // 3. create snapshot + steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, WRITE_SONG_INDEX_NAME); + + // 4. wait till snapshot is ready + steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + + // 5. restore the snapshot + assertThatThrownBy( + () -> steps.restoreSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, "(.+)", restoreToIndex), + statusException(FORBIDDEN) + ); + + // 6. verify that document are not present in restored index + assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Eins"))); + assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Zwei"))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_WRITE_USER).withRestRequest( + PUT, + "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-index" + ) + ); + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_WRITE_USER).withRestRequest( + POST, + "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-index/_restore" + ) + ); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertAtLeast( + 1, + userAuthenticated(LIMITED_WRITE_USER).withRestRequest( + GET, + "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-index" + ) + ); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "RestoreSnapshotRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + auditLogsRule.assertExactlyOne(missingPrivilege(LIMITED_WRITE_USER, "RestoreSnapshotRequest")); + } + + @Test + public void shouldRestoreSnapshot_failureOperationForbidden() throws IOException { + String snapshotName = "restore-snapshot-negative-forbidden-operation"; + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_WRITE_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + // 1. create some documents + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Eins").source(SONGS[0].asMap())); + bulkRequest.add(new IndexRequest(WRITE_SONG_INDEX_NAME).id("Zwei").source(SONGS[1].asMap())); + bulkRequest.setRefreshPolicy(IMMEDIATE); + restHighLevelClient.bulk(bulkRequest, DEFAULT); + + // 2. create snapshot repository + steps.createSnapshotRepository(TEST_SNAPSHOT_REPOSITORY_NAME, cluster.getSnapshotDirPath(), "fs"); + + // 3. create snapshot + steps.createSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, WRITE_SONG_INDEX_NAME); + + // 4. wait till snapshot is ready + steps.waitForSnapshotCreation(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName); + } + // 5. restore the snapshot + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_READ_USER)) { + SnapshotSteps steps = new SnapshotSteps(restHighLevelClient); + assertThatThrownBy( + () -> steps.restoreSnapshot(TEST_SNAPSHOT_REPOSITORY_NAME, snapshotName, "(.+)", "restored_$1"), + statusException(FORBIDDEN) + ); + + // 6. verify that documents does not exist + assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Eins"))); + assertThat(internalClient, not(clusterContainsDocument(RESTORED_SONG_INDEX_NAME, "Zwei"))); + } + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(PUT, "/_snapshot/test-snapshot-repository")); + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_WRITE_USER).withRestRequest( + PUT, + "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-operation" + ) + ); + auditLogsRule.assertExactlyOne( + userAuthenticated(LIMITED_READ_USER).withRestRequest( + POST, + "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-operation/_restore" + ) + ); + auditLogsRule.assertExactlyOne(userAuthenticated(LIMITED_WRITE_USER).withRestRequest(POST, "/_bulk")); + auditLogsRule.assertAtLeast( + 1, + userAuthenticated(LIMITED_WRITE_USER).withRestRequest( + GET, + "/_snapshot/test-snapshot-repository/restore-snapshot-negative-forbidden-operation" + ) + ); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "PutRepositoryRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateSnapshotRequest")); + auditLogsRule.assertExactlyOne(grantedPrivilege(LIMITED_WRITE_USER, "BulkRequest")); + auditLogsRule.assertExactly(2, grantedPrivilege(LIMITED_WRITE_USER, "CreateIndexRequest")); + auditLogsRule.assertExactly(4, grantedPrivilege(LIMITED_WRITE_USER, "PutMappingRequest")); + auditLogsRule.assertExactly(1, missingPrivilege(LIMITED_READ_USER, "RestoreSnapshotRequest")); + auditLogsRule.assertAtLeast(2, grantedPrivilege(LIMITED_WRITE_USER, "GetSnapshotsRequest")); + auditLogsRule.assertExactly(6, auditPredicate(INDEX_EVENT).withEffectiveUser(LIMITED_WRITE_USER)); + } + + @Test + // required permissions: "indices:admin/create" + public void createIndex_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("create_index_positive"); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName); + CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, DEFAULT); + + assertThat(createIndexResponse, isSuccessfulCreateIndexResponse(indexName)); + assertThat(cluster, indexExists(indexName)); + } + } + + @Test + public void createIndex_negative() throws IOException { + String indexName = "create_index_negative"; + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName); + + assertThatThrownBy(() -> restHighLevelClient.indices().create(createIndexRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(indexName))); + } + } + + @Test + // required permissions: "indices:admin/get" + public void checkIfIndexExists_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("index_exists_positive"); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + boolean exists = restHighLevelClient.indices().exists(new GetIndexRequest(indexName), DEFAULT); + + assertThat(exists, is(false)); + } + } + + @Test + public void checkIfIndexExists_negative() throws IOException { + String indexThatUserHasNoAccessTo = "index_exists_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + assertThatThrownBy( + () -> restHighLevelClient.indices().exists(new GetIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices() + .exists(new GetIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> restHighLevelClient.indices().exists(new GetIndexRequest("*"), DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + // required permissions: "indices:admin/delete" + public void deleteIndex_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("delete_index_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName); + var response = restHighLevelClient.indices().delete(deleteIndexRequest, DEFAULT); + + assertThat(response.isAcknowledged(), is(true)); + assertThat(cluster, not(indexExists(indexName))); + } + } + + @Test + public void deleteIndex_negative() throws IOException { + String indexThatUserHasNoAccessTo = "delete_index_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + + assertThatThrownBy( + () -> restHighLevelClient.indices().delete(new DeleteIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices() + .delete(new DeleteIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices().delete(new DeleteIndexRequest("*"), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + // required permissions: indices:admin/aliases, indices:admin/delete + public void shouldDeleteIndexByAliasRequest_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("delete_index_by_alias_request_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + IndicesAliasesRequest request = new IndicesAliasesRequest().addAliasAction(new AliasActions(REMOVE_INDEX).indices(indexName)); + + var response = restHighLevelClient.indices().updateAliases(request, DEFAULT); + + assertThat(response.isAcknowledged(), is(true)); + assertThat(cluster, not(indexExists(indexName))); + } + auditLogsRule.assertExactlyOne( + userAuthenticated(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES).withRestRequest(POST, "/_aliases") + ); + auditLogsRule.assertExactly( + 2, + grantedPrivilege(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES, "IndicesAliasesRequest") + ); + auditLogsRule.assertExactly( + 2, + auditPredicate(INDEX_EVENT).withEffectiveUser(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES) + ); + } + + @Test + public void shouldDeleteIndexByAliasRequest_negative() throws IOException { + String indexName = "delete_index_by_alias_request_negative"; + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + IndicesAliasesRequest request = new IndicesAliasesRequest().addAliasAction(new AliasActions(REMOVE_INDEX).indices(indexName)); + + assertThatThrownBy(() -> restHighLevelClient.indices().updateAliases(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + // required permissions: "indices:admin/get" + public void getIndex_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("get_index_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + GetIndexRequest getIndexRequest = new GetIndexRequest(indexName); + GetIndexResponse response = restHighLevelClient.indices().get(getIndexRequest, DEFAULT); + + assertThat(response, getIndexResponseContainsIndices(indexName)); + } + } + + @Test + public void getIndex_negative() throws IOException { + String indexThatUserHasNoAccessTo = "get_index_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + + assertThatThrownBy( + () -> restHighLevelClient.indices().get(new GetIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices().get(new GetIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> restHighLevelClient.indices().get(new GetIndexRequest("*"), DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + // required permissions: "indices:admin/close", "indices:admin/close*" + public void closeIndex_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("close_index_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + CloseIndexRequest closeIndexRequest = new CloseIndexRequest(indexName); + CloseIndexResponse response = restHighLevelClient.indices().close(closeIndexRequest, DEFAULT); + + assertThat(response, isSuccessfulCloseIndexResponse()); + assertThat(cluster, indexStateIsEqualTo(indexName, IndexMetadata.State.CLOSE)); + } + } + + @Test + public void closeIndex_negative() throws IOException { + String indexThatUserHasNoAccessTo = "close_index_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + + assertThatThrownBy( + () -> restHighLevelClient.indices().close(new CloseIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices() + .close(new CloseIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> restHighLevelClient.indices().close(new CloseIndexRequest("*"), DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + // required permissions: "indices:admin/open" + public void openIndex_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("open_index_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + IndexOperationsHelper.closeIndex(cluster, indexName); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + OpenIndexRequest closeIndexRequest = new OpenIndexRequest(indexName); + OpenIndexResponse response = restHighLevelClient.indices().open(closeIndexRequest, DEFAULT); + + assertThat(response, isSuccessfulOpenIndexResponse()); + assertThat(cluster, indexStateIsEqualTo(indexName, IndexMetadata.State.OPEN)); + } + } + + @Test + public void openIndex_negative() throws IOException { + String indexThatUserHasNoAccessTo = "open_index_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + + assertThatThrownBy( + () -> restHighLevelClient.indices().open(new OpenIndexRequest(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices() + .open(new OpenIndexRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy(() -> restHighLevelClient.indices().open(new OpenIndexRequest("*"), DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + @Ignore + // required permissions: "indices:admin/resize", "indices:monitor/stats + // todo even when I assign the `indices:admin/resize` and `indices:monitor/stats` permissions to test user, this test fails. + // Issue: https://github.com/opensearch-project/security/issues/2141 + public void shrinkIndex_positive() throws IOException { + String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("shrink_index_positive_source"); + Settings sourceIndexSettings = Settings.builder().put("index.blocks.write", true).put("index.number_of_shards", 2).build(); + String targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("shrink_index_positive_target"); + IndexOperationsHelper.createIndex(cluster, sourceIndexName, sourceIndexSettings); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + ResizeResponse response = restHighLevelClient.indices().shrink(resizeRequest, DEFAULT); + + assertThat(response, isSuccessfulResizeResponse(targetIndexName)); + assertThat(cluster, indexExists(targetIndexName)); + } + } + + @Test + public void shrinkIndex_negative() throws IOException { + // user cannot access target index + String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("shrink_index_negative_source"); + String targetIndexName = "shrink_index_negative_target"; + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + + assertThatThrownBy(() -> restHighLevelClient.indices().shrink(resizeRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(targetIndexName))); + } + + // user cannot access source index + sourceIndexName = "shrink_index_negative_source"; + targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("shrink_index_negative_target"); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + + assertThatThrownBy(() -> restHighLevelClient.indices().shrink(resizeRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(targetIndexName))); + } + } + + @Test + @Ignore + // required permissions: "indices:admin/resize", "indices:monitor/stats + // todo even when I assign the `indices:admin/resize` and `indices:monitor/stats` permissions to test user, this test fails. + // Issue: https://github.com/opensearch-project/security/issues/2141 + public void cloneIndex_positive() throws IOException { + String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clone_index_positive_source"); + Settings sourceIndexSettings = Settings.builder().put("index.blocks.write", true).build(); + String targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clone_index_positive_target"); + IndexOperationsHelper.createIndex(cluster, sourceIndexName, sourceIndexSettings); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + ResizeResponse response = restHighLevelClient.indices().clone(resizeRequest, DEFAULT); + + assertThat(response, isSuccessfulResizeResponse(targetIndexName)); + assertThat(cluster, indexExists(targetIndexName)); + } + } + + @Test + public void cloneIndex_negative() throws IOException { + // user cannot access target index + String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clone_index_negative_source"); + String targetIndexName = "clone_index_negative_target"; + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + + assertThatThrownBy(() -> restHighLevelClient.indices().clone(resizeRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(targetIndexName))); + } + + // user cannot access source index + sourceIndexName = "clone_index_negative_source"; + targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clone_index_negative_target"); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + + assertThatThrownBy(() -> restHighLevelClient.indices().clone(resizeRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(targetIndexName))); + } + } + + @Test + @Ignore + // required permissions: "indices:admin/resize", "indices:monitor/stats + // todo even when I assign the `indices:admin/resize` and `indices:monitor/stats` permissions to test user, this test fails. + // Issue: https://github.com/opensearch-project/security/issues/2141 + public void splitIndex_positive() throws IOException { + String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("split_index_positive_source"); + Settings sourceIndexSettings = Settings.builder().put("index.blocks.write", true).build(); + String targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("split_index_positive_target"); + IndexOperationsHelper.createIndex(cluster, sourceIndexName, sourceIndexSettings); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + resizeRequest.setSettings(Settings.builder().put("index.number_of_shards", 2).build()); + ResizeResponse response = restHighLevelClient.indices().split(resizeRequest, DEFAULT); + + assertThat(response, isSuccessfulResizeResponse(targetIndexName)); + assertThat(cluster, indexExists(targetIndexName)); + } + } + + @Test + public void splitIndex_negative() throws IOException { + // user cannot access target index + String sourceIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("split_index_negative_source"); + String targetIndexName = "split_index_negative_target"; + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + resizeRequest.setSettings(Settings.builder().put("index.number_of_shards", 2).build()); + + assertThatThrownBy(() -> restHighLevelClient.indices().split(resizeRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(targetIndexName))); + } + + // user cannot access source index + sourceIndexName = "split_index_negative_source"; + targetIndexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("split_index_negative_target"); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + ResizeRequest resizeRequest = new ResizeRequest(targetIndexName, sourceIndexName); + resizeRequest.setSettings(Settings.builder().put("index.number_of_shards", 2).build()); + + assertThatThrownBy(() -> restHighLevelClient.indices().split(resizeRequest, DEFAULT), statusException(FORBIDDEN)); + assertThat(cluster, not(indexExists(targetIndexName))); + } + } + + @Test + // required permissions: "indices:monitor/settings/get" + public void getIndexSettings_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("get_index_settings_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + GetSettingsRequest getSettingsRequest = new GetSettingsRequest().indices(indexName); + GetSettingsResponse response = restHighLevelClient.indices().getSettings(getSettingsRequest, DEFAULT); + + assertThat(response, getSettingsResponseContainsIndices(indexName)); + } + } + + @Test + public void getIndexSettings_negative() throws IOException { + String indexThatUserHasNoAccessTo = "get_index_settings_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + assertThatThrownBy( + () -> restHighLevelClient.indices().getSettings(new GetSettingsRequest().indices(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices() + .getSettings(new GetSettingsRequest().indices(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices().getSettings(new GetSettingsRequest().indices("*"), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + // required permissions: "indices:admin/settings/update" + public void updateIndexSettings_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("update_index_settings_positive"); + Settings initialSettings = Settings.builder().put("index.number_of_replicas", "2").build(); + Settings updatedSettings = Settings.builder().put("index.number_of_replicas", "4").build(); + IndexOperationsHelper.createIndex(cluster, indexName, initialSettings); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + UpdateSettingsRequest updateSettingsRequest = new UpdateSettingsRequest(indexName).settings(updatedSettings); + var response = restHighLevelClient.indices().putSettings(updateSettingsRequest, DEFAULT); + + assertThat(response.isAcknowledged(), is(true)); + assertThat(cluster, indexSettingsContainValues(indexName, updatedSettings)); + } + } + + @Test + public void updateIndexSettings_negative() throws IOException { + String indexThatUserHasNoAccessTo = "update_index_settings_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + Settings settingsToUpdate = Settings.builder().put("index.number_of_replicas", 2).build(); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + + assertThatThrownBy( + () -> restHighLevelClient.indices() + .putSettings(new UpdateSettingsRequest(indexThatUserHasNoAccessTo).settings(settingsToUpdate), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices() + .putSettings( + new UpdateSettingsRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo).settings(settingsToUpdate), + DEFAULT + ), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices().putSettings(new UpdateSettingsRequest("*").settings(settingsToUpdate), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + // required permissions: indices:admin/mapping/put + public void createIndexMappings_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("create_index_mappings_positive"); + Map indexMapping = Map.of("properties", Map.of("message", Map.of("type", "text"))); + IndexOperationsHelper.createIndex(cluster, indexName); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + PutMappingRequest putMappingRequest = new PutMappingRequest(indexName).source(indexMapping); + var response = restHighLevelClient.indices().putMapping(putMappingRequest, DEFAULT); + + assertThat(response.isAcknowledged(), is(true)); + assertThat(cluster, indexMappingIsEqualTo(indexName, indexMapping)); + } + } + + @Test + public void createIndexMappings_negative() throws IOException { + String indexThatUserHasNoAccessTo = "create_index_mappings_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + Map indexMapping = Map.of("properties", Map.of("message", Map.of("type", "text"))); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + + assertThatThrownBy( + () -> restHighLevelClient.indices() + .putMapping(new PutMappingRequest(indexThatUserHasNoAccessTo).source(indexMapping), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices() + .putMapping(new PutMappingRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo).source(indexMapping), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices().putMapping(new PutMappingRequest("*").source(indexMapping), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + // required permissions: indices:admin/mappings/get + public void getIndexMappings_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("get_index_mappings_positive"); + Map indexMapping = Map.of("properties", Map.of("message", Map.of("type", "text"))); + IndexOperationsHelper.createIndex(cluster, indexName); + IndexOperationsHelper.createMapping(cluster, indexName, indexMapping); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + GetMappingsRequest getMappingsRequest = new GetMappingsRequest().indices(indexName); + GetMappingsResponse response = restHighLevelClient.indices().getMapping(getMappingsRequest, DEFAULT); + + assertThat(response, getMappingsResponseContainsIndices(indexName)); + } + } + + @Test + public void getIndexMappings_negative() throws IOException { + String indexThatUserHasNoAccessTo = "get_index_mappings_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + + assertThatThrownBy( + () -> restHighLevelClient.indices().getMapping(new GetMappingsRequest().indices(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices() + .getMapping(new GetMappingsRequest().indices(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices().getMapping(new GetMappingsRequest().indices("*"), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + // required permissions: "indices:admin/cache/clear" + public void clearIndexCache_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("clear_index_cache_positive"); + IndexOperationsHelper.createIndex(cluster, indexName); + + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + ClearIndicesCacheRequest clearIndicesCacheRequest = new ClearIndicesCacheRequest(indexName); + ClearIndicesCacheResponse response = restHighLevelClient.indices().clearCache(clearIndicesCacheRequest, DEFAULT); + + assertThat(response, isSuccessfulClearIndicesCacheResponse()); + } + } + + @Test + public void clearIndexCache_negative() throws IOException { + String indexThatUserHasNoAccessTo = "clear_index_cache_negative"; + String indexThatUserHasAccessTo = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat(indexThatUserHasNoAccessTo); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + + assertThatThrownBy( + () -> restHighLevelClient.indices().clearCache(new ClearIndicesCacheRequest(indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices() + .clearCache(new ClearIndicesCacheRequest(indexThatUserHasAccessTo, indexThatUserHasNoAccessTo), DEFAULT), + statusException(FORBIDDEN) + ); + assertThatThrownBy( + () -> restHighLevelClient.indices().clearCache(new ClearIndicesCacheRequest("*"), DEFAULT), + statusException(FORBIDDEN) + ); + } + } + + @Test + // required permissions: "indices:admin/create", "indices:admin/aliases" + public void shouldCreateIndexWithAlias_positive() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("create_index_with_alias_positive"); + try ( + RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient( + USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES + ) + ) { + CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName).alias( + new Alias(ALIAS_CREATE_INDEX_WITH_ALIAS_POSITIVE) + ); + + CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, DEFAULT); + + assertThat(createIndexResponse, isSuccessfulCreateIndexResponse(indexName)); + assertThat(cluster, indexExists(indexName)); + assertThat(internalClient, aliasExists(ALIAS_CREATE_INDEX_WITH_ALIAS_POSITIVE)); + } + auditLogsRule.assertExactlyOne( + userAuthenticated(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES).withRestRequest( + PUT, + "/index_operations_create_index_with_alias_positive" + ) + ); + auditLogsRule.assertExactly( + 2, + grantedPrivilege(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES, "CreateIndexRequest") + ); + auditLogsRule.assertExactly( + 2, + auditPredicate(INDEX_EVENT).withEffectiveUser(USER_ALLOWED_TO_PERFORM_INDEX_OPERATIONS_ON_SELECTED_INDICES) + ); + } + + @Test + public void shouldCreateIndexWithAlias_negative() throws IOException { + String indexName = INDICES_ON_WHICH_USER_CAN_PERFORM_INDEX_OPERATIONS_PREFIX.concat("create_index_with_alias_negative"); + try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(USER_ALLOWED_TO_CREATE_INDEX)) { + CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName).alias( + new Alias(ALIAS_CREATE_INDEX_WITH_ALIAS_NEGATIVE) + ); + + assertThatThrownBy(() -> restHighLevelClient.indices().create(createIndexRequest, DEFAULT), statusException(FORBIDDEN)); + + assertThat(internalClient, not(aliasExists(ALIAS_CREATE_INDEX_WITH_ALIAS_NEGATIVE))); + } + auditLogsRule.assertExactlyOne( + userAuthenticated(USER_ALLOWED_TO_CREATE_INDEX).withRestRequest(PUT, "/index_operations_create_index_with_alias_negative") + ); + auditLogsRule.assertExactlyOne(missingPrivilege(USER_ALLOWED_TO_CREATE_INDEX, "CreateIndexRequest")); + } } diff --git a/src/integrationTest/java/org/opensearch/security/SecurityAdminLauncher.java b/src/integrationTest/java/org/opensearch/security/SecurityAdminLauncher.java index 0cd8b23f5d..164b2cb714 100644 --- a/src/integrationTest/java/org/opensearch/security/SecurityAdminLauncher.java +++ b/src/integrationTest/java/org/opensearch/security/SecurityAdminLauncher.java @@ -18,24 +18,30 @@ class SecurityAdminLauncher { - private final TestCertificates certificates; - private int port; - - public SecurityAdminLauncher(int port, TestCertificates certificates) { - this.port = port; - this.certificates = requireNonNull(certificates, "Certificates are required to communicate with cluster."); - } - - public int updateRoleMappings(File roleMappingsConfigurationFile) throws Exception { - String[] commandLineArguments = {"-cacert", certificates.getRootCertificate().getAbsolutePath(), - "-cert", certificates.getAdminCertificate().getAbsolutePath(), - "-key", certificates.getAdminKey(null).getAbsolutePath(), - "-nhnv", - "-p", String.valueOf(port), - "-f", roleMappingsConfigurationFile.getAbsolutePath(), - "-t", "rolesmapping" - }; - - return SecurityAdmin.execute(commandLineArguments); - } + private final TestCertificates certificates; + private int port; + + public SecurityAdminLauncher(int port, TestCertificates certificates) { + this.port = port; + this.certificates = requireNonNull(certificates, "Certificates are required to communicate with cluster."); + } + + public int updateRoleMappings(File roleMappingsConfigurationFile) throws Exception { + String[] commandLineArguments = { + "-cacert", + certificates.getRootCertificate().getAbsolutePath(), + "-cert", + certificates.getAdminCertificate().getAbsolutePath(), + "-key", + certificates.getAdminKey(null).getAbsolutePath(), + "-nhnv", + "-p", + String.valueOf(port), + "-f", + roleMappingsConfigurationFile.getAbsolutePath(), + "-t", + "rolesmapping" }; + + return SecurityAdmin.execute(commandLineArguments); + } } diff --git a/src/integrationTest/java/org/opensearch/security/SecurityConfigurationTests.java b/src/integrationTest/java/org/opensearch/security/SecurityConfigurationTests.java index 9d86266f8e..b35495e23e 100644 --- a/src/integrationTest/java/org/opensearch/security/SecurityConfigurationTests.java +++ b/src/integrationTest/java/org/opensearch/security/SecurityConfigurationTests.java @@ -44,172 +44,189 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class SecurityConfigurationTests { - private static final User USER_ADMIN = new User("admin").roles(ALL_ACCESS); - private static final User LIMITED_USER = new User("limited-user") - .roles(new Role("limited-role").indexPermissions("indices:data/read/search", "indices:data/read/get").on("user-${user.name}")); - public static final String LIMITED_USER_INDEX = "user-" + LIMITED_USER.getName(); - public static final String ADDITIONAL_USER_1 = "additional00001"; - public static final String ADDITIONAL_PASSWORD_1 = "user 1 fair password"; - - public static final String ADDITIONAL_USER_2 = "additional2"; - public static final String ADDITIONAL_PASSWORD_2 = "user 2 fair password"; - public static final String CREATE_USER_BODY = "{\"password\": \"%s\",\"opendistro_security_roles\": []}"; - public static final String INTERNAL_USERS_RESOURCE = "_plugins/_security/api/internalusers/"; - public static final String ID_1 = "one"; - public static final String PROHIBITED_INDEX = "prohibited"; - public static final String ID_2 = "two"; - - @ClassRule - public static LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) - .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_ADMIN, LIMITED_USER).anonymousAuth(false) - .nodeSettings(Map.of(SECURITY_RESTAPI_ROLES_ENABLED, List.of("user_" + USER_ADMIN.getName() +"__" + ALL_ACCESS.getName()), - SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, true)) - .build(); - - @Rule - public TemporaryFolder configurationDirectory = new TemporaryFolder(); - - @BeforeClass - public static void initData() { - try(Client client = cluster.getInternalNodeClient()){ - client.prepareIndex(LIMITED_USER_INDEX).setId(ID_1).setRefreshPolicy(IMMEDIATE).setSource("foo", "bar").get(); - client.prepareIndex(PROHIBITED_INDEX).setId(ID_2).setRefreshPolicy(IMMEDIATE).setSource("three", "four").get(); - } - } - - @Test - public void shouldCreateUserViaRestApi_success() { - try(TestRestClient client = cluster.getRestClient(USER_ADMIN)) { - HttpResponse httpResponse = client.putJson(INTERNAL_USERS_RESOURCE + ADDITIONAL_USER_1, String.format(CREATE_USER_BODY, - ADDITIONAL_PASSWORD_1)); - - assertThat(httpResponse.getStatusCode(), equalTo(201)); - } - try(TestRestClient client = cluster.getRestClient(USER_ADMIN)) { - client.assertCorrectCredentials(USER_ADMIN.getName()); - } - try(TestRestClient client = cluster.getRestClient(ADDITIONAL_USER_1, ADDITIONAL_PASSWORD_1)) { - client.assertCorrectCredentials(ADDITIONAL_USER_1); - } - } - - @Test - public void shouldCreateUserViaRestApi_failure() { - try(TestRestClient client = cluster.getRestClient(LIMITED_USER)) { - HttpResponse httpResponse = client.putJson(INTERNAL_USERS_RESOURCE + ADDITIONAL_USER_1, String.format(CREATE_USER_BODY, - ADDITIONAL_PASSWORD_1)); - - httpResponse.assertStatusCode(403); - } - } - - @Test - public void shouldAuthenticateAsAdminWithCertificate_positive() { - try(TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { - HttpResponse httpResponse = client.get("/_plugins/_security/whoami"); - - httpResponse.assertStatusCode(200); - assertThat(httpResponse.getTextFromJsonBody("/is_admin"), equalTo("true")); - } - } - - @Test - public void shouldAuthenticateAsAdminWithCertificate_negativeSelfSignedCertificate() { - TestCertificates testCertificates = cluster.getTestCertificates(); - try(TestRestClient client = cluster.getRestClient(testCertificates.createSelfSignedCertificate("CN=bond"))) { - HttpResponse httpResponse = client.get("/_plugins/_security/whoami"); - - httpResponse.assertStatusCode(200); - assertThat(httpResponse.getTextFromJsonBody("/is_admin"), equalTo("false")); - } - } - - @Test - public void shouldAuthenticateAsAdminWithCertificate_negativeIncorrectDn() { - TestCertificates testCertificates = cluster.getTestCertificates(); - try(TestRestClient client = cluster.getRestClient(testCertificates.createAdminCertificate("CN=non_admin"))) { - HttpResponse httpResponse = client.get("/_plugins/_security/whoami"); - - httpResponse.assertStatusCode(200); - assertThat(httpResponse.getTextFromJsonBody("/is_admin"), equalTo("false")); - } - } - - @Test - public void shouldCreateUserViaRestApiWhenAdminIsAuthenticatedViaCertificate_positive() { - try(TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { - - HttpResponse httpResponse = client.putJson(INTERNAL_USERS_RESOURCE + ADDITIONAL_USER_2, String.format(CREATE_USER_BODY, - ADDITIONAL_PASSWORD_2)); - - httpResponse.assertStatusCode(201); - } - try(TestRestClient client = cluster.getRestClient(USER_ADMIN)) { - client.assertCorrectCredentials(USER_ADMIN.getName()); - } - try(TestRestClient client = cluster.getRestClient(ADDITIONAL_USER_2, ADDITIONAL_PASSWORD_2)) { - client.assertCorrectCredentials(ADDITIONAL_USER_2); - } - } - - @Test - public void shouldCreateUserViaRestApiWhenAdminIsAuthenticatedViaCertificate_negative() { - TestCertificates testCertificates = cluster.getTestCertificates(); - try(TestRestClient client = cluster.getRestClient(testCertificates.createSelfSignedCertificate("CN=attacker"))) { - HttpResponse httpResponse = client.putJson(INTERNAL_USERS_RESOURCE + ADDITIONAL_USER_2, String.format(CREATE_USER_BODY, - ADDITIONAL_PASSWORD_2)); - - httpResponse.assertStatusCode(401); - } - } - - @Test - public void shouldStillWorkAfterUpdateOfSecurityConfig() { - List users = new ArrayList<>(cluster.getConfiguredUsers()); - User newUser = new User("new-user"); - users.add(newUser); - - cluster.updateUserConfiguration(users); - - try(TestRestClient client = cluster.getRestClient(USER_ADMIN)) { - client.assertCorrectCredentials(USER_ADMIN.getName()); - } - try(TestRestClient client = cluster.getRestClient(newUser)) { - client.assertCorrectCredentials(newUser.getName()); - } - } - - @Test - public void shouldAccessIndexWithPlaceholder_positive() { - try(TestRestClient client = cluster.getRestClient(LIMITED_USER)) { - HttpResponse httpResponse = client.get("/" + LIMITED_USER_INDEX + "/_doc/" + ID_1); - - httpResponse.assertStatusCode(200); - } - } - - @Test - public void shouldAccessIndexWithPlaceholder_negative() { - try(TestRestClient client = cluster.getRestClient(LIMITED_USER)) { - HttpResponse httpResponse = client.get("/" + PROHIBITED_INDEX + "/_doc/" + ID_2); - - httpResponse.assertStatusCode(403); - } - } - - @Test - public void shouldUseSecurityAdminTool() throws Exception { - SecurityAdminLauncher securityAdminLauncher = new SecurityAdminLauncher(cluster.getHttpPort(), cluster.getTestCertificates()); - File rolesMapping = configurationDirectory.newFile("roles_mapping.yml"); - ConfigurationFiles.createRoleMappingFile(rolesMapping); - - int exitCode = securityAdminLauncher.updateRoleMappings(rolesMapping); - - assertThat(exitCode, equalTo(0)); - try(TestRestClient client = cluster.getRestClient(USER_ADMIN)) { - Awaitility.await().alias("Waiting for rolemapping 'readall' availability.") - .until(() -> client.get("_plugins/_security/api/rolesmapping/readall").getStatusCode(), equalTo(200)); - } - } + private static final User USER_ADMIN = new User("admin").roles(ALL_ACCESS); + private static final User LIMITED_USER = new User("limited-user").roles( + new Role("limited-role").indexPermissions("indices:data/read/search", "indices:data/read/get").on("user-${user.name}") + ); + public static final String LIMITED_USER_INDEX = "user-" + LIMITED_USER.getName(); + public static final String ADDITIONAL_USER_1 = "additional00001"; + public static final String ADDITIONAL_PASSWORD_1 = "user 1 fair password"; + + public static final String ADDITIONAL_USER_2 = "additional2"; + public static final String ADDITIONAL_PASSWORD_2 = "user 2 fair password"; + public static final String CREATE_USER_BODY = "{\"password\": \"%s\",\"opendistro_security_roles\": []}"; + public static final String INTERNAL_USERS_RESOURCE = "_plugins/_security/api/internalusers/"; + public static final String ID_1 = "one"; + public static final String PROHIBITED_INDEX = "prohibited"; + public static final String ID_2 = "two"; + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(USER_ADMIN, LIMITED_USER) + .anonymousAuth(false) + .nodeSettings( + Map.of( + SECURITY_RESTAPI_ROLES_ENABLED, + List.of("user_" + USER_ADMIN.getName() + "__" + ALL_ACCESS.getName()), + SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, + true + ) + ) + .build(); + + @Rule + public TemporaryFolder configurationDirectory = new TemporaryFolder(); + + @BeforeClass + public static void initData() { + try (Client client = cluster.getInternalNodeClient()) { + client.prepareIndex(LIMITED_USER_INDEX).setId(ID_1).setRefreshPolicy(IMMEDIATE).setSource("foo", "bar").get(); + client.prepareIndex(PROHIBITED_INDEX).setId(ID_2).setRefreshPolicy(IMMEDIATE).setSource("three", "four").get(); + } + } + + @Test + public void shouldCreateUserViaRestApi_success() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse httpResponse = client.putJson( + INTERNAL_USERS_RESOURCE + ADDITIONAL_USER_1, + String.format(CREATE_USER_BODY, ADDITIONAL_PASSWORD_1) + ); + + assertThat(httpResponse.getStatusCode(), equalTo(201)); + } + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + client.assertCorrectCredentials(USER_ADMIN.getName()); + } + try (TestRestClient client = cluster.getRestClient(ADDITIONAL_USER_1, ADDITIONAL_PASSWORD_1)) { + client.assertCorrectCredentials(ADDITIONAL_USER_1); + } + } + + @Test + public void shouldCreateUserViaRestApi_failure() { + try (TestRestClient client = cluster.getRestClient(LIMITED_USER)) { + HttpResponse httpResponse = client.putJson( + INTERNAL_USERS_RESOURCE + ADDITIONAL_USER_1, + String.format(CREATE_USER_BODY, ADDITIONAL_PASSWORD_1) + ); + + httpResponse.assertStatusCode(403); + } + } + + @Test + public void shouldAuthenticateAsAdminWithCertificate_positive() { + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + HttpResponse httpResponse = client.get("/_plugins/_security/whoami"); + + httpResponse.assertStatusCode(200); + assertThat(httpResponse.getTextFromJsonBody("/is_admin"), equalTo("true")); + } + } + + @Test + public void shouldAuthenticateAsAdminWithCertificate_negativeSelfSignedCertificate() { + TestCertificates testCertificates = cluster.getTestCertificates(); + try (TestRestClient client = cluster.getRestClient(testCertificates.createSelfSignedCertificate("CN=bond"))) { + HttpResponse httpResponse = client.get("/_plugins/_security/whoami"); + + httpResponse.assertStatusCode(200); + assertThat(httpResponse.getTextFromJsonBody("/is_admin"), equalTo("false")); + } + } + + @Test + public void shouldAuthenticateAsAdminWithCertificate_negativeIncorrectDn() { + TestCertificates testCertificates = cluster.getTestCertificates(); + try (TestRestClient client = cluster.getRestClient(testCertificates.createAdminCertificate("CN=non_admin"))) { + HttpResponse httpResponse = client.get("/_plugins/_security/whoami"); + + httpResponse.assertStatusCode(200); + assertThat(httpResponse.getTextFromJsonBody("/is_admin"), equalTo("false")); + } + } + + @Test + public void shouldCreateUserViaRestApiWhenAdminIsAuthenticatedViaCertificate_positive() { + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + + HttpResponse httpResponse = client.putJson( + INTERNAL_USERS_RESOURCE + ADDITIONAL_USER_2, + String.format(CREATE_USER_BODY, ADDITIONAL_PASSWORD_2) + ); + + httpResponse.assertStatusCode(201); + } + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + client.assertCorrectCredentials(USER_ADMIN.getName()); + } + try (TestRestClient client = cluster.getRestClient(ADDITIONAL_USER_2, ADDITIONAL_PASSWORD_2)) { + client.assertCorrectCredentials(ADDITIONAL_USER_2); + } + } + + @Test + public void shouldCreateUserViaRestApiWhenAdminIsAuthenticatedViaCertificate_negative() { + TestCertificates testCertificates = cluster.getTestCertificates(); + try (TestRestClient client = cluster.getRestClient(testCertificates.createSelfSignedCertificate("CN=attacker"))) { + HttpResponse httpResponse = client.putJson( + INTERNAL_USERS_RESOURCE + ADDITIONAL_USER_2, + String.format(CREATE_USER_BODY, ADDITIONAL_PASSWORD_2) + ); + + httpResponse.assertStatusCode(401); + } + } + + @Test + public void shouldStillWorkAfterUpdateOfSecurityConfig() { + List users = new ArrayList<>(cluster.getConfiguredUsers()); + User newUser = new User("new-user"); + users.add(newUser); + + cluster.updateUserConfiguration(users); + + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + client.assertCorrectCredentials(USER_ADMIN.getName()); + } + try (TestRestClient client = cluster.getRestClient(newUser)) { + client.assertCorrectCredentials(newUser.getName()); + } + } + + @Test + public void shouldAccessIndexWithPlaceholder_positive() { + try (TestRestClient client = cluster.getRestClient(LIMITED_USER)) { + HttpResponse httpResponse = client.get("/" + LIMITED_USER_INDEX + "/_doc/" + ID_1); + + httpResponse.assertStatusCode(200); + } + } + + @Test + public void shouldAccessIndexWithPlaceholder_negative() { + try (TestRestClient client = cluster.getRestClient(LIMITED_USER)) { + HttpResponse httpResponse = client.get("/" + PROHIBITED_INDEX + "/_doc/" + ID_2); + + httpResponse.assertStatusCode(403); + } + } + + @Test + public void shouldUseSecurityAdminTool() throws Exception { + SecurityAdminLauncher securityAdminLauncher = new SecurityAdminLauncher(cluster.getHttpPort(), cluster.getTestCertificates()); + File rolesMapping = configurationDirectory.newFile("roles_mapping.yml"); + ConfigurationFiles.createRoleMappingFile(rolesMapping); + + int exitCode = securityAdminLauncher.updateRoleMappings(rolesMapping); + + assertThat(exitCode, equalTo(0)); + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + Awaitility.await() + .alias("Waiting for rolemapping 'readall' availability.") + .until(() -> client.get("_plugins/_security/api/rolesmapping/readall").getStatusCode(), equalTo(200)); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java b/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java index c25d28ef12..fcccaf53b3 100644 --- a/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java +++ b/src/integrationTest/java/org/opensearch/security/SecurityRolesTests.java @@ -32,29 +32,32 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class SecurityRolesTests { - protected final static TestSecurityConfig.User USER_SR = new TestSecurityConfig.User("sr_user").roles( - new Role("abc_ber").indexPermissions("*").on("*").clusterPermissions("*"), - new Role("def_efg").indexPermissions("*").on("*").clusterPermissions("*")); - - @ClassRule - public static LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(true) - .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_SR).build(); - - @Test - public void testSecurityRoles() throws Exception { - try (TestRestClient client = cluster.getRestClient(USER_SR)) { - HttpResponse response = client.getAuthInfo(); - response.assertStatusCode(HttpStatus.SC_OK); - - // Check username - assertThat(response.getTextFromJsonBody("/user_name"), equalTo("sr_user")); - - // Check security roles - assertThat(response.getTextFromJsonBody("/roles/0"), equalTo("user_sr_user__abc_ber")); - assertThat(response.getTextFromJsonBody("/roles/1"), equalTo("user_sr_user__def_efg")); - - } - } + protected final static TestSecurityConfig.User USER_SR = new TestSecurityConfig.User("sr_user").roles( + new Role("abc_ber").indexPermissions("*").on("*").clusterPermissions("*"), + new Role("def_efg").indexPermissions("*").on("*").clusterPermissions("*") + ); + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .anonymousAuth(true) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(USER_SR) + .build(); + + @Test + public void testSecurityRoles() throws Exception { + try (TestRestClient client = cluster.getRestClient(USER_SR)) { + HttpResponse response = client.getAuthInfo(); + response.assertStatusCode(HttpStatus.SC_OK); + + // Check username + assertThat(response.getTextFromJsonBody("/user_name"), equalTo("sr_user")); + + // Check security roles + assertThat(response.getTextFromJsonBody("/roles/0"), equalTo("user_sr_user__abc_ber")); + assertThat(response.getTextFromJsonBody("/roles/1"), equalTo("user_sr_user__def_efg")); + + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java b/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java index f346bedd31..28aa6abd43 100644 --- a/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java +++ b/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java @@ -33,55 +33,63 @@ class SnapshotSteps { - private final SnapshotClient snapshotClient; + private final SnapshotClient snapshotClient; - public SnapshotSteps(RestHighLevelClient restHighLevelClient) { - this.snapshotClient = requireNonNull(restHighLevelClient, "Rest high level client is required.").snapshot(); - } + public SnapshotSteps(RestHighLevelClient restHighLevelClient) { + this.snapshotClient = requireNonNull(restHighLevelClient, "Rest high level client is required.").snapshot(); + } - // CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here - public org.opensearch.action.support.master.AcknowledgedResponse createSnapshotRepository(String repositoryName, String snapshotDirPath, String type) - // CS-ENFORCE-SINGLE - throws IOException { - PutRepositoryRequest createRepositoryRequest = new PutRepositoryRequest().name(repositoryName).type(type) - .settings(Map.of("location", snapshotDirPath)); - return snapshotClient.createRepository(createRepositoryRequest, DEFAULT); - } + // CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here + public org.opensearch.action.support.master.AcknowledgedResponse createSnapshotRepository( + String repositoryName, + String snapshotDirPath, + String type + ) + // CS-ENFORCE-SINGLE + throws IOException { + PutRepositoryRequest createRepositoryRequest = new PutRepositoryRequest().name(repositoryName) + .type(type) + .settings(Map.of("location", snapshotDirPath)); + return snapshotClient.createRepository(createRepositoryRequest, DEFAULT); + } - public CreateSnapshotResponse createSnapshot(String repositoryName, String snapshotName, String...indices) throws IOException { - CreateSnapshotRequest createSnapshotRequest = new CreateSnapshotRequest(repositoryName, snapshotName) - .indices(indices); - return snapshotClient.create(createSnapshotRequest, DEFAULT); - } + public CreateSnapshotResponse createSnapshot(String repositoryName, String snapshotName, String... indices) throws IOException { + CreateSnapshotRequest createSnapshotRequest = new CreateSnapshotRequest(repositoryName, snapshotName).indices(indices); + return snapshotClient.create(createSnapshotRequest, DEFAULT); + } - public void waitForSnapshotCreation(String repositoryName, String snapshotName) { - GetSnapshotsRequest getSnapshotsRequest = new GetSnapshotsRequest(repositoryName, new String[] { snapshotName }); - Awaitility.await().alias("wait for snapshot creation").ignoreExceptions().until(() -> { - GetSnapshotsResponse snapshotsResponse = snapshotClient.get(getSnapshotsRequest, DEFAULT); - SnapshotInfo snapshotInfo = snapshotsResponse.getSnapshots().get(0); - return SnapshotState.SUCCESS.equals(snapshotInfo.state()); - }); - } + public void waitForSnapshotCreation(String repositoryName, String snapshotName) { + GetSnapshotsRequest getSnapshotsRequest = new GetSnapshotsRequest(repositoryName, new String[] { snapshotName }); + Awaitility.await().alias("wait for snapshot creation").ignoreExceptions().until(() -> { + GetSnapshotsResponse snapshotsResponse = snapshotClient.get(getSnapshotsRequest, DEFAULT); + SnapshotInfo snapshotInfo = snapshotsResponse.getSnapshots().get(0); + return SnapshotState.SUCCESS.equals(snapshotInfo.state()); + }); + } - //CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here - public org.opensearch.action.support.master.AcknowledgedResponse deleteSnapshotRepository(String repositoryName) throws IOException { - //CS-ENFORCE-SINGLE - DeleteRepositoryRequest request = new DeleteRepositoryRequest(repositoryName); - return snapshotClient.deleteRepository(request, DEFAULT); - } + // CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here + public org.opensearch.action.support.master.AcknowledgedResponse deleteSnapshotRepository(String repositoryName) throws IOException { + // CS-ENFORCE-SINGLE + DeleteRepositoryRequest request = new DeleteRepositoryRequest(repositoryName); + return snapshotClient.deleteRepository(request, DEFAULT); + } - //CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here - public org.opensearch.action.support.master.AcknowledgedResponse deleteSnapshot(String repositoryName, String snapshotName) throws IOException { - //CS-ENFORCE-SINGLE - return snapshotClient.delete(new DeleteSnapshotRequest(repositoryName, snapshotName), DEFAULT); - } + // CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here + public org.opensearch.action.support.master.AcknowledgedResponse deleteSnapshot(String repositoryName, String snapshotName) + throws IOException { + // CS-ENFORCE-SINGLE + return snapshotClient.delete(new DeleteSnapshotRequest(repositoryName, snapshotName), DEFAULT); + } - public RestoreSnapshotResponse restoreSnapshot( - String repositoryName, String snapshotName, String renamePattern, - String renameReplacement) throws IOException { - RestoreSnapshotRequest restoreSnapshotRequest = new RestoreSnapshotRequest(repositoryName, snapshotName) - .renamePattern(renamePattern) - .renameReplacement(renameReplacement); - return snapshotClient.restore(restoreSnapshotRequest, DEFAULT); - } + public RestoreSnapshotResponse restoreSnapshot( + String repositoryName, + String snapshotName, + String renamePattern, + String renameReplacement + ) throws IOException { + RestoreSnapshotRequest restoreSnapshotRequest = new RestoreSnapshotRequest(repositoryName, snapshotName).renamePattern( + renamePattern + ).renameReplacement(renameReplacement); + return snapshotClient.restore(restoreSnapshotRequest, DEFAULT); + } } diff --git a/src/integrationTest/java/org/opensearch/security/Song.java b/src/integrationTest/java/org/opensearch/security/Song.java index c0e5749d29..b7e6c4ef05 100644 --- a/src/integrationTest/java/org/opensearch/security/Song.java +++ b/src/integrationTest/java/org/opensearch/security/Song.java @@ -14,90 +14,85 @@ public class Song { - public static final String FIELD_TITLE = "title"; - public static final String FIELD_ARTIST = "artist"; - public static final String FIELD_LYRICS = "lyrics"; - public static final String FIELD_STARS = "stars"; - public static final String FIELD_GENRE = "genre"; - public static final String ARTIST_FIRST = "First artist"; - public static final String ARTIST_STRING = "String"; - public static final String ARTIST_TWINS = "Twins"; - public static final String TITLE_MAGNUM_OPUS = "Magnum Opus"; - public static final String TITLE_SONG_1_PLUS_1 = "Song 1+1"; - public static final String TITLE_NEXT_SONG = "Next song"; - public static final String ARTIST_NO = "No!"; - public static final String TITLE_POISON = "Poison"; - - public static final String ARTIST_YES = "yes"; - - public static final String TITLE_AFFIRMATIVE = "Affirmative"; - - public static final String ARTIST_UNKNOWN = "unknown"; - public static final String TITLE_CONFIDENTIAL = "confidential"; - - public static final String LYRICS_1 = "Very deep subject"; - public static final String LYRICS_2 = "Once upon a time"; - public static final String LYRICS_3 = "giant nonsense"; - public static final String LYRICS_4 = "Much too much"; - public static final String LYRICS_5 = "Little to little"; - public static final String LYRICS_6 = "confidential secret classified"; - - public static final String GENRE_ROCK = "rock"; - public static final String GENRE_JAZZ = "jazz"; - public static final String GENRE_BLUES = "blues"; - - public static final String QUERY_TITLE_NEXT_SONG = FIELD_TITLE + ":" + "\"" + TITLE_NEXT_SONG + "\""; - public static final String QUERY_TITLE_POISON = FIELD_TITLE + ":" + TITLE_POISON; - public static final String QUERY_TITLE_MAGNUM_OPUS = FIELD_TITLE + ":" + TITLE_MAGNUM_OPUS; - - public static final Song[] SONGS = { - new Song(ARTIST_FIRST, TITLE_MAGNUM_OPUS ,LYRICS_1, 1, GENRE_ROCK), - new Song(ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES), - new Song(ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ), - new Song(ARTIST_NO, TITLE_POISON, LYRICS_4, 4, GENRE_ROCK), - new Song(ARTIST_YES, TITLE_AFFIRMATIVE,LYRICS_5, 5, GENRE_BLUES), - new Song(ARTIST_UNKNOWN, TITLE_CONFIDENTIAL, LYRICS_6, 6, GENRE_JAZZ) - }; - - private final String artist; - private final String title; - private final String lyrics; - private final Integer stars; - private final String genre; - - public Song(String artist, String title, String lyrics, Integer stars, String genre) { - this.artist = Objects.requireNonNull(artist, "Artist is required"); - this.title = Objects.requireNonNull(title, "Title is required"); - this.lyrics = Objects.requireNonNull(lyrics, "Lyrics is required"); - this.stars = Objects.requireNonNull(stars, "Stars field is required"); - this.genre = Objects.requireNonNull(genre, "Genre field is required"); - } - - public String getArtist() { - return artist; - } - - public String getTitle() { - return title; - } - - public String getLyrics() { - return lyrics; - } - - public Integer getStars() { - return stars; - } - - public String getGenre() { - return genre; - } - - public Map asMap() { - return Map.of(FIELD_ARTIST, artist, - FIELD_TITLE, title, - FIELD_LYRICS, lyrics, - FIELD_STARS, stars, - FIELD_GENRE, genre); - } + public static final String FIELD_TITLE = "title"; + public static final String FIELD_ARTIST = "artist"; + public static final String FIELD_LYRICS = "lyrics"; + public static final String FIELD_STARS = "stars"; + public static final String FIELD_GENRE = "genre"; + public static final String ARTIST_FIRST = "First artist"; + public static final String ARTIST_STRING = "String"; + public static final String ARTIST_TWINS = "Twins"; + public static final String TITLE_MAGNUM_OPUS = "Magnum Opus"; + public static final String TITLE_SONG_1_PLUS_1 = "Song 1+1"; + public static final String TITLE_NEXT_SONG = "Next song"; + public static final String ARTIST_NO = "No!"; + public static final String TITLE_POISON = "Poison"; + + public static final String ARTIST_YES = "yes"; + + public static final String TITLE_AFFIRMATIVE = "Affirmative"; + + public static final String ARTIST_UNKNOWN = "unknown"; + public static final String TITLE_CONFIDENTIAL = "confidential"; + + public static final String LYRICS_1 = "Very deep subject"; + public static final String LYRICS_2 = "Once upon a time"; + public static final String LYRICS_3 = "giant nonsense"; + public static final String LYRICS_4 = "Much too much"; + public static final String LYRICS_5 = "Little to little"; + public static final String LYRICS_6 = "confidential secret classified"; + + public static final String GENRE_ROCK = "rock"; + public static final String GENRE_JAZZ = "jazz"; + public static final String GENRE_BLUES = "blues"; + + public static final String QUERY_TITLE_NEXT_SONG = FIELD_TITLE + ":" + "\"" + TITLE_NEXT_SONG + "\""; + public static final String QUERY_TITLE_POISON = FIELD_TITLE + ":" + TITLE_POISON; + public static final String QUERY_TITLE_MAGNUM_OPUS = FIELD_TITLE + ":" + TITLE_MAGNUM_OPUS; + + public static final Song[] SONGS = { + new Song(ARTIST_FIRST, TITLE_MAGNUM_OPUS, LYRICS_1, 1, GENRE_ROCK), + new Song(ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES), + new Song(ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ), + new Song(ARTIST_NO, TITLE_POISON, LYRICS_4, 4, GENRE_ROCK), + new Song(ARTIST_YES, TITLE_AFFIRMATIVE, LYRICS_5, 5, GENRE_BLUES), + new Song(ARTIST_UNKNOWN, TITLE_CONFIDENTIAL, LYRICS_6, 6, GENRE_JAZZ) }; + + private final String artist; + private final String title; + private final String lyrics; + private final Integer stars; + private final String genre; + + public Song(String artist, String title, String lyrics, Integer stars, String genre) { + this.artist = Objects.requireNonNull(artist, "Artist is required"); + this.title = Objects.requireNonNull(title, "Title is required"); + this.lyrics = Objects.requireNonNull(lyrics, "Lyrics is required"); + this.stars = Objects.requireNonNull(stars, "Stars field is required"); + this.genre = Objects.requireNonNull(genre, "Genre field is required"); + } + + public String getArtist() { + return artist; + } + + public String getTitle() { + return title; + } + + public String getLyrics() { + return lyrics; + } + + public Integer getStars() { + return stars; + } + + public String getGenre() { + return genre; + } + + public Map asMap() { + return Map.of(FIELD_ARTIST, artist, FIELD_TITLE, title, FIELD_LYRICS, lyrics, FIELD_STARS, stars, FIELD_GENRE, genre); + } } diff --git a/src/integrationTest/java/org/opensearch/security/SslOnlyTests.java b/src/integrationTest/java/org/opensearch/security/SslOnlyTests.java index b258f0fed2..25feffb2b4 100644 --- a/src/integrationTest/java/org/opensearch/security/SslOnlyTests.java +++ b/src/integrationTest/java/org/opensearch/security/SslOnlyTests.java @@ -33,37 +33,37 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class SslOnlyTests { + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .anonymousAuth(false) + .loadConfigurationIntoIndex(false) + .nodeSettings(Map.of(ConfigConstants.SECURITY_SSL_ONLY, true)) + .sslOnly(true) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .build(); - @ClassRule - public static LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) - .loadConfigurationIntoIndex(false) - .nodeSettings(Map.of(ConfigConstants.SECURITY_SSL_ONLY, true)) - .sslOnly(true) - .authc(AUTHC_HTTPBASIC_INTERNAL).build(); + @Test + public void shouldNotLoadSecurityPluginResources() { + try (TestRestClient client = cluster.getRestClient()) { - @Test - public void shouldNotLoadSecurityPluginResources() { - try(TestRestClient client = cluster.getRestClient()) { + HttpResponse response = client.getAuthInfo(); - HttpResponse response = client.getAuthInfo(); + // in SSL only mode the security plugin does not register a handler for resource /_plugins/_security/whoami. Therefore error + // response is returned. + response.assertStatusCode(400); + } + } - // in SSL only mode the security plugin does not register a handler for resource /_plugins/_security/whoami. Therefore error - // response is returned. - response.assertStatusCode(400); - } - } + @Test + public void shouldGetIndicesWithoutAuthentication() { + try (TestRestClient client = cluster.getRestClient()) { - @Test - public void shouldGetIndicesWithoutAuthentication() { - try(TestRestClient client = cluster.getRestClient()) { + // request does not contains credential + HttpResponse response = client.get("/_cat/indices"); - // request does not contains credential - HttpResponse response = client.get("/_cat/indices"); - - // successful response is returned because the security plugin in SSL only mode - // does not perform authentication and authorization - response.assertStatusCode(200); - } - } + // successful response is returned because the security plugin in SSL only mode + // does not perform authentication and authorization + response.assertStatusCode(200); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/TlsTests.java b/src/integrationTest/java/org/opensearch/security/TlsTests.java index 7a57cb57b8..de362a544e 100644 --- a/src/integrationTest/java/org/opensearch/security/TlsTests.java +++ b/src/integrationTest/java/org/opensearch/security/TlsTests.java @@ -50,55 +50,57 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class TlsTests { - private static final User USER_ADMIN = new User("admin").roles(ALL_ACCESS); - - public static final String SUPPORTED_CIPHER_SUIT = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; - public static final String NOT_SUPPORTED_CIPHER_SUITE = "TLS_RSA_WITH_AES_128_CBC_SHA"; - public static final String AUTH_INFO_ENDPOINT = "/_opendistro/_security/authinfo?pretty"; - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) - .nodeSettings(Map.of(SECURITY_SSL_HTTP_ENABLED_CIPHERS, List.of(SUPPORTED_CIPHER_SUIT))) - .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_ADMIN) - .audit(new AuditConfiguration(true) - .compliance(new AuditCompliance().enabled(true)) - .filters(new AuditFilters().enabledRest(true).enabledTransport(true)) - ).build(); - - @Rule - public AuditLogsRule auditLogsRule = new AuditLogsRule(); - - @Test - public void shouldCreateAuditOnIncomingNonTlsConnection() throws IOException { - try(CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpGet request = new HttpGet("http://localhost:" + cluster.getHttpPort()); - - assertThatThrownBy(() -> httpClient.execute(request), instanceOf(NoHttpResponseException.class)); - } - auditLogsRule.assertAtLeast(1, auditPredicate(AuditCategory.SSL_EXCEPTION).withLayer(REST)); - } - - @Test - public void shouldSupportClientCipherSuite_positive() throws IOException { - try(CloseableHttpClient client = cluster.getClosableHttpClient(new String[] { SUPPORTED_CIPHER_SUIT })) { - HttpGet httpGet = new HttpGet("https://localhost:" + cluster.getHttpPort() + AUTH_INFO_ENDPOINT); - httpGet.addHeader(getBasicAuthHeader(USER_ADMIN.getName(), USER_ADMIN.getPassword())); - - try(CloseableHttpResponse response = client.execute(httpGet)) { - - int responseStatusCode = response.getCode(); - assertThat(responseStatusCode, equalTo(200)); - } - } - } - - @Test - public void shouldSupportClientCipherSuite_negative() throws IOException { - try(CloseableHttpClient client = cluster.getClosableHttpClient(new String[]{ NOT_SUPPORTED_CIPHER_SUITE })) { - HttpGet httpGet = new HttpGet("https://localhost:" + cluster.getHttpPort() + AUTH_INFO_ENDPOINT); - - assertThatThrownBy(() -> client.execute(httpGet), instanceOf(SSLHandshakeException.class)); - } - } + private static final User USER_ADMIN = new User("admin").roles(ALL_ACCESS); + + public static final String SUPPORTED_CIPHER_SUIT = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; + public static final String NOT_SUPPORTED_CIPHER_SUITE = "TLS_RSA_WITH_AES_128_CBC_SHA"; + public static final String AUTH_INFO_ENDPOINT = "/_opendistro/_security/authinfo?pretty"; + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .anonymousAuth(false) + .nodeSettings(Map.of(SECURITY_SSL_HTTP_ENABLED_CIPHERS, List.of(SUPPORTED_CIPHER_SUIT))) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(USER_ADMIN) + .audit( + new AuditConfiguration(true).compliance(new AuditCompliance().enabled(true)) + .filters(new AuditFilters().enabledRest(true).enabledTransport(true)) + ) + .build(); + + @Rule + public AuditLogsRule auditLogsRule = new AuditLogsRule(); + + @Test + public void shouldCreateAuditOnIncomingNonTlsConnection() throws IOException { + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpGet request = new HttpGet("http://localhost:" + cluster.getHttpPort()); + + assertThatThrownBy(() -> httpClient.execute(request), instanceOf(NoHttpResponseException.class)); + } + auditLogsRule.assertAtLeast(1, auditPredicate(AuditCategory.SSL_EXCEPTION).withLayer(REST)); + } + + @Test + public void shouldSupportClientCipherSuite_positive() throws IOException { + try (CloseableHttpClient client = cluster.getClosableHttpClient(new String[] { SUPPORTED_CIPHER_SUIT })) { + HttpGet httpGet = new HttpGet("https://localhost:" + cluster.getHttpPort() + AUTH_INFO_ENDPOINT); + httpGet.addHeader(getBasicAuthHeader(USER_ADMIN.getName(), USER_ADMIN.getPassword())); + + try (CloseableHttpResponse response = client.execute(httpGet)) { + + int responseStatusCode = response.getCode(); + assertThat(responseStatusCode, equalTo(200)); + } + } + } + + @Test + public void shouldSupportClientCipherSuite_negative() throws IOException { + try (CloseableHttpClient client = cluster.getClosableHttpClient(new String[] { NOT_SUPPORTED_CIPHER_SUITE })) { + HttpGet httpGet = new HttpGet("https://localhost:" + cluster.getHttpPort() + AUTH_INFO_ENDPOINT); + + assertThatThrownBy(() -> client.execute(httpGet), instanceOf(SSLHandshakeException.class)); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java b/src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java index e0e9d5beb6..2da3446e75 100644 --- a/src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java +++ b/src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java @@ -35,91 +35,99 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class UserBruteForceAttacksPreventionTests { - private static final User USER_1 = new User("simple-user-1").roles(ALL_ACCESS); - private static final User USER_2 = new User("simple-user-2").roles(ALL_ACCESS); - private static final User USER_3 = new User("simple-user-3").roles(ALL_ACCESS); - private static final User USER_4 = new User("simple-user-4").roles(ALL_ACCESS); - private static final User USER_5 = new User("simple-user-5").roles(ALL_ACCESS); - - public static final int ALLOWED_TRIES = 3; - public static final int TIME_WINDOW_SECONDS = 3; - private static final AuthFailureListeners listener = new AuthFailureListeners() - .addRateLimit(new RateLimiting("internal_authentication_backend_limiting").type("username").authenticationBackend("intern") - .allowedTries(ALLOWED_TRIES).timeWindowSeconds(TIME_WINDOW_SECONDS).blockExpirySeconds(2).maxBlockedClients(500) - .maxTrackedClients(500)); - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false).authFailureListeners(listener) - .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_1, USER_2, USER_3, USER_4, USER_5).build(); - - @Rule - public LogsRule logsRule = new LogsRule("org.opensearch.security.auth.BackendRegistry"); - - @Test - public void shouldAuthenticateUserWhenBlockadeIsNotActive() { - try(TestRestClient client = cluster.getRestClient(USER_1)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_OK); - } - } - - @Test - public void shouldBlockUserWhenNumberOfFailureLoginAttemptIsEqualToLimit() { - authenticateUserWithIncorrectPassword(USER_2, ALLOWED_TRIES); - try(TestRestClient client = cluster.getRestClient(USER_2)) { - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_UNAUTHORIZED); - } - //Rejecting REST request because of blocked user: - logsRule.assertThatContain("Rejecting REST request because of blocked user: " + USER_2.getName()); - } - - @Test - public void shouldBlockUserWhenNumberOfFailureLoginAttemptIsGreaterThanLimit() { - authenticateUserWithIncorrectPassword(USER_3, ALLOWED_TRIES * 2); - try(TestRestClient client = cluster.getRestClient(USER_3)) { - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_UNAUTHORIZED); - } - logsRule.assertThatContain("Rejecting REST request because of blocked user: " + USER_3.getName()); - } - - @Test - public void shouldNotBlockUserWhenNumberOfLoginAttemptIsBelowLimit() { - authenticateUserWithIncorrectPassword(USER_4, ALLOWED_TRIES - 1); - try(TestRestClient client = cluster.getRestClient(USER_4)) { - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_OK); - } - } - - @Test - public void shouldReleaseLock() throws InterruptedException { - authenticateUserWithIncorrectPassword(USER_5, ALLOWED_TRIES); - try(TestRestClient client = cluster.getRestClient(USER_5)) { - HttpResponse response = client.getAuthInfo(); - response.assertStatusCode(SC_UNAUTHORIZED); - TimeUnit.SECONDS.sleep(TIME_WINDOW_SECONDS); - - response = client.getAuthInfo(); - - response.assertStatusCode(SC_OK); - } - logsRule.assertThatContain("Rejecting REST request because of blocked user: " + USER_5.getName()); - } - - private static void authenticateUserWithIncorrectPassword(User user, int numberOfAttempts) { - try(TestRestClient client = cluster.getRestClient(user.getName(), "incorrect password")) { - for(int i = 0; i < numberOfAttempts; ++i) { - HttpResponse response = client.getAuthInfo(); - response.assertStatusCode(SC_UNAUTHORIZED); - } - } - } + private static final User USER_1 = new User("simple-user-1").roles(ALL_ACCESS); + private static final User USER_2 = new User("simple-user-2").roles(ALL_ACCESS); + private static final User USER_3 = new User("simple-user-3").roles(ALL_ACCESS); + private static final User USER_4 = new User("simple-user-4").roles(ALL_ACCESS); + private static final User USER_5 = new User("simple-user-5").roles(ALL_ACCESS); + + public static final int ALLOWED_TRIES = 3; + public static final int TIME_WINDOW_SECONDS = 3; + private static final AuthFailureListeners listener = new AuthFailureListeners().addRateLimit( + new RateLimiting("internal_authentication_backend_limiting").type("username") + .authenticationBackend("intern") + .allowedTries(ALLOWED_TRIES) + .timeWindowSeconds(TIME_WINDOW_SECONDS) + .blockExpirySeconds(2) + .maxBlockedClients(500) + .maxTrackedClients(500) + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .authFailureListeners(listener) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(USER_1, USER_2, USER_3, USER_4, USER_5) + .build(); + + @Rule + public LogsRule logsRule = new LogsRule("org.opensearch.security.auth.BackendRegistry"); + + @Test + public void shouldAuthenticateUserWhenBlockadeIsNotActive() { + try (TestRestClient client = cluster.getRestClient(USER_1)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + } + + @Test + public void shouldBlockUserWhenNumberOfFailureLoginAttemptIsEqualToLimit() { + authenticateUserWithIncorrectPassword(USER_2, ALLOWED_TRIES); + try (TestRestClient client = cluster.getRestClient(USER_2)) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + } + // Rejecting REST request because of blocked user: + logsRule.assertThatContain("Rejecting REST request because of blocked user: " + USER_2.getName()); + } + + @Test + public void shouldBlockUserWhenNumberOfFailureLoginAttemptIsGreaterThanLimit() { + authenticateUserWithIncorrectPassword(USER_3, ALLOWED_TRIES * 2); + try (TestRestClient client = cluster.getRestClient(USER_3)) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + } + logsRule.assertThatContain("Rejecting REST request because of blocked user: " + USER_3.getName()); + } + + @Test + public void shouldNotBlockUserWhenNumberOfLoginAttemptIsBelowLimit() { + authenticateUserWithIncorrectPassword(USER_4, ALLOWED_TRIES - 1); + try (TestRestClient client = cluster.getRestClient(USER_4)) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + } + + @Test + public void shouldReleaseLock() throws InterruptedException { + authenticateUserWithIncorrectPassword(USER_5, ALLOWED_TRIES); + try (TestRestClient client = cluster.getRestClient(USER_5)) { + HttpResponse response = client.getAuthInfo(); + response.assertStatusCode(SC_UNAUTHORIZED); + TimeUnit.SECONDS.sleep(TIME_WINDOW_SECONDS); + + response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + logsRule.assertThatContain("Rejecting REST request because of blocked user: " + USER_5.getName()); + } + + private static void authenticateUserWithIncorrectPassword(User user, int numberOfAttempts) { + try (TestRestClient client = cluster.getRestClient(user.getName(), "incorrect password")) { + for (int i = 0; i < numberOfAttempts; ++i) { + HttpResponse response = client.getAuthInfo(); + response.assertStatusCode(SC_UNAUTHORIZED); + } + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/AnonymousAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/AnonymousAuthenticationTest.java index d50ed081c8..b1c13aeedc 100644 --- a/src/integrationTest/java/org/opensearch/security/http/AnonymousAuthenticationTest.java +++ b/src/integrationTest/java/org/opensearch/security/http/AnonymousAuthenticationTest.java @@ -34,95 +34,96 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class AnonymousAuthenticationTest { - private static final String DEFAULT_ANONYMOUS_USER_NAME = "opendistro_security_anonymous"; - private static final String DEFAULT_ANONYMOUS_USER_BACKEND_ROLE_NAME = "opendistro_security_anonymous_backendrole"; - - /** - * Custom role assigned to the anonymous user via {@link #ANONYMOUS_USER_CUSTOM_ROLE_MAPPING} - */ - private static final TestSecurityConfig.Role ANONYMOUS_USER_CUSTOM_ROLE = new TestSecurityConfig.Role("anonymous_user_custom_role"); - - /** - * Maps {@link #ANONYMOUS_USER_CUSTOM_ROLE} to {@link #DEFAULT_ANONYMOUS_USER_BACKEND_ROLE_NAME} - */ - private static final RolesMapping ANONYMOUS_USER_CUSTOM_ROLE_MAPPING = new RolesMapping(ANONYMOUS_USER_CUSTOM_ROLE) - .backendRoles(DEFAULT_ANONYMOUS_USER_BACKEND_ROLE_NAME); - - /** - * User who is stored in the internal user database and can authenticate - */ - private static final TestSecurityConfig.User EXISTING_USER = new TestSecurityConfig.User("existing_user") - .roles(new TestSecurityConfig.Role("existing_user")); - - /** - * User who is not stored in the internal user database and can not authenticate - */ - private static final TestSecurityConfig.User NOT_EXISTING_USER = new TestSecurityConfig.User("not_existing_user") - .roles(new TestSecurityConfig.Role("not_existing_user")); - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.SINGLENODE) - .anonymousAuth(true) - .authc(AUTHC_HTTPBASIC_INTERNAL) - .users(EXISTING_USER) - .roles(ANONYMOUS_USER_CUSTOM_ROLE) - .rolesMapping(ANONYMOUS_USER_CUSTOM_ROLE_MAPPING) - .build(); - - private static final String USER_NAME_POINTER = "/user_name"; - private static final String BACKEND_ROLES_POINTER = "/backend_roles"; - private static final String ROLES_POINTER = "/roles"; - - - @Test - public void shouldAuthenticate_positive_anonymousUser() { - try(TestRestClient client = cluster.getRestClient()){ - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - - String username = response.getTextFromJsonBody(USER_NAME_POINTER); - assertThat(username, equalTo(DEFAULT_ANONYMOUS_USER_NAME)); - - List backendRoles = response.getTextArrayFromJsonBody(BACKEND_ROLES_POINTER); - assertThat(backendRoles, hasSize(1)); - assertThat(backendRoles, contains(DEFAULT_ANONYMOUS_USER_BACKEND_ROLE_NAME)); - - List roles = response.getTextArrayFromJsonBody(ROLES_POINTER); - assertThat(roles, hasSize(1)); - assertThat(roles, contains(ANONYMOUS_USER_CUSTOM_ROLE.getName())); - } - } - - @Test - public void shouldAuthenticate_positive_existingUser() { - try(TestRestClient client = cluster.getRestClient(EXISTING_USER)){ - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - - String username = response.getTextFromJsonBody(USER_NAME_POINTER); - assertThat(username, equalTo(EXISTING_USER.getName())); - - List backendRoles = response.getTextArrayFromJsonBody(BACKEND_ROLES_POINTER); - assertThat(backendRoles, hasSize(0)); - - List roles = response.getTextArrayFromJsonBody(ROLES_POINTER); - assertThat(roles, hasSize(EXISTING_USER.getRoleNames().size())); - assertThat(roles, containsInAnyOrder(EXISTING_USER.getRoleNames().toArray())); - } - } - - @Test - public void shouldAuthenticate_negative_notExistingUser() { - try(TestRestClient client = cluster.getRestClient(NOT_EXISTING_USER)){ - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - } - } + private static final String DEFAULT_ANONYMOUS_USER_NAME = "opendistro_security_anonymous"; + private static final String DEFAULT_ANONYMOUS_USER_BACKEND_ROLE_NAME = "opendistro_security_anonymous_backendrole"; + + /** + * Custom role assigned to the anonymous user via {@link #ANONYMOUS_USER_CUSTOM_ROLE_MAPPING} + */ + private static final TestSecurityConfig.Role ANONYMOUS_USER_CUSTOM_ROLE = new TestSecurityConfig.Role("anonymous_user_custom_role"); + + /** + * Maps {@link #ANONYMOUS_USER_CUSTOM_ROLE} to {@link #DEFAULT_ANONYMOUS_USER_BACKEND_ROLE_NAME} + */ + private static final RolesMapping ANONYMOUS_USER_CUSTOM_ROLE_MAPPING = new RolesMapping(ANONYMOUS_USER_CUSTOM_ROLE).backendRoles( + DEFAULT_ANONYMOUS_USER_BACKEND_ROLE_NAME + ); + + /** + * User who is stored in the internal user database and can authenticate + */ + private static final TestSecurityConfig.User EXISTING_USER = new TestSecurityConfig.User("existing_user").roles( + new TestSecurityConfig.Role("existing_user") + ); + + /** + * User who is not stored in the internal user database and can not authenticate + */ + private static final TestSecurityConfig.User NOT_EXISTING_USER = new TestSecurityConfig.User("not_existing_user").roles( + new TestSecurityConfig.Role("not_existing_user") + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(true) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(EXISTING_USER) + .roles(ANONYMOUS_USER_CUSTOM_ROLE) + .rolesMapping(ANONYMOUS_USER_CUSTOM_ROLE_MAPPING) + .build(); + + private static final String USER_NAME_POINTER = "/user_name"; + private static final String BACKEND_ROLES_POINTER = "/backend_roles"; + private static final String ROLES_POINTER = "/roles"; + + @Test + public void shouldAuthenticate_positive_anonymousUser() { + try (TestRestClient client = cluster.getRestClient()) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + + String username = response.getTextFromJsonBody(USER_NAME_POINTER); + assertThat(username, equalTo(DEFAULT_ANONYMOUS_USER_NAME)); + + List backendRoles = response.getTextArrayFromJsonBody(BACKEND_ROLES_POINTER); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, contains(DEFAULT_ANONYMOUS_USER_BACKEND_ROLE_NAME)); + + List roles = response.getTextArrayFromJsonBody(ROLES_POINTER); + assertThat(roles, hasSize(1)); + assertThat(roles, contains(ANONYMOUS_USER_CUSTOM_ROLE.getName())); + } + } + + @Test + public void shouldAuthenticate_positive_existingUser() { + try (TestRestClient client = cluster.getRestClient(EXISTING_USER)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + + String username = response.getTextFromJsonBody(USER_NAME_POINTER); + assertThat(username, equalTo(EXISTING_USER.getName())); + + List backendRoles = response.getTextArrayFromJsonBody(BACKEND_ROLES_POINTER); + assertThat(backendRoles, hasSize(0)); + + List roles = response.getTextArrayFromJsonBody(ROLES_POINTER); + assertThat(roles, hasSize(EXISTING_USER.getRoleNames().size())); + assertThat(roles, containsInAnyOrder(EXISTING_USER.getRoleNames().toArray())); + } + } + + @Test + public void shouldAuthenticate_negative_notExistingUser() { + try (TestRestClient client = cluster.getRestClient(NOT_EXISTING_USER)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/AuthInfo.java b/src/integrationTest/java/org/opensearch/security/http/AuthInfo.java index cb08c2f36d..53ea6ab859 100644 --- a/src/integrationTest/java/org/opensearch/security/http/AuthInfo.java +++ b/src/integrationTest/java/org/opensearch/security/http/AuthInfo.java @@ -17,14 +17,14 @@ @JsonIgnoreProperties(ignoreUnknown = true) class AuthInfo { - private final List customAttributeNames; + private final List customAttributeNames; - @ConstructorProperties("custom_attribute_names") - public AuthInfo(List customAttributeNames) { - this.customAttributeNames = customAttributeNames; - } + @ConstructorProperties("custom_attribute_names") + public AuthInfo(List customAttributeNames) { + this.customAttributeNames = customAttributeNames; + } - public List getCustomAttributeNames() { - return customAttributeNames; - } + public List getCustomAttributeNames() { + return customAttributeNames; + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java b/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java index dafedcdf38..3f4ada4c68 100644 --- a/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/BasicAuthTests.java @@ -37,110 +37,110 @@ @RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class BasicAuthTests { - static final User TEST_USER = new User("test_user").password("s3cret"); - - public static final String CUSTOM_ATTRIBUTE_NAME = "superhero"; - static final User SUPER_USER = new User("super-user").password("super-password") - .attr(CUSTOM_ATTRIBUTE_NAME, true); - public static final String NOT_EXISTING_USER = "not-existing-user"; - public static final String INVALID_PASSWORD = "secret-password"; - - public static final AuthcDomain AUTHC_DOMAIN = new AuthcDomain("basic", 0) - .httpAuthenticatorWithChallenge("basic").backend("internal"); - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) - .authc(AUTHC_DOMAIN).users(TEST_USER, SUPER_USER).build(); - - @Test - public void shouldRespondWith401WhenUserDoesNotExist() { - try (TestRestClient client = cluster.getRestClient(NOT_EXISTING_USER, INVALID_PASSWORD)) { - HttpResponse response = client.getAuthInfo(); - - assertThat(response, is(notNullValue())); - response.assertStatusCode(SC_UNAUTHORIZED); - } - } - - @Test - public void shouldRespondWith401WhenUserNameIsIncorrect() { - try (TestRestClient client = cluster.getRestClient(NOT_EXISTING_USER, TEST_USER.getPassword())) { - HttpResponse response = client.getAuthInfo(); - - assertThat(response, is(notNullValue())); - response.assertStatusCode(SC_UNAUTHORIZED); - } - } - - @Test - public void shouldRespondWith401WhenPasswordIsIncorrect() { - try (TestRestClient client = cluster.getRestClient(TEST_USER.getName(), INVALID_PASSWORD)) { - HttpResponse response = client.getAuthInfo(); - - assertThat(response, is(notNullValue())); - response.assertStatusCode(SC_UNAUTHORIZED); - } - } - - @Test - public void shouldRespondWith200WhenCredentialsAreCorrect() { - try (TestRestClient client = cluster.getRestClient(TEST_USER)) { - - HttpResponse response = client.getAuthInfo(); - - assertThat(response, is(notNullValue())); - response.assertStatusCode(SC_OK); - } - } - - @Test - public void testBrowserShouldRequestForCredentials() { - try (TestRestClient client = cluster.getRestClient()) { - - HttpResponse response = client.getAuthInfo(); - - assertThat(response, is(notNullValue())); - response.assertStatusCode(SC_UNAUTHORIZED); - assertThatBrowserAskUserForCredentials(response); - } - } - - @Test - public void testUserShouldNotHaveAssignedCustomAttributes() { - try (TestRestClient client = cluster.getRestClient(TEST_USER)) { - - HttpResponse response = client.getAuthInfo(); - - assertThat(response, is(notNullValue())); - response.assertStatusCode(SC_OK); - AuthInfo authInfo = response.getBodyAs(AuthInfo.class); - assertThat(authInfo, is(notNullValue())); - assertThat(authInfo.getCustomAttributeNames(), is(notNullValue())); - assertThat(authInfo.getCustomAttributeNames(), hasSize(0)); - } - } - - @Test - public void testUserShouldHaveAssignedCustomAttributes() { - try (TestRestClient client = cluster.getRestClient(SUPER_USER)) { - - HttpResponse response = client.getAuthInfo(); - - assertThat(response, is(notNullValue())); - response.assertStatusCode(SC_OK); - AuthInfo authInfo = response.getBodyAs(AuthInfo.class); - assertThat(authInfo, is(notNullValue())); - List customAttributeNames = authInfo.getCustomAttributeNames(); - assertThat(customAttributeNames, is(notNullValue())); - assertThat(customAttributeNames, hasSize(1)); - assertThat(customAttributeNames.get(0), Matchers.equalTo("attr.internal." + CUSTOM_ATTRIBUTE_NAME)); - } - } - - private void assertThatBrowserAskUserForCredentials(HttpResponse response) { - String reason = "Browser does not ask user for credentials"; - assertThat(reason, response.containHeader(HttpHeaders.WWW_AUTHENTICATE), equalTo(true)); - assertThat(response.getHeader(HttpHeaders.WWW_AUTHENTICATE).getValue(), containsStringIgnoringCase("basic")); - } + static final User TEST_USER = new User("test_user").password("s3cret"); + + public static final String CUSTOM_ATTRIBUTE_NAME = "superhero"; + static final User SUPER_USER = new User("super-user").password("super-password").attr(CUSTOM_ATTRIBUTE_NAME, true); + public static final String NOT_EXISTING_USER = "not-existing-user"; + public static final String INVALID_PASSWORD = "secret-password"; + + public static final AuthcDomain AUTHC_DOMAIN = new AuthcDomain("basic", 0).httpAuthenticatorWithChallenge("basic").backend("internal"); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .authc(AUTHC_DOMAIN) + .users(TEST_USER, SUPER_USER) + .build(); + + @Test + public void shouldRespondWith401WhenUserDoesNotExist() { + try (TestRestClient client = cluster.getRestClient(NOT_EXISTING_USER, INVALID_PASSWORD)) { + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_UNAUTHORIZED); + } + } + + @Test + public void shouldRespondWith401WhenUserNameIsIncorrect() { + try (TestRestClient client = cluster.getRestClient(NOT_EXISTING_USER, TEST_USER.getPassword())) { + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_UNAUTHORIZED); + } + } + + @Test + public void shouldRespondWith401WhenPasswordIsIncorrect() { + try (TestRestClient client = cluster.getRestClient(TEST_USER.getName(), INVALID_PASSWORD)) { + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_UNAUTHORIZED); + } + } + + @Test + public void shouldRespondWith200WhenCredentialsAreCorrect() { + try (TestRestClient client = cluster.getRestClient(TEST_USER)) { + + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_OK); + } + } + + @Test + public void testBrowserShouldRequestForCredentials() { + try (TestRestClient client = cluster.getRestClient()) { + + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_UNAUTHORIZED); + assertThatBrowserAskUserForCredentials(response); + } + } + + @Test + public void testUserShouldNotHaveAssignedCustomAttributes() { + try (TestRestClient client = cluster.getRestClient(TEST_USER)) { + + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_OK); + AuthInfo authInfo = response.getBodyAs(AuthInfo.class); + assertThat(authInfo, is(notNullValue())); + assertThat(authInfo.getCustomAttributeNames(), is(notNullValue())); + assertThat(authInfo.getCustomAttributeNames(), hasSize(0)); + } + } + + @Test + public void testUserShouldHaveAssignedCustomAttributes() { + try (TestRestClient client = cluster.getRestClient(SUPER_USER)) { + + HttpResponse response = client.getAuthInfo(); + + assertThat(response, is(notNullValue())); + response.assertStatusCode(SC_OK); + AuthInfo authInfo = response.getBodyAs(AuthInfo.class); + assertThat(authInfo, is(notNullValue())); + List customAttributeNames = authInfo.getCustomAttributeNames(); + assertThat(customAttributeNames, is(notNullValue())); + assertThat(customAttributeNames, hasSize(1)); + assertThat(customAttributeNames.get(0), Matchers.equalTo("attr.internal." + CUSTOM_ATTRIBUTE_NAME)); + } + } + + private void assertThatBrowserAskUserForCredentials(HttpResponse response) { + String reason = "Browser does not ask user for credentials"; + assertThat(reason, response.containHeader(HttpHeaders.WWW_AUTHENTICATE), equalTo(true)); + assertThat(response.getHeader(HttpHeaders.WWW_AUTHENTICATE).getValue(), containsStringIgnoringCase("basic")); + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java b/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java index 4af5563e53..940f326cc1 100644 --- a/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/BasicAuthWithoutChallengeTests.java @@ -28,24 +28,25 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class BasicAuthWithoutChallengeTests { - @ClassRule - public static LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) - .authc(AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE).build(); - - @Test - public void browserShouldNotRequestUserForCredentials() { - try (TestRestClient client = cluster.getRestClient()) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - assertThatBrowserDoesNotAskUserForCredentials(response); - } - } - - private void assertThatBrowserDoesNotAskUserForCredentials(HttpResponse response) { - String reason = "Browser asked user for credentials which is not expected"; - assertThat(reason, response.containHeader(HttpHeaders.WWW_AUTHENTICATE), equalTo(false)); - } + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .authc(AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE) + .build(); + + @Test + public void browserShouldNotRequestUserForCredentials() { + try (TestRestClient client = cluster.getRestClient()) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + assertThatBrowserDoesNotAskUserForCredentials(response); + } + } + + private void assertThatBrowserDoesNotAskUserForCredentials(HttpResponse response) { + String reason = "Browser asked user for credentials which is not expected"; + assertThat(reason, response.containHeader(HttpHeaders.WWW_AUTHENTICATE), equalTo(false)); + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/CertificateAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/CertificateAuthenticationTest.java index 18a79abcef..144a54c4d6 100644 --- a/src/integrationTest/java/org/opensearch/security/http/CertificateAuthenticationTest.java +++ b/src/integrationTest/java/org/opensearch/security/http/CertificateAuthenticationTest.java @@ -40,106 +40,109 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class CertificateAuthenticationTest { - private static final User USER_ADMIN = new User("admin").roles(ALL_ACCESS); - - public static final String POINTER_BACKEND_ROLES = "/backend_roles"; - public static final String POINTER_ROLES = "/roles"; - - private static final String USER_SPOCK = "spock"; - private static final String USER_KIRK = "kirk"; - - private static final String BACKEND_ROLE_BRIDGE = "bridge"; - private static final String BACKEND_ROLE_CAPTAIN = "captain"; - - private static final Role ROLE_ALL_INDEX_SEARCH = new Role("all-index-search").indexPermissions("indices:data/read/search") - .on("*"); - - private static final Map CERT_AUTH_CONFIG = Map.of( - "username_attribute", "cn", - "roles_attribute", "ou" - ); - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .nodeSettings(Map.of("plugins.security.ssl.http.clientauth_mode", "OPTIONAL")) - .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).anonymousAuth(false) - .authc(new AuthcDomain("clientcert_auth_domain", -1, true) - .httpAuthenticator(new HttpAuthenticator("clientcert").challenge(false) - .config(CERT_AUTH_CONFIG)).backend("noop")) - .authc(AUTHC_HTTPBASIC_INTERNAL).roles(ROLE_ALL_INDEX_SEARCH).users(USER_ADMIN) - .rolesMapping(new RolesMapping(ROLE_ALL_INDEX_SEARCH).backendRoles(BACKEND_ROLE_BRIDGE)).build(); - - private static final TestCertificates TEST_CERTIFICATES = cluster.getTestCertificates(); - - @Test - public void shouldAuthenticateUserWithBasicAuthWhenCertificateAuthenticationIsConfigured() { - try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_OK); - } - } - - @Test - public void shouldAuthenticateUserWithCertificate_positiveUserSpoke() { - CertificateData userSpockCertificate = TEST_CERTIFICATES.issueUserCertificate(BACKEND_ROLE_BRIDGE, USER_SPOCK); - try (TestRestClient client = cluster.getRestClient(userSpockCertificate)) { - - client.assertCorrectCredentials(USER_SPOCK); - } - } - - @Test - public void shouldAuthenticateUserWithCertificate_positiveUserKirk() { - CertificateData userSpockCertificate = TEST_CERTIFICATES.issueUserCertificate(BACKEND_ROLE_BRIDGE, USER_KIRK); - try (TestRestClient client = cluster.getRestClient(userSpockCertificate)) { - - client.assertCorrectCredentials(USER_KIRK); - } - } - - @Test - public void shouldAuthenticateUserWithCertificate_negative() { - CertificateData untrustedUserCertificate = TEST_CERTIFICATES.createSelfSignedCertificate("CN=untrusted"); - try (TestRestClient client = cluster.getRestClient(untrustedUserCertificate)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - } - } - - @Test - public void shouldRetrieveBackendRoleFromCertificate_positiveRoleBridge() { - CertificateData userSpockCertificate = TEST_CERTIFICATES.issueUserCertificate(BACKEND_ROLE_BRIDGE, USER_KIRK); - try (TestRestClient client = cluster.getRestClient(userSpockCertificate)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); - assertThat(backendRoles, hasSize(1)); - assertThat(backendRoles, containsInAnyOrder(BACKEND_ROLE_BRIDGE)); - List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); - assertThat(roles, hasSize(1)); - assertThat(roles, containsInAnyOrder(ROLE_ALL_INDEX_SEARCH.getName())); - } - } - - @Test - public void shouldRetrieveBackendRoleFromCertificate_positiveRoleCaptain() { - CertificateData userSpockCertificate = TEST_CERTIFICATES.issueUserCertificate(BACKEND_ROLE_CAPTAIN, USER_KIRK); - try (TestRestClient client = cluster.getRestClient(userSpockCertificate)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); - assertThat(backendRoles, hasSize(1)); - assertThat(backendRoles, containsInAnyOrder(BACKEND_ROLE_CAPTAIN)); - List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); - assertThat(roles, hasSize(0)); - } - } + private static final User USER_ADMIN = new User("admin").roles(ALL_ACCESS); + + public static final String POINTER_BACKEND_ROLES = "/backend_roles"; + public static final String POINTER_ROLES = "/roles"; + + private static final String USER_SPOCK = "spock"; + private static final String USER_KIRK = "kirk"; + + private static final String BACKEND_ROLE_BRIDGE = "bridge"; + private static final String BACKEND_ROLE_CAPTAIN = "captain"; + + private static final Role ROLE_ALL_INDEX_SEARCH = new Role("all-index-search").indexPermissions("indices:data/read/search").on("*"); + + private static final Map CERT_AUTH_CONFIG = Map.of("username_attribute", "cn", "roles_attribute", "ou"); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().nodeSettings( + Map.of("plugins.security.ssl.http.clientauth_mode", "OPTIONAL") + ) + .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .anonymousAuth(false) + .authc( + new AuthcDomain("clientcert_auth_domain", -1, true).httpAuthenticator( + new HttpAuthenticator("clientcert").challenge(false).config(CERT_AUTH_CONFIG) + ).backend("noop") + ) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .roles(ROLE_ALL_INDEX_SEARCH) + .users(USER_ADMIN) + .rolesMapping(new RolesMapping(ROLE_ALL_INDEX_SEARCH).backendRoles(BACKEND_ROLE_BRIDGE)) + .build(); + + private static final TestCertificates TEST_CERTIFICATES = cluster.getTestCertificates(); + + @Test + public void shouldAuthenticateUserWithBasicAuthWhenCertificateAuthenticationIsConfigured() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_OK); + } + } + + @Test + public void shouldAuthenticateUserWithCertificate_positiveUserSpoke() { + CertificateData userSpockCertificate = TEST_CERTIFICATES.issueUserCertificate(BACKEND_ROLE_BRIDGE, USER_SPOCK); + try (TestRestClient client = cluster.getRestClient(userSpockCertificate)) { + + client.assertCorrectCredentials(USER_SPOCK); + } + } + + @Test + public void shouldAuthenticateUserWithCertificate_positiveUserKirk() { + CertificateData userSpockCertificate = TEST_CERTIFICATES.issueUserCertificate(BACKEND_ROLE_BRIDGE, USER_KIRK); + try (TestRestClient client = cluster.getRestClient(userSpockCertificate)) { + + client.assertCorrectCredentials(USER_KIRK); + } + } + + @Test + public void shouldAuthenticateUserWithCertificate_negative() { + CertificateData untrustedUserCertificate = TEST_CERTIFICATES.createSelfSignedCertificate("CN=untrusted"); + try (TestRestClient client = cluster.getRestClient(untrustedUserCertificate)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + } + } + + @Test + public void shouldRetrieveBackendRoleFromCertificate_positiveRoleBridge() { + CertificateData userSpockCertificate = TEST_CERTIFICATES.issueUserCertificate(BACKEND_ROLE_BRIDGE, USER_KIRK); + try (TestRestClient client = cluster.getRestClient(userSpockCertificate)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, containsInAnyOrder(BACKEND_ROLE_BRIDGE)); + List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); + assertThat(roles, hasSize(1)); + assertThat(roles, containsInAnyOrder(ROLE_ALL_INDEX_SEARCH.getName())); + } + } + + @Test + public void shouldRetrieveBackendRoleFromCertificate_positiveRoleCaptain() { + CertificateData userSpockCertificate = TEST_CERTIFICATES.issueUserCertificate(BACKEND_ROLE_CAPTAIN, USER_KIRK); + try (TestRestClient client = cluster.getRestClient(userSpockCertificate)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, containsInAnyOrder(BACKEND_ROLE_CAPTAIN)); + List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); + assertThat(roles, hasSize(0)); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/CommonProxyAuthenticationTests.java b/src/integrationTest/java/org/opensearch/security/http/CommonProxyAuthenticationTests.java index c17ccb8ea3..49ded4f2a9 100644 --- a/src/integrationTest/java/org/opensearch/security/http/CommonProxyAuthenticationTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/CommonProxyAuthenticationTests.java @@ -31,228 +31,225 @@ */ abstract class CommonProxyAuthenticationTests { - protected static final String RESOURCE_AUTH_INFO = "/_opendistro/_security/authinfo"; - protected static final TestSecurityConfig.User USER_ADMIN = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); - - protected static final String ATTRIBUTE_DEPARTMENT = "department"; - protected static final String ATTRIBUTE_SKILLS = "skills"; - - protected static final String USER_ATTRIBUTE_DEPARTMENT_NAME = "attr.proxy." + ATTRIBUTE_DEPARTMENT; - protected static final String USER_ATTRIBUTE_SKILLS_NAME = "attr.proxy." + ATTRIBUTE_SKILLS; - protected static final String USER_ATTRIBUTE_USERNAME_NAME = "attr.proxy.username"; - - protected static final String HEADER_PREFIX_CUSTOM_ATTRIBUTES = "x-custom-attr"; - protected static final String HEADER_PROXY_USER = "x-proxy-user"; - protected static final String HEADER_PROXY_ROLES = "x-proxy-roles"; - protected static final String HEADER_FORWARDED_FOR = "X-Forwarded-For"; - protected static final String HEADER_DEPARTMENT = HEADER_PREFIX_CUSTOM_ATTRIBUTES + ATTRIBUTE_DEPARTMENT; - protected static final String HEADER_SKILLS = HEADER_PREFIX_CUSTOM_ATTRIBUTES + ATTRIBUTE_SKILLS; - - protected static final String IP_PROXY = "127.0.0.10"; - protected static final String IP_NON_PROXY = "127.0.0.5"; - protected static final String IP_CLIENT = "127.0.0.1"; - - protected static final String USER_KIRK = "kirk"; - protected static final String USER_SPOCK = "spock"; - - protected static final String BACKEND_ROLE_FIRST_MATE = "firstMate"; - protected static final String BACKEND_ROLE_CAPTAIN = "captain"; - protected static final String DEPARTMENT_BRIDGE = "bridge"; - - protected static final String PERSONAL_INDEX_NAME_PATTERN = "personal-${" + USER_ATTRIBUTE_DEPARTMENT_NAME + "}-${" + USER_ATTRIBUTE_USERNAME_NAME + "}"; - protected static final String PERSONAL_INDEX_NAME_SPOCK = "personal-" + DEPARTMENT_BRIDGE + "-" + USER_SPOCK; - protected static final String PERSONAL_INDEX_NAME_KIRK = "personal-" + DEPARTMENT_BRIDGE + "-" + USER_KIRK; - - protected static final String POINTER_USERNAME = "/user_name"; - protected static final String POINTER_BACKEND_ROLES = "/backend_roles"; - protected static final String POINTER_ROLES = "/roles"; - protected static final String POINTER_CUSTOM_ATTRIBUTES = "/custom_attribute_names"; - protected static final String POINTER_TOTAL_HITS = "/hits/total/value"; - protected static final String POINTER_FIRST_DOCUMENT_ID = "/hits/hits/0/_id"; - protected static final String POINTER_FIRST_DOCUMENT_INDEX = "/hits/hits/0/_index"; - protected static final String POINTER_FIRST_DOCUMENT_SOURCE_TITLE = "/hits/hits/0/_source/title"; - - protected static final TestSecurityConfig.Role ROLE_ALL_INDEX_SEARCH = new TestSecurityConfig.Role("all-index-search").indexPermissions("indices:data/read/search") - .on("*"); - - protected static final TestSecurityConfig.Role ROLE_PERSONAL_INDEX_SEARCH = new TestSecurityConfig.Role("personal-index-search").indexPermissions("indices:data/read/search") - .on(PERSONAL_INDEX_NAME_PATTERN); - - protected static final RolesMapping ROLES_MAPPING_CAPTAIN = new RolesMapping(ROLE_PERSONAL_INDEX_SEARCH).backendRoles(BACKEND_ROLE_CAPTAIN); - - protected static final RolesMapping ROLES_MAPPING_FIRST_MATE = new RolesMapping(ROLE_ALL_INDEX_SEARCH) - .backendRoles(BACKEND_ROLE_FIRST_MATE); - - protected abstract LocalCluster getCluster(); - - protected void shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured() { - try(TestRestClient client = getCluster().getRestClient(USER_ADMIN)) { - TestRestClient.HttpResponse response = client.get(RESOURCE_AUTH_INFO); - - response.assertStatusCode(200); - } - } - - protected void shouldAuthenticateWithProxy_positiveUserKirk() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_KIRK) - .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN); - try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - String username = response.getTextFromJsonBody(POINTER_USERNAME); - assertThat(username, equalTo(USER_KIRK)); - } - } - - protected void shouldAuthenticateWithProxy_positiveUserSpock() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_SPOCK) - .header(HEADER_PROXY_ROLES, BACKEND_ROLE_FIRST_MATE); - try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - String username = response.getTextFromJsonBody(POINTER_USERNAME); - assertThat(username, equalTo(USER_SPOCK)); - } - } - - protected void shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_PROXY_USER, USER_KIRK) - .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN); - try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - } - } - - protected void shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN); - try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - } - } - - protected void shouldAuthenticateWithProxyWhenRolesHeaderIsMissing() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_KIRK); - try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - String username = response.getTextFromJsonBody(POINTER_USERNAME); - assertThat(username, equalTo(USER_KIRK)); - } - } - - protected void shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_NON_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_KIRK); - try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - } - } - - protected void shouldRetrieveEmptyListOfRoles() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_SPOCK); - try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); - assertThat(backendRoles, hasSize(0)); - List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); - assertThat(roles, hasSize(0)); - } - } - - protected void shouldRetrieveSingleRoleFirstMate() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_SPOCK) - .header(HEADER_PROXY_ROLES, BACKEND_ROLE_FIRST_MATE); - try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); - assertThat(backendRoles, hasSize(1)); - assertThat(backendRoles, contains(BACKEND_ROLE_FIRST_MATE)); - List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); - assertThat(roles, hasSize(1)); - assertThat(roles, contains(ROLE_ALL_INDEX_SEARCH.getName())); - } - } - - protected void shouldRetrieveSingleRoleCaptain() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_SPOCK) - .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN); - try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); - assertThat(backendRoles, hasSize(1)); - assertThat(backendRoles, contains(BACKEND_ROLE_CAPTAIN)); - List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); - assertThat(roles, hasSize(1)); - assertThat(roles, contains(ROLE_PERSONAL_INDEX_SEARCH.getName())); - } - } - - protected void shouldRetrieveMultipleRoles() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_SPOCK) - .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN + "," + BACKEND_ROLE_FIRST_MATE); - try(TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { - - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); - assertThat(backendRoles, hasSize(2)); - assertThat(backendRoles, containsInAnyOrder(BACKEND_ROLE_CAPTAIN, BACKEND_ROLE_FIRST_MATE)); - List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); - assertThat(roles, hasSize(2)); - assertThat(roles, containsInAnyOrder(ROLE_PERSONAL_INDEX_SEARCH.getName(), ROLE_ALL_INDEX_SEARCH.getName())); - } - } + protected static final String RESOURCE_AUTH_INFO = "/_opendistro/_security/authinfo"; + protected static final TestSecurityConfig.User USER_ADMIN = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + protected static final String ATTRIBUTE_DEPARTMENT = "department"; + protected static final String ATTRIBUTE_SKILLS = "skills"; + + protected static final String USER_ATTRIBUTE_DEPARTMENT_NAME = "attr.proxy." + ATTRIBUTE_DEPARTMENT; + protected static final String USER_ATTRIBUTE_SKILLS_NAME = "attr.proxy." + ATTRIBUTE_SKILLS; + protected static final String USER_ATTRIBUTE_USERNAME_NAME = "attr.proxy.username"; + + protected static final String HEADER_PREFIX_CUSTOM_ATTRIBUTES = "x-custom-attr"; + protected static final String HEADER_PROXY_USER = "x-proxy-user"; + protected static final String HEADER_PROXY_ROLES = "x-proxy-roles"; + protected static final String HEADER_FORWARDED_FOR = "X-Forwarded-For"; + protected static final String HEADER_DEPARTMENT = HEADER_PREFIX_CUSTOM_ATTRIBUTES + ATTRIBUTE_DEPARTMENT; + protected static final String HEADER_SKILLS = HEADER_PREFIX_CUSTOM_ATTRIBUTES + ATTRIBUTE_SKILLS; + + protected static final String IP_PROXY = "127.0.0.10"; + protected static final String IP_NON_PROXY = "127.0.0.5"; + protected static final String IP_CLIENT = "127.0.0.1"; + + protected static final String USER_KIRK = "kirk"; + protected static final String USER_SPOCK = "spock"; + + protected static final String BACKEND_ROLE_FIRST_MATE = "firstMate"; + protected static final String BACKEND_ROLE_CAPTAIN = "captain"; + protected static final String DEPARTMENT_BRIDGE = "bridge"; + + protected static final String PERSONAL_INDEX_NAME_PATTERN = "personal-${" + + USER_ATTRIBUTE_DEPARTMENT_NAME + + "}-${" + + USER_ATTRIBUTE_USERNAME_NAME + + "}"; + protected static final String PERSONAL_INDEX_NAME_SPOCK = "personal-" + DEPARTMENT_BRIDGE + "-" + USER_SPOCK; + protected static final String PERSONAL_INDEX_NAME_KIRK = "personal-" + DEPARTMENT_BRIDGE + "-" + USER_KIRK; + + protected static final String POINTER_USERNAME = "/user_name"; + protected static final String POINTER_BACKEND_ROLES = "/backend_roles"; + protected static final String POINTER_ROLES = "/roles"; + protected static final String POINTER_CUSTOM_ATTRIBUTES = "/custom_attribute_names"; + protected static final String POINTER_TOTAL_HITS = "/hits/total/value"; + protected static final String POINTER_FIRST_DOCUMENT_ID = "/hits/hits/0/_id"; + protected static final String POINTER_FIRST_DOCUMENT_INDEX = "/hits/hits/0/_index"; + protected static final String POINTER_FIRST_DOCUMENT_SOURCE_TITLE = "/hits/hits/0/_source/title"; + + protected static final TestSecurityConfig.Role ROLE_ALL_INDEX_SEARCH = new TestSecurityConfig.Role("all-index-search").indexPermissions( + "indices:data/read/search" + ).on("*"); + + protected static final TestSecurityConfig.Role ROLE_PERSONAL_INDEX_SEARCH = new TestSecurityConfig.Role("personal-index-search") + .indexPermissions("indices:data/read/search") + .on(PERSONAL_INDEX_NAME_PATTERN); + + protected static final RolesMapping ROLES_MAPPING_CAPTAIN = new RolesMapping(ROLE_PERSONAL_INDEX_SEARCH).backendRoles( + BACKEND_ROLE_CAPTAIN + ); + + protected static final RolesMapping ROLES_MAPPING_FIRST_MATE = new RolesMapping(ROLE_ALL_INDEX_SEARCH).backendRoles( + BACKEND_ROLE_FIRST_MATE + ); + + protected abstract LocalCluster getCluster(); + + protected void shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured() { + try (TestRestClient client = getCluster().getRestClient(USER_ADMIN)) { + TestRestClient.HttpResponse response = client.get(RESOURCE_AUTH_INFO); + + response.assertStatusCode(200); + } + } + + protected void shouldAuthenticateWithProxy_positiveUserKirk() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ).header(HEADER_FORWARDED_FOR, IP_CLIENT).header(HEADER_PROXY_USER, USER_KIRK).header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN); + try (TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USER_KIRK)); + } + } + + protected void shouldAuthenticateWithProxy_positiveUserSpock() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ).header(HEADER_FORWARDED_FOR, IP_CLIENT).header(HEADER_PROXY_USER, USER_SPOCK).header(HEADER_PROXY_ROLES, BACKEND_ROLE_FIRST_MATE); + try (TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USER_SPOCK)); + } + } + + protected void shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ).header(HEADER_PROXY_USER, USER_KIRK).header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN); + try (TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + } + } + + protected void shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ).header(HEADER_FORWARDED_FOR, IP_CLIENT).header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN); + try (TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + } + } + + protected void shouldAuthenticateWithProxyWhenRolesHeaderIsMissing() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ).header(HEADER_FORWARDED_FOR, IP_CLIENT).header(HEADER_PROXY_USER, USER_KIRK); + try (TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USER_KIRK)); + } + } + + protected void shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_NON_PROXY) + ).header(HEADER_FORWARDED_FOR, IP_CLIENT).header(HEADER_PROXY_USER, USER_KIRK); + try (TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + } + } + + protected void shouldRetrieveEmptyListOfRoles() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ).header(HEADER_FORWARDED_FOR, IP_CLIENT).header(HEADER_PROXY_USER, USER_SPOCK); + try (TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(0)); + List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); + assertThat(roles, hasSize(0)); + } + } + + protected void shouldRetrieveSingleRoleFirstMate() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ).header(HEADER_FORWARDED_FOR, IP_CLIENT).header(HEADER_PROXY_USER, USER_SPOCK).header(HEADER_PROXY_ROLES, BACKEND_ROLE_FIRST_MATE); + try (TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, contains(BACKEND_ROLE_FIRST_MATE)); + List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); + assertThat(roles, hasSize(1)); + assertThat(roles, contains(ROLE_ALL_INDEX_SEARCH.getName())); + } + } + + protected void shouldRetrieveSingleRoleCaptain() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ).header(HEADER_FORWARDED_FOR, IP_CLIENT).header(HEADER_PROXY_USER, USER_SPOCK).header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN); + try (TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, contains(BACKEND_ROLE_CAPTAIN)); + List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); + assertThat(roles, hasSize(1)); + assertThat(roles, contains(ROLE_PERSONAL_INDEX_SEARCH.getName())); + } + } + + protected void shouldRetrieveMultipleRoles() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN + "," + BACKEND_ROLE_FIRST_MATE); + try (TestRestClient client = getCluster().createGenericClientRestClient(testRestClientConfiguration)) { + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(2)); + assertThat(backendRoles, containsInAnyOrder(BACKEND_ROLE_CAPTAIN, BACKEND_ROLE_FIRST_MATE)); + List roles = response.getTextArrayFromJsonBody(POINTER_ROLES); + assertThat(roles, hasSize(2)); + assertThat(roles, containsInAnyOrder(ROLE_PERSONAL_INDEX_SEARCH.getName(), ROLE_ALL_INDEX_SEARCH.getName())); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/DirectoryInformationTrees.java b/src/integrationTest/java/org/opensearch/security/http/DirectoryInformationTrees.java index 8403106522..3f9c220923 100644 --- a/src/integrationTest/java/org/opensearch/security/http/DirectoryInformationTrees.java +++ b/src/integrationTest/java/org/opensearch/security/http/DirectoryInformationTrees.java @@ -14,111 +14,110 @@ class DirectoryInformationTrees { - public static final String DN_PEOPLE_TEST_ORG = "ou=people,o=test.org"; - public static final String DN_OPEN_SEARCH_PEOPLE_TEST_ORG = "cn=Open Search,ou=people,o=test.org"; - public static final String DN_CHRISTPHER_PEOPLE_TEST_ORG = "cn=Christpher,ou=people,o=test.org"; - public static final String DN_KIRK_PEOPLE_TEST_ORG = "cn=Kirk,ou=people,o=test.org"; - public static final String DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG = "cn=Captain Spock,ou=people,o=test.org"; - public static final String DN_LEONARD_PEOPLE_TEST_ORG = "cn=Leonard,ou=people,o=test.org"; - public static final String DN_JEAN_PEOPLE_TEST_ORG = "cn=Jean,ou=people,o=test.org"; - public static final String DN_GROUPS_TEST_ORG = "ou=groups,o=test.org"; - public static final String DN_BRIDGE_GROUPS_TEST_ORG = "cn=bridge,ou=groups,o=test.org"; + public static final String DN_PEOPLE_TEST_ORG = "ou=people,o=test.org"; + public static final String DN_OPEN_SEARCH_PEOPLE_TEST_ORG = "cn=Open Search,ou=people,o=test.org"; + public static final String DN_CHRISTPHER_PEOPLE_TEST_ORG = "cn=Christpher,ou=people,o=test.org"; + public static final String DN_KIRK_PEOPLE_TEST_ORG = "cn=Kirk,ou=people,o=test.org"; + public static final String DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG = "cn=Captain Spock,ou=people,o=test.org"; + public static final String DN_LEONARD_PEOPLE_TEST_ORG = "cn=Leonard,ou=people,o=test.org"; + public static final String DN_JEAN_PEOPLE_TEST_ORG = "cn=Jean,ou=people,o=test.org"; + public static final String DN_GROUPS_TEST_ORG = "ou=groups,o=test.org"; + public static final String DN_BRIDGE_GROUPS_TEST_ORG = "cn=bridge,ou=groups,o=test.org"; - public static final String USER_KIRK = "kirk"; - public static final String PASSWORD_KIRK = "kirk-secret"; - public static final String USER_SPOCK = "spock"; - public static final String PASSWORD_SPOCK = "spocksecret"; - public static final String USER_OPENS = "opens"; - public static final String PASSWORD_OPEN_SEARCH = "open_search-secret"; - public static final String USER_JEAN = "jean"; - public static final String PASSWORD_JEAN = "jeansecret"; - public static final String USER_LEONARD = "leonard"; - public static final String PASSWORD_LEONARD = "Leonard-secret"; - public static final String PASSWORD_CHRISTPHER = "christpher_secret"; + public static final String USER_KIRK = "kirk"; + public static final String PASSWORD_KIRK = "kirk-secret"; + public static final String USER_SPOCK = "spock"; + public static final String PASSWORD_SPOCK = "spocksecret"; + public static final String USER_OPENS = "opens"; + public static final String PASSWORD_OPEN_SEARCH = "open_search-secret"; + public static final String USER_JEAN = "jean"; + public static final String PASSWORD_JEAN = "jeansecret"; + public static final String USER_LEONARD = "leonard"; + public static final String PASSWORD_LEONARD = "Leonard-secret"; + public static final String PASSWORD_CHRISTPHER = "christpher_secret"; - public static final String CN_GROUP_ADMIN = "admin"; - public static final String CN_GROUP_CREW = "crew"; - public static final String CN_GROUP_BRIDGE = "bridge"; + public static final String CN_GROUP_ADMIN = "admin"; + public static final String CN_GROUP_CREW = "crew"; + public static final String CN_GROUP_BRIDGE = "bridge"; - public static final String USER_SEARCH = "(uid={0})"; - public static final String USERNAME_ATTRIBUTE = "uid"; + public static final String USER_SEARCH = "(uid={0})"; + public static final String USERNAME_ATTRIBUTE = "uid"; - static final LdifData LDIF_DATA = new LdifBuilder() - .root("o=test.org") - .dc("TEST") - .classes("top", "domain") - .newRecord(DN_PEOPLE_TEST_ORG) - .ou("people") - .classes("organizationalUnit", "top") - .newRecord(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) - .classes("inetOrgPerson") - .cn("Open Search") - .sn("Search") - .uid(USER_OPENS) - .userPassword(PASSWORD_OPEN_SEARCH) - .mail("open.search@example.com") - .ou("Human Resources") - .newRecord(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) - .classes("inetOrgPerson") - .cn("Captain Spock") - .sn(USER_SPOCK) - .uid(USER_SPOCK) - .userPassword(PASSWORD_SPOCK) - .mail("spock@example.com") - .ou("Human Resources") - .newRecord(DN_KIRK_PEOPLE_TEST_ORG) - .classes("inetOrgPerson") - .cn("Kirk") - .sn("Kirk") - .uid(USER_KIRK) - .userPassword(PASSWORD_KIRK) - .mail("spock@example.com") - .ou("Human Resources") - .newRecord(DN_CHRISTPHER_PEOPLE_TEST_ORG) - .classes("inetOrgPerson") - .cn("Christpher") - .sn("Christpher") - .uid("christpher") - .userPassword(PASSWORD_CHRISTPHER) - .mail("christpher@example.com") - .ou("Human Resources") - .newRecord(DN_LEONARD_PEOPLE_TEST_ORG) - .classes("inetOrgPerson") - .cn("Leonard") - .sn("Leonard") - .uid(USER_LEONARD) - .userPassword(PASSWORD_LEONARD) - .mail("leonard@example.com") - .ou("Human Resources") - .newRecord(DN_JEAN_PEOPLE_TEST_ORG) - .classes("inetOrgPerson") - .cn("Jean") - .sn("Jean") - .uid(USER_JEAN) - .userPassword(PASSWORD_JEAN) - .mail("jean@example.com") - .ou("Human Resources") - .newRecord(DN_GROUPS_TEST_ORG) - .ou("groups") - .cn("groupsRoot") - .classes("groupofuniquenames", "top") - .newRecord("cn=admin,ou=groups,o=test.org") - .ou("groups") - .cn(CN_GROUP_ADMIN) - .uniqueMember(DN_KIRK_PEOPLE_TEST_ORG) - .classes("groupofuniquenames", "top") - .newRecord("cn=crew,ou=groups,o=test.org") - .ou("groups") - .cn(CN_GROUP_CREW) - .uniqueMember(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) - .uniqueMember(DN_CHRISTPHER_PEOPLE_TEST_ORG) - .uniqueMember(DN_BRIDGE_GROUPS_TEST_ORG) - .classes("groupofuniquenames", "top") - .newRecord(DN_BRIDGE_GROUPS_TEST_ORG) - .ou("groups") - .cn(CN_GROUP_BRIDGE) - .uniqueMember(DN_JEAN_PEOPLE_TEST_ORG) - .classes("groupofuniquenames", "top") - .buildRecord() - .buildLdif(); + static final LdifData LDIF_DATA = new LdifBuilder().root("o=test.org") + .dc("TEST") + .classes("top", "domain") + .newRecord(DN_PEOPLE_TEST_ORG) + .ou("people") + .classes("organizationalUnit", "top") + .newRecord(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) + .classes("inetOrgPerson") + .cn("Open Search") + .sn("Search") + .uid(USER_OPENS) + .userPassword(PASSWORD_OPEN_SEARCH) + .mail("open.search@example.com") + .ou("Human Resources") + .newRecord(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) + .classes("inetOrgPerson") + .cn("Captain Spock") + .sn(USER_SPOCK) + .uid(USER_SPOCK) + .userPassword(PASSWORD_SPOCK) + .mail("spock@example.com") + .ou("Human Resources") + .newRecord(DN_KIRK_PEOPLE_TEST_ORG) + .classes("inetOrgPerson") + .cn("Kirk") + .sn("Kirk") + .uid(USER_KIRK) + .userPassword(PASSWORD_KIRK) + .mail("spock@example.com") + .ou("Human Resources") + .newRecord(DN_CHRISTPHER_PEOPLE_TEST_ORG) + .classes("inetOrgPerson") + .cn("Christpher") + .sn("Christpher") + .uid("christpher") + .userPassword(PASSWORD_CHRISTPHER) + .mail("christpher@example.com") + .ou("Human Resources") + .newRecord(DN_LEONARD_PEOPLE_TEST_ORG) + .classes("inetOrgPerson") + .cn("Leonard") + .sn("Leonard") + .uid(USER_LEONARD) + .userPassword(PASSWORD_LEONARD) + .mail("leonard@example.com") + .ou("Human Resources") + .newRecord(DN_JEAN_PEOPLE_TEST_ORG) + .classes("inetOrgPerson") + .cn("Jean") + .sn("Jean") + .uid(USER_JEAN) + .userPassword(PASSWORD_JEAN) + .mail("jean@example.com") + .ou("Human Resources") + .newRecord(DN_GROUPS_TEST_ORG) + .ou("groups") + .cn("groupsRoot") + .classes("groupofuniquenames", "top") + .newRecord("cn=admin,ou=groups,o=test.org") + .ou("groups") + .cn(CN_GROUP_ADMIN) + .uniqueMember(DN_KIRK_PEOPLE_TEST_ORG) + .classes("groupofuniquenames", "top") + .newRecord("cn=crew,ou=groups,o=test.org") + .ou("groups") + .cn(CN_GROUP_CREW) + .uniqueMember(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) + .uniqueMember(DN_CHRISTPHER_PEOPLE_TEST_ORG) + .uniqueMember(DN_BRIDGE_GROUPS_TEST_ORG) + .classes("groupofuniquenames", "top") + .newRecord(DN_BRIDGE_GROUPS_TEST_ORG) + .ou("groups") + .cn(CN_GROUP_BRIDGE) + .uniqueMember(DN_JEAN_PEOPLE_TEST_ORG) + .classes("groupofuniquenames", "top") + .buildRecord() + .buildLdif(); } diff --git a/src/integrationTest/java/org/opensearch/security/http/DisabledBasicAuthTests.java b/src/integrationTest/java/org/opensearch/security/http/DisabledBasicAuthTests.java index 5398ea77f7..7ae856ba8b 100644 --- a/src/integrationTest/java/org/opensearch/security/http/DisabledBasicAuthTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/DisabledBasicAuthTests.java @@ -28,20 +28,21 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class DisabledBasicAuthTests { - @ClassRule - public static LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) - .authc(DISABLED_AUTHC_HTTPBASIC_INTERNAL).users(TEST_USER) - .authc(JWT_AUTH_DOMAIN) - .build(); - - @Test - public void shouldRespondWith401WhenCredentialsAreCorrectButBasicAuthIsDisabled() { - try (TestRestClient client = cluster.getRestClient(TEST_USER)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(SC_UNAUTHORIZED); - } - } + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .authc(DISABLED_AUTHC_HTTPBASIC_INTERNAL) + .users(TEST_USER) + .authc(JWT_AUTH_DOMAIN) + .build(); + + @Test + public void shouldRespondWith401WhenCredentialsAreCorrectButBasicAuthIsDisabled() { + try (TestRestClient client = cluster.getRestClient(TEST_USER)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(SC_UNAUTHORIZED); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/ExtendedProxyAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/ExtendedProxyAuthenticationTest.java index 4278a07a53..6fcc7eac83 100644 --- a/src/integrationTest/java/org/opensearch/security/http/ExtendedProxyAuthenticationTest.java +++ b/src/integrationTest/java/org/opensearch/security/http/ExtendedProxyAuthenticationTest.java @@ -48,202 +48,213 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class ExtendedProxyAuthenticationTest extends CommonProxyAuthenticationTests { - - public static final String ID_ONE_1 = "one#1"; - public static final String ID_TWO_2 = "two#2"; - public static final Map PROXY_AUTHENTICATOR_CONFIG = Map.of( - "user_header", HEADER_PROXY_USER, - "roles_header", HEADER_PROXY_ROLES, - "attr_header_prefix", HEADER_PREFIX_CUSTOM_ATTRIBUTES - ); - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) - .xff(new XffConfig(true).internalProxiesRegexp("127\\.0\\.0\\.10")) - .authc(new AuthcDomain("proxy_auth_domain", -5, true) - .httpAuthenticator(new HttpAuthenticator("extended-proxy").challenge(false).config(PROXY_AUTHENTICATOR_CONFIG)) - .backend(new AuthenticationBackend("noop"))) - .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_ADMIN).roles(ROLE_ALL_INDEX_SEARCH, ROLE_PERSONAL_INDEX_SEARCH) - .rolesMapping(ROLES_MAPPING_CAPTAIN, ROLES_MAPPING_FIRST_MATE).build(); - - @Override - protected LocalCluster getCluster() { - return cluster; - } - - @BeforeClass - public static void createTestData() { - try(Client client = cluster.getInternalNodeClient()){ - client.prepareIndex(PERSONAL_INDEX_NAME_SPOCK).setId(ID_ONE_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); - client.prepareIndex(PERSONAL_INDEX_NAME_KIRK).setId(ID_TWO_2).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1].asMap()).get(); - } - } - - @Test - @Override - public void shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured() { - super.shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured(); - } - - @Test - @Override - public void shouldAuthenticateWithProxy_positiveUserKirk() throws IOException { - super.shouldAuthenticateWithProxy_positiveUserKirk(); - } - - @Test - @Override - public void shouldAuthenticateWithProxy_positiveUserSpock() throws IOException { - super.shouldAuthenticateWithProxy_positiveUserSpock(); - } - - @Test - @Override - public void shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing() throws IOException { - super.shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing(); - } - - @Test - @Override - public void shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing() throws IOException { - super.shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing(); - } - - @Test - @Override - public void shouldAuthenticateWithProxyWhenRolesHeaderIsMissing() throws IOException { - super.shouldAuthenticateWithProxyWhenRolesHeaderIsMissing(); - } - - @Test - @Override - public void shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy() throws IOException { - super.shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy(); - } - - @Test - @Override - public void shouldRetrieveEmptyListOfRoles() throws IOException { - super.shouldRetrieveEmptyListOfRoles(); - } - - @Test - @Override - public void shouldRetrieveSingleRoleFirstMate() throws IOException { - super.shouldRetrieveSingleRoleFirstMate(); - } - - @Test - @Override - public void shouldRetrieveSingleRoleCaptain() throws IOException { - super.shouldRetrieveSingleRoleCaptain(); - } - - @Test - @Override - public void shouldRetrieveMultipleRoles() throws IOException { - super.shouldRetrieveMultipleRoles(); - } - - // tests specific for extended proxy authentication - - @Test - public void shouldRetrieveCustomAttributeNameDepartment() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_SPOCK) - .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) - .header(HEADER_DEPARTMENT, DEPARTMENT_BRIDGE); - try(TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List customAttributes = response.getTextArrayFromJsonBody(POINTER_CUSTOM_ATTRIBUTES); - assertThat(customAttributes, hasSize(2)); - assertThat(customAttributes, containsInAnyOrder(USER_ATTRIBUTE_USERNAME_NAME, USER_ATTRIBUTE_DEPARTMENT_NAME)); - } - } - - @Test - public void shouldRetrieveCustomAttributeNameSkills() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_SPOCK) - .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) - .header(HEADER_SKILLS, "bilocation"); - try(TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List customAttributes = response.getTextArrayFromJsonBody(POINTER_CUSTOM_ATTRIBUTES); - assertThat(customAttributes, hasSize(2)); - assertThat(customAttributes, containsInAnyOrder(USER_ATTRIBUTE_USERNAME_NAME, USER_ATTRIBUTE_SKILLS_NAME)); - } - } - - @Test - public void shouldRetrieveMultipleCustomAttributes() throws IOException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_SPOCK) - .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) - .header(HEADER_DEPARTMENT, DEPARTMENT_BRIDGE) - .header(HEADER_SKILLS, "bilocation"); - try(TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List customAttributes = response.getTextArrayFromJsonBody(POINTER_CUSTOM_ATTRIBUTES); - assertThat(customAttributes, hasSize(3)); - assertThat(customAttributes, containsInAnyOrder( - USER_ATTRIBUTE_DEPARTMENT_NAME, - USER_ATTRIBUTE_USERNAME_NAME, - USER_ATTRIBUTE_SKILLS_NAME) - ); - } - } - - @Test - public void shouldRetrieveUserRolesAndAttributesSoThatAccessToPersonalIndexIsPossible_positive() throws UnknownHostException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_SPOCK) - .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) - .header(HEADER_DEPARTMENT, DEPARTMENT_BRIDGE); - try(TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { - - HttpResponse response = client.get("/" + PERSONAL_INDEX_NAME_SPOCK + "/_search"); - - response.assertStatusCode(200); - assertThat(response.getLongFromJsonBody(POINTER_TOTAL_HITS), equalTo(1L)); - assertThat(response.getTextFromJsonBody(POINTER_FIRST_DOCUMENT_ID), equalTo(ID_ONE_1)); - assertThat(response.getTextFromJsonBody(POINTER_FIRST_DOCUMENT_INDEX), equalTo(PERSONAL_INDEX_NAME_SPOCK)); - assertThat(response.getTextFromJsonBody(POINTER_FIRST_DOCUMENT_SOURCE_TITLE), equalTo(TITLE_MAGNUM_OPUS)); - } - } - - @Test - public void shouldRetrieveUserRolesAndAttributesSoThatAccessToPersonalIndexIsPossible_negative() throws UnknownHostException { - TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration() - .sourceInetAddress(InetAddress.getByName(IP_PROXY)) - .header(HEADER_FORWARDED_FOR, IP_CLIENT) - .header(HEADER_PROXY_USER, USER_SPOCK) - .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) - .header(HEADER_DEPARTMENT, DEPARTMENT_BRIDGE); - try(TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { - - HttpResponse response = client.get("/" + PERSONAL_INDEX_NAME_KIRK + "/_search"); - - response.assertStatusCode(403); - } - } + public static final String ID_ONE_1 = "one#1"; + public static final String ID_TWO_2 = "two#2"; + public static final Map PROXY_AUTHENTICATOR_CONFIG = Map.of( + "user_header", + HEADER_PROXY_USER, + "roles_header", + HEADER_PROXY_ROLES, + "attr_header_prefix", + HEADER_PREFIX_CUSTOM_ATTRIBUTES + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .xff(new XffConfig(true).internalProxiesRegexp("127\\.0\\.0\\.10")) + .authc( + new AuthcDomain("proxy_auth_domain", -5, true).httpAuthenticator( + new HttpAuthenticator("extended-proxy").challenge(false).config(PROXY_AUTHENTICATOR_CONFIG) + ).backend(new AuthenticationBackend("noop")) + ) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(USER_ADMIN) + .roles(ROLE_ALL_INDEX_SEARCH, ROLE_PERSONAL_INDEX_SEARCH) + .rolesMapping(ROLES_MAPPING_CAPTAIN, ROLES_MAPPING_FIRST_MATE) + .build(); + + @Override + protected LocalCluster getCluster() { + return cluster; + } + + @BeforeClass + public static void createTestData() { + try (Client client = cluster.getInternalNodeClient()) { + client.prepareIndex(PERSONAL_INDEX_NAME_SPOCK).setId(ID_ONE_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); + client.prepareIndex(PERSONAL_INDEX_NAME_KIRK).setId(ID_TWO_2).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1].asMap()).get(); + } + } + + @Test + @Override + public void shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured() { + super.shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_positiveUserKirk() throws IOException { + super.shouldAuthenticateWithProxy_positiveUserKirk(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_positiveUserSpock() throws IOException { + super.shouldAuthenticateWithProxy_positiveUserSpock(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing() throws IOException { + super.shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing() throws IOException { + super.shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing(); + } + + @Test + @Override + public void shouldAuthenticateWithProxyWhenRolesHeaderIsMissing() throws IOException { + super.shouldAuthenticateWithProxyWhenRolesHeaderIsMissing(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy() throws IOException { + super.shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy(); + } + + @Test + @Override + public void shouldRetrieveEmptyListOfRoles() throws IOException { + super.shouldRetrieveEmptyListOfRoles(); + } + + @Test + @Override + public void shouldRetrieveSingleRoleFirstMate() throws IOException { + super.shouldRetrieveSingleRoleFirstMate(); + } + + @Test + @Override + public void shouldRetrieveSingleRoleCaptain() throws IOException { + super.shouldRetrieveSingleRoleCaptain(); + } + + @Test + @Override + public void shouldRetrieveMultipleRoles() throws IOException { + super.shouldRetrieveMultipleRoles(); + } + + // tests specific for extended proxy authentication + + @Test + public void shouldRetrieveCustomAttributeNameDepartment() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) + .header(HEADER_DEPARTMENT, DEPARTMENT_BRIDGE); + try (TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List customAttributes = response.getTextArrayFromJsonBody(POINTER_CUSTOM_ATTRIBUTES); + assertThat(customAttributes, hasSize(2)); + assertThat(customAttributes, containsInAnyOrder(USER_ATTRIBUTE_USERNAME_NAME, USER_ATTRIBUTE_DEPARTMENT_NAME)); + } + } + + @Test + public void shouldRetrieveCustomAttributeNameSkills() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) + .header(HEADER_SKILLS, "bilocation"); + try (TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List customAttributes = response.getTextArrayFromJsonBody(POINTER_CUSTOM_ATTRIBUTES); + assertThat(customAttributes, hasSize(2)); + assertThat(customAttributes, containsInAnyOrder(USER_ATTRIBUTE_USERNAME_NAME, USER_ATTRIBUTE_SKILLS_NAME)); + } + } + + @Test + public void shouldRetrieveMultipleCustomAttributes() throws IOException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) + .header(HEADER_DEPARTMENT, DEPARTMENT_BRIDGE) + .header(HEADER_SKILLS, "bilocation"); + try (TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List customAttributes = response.getTextArrayFromJsonBody(POINTER_CUSTOM_ATTRIBUTES); + assertThat(customAttributes, hasSize(3)); + assertThat( + customAttributes, + containsInAnyOrder(USER_ATTRIBUTE_DEPARTMENT_NAME, USER_ATTRIBUTE_USERNAME_NAME, USER_ATTRIBUTE_SKILLS_NAME) + ); + } + } + + @Test + public void shouldRetrieveUserRolesAndAttributesSoThatAccessToPersonalIndexIsPossible_positive() throws UnknownHostException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) + .header(HEADER_DEPARTMENT, DEPARTMENT_BRIDGE); + try (TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { + + HttpResponse response = client.get("/" + PERSONAL_INDEX_NAME_SPOCK + "/_search"); + + response.assertStatusCode(200); + assertThat(response.getLongFromJsonBody(POINTER_TOTAL_HITS), equalTo(1L)); + assertThat(response.getTextFromJsonBody(POINTER_FIRST_DOCUMENT_ID), equalTo(ID_ONE_1)); + assertThat(response.getTextFromJsonBody(POINTER_FIRST_DOCUMENT_INDEX), equalTo(PERSONAL_INDEX_NAME_SPOCK)); + assertThat(response.getTextFromJsonBody(POINTER_FIRST_DOCUMENT_SOURCE_TITLE), equalTo(TITLE_MAGNUM_OPUS)); + } + } + + @Test + public void shouldRetrieveUserRolesAndAttributesSoThatAccessToPersonalIndexIsPossible_negative() throws UnknownHostException { + TestRestClientConfiguration testRestClientConfiguration = new TestRestClientConfiguration().sourceInetAddress( + InetAddress.getByName(IP_PROXY) + ) + .header(HEADER_FORWARDED_FOR, IP_CLIENT) + .header(HEADER_PROXY_USER, USER_SPOCK) + .header(HEADER_PROXY_ROLES, BACKEND_ROLE_CAPTAIN) + .header(HEADER_DEPARTMENT, DEPARTMENT_BRIDGE); + try (TestRestClient client = cluster.createGenericClientRestClient(testRestClientConfiguration)) { + + HttpResponse response = client.get("/" + PERSONAL_INDEX_NAME_KIRK + "/_search"); + + response.assertStatusCode(403); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java b/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java index 65a4e32d7e..5226d8854c 100644 --- a/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java +++ b/src/integrationTest/java/org/opensearch/security/http/JwtAuthenticationTests.java @@ -19,7 +19,7 @@ import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import org.apache.hc.core5.http.Header; -import org.apache.hc.core5.http.message.BasicHeader ; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; @@ -67,197 +67,204 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class JwtAuthenticationTests { - public static final String CLAIM_USERNAME = "preferred-username"; - public static final String CLAIM_ROLES = "backend-user-roles"; - - public static final String USER_SUPERHERO = "superhero"; - public static final String USERNAME_ROOT = "root"; - public static final String ROLE_ADMIN = "role_admin"; - public static final String ROLE_DEVELOPER = "role_developer"; - public static final String ROLE_QA = "role_qa"; - public static final String ROLE_CTO = "role_cto"; - public static final String ROLE_CEO = "role_ceo"; - public static final String ROLE_VP = "role_vp"; - public static final String POINTER_BACKEND_ROLES = "/backend_roles"; - public static final String POINTER_USERNAME = "/user_name"; - - public static final String QA_DEPARTMENT = "qa-department"; - - public static final String CLAIM_DEPARTMENT = "department"; - - public static final String DEPARTMENT_SONG_INDEX_PATTERN = String.format("song_lyrics_${attr.jwt.%s}", CLAIM_DEPARTMENT); - - public static final String QA_SONG_INDEX_NAME = String.format("song_lyrics_%s", QA_DEPARTMENT); - - private static final KeyPair KEY_PAIR = Keys.keyPairFor(SignatureAlgorithm.RS256); - private static final String PUBLIC_KEY = new String(Base64.getEncoder().encode(KEY_PAIR.getPublic().getEncoded()), US_ASCII); - - static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); - - private static final String JWT_AUTH_HEADER = "jwt-auth"; - - private static final JwtAuthorizationHeaderFactory tokenFactory = new JwtAuthorizationHeaderFactory( - KEY_PAIR.getPrivate(), - CLAIM_USERNAME, - CLAIM_ROLES, - JWT_AUTH_HEADER); - - public static final TestSecurityConfig.AuthcDomain JWT_AUTH_DOMAIN = new TestSecurityConfig - .AuthcDomain("jwt", BASIC_AUTH_DOMAIN_ORDER - 1) - .jwtHttpAuthenticator(new JwtConfigBuilder().jwtHeader(JWT_AUTH_HEADER).signingKey(PUBLIC_KEY).subjectKey(CLAIM_USERNAME) - .rolesKey(CLAIM_ROLES)) - .backend("noop"); - public static final String SONG_ID_1 = "song-id-01"; - - public static final Role DEPARTMENT_SONG_LISTENER_ROLE = new Role("department-song-listener-role") - .indexPermissions("indices:data/read/search").on(DEPARTMENT_SONG_INDEX_PATTERN); - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) - .nodeSettings(Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() +"__" + ALL_ACCESS.getName()))) - .authc(AUTHC_HTTPBASIC_INTERNAL).users(ADMIN_USER).roles(DEPARTMENT_SONG_LISTENER_ROLE) - .authc(JWT_AUTH_DOMAIN) - .build(); - - @Rule - public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator"); - - @BeforeClass - public static void createTestData() { - try (Client client = cluster.getInternalNodeClient()) { - client.prepareIndex(QA_SONG_INDEX_NAME).setId(SONG_ID_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); - } - try(TestRestClient client = cluster.getRestClient(ADMIN_USER)){ - client.createRoleMapping(ROLE_VP, DEPARTMENT_SONG_LISTENER_ROLE.getName()); - } - } - - @Test - public void shouldAuthenticateWithJwtToken_positive() { - try(TestRestClient client = cluster.getRestClient(tokenFactory.generateValidToken(USER_SUPERHERO))){ - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - String username = response.getTextFromJsonBody(POINTER_USERNAME); - assertThat(username, equalTo(USER_SUPERHERO)); - } - } - - @Test - public void shouldAuthenticateWithJwtToken_positiveWithAnotherUsername() { - try(TestRestClient client = cluster.getRestClient(tokenFactory.generateValidToken(USERNAME_ROOT))){ - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - String username = response.getTextFromJsonBody(POINTER_USERNAME); - assertThat(username, equalTo(USERNAME_ROOT)); - } - } - - @Test - public void shouldAuthenticateWithJwtToken_failureLackingUserName() { - try(TestRestClient client = cluster.getRestClient(tokenFactory.generateTokenWithoutPreferredUsername(USER_SUPERHERO))){ - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - logsRule.assertThatContainExactly("No subject found in JWT token"); - } - } - - @Test - public void shouldAuthenticateWithJwtToken_failureExpiredToken() { - try(TestRestClient client = cluster.getRestClient(tokenFactory.generateExpiredToken(USER_SUPERHERO))){ - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - logsRule.assertThatContainExactly("Invalid or expired JWT token."); - } - } - - @Test - public void shouldAuthenticateWithJwtToken_failureIncorrectFormatOfToken() { - Header header = new BasicHeader(AUTHORIZATION, "not.a.token"); - try(TestRestClient client = cluster.getRestClient(header)){ - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - logsRule.assertThatContainExactly(String.format("No JWT token found in '%s' header header", JWT_AUTH_HEADER)); - } - } - - @Test - public void shouldAuthenticateWithJwtToken_failureIncorrectSignature() { - KeyPair incorrectKeyPair = Keys.keyPairFor(SignatureAlgorithm.RS256); - Header header = tokenFactory.generateTokenSignedWithKey(incorrectKeyPair.getPrivate(), USER_SUPERHERO); - try(TestRestClient client = cluster.getRestClient(header)){ - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - logsRule.assertThatContainExactly("Invalid or expired JWT token."); - } - } - - @Test - public void shouldReadRolesFromToken_positiveFirstRoleSet() { - Header header = tokenFactory.generateValidToken(USER_SUPERHERO, ROLE_ADMIN, ROLE_DEVELOPER, ROLE_QA); - try(TestRestClient client = cluster.getRestClient(header)){ - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List roles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); - assertThat(roles, hasSize(3)); - assertThat(roles, containsInAnyOrder(ROLE_ADMIN, ROLE_DEVELOPER, ROLE_QA)); - } - } - - @Test - public void shouldReadRolesFromToken_positiveSecondRoleSet() { - Header header = tokenFactory.generateValidToken(USER_SUPERHERO, ROLE_CTO, ROLE_CEO, ROLE_VP); - try(TestRestClient client = cluster.getRestClient(header)){ - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List roles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); - assertThat(roles, hasSize(3)); - assertThat(roles, containsInAnyOrder(ROLE_CTO, ROLE_CEO, ROLE_VP)); - } - } - - @Test - public void shouldExposeTokenClaimsAsUserAttributes_positive() throws IOException { - String[] roles = { ROLE_VP }; - Map additionalClaims = Map.of(CLAIM_DEPARTMENT, QA_DEPARTMENT); - Header header = tokenFactory.generateValidTokenWithCustomClaims(USER_SUPERHERO, roles, additionalClaims); - try(RestHighLevelClient client = cluster.getRestHighLevelClient(List.of(header))){ - SearchRequest searchRequest = queryStringQueryRequest(QA_SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS); - - SearchResponse response = client.search(searchRequest, DEFAULT); - - assertThat(response, isSuccessfulSearchResponse()); - assertThat(response, numberOfTotalHitsIsEqualTo(1)); - assertThat(response, searchHitsContainDocumentWithId(0, QA_SONG_INDEX_NAME, SONG_ID_1)); - assertThat(response, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); - } - } - - @Test - public void shouldExposeTokenClaimsAsUserAttributes_negative() throws IOException { - String[] roles = { ROLE_VP }; - Map additionalClaims = Map.of(CLAIM_DEPARTMENT, "department-without-access-to-qa-song-index"); - Header header = tokenFactory.generateValidTokenWithCustomClaims(USER_SUPERHERO, roles, additionalClaims); - try(RestHighLevelClient client = cluster.getRestHighLevelClient(List.of(header))){ - SearchRequest searchRequest = queryStringQueryRequest(QA_SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS); - - assertThatThrownBy(() -> client.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); - } - } + public static final String CLAIM_USERNAME = "preferred-username"; + public static final String CLAIM_ROLES = "backend-user-roles"; + + public static final String USER_SUPERHERO = "superhero"; + public static final String USERNAME_ROOT = "root"; + public static final String ROLE_ADMIN = "role_admin"; + public static final String ROLE_DEVELOPER = "role_developer"; + public static final String ROLE_QA = "role_qa"; + public static final String ROLE_CTO = "role_cto"; + public static final String ROLE_CEO = "role_ceo"; + public static final String ROLE_VP = "role_vp"; + public static final String POINTER_BACKEND_ROLES = "/backend_roles"; + public static final String POINTER_USERNAME = "/user_name"; + + public static final String QA_DEPARTMENT = "qa-department"; + + public static final String CLAIM_DEPARTMENT = "department"; + + public static final String DEPARTMENT_SONG_INDEX_PATTERN = String.format("song_lyrics_${attr.jwt.%s}", CLAIM_DEPARTMENT); + + public static final String QA_SONG_INDEX_NAME = String.format("song_lyrics_%s", QA_DEPARTMENT); + + private static final KeyPair KEY_PAIR = Keys.keyPairFor(SignatureAlgorithm.RS256); + private static final String PUBLIC_KEY = new String(Base64.getEncoder().encode(KEY_PAIR.getPublic().getEncoded()), US_ASCII); + + static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + private static final String JWT_AUTH_HEADER = "jwt-auth"; + + private static final JwtAuthorizationHeaderFactory tokenFactory = new JwtAuthorizationHeaderFactory( + KEY_PAIR.getPrivate(), + CLAIM_USERNAME, + CLAIM_ROLES, + JWT_AUTH_HEADER + ); + + public static final TestSecurityConfig.AuthcDomain JWT_AUTH_DOMAIN = new TestSecurityConfig.AuthcDomain( + "jwt", + BASIC_AUTH_DOMAIN_ORDER - 1 + ).jwtHttpAuthenticator( + new JwtConfigBuilder().jwtHeader(JWT_AUTH_HEADER).signingKey(PUBLIC_KEY).subjectKey(CLAIM_USERNAME).rolesKey(CLAIM_ROLES) + ).backend("noop"); + public static final String SONG_ID_1 = "song-id-01"; + + public static final Role DEPARTMENT_SONG_LISTENER_ROLE = new Role("department-song-listener-role").indexPermissions( + "indices:data/read/search" + ).on(DEPARTMENT_SONG_INDEX_PATTERN); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .nodeSettings( + Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName())) + ) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER) + .roles(DEPARTMENT_SONG_LISTENER_ROLE) + .authc(JWT_AUTH_DOMAIN) + .build(); + + @Rule + public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator"); + + @BeforeClass + public static void createTestData() { + try (Client client = cluster.getInternalNodeClient()) { + client.prepareIndex(QA_SONG_INDEX_NAME).setId(SONG_ID_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); + } + try (TestRestClient client = cluster.getRestClient(ADMIN_USER)) { + client.createRoleMapping(ROLE_VP, DEPARTMENT_SONG_LISTENER_ROLE.getName()); + } + } + + @Test + public void shouldAuthenticateWithJwtToken_positive() { + try (TestRestClient client = cluster.getRestClient(tokenFactory.generateValidToken(USER_SUPERHERO))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USER_SUPERHERO)); + } + } + + @Test + public void shouldAuthenticateWithJwtToken_positiveWithAnotherUsername() { + try (TestRestClient client = cluster.getRestClient(tokenFactory.generateValidToken(USERNAME_ROOT))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USERNAME_ROOT)); + } + } + + @Test + public void shouldAuthenticateWithJwtToken_failureLackingUserName() { + try (TestRestClient client = cluster.getRestClient(tokenFactory.generateTokenWithoutPreferredUsername(USER_SUPERHERO))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + logsRule.assertThatContainExactly("No subject found in JWT token"); + } + } + + @Test + public void shouldAuthenticateWithJwtToken_failureExpiredToken() { + try (TestRestClient client = cluster.getRestClient(tokenFactory.generateExpiredToken(USER_SUPERHERO))) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + logsRule.assertThatContainExactly("Invalid or expired JWT token."); + } + } + + @Test + public void shouldAuthenticateWithJwtToken_failureIncorrectFormatOfToken() { + Header header = new BasicHeader(AUTHORIZATION, "not.a.token"); + try (TestRestClient client = cluster.getRestClient(header)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + logsRule.assertThatContainExactly(String.format("No JWT token found in '%s' header header", JWT_AUTH_HEADER)); + } + } + + @Test + public void shouldAuthenticateWithJwtToken_failureIncorrectSignature() { + KeyPair incorrectKeyPair = Keys.keyPairFor(SignatureAlgorithm.RS256); + Header header = tokenFactory.generateTokenSignedWithKey(incorrectKeyPair.getPrivate(), USER_SUPERHERO); + try (TestRestClient client = cluster.getRestClient(header)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + logsRule.assertThatContainExactly("Invalid or expired JWT token."); + } + } + + @Test + public void shouldReadRolesFromToken_positiveFirstRoleSet() { + Header header = tokenFactory.generateValidToken(USER_SUPERHERO, ROLE_ADMIN, ROLE_DEVELOPER, ROLE_QA); + try (TestRestClient client = cluster.getRestClient(header)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List roles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(roles, hasSize(3)); + assertThat(roles, containsInAnyOrder(ROLE_ADMIN, ROLE_DEVELOPER, ROLE_QA)); + } + } + + @Test + public void shouldReadRolesFromToken_positiveSecondRoleSet() { + Header header = tokenFactory.generateValidToken(USER_SUPERHERO, ROLE_CTO, ROLE_CEO, ROLE_VP); + try (TestRestClient client = cluster.getRestClient(header)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List roles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(roles, hasSize(3)); + assertThat(roles, containsInAnyOrder(ROLE_CTO, ROLE_CEO, ROLE_VP)); + } + } + + @Test + public void shouldExposeTokenClaimsAsUserAttributes_positive() throws IOException { + String[] roles = { ROLE_VP }; + Map additionalClaims = Map.of(CLAIM_DEPARTMENT, QA_DEPARTMENT); + Header header = tokenFactory.generateValidTokenWithCustomClaims(USER_SUPERHERO, roles, additionalClaims); + try (RestHighLevelClient client = cluster.getRestHighLevelClient(List.of(header))) { + SearchRequest searchRequest = queryStringQueryRequest(QA_SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS); + + SearchResponse response = client.search(searchRequest, DEFAULT); + + assertThat(response, isSuccessfulSearchResponse()); + assertThat(response, numberOfTotalHitsIsEqualTo(1)); + assertThat(response, searchHitsContainDocumentWithId(0, QA_SONG_INDEX_NAME, SONG_ID_1)); + assertThat(response, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS)); + } + } + + @Test + public void shouldExposeTokenClaimsAsUserAttributes_negative() throws IOException { + String[] roles = { ROLE_VP }; + Map additionalClaims = Map.of(CLAIM_DEPARTMENT, "department-without-access-to-qa-song-index"); + Header header = tokenFactory.generateValidTokenWithCustomClaims(USER_SUPERHERO, roles, additionalClaims); + try (RestHighLevelClient client = cluster.getRestHighLevelClient(List.of(header))) { + SearchRequest searchRequest = queryStringQueryRequest(QA_SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS); + + assertThatThrownBy(() -> client.search(searchRequest, DEFAULT), statusException(FORBIDDEN)); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/JwtAuthorizationHeaderFactory.java b/src/integrationTest/java/org/opensearch/security/http/JwtAuthorizationHeaderFactory.java index 61d87b173f..65d4e7df6f 100644 --- a/src/integrationTest/java/org/opensearch/security/http/JwtAuthorizationHeaderFactory.java +++ b/src/integrationTest/java/org/opensearch/security/http/JwtAuthorizationHeaderFactory.java @@ -26,116 +26,116 @@ import static java.util.Objects.requireNonNull; class JwtAuthorizationHeaderFactory { - public static final String AUDIENCE = "OpenSearch"; - public static final String ISSUER = "test-code"; - private final PrivateKey privateKey; - - private final String usernameClaimName; - - private final String rolesClaimName; - - private final String headerName; - - public JwtAuthorizationHeaderFactory(PrivateKey privateKey, String usernameClaimName, String rolesClaimName, String headerName) { - this.privateKey = requireNonNull(privateKey, "Private key is required"); - this.usernameClaimName = requireNonNull(usernameClaimName, "Username claim name is required"); - this.rolesClaimName = requireNonNull(rolesClaimName, "Roles claim name is required."); - this.headerName = requireNonNull(headerName, "Header name is required"); - } - - Header generateValidToken(String username, String...roles) { - requireNonNull(username, "Username is required"); - Date now = new Date(); - String token = Jwts.builder() - .setClaims(customClaimsMap(username, roles)) - .setIssuer(ISSUER) - .setSubject(subject(username)) - .setAudience(AUDIENCE) - .setIssuedAt(now) - .setExpiration(new Date(now.getTime() + 3600 * 1000)) - .signWith(privateKey, RS256) - .compact(); - return toHeader(token); - } - - private Map customClaimsMap(String username, String[] roles) { - ImmutableMap.Builder builder = new ImmutableMap.Builder(); - if(StringUtils.isNoneEmpty(username)) { - builder.put(usernameClaimName, username); - } - if((roles != null) && (roles.length > 0)) { - builder.put(rolesClaimName, Arrays.stream(roles).collect(Collectors.joining(","))); - } - return builder.build(); - } - - Header generateValidTokenWithCustomClaims(String username, String[] roles, Map additionalClaims) { - requireNonNull(username, "Username is required"); - requireNonNull(additionalClaims, "Custom claims are required"); - Map claims = new HashMap<>(customClaimsMap(username, roles)); - claims.putAll(additionalClaims); - Date now = new Date(); - String token = Jwts.builder() - .setClaims(claims) - .setIssuer(ISSUER) - .setSubject(subject(username)) - .setAudience(AUDIENCE) - .setIssuedAt(now) - .setExpiration(new Date(now.getTime() + 3600 * 1000)) - .signWith(privateKey, RS256) - .compact(); - return toHeader(token); - } - - private BasicHeader toHeader(String token) { - return new BasicHeader(headerName, token); - } - - Header generateTokenWithoutPreferredUsername(String username) { - requireNonNull(username, "Username is required"); - Date now = new Date(); - String token = Jwts.builder() - .setIssuer(ISSUER) - .setSubject(username) - .setIssuedAt(now) - .setExpiration(new Date(now.getTime() + 3600 * 1000)) - .signWith(privateKey, RS256) - .compact(); - return toHeader(token); - } - - public Header generateExpiredToken(String username) { - requireNonNull(username, "Username is required"); - Date now = new Date(1000); - String token = Jwts.builder() - .setClaims(Map.of(usernameClaimName, username)) - .setIssuer(ISSUER) - .setSubject(subject(username)) - .setAudience(AUDIENCE) - .setIssuedAt(now) - .setExpiration(new Date(now.getTime() + 3600 * 1000)) - .signWith(privateKey, RS256) - .compact(); - return toHeader(token); - } - - public Header generateTokenSignedWithKey(PrivateKey key, String username) { - requireNonNull(key, "Private key is required"); - requireNonNull(username, "Username is required"); - Date now = new Date(); - String token = Jwts.builder() - .setClaims(Map.of(usernameClaimName, username)) - .setIssuer(ISSUER) - .setSubject(subject(username)) - .setAudience(AUDIENCE) - .setIssuedAt(now) - .setExpiration(new Date(now.getTime() + 3600 * 1000)) - .signWith(key, RS256) - .compact(); - return toHeader(token); - } - - private static String subject(String username) { - return "subject-" + username; - } + public static final String AUDIENCE = "OpenSearch"; + public static final String ISSUER = "test-code"; + private final PrivateKey privateKey; + + private final String usernameClaimName; + + private final String rolesClaimName; + + private final String headerName; + + public JwtAuthorizationHeaderFactory(PrivateKey privateKey, String usernameClaimName, String rolesClaimName, String headerName) { + this.privateKey = requireNonNull(privateKey, "Private key is required"); + this.usernameClaimName = requireNonNull(usernameClaimName, "Username claim name is required"); + this.rolesClaimName = requireNonNull(rolesClaimName, "Roles claim name is required."); + this.headerName = requireNonNull(headerName, "Header name is required"); + } + + Header generateValidToken(String username, String... roles) { + requireNonNull(username, "Username is required"); + Date now = new Date(); + String token = Jwts.builder() + .setClaims(customClaimsMap(username, roles)) + .setIssuer(ISSUER) + .setSubject(subject(username)) + .setAudience(AUDIENCE) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + 3600 * 1000)) + .signWith(privateKey, RS256) + .compact(); + return toHeader(token); + } + + private Map customClaimsMap(String username, String[] roles) { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + if (StringUtils.isNoneEmpty(username)) { + builder.put(usernameClaimName, username); + } + if ((roles != null) && (roles.length > 0)) { + builder.put(rolesClaimName, Arrays.stream(roles).collect(Collectors.joining(","))); + } + return builder.build(); + } + + Header generateValidTokenWithCustomClaims(String username, String[] roles, Map additionalClaims) { + requireNonNull(username, "Username is required"); + requireNonNull(additionalClaims, "Custom claims are required"); + Map claims = new HashMap<>(customClaimsMap(username, roles)); + claims.putAll(additionalClaims); + Date now = new Date(); + String token = Jwts.builder() + .setClaims(claims) + .setIssuer(ISSUER) + .setSubject(subject(username)) + .setAudience(AUDIENCE) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + 3600 * 1000)) + .signWith(privateKey, RS256) + .compact(); + return toHeader(token); + } + + private BasicHeader toHeader(String token) { + return new BasicHeader(headerName, token); + } + + Header generateTokenWithoutPreferredUsername(String username) { + requireNonNull(username, "Username is required"); + Date now = new Date(); + String token = Jwts.builder() + .setIssuer(ISSUER) + .setSubject(username) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + 3600 * 1000)) + .signWith(privateKey, RS256) + .compact(); + return toHeader(token); + } + + public Header generateExpiredToken(String username) { + requireNonNull(username, "Username is required"); + Date now = new Date(1000); + String token = Jwts.builder() + .setClaims(Map.of(usernameClaimName, username)) + .setIssuer(ISSUER) + .setSubject(subject(username)) + .setAudience(AUDIENCE) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + 3600 * 1000)) + .signWith(privateKey, RS256) + .compact(); + return toHeader(token); + } + + public Header generateTokenSignedWithKey(PrivateKey key, String username) { + requireNonNull(key, "Private key is required"); + requireNonNull(username, "Username is required"); + Date now = new Date(); + String token = Jwts.builder() + .setClaims(Map.of(usernameClaimName, username)) + .setIssuer(ISSUER) + .setSubject(subject(username)) + .setAudience(AUDIENCE) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + 3600 * 1000)) + .signWith(key, RS256) + .compact(); + return toHeader(token); + } + + private static String subject(String username) { + return "subject-" + username; + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/LdapAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/LdapAuthenticationTest.java index f6c85165d2..299b2cc7d2 100644 --- a/src/integrationTest/java/org/opensearch/security/http/LdapAuthenticationTest.java +++ b/src/integrationTest/java/org/opensearch/security/http/LdapAuthenticationTest.java @@ -52,61 +52,69 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class LdapAuthenticationTest { - private static final Logger log = LogManager.getLogger(LdapAuthenticationTest.class); - - private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); - - private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); - - public static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer(TEST_CERTIFICATES.getRootCertificateData(), - TEST_CERTIFICATES.getLdapCertificateData(), LDIF_DATA); - - public static LocalCluster cluster = new LocalCluster.Builder() - .testCertificates(TEST_CERTIFICATES) - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) - .authc(new AuthcDomain("ldap", BASIC_AUTH_DOMAIN_ORDER + 1, true) - .httpAuthenticator(new HttpAuthenticator("basic").challenge(false)) - .backend(new AuthenticationBackend("ldap") - .config(() -> LdapAuthenticationConfigBuilder.config() - // this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used to postpone - // execution of the code in this block. - .enableSsl(false) - .enableStartTls(false) - .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapNonTlsPort())) - .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) - .password(PASSWORD_OPEN_SEARCH) - .userBase(DN_PEOPLE_TEST_ORG) - .userSearch(USER_SEARCH) - .usernameAttribute(USERNAME_ATTRIBUTE) - .build()))) - .authc(AUTHC_HTTPBASIC_INTERNAL) - .users(ADMIN_USER) - .build(); - - @ClassRule - public static RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster); - - @Rule - public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); - - @Test - public void shouldAuthenticateUserWithLdap_positive() { - try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - } - } - - @Test - public void shouldAuthenticateUserWithLdap_negativeWhenIncorrectPassword() { - try (TestRestClient client = cluster.getRestClient(USER_SPOCK, "incorrect password")) { - TestRestClient.HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - String expectedStackTraceFragment = "Unable to bind as user '".concat(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) - .concat("' because the provided password was incorrect."); - logsRule.assertThatStackTraceContain(expectedStackTraceFragment); - } - } + private static final Logger log = LogManager.getLogger(LdapAuthenticationTest.class); + + private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); + + public static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer( + TEST_CERTIFICATES.getRootCertificateData(), + TEST_CERTIFICATES.getLdapCertificateData(), + LDIF_DATA + ); + + public static LocalCluster cluster = new LocalCluster.Builder().testCertificates(TEST_CERTIFICATES) + .clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .authc( + new AuthcDomain("ldap", BASIC_AUTH_DOMAIN_ORDER + 1, true).httpAuthenticator(new HttpAuthenticator("basic").challenge(false)) + .backend( + new AuthenticationBackend("ldap").config( + () -> LdapAuthenticationConfigBuilder.config() + // this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used to + // postpone + // execution of the code in this block. + .enableSsl(false) + .enableStartTls(false) + .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapNonTlsPort())) + .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) + .password(PASSWORD_OPEN_SEARCH) + .userBase(DN_PEOPLE_TEST_ORG) + .userSearch(USER_SEARCH) + .usernameAttribute(USERNAME_ATTRIBUTE) + .build() + ) + ) + ) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER) + .build(); + + @ClassRule + public static RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster); + + @Rule + public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); + + @Test + public void shouldAuthenticateUserWithLdap_positive() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_negativeWhenIncorrectPassword() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, "incorrect password")) { + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + String expectedStackTraceFragment = "Unable to bind as user '".concat(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) + .concat("' because the provided password was incorrect."); + logsRule.assertThatStackTraceContain(expectedStackTraceFragment); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/LdapStartTlsAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/LdapStartTlsAuthenticationTest.java index 6a117a8b5a..395467897d 100644 --- a/src/integrationTest/java/org/opensearch/security/http/LdapStartTlsAuthenticationTest.java +++ b/src/integrationTest/java/org/opensearch/security/http/LdapStartTlsAuthenticationTest.java @@ -51,59 +51,68 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class LdapStartTlsAuthenticationTest { - private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); - private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); + private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); - public static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer(TEST_CERTIFICATES.getRootCertificateData(), - TEST_CERTIFICATES.getLdapCertificateData(), LDIF_DATA); + public static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer( + TEST_CERTIFICATES.getRootCertificateData(), + TEST_CERTIFICATES.getLdapCertificateData(), + LDIF_DATA + ); - public static LocalCluster cluster = new LocalCluster.Builder() - .testCertificates(TEST_CERTIFICATES) - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) - .authc(new AuthcDomain("ldap-config-id", BASIC_AUTH_DOMAIN_ORDER + 1, true) - .httpAuthenticator(new HttpAuthenticator("basic").challenge(false)) - .backend(new AuthenticationBackend("ldap") - .config(() -> LdapAuthenticationConfigBuilder.config() - // this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used - .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapNonTlsPort())) - .enableSsl(false) - .enableStartTls(true) - .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) - .password(PASSWORD_OPEN_SEARCH) - .userBase(DN_PEOPLE_TEST_ORG) - .userSearch(USER_SEARCH) - .usernameAttribute(USERNAME_ATTRIBUTE) - .penTrustedCasFilePath(TEST_CERTIFICATES.getRootCertificate().getAbsolutePath()) - .build()))) - .authc(AUTHC_HTTPBASIC_INTERNAL) - .users(ADMIN_USER) - .build(); + public static LocalCluster cluster = new LocalCluster.Builder().testCertificates(TEST_CERTIFICATES) + .clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .authc( + new AuthcDomain("ldap-config-id", BASIC_AUTH_DOMAIN_ORDER + 1, true).httpAuthenticator( + new HttpAuthenticator("basic").challenge(false) + ) + .backend( + new AuthenticationBackend("ldap").config( + () -> LdapAuthenticationConfigBuilder.config() + // this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used + .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapNonTlsPort())) + .enableSsl(false) + .enableStartTls(true) + .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) + .password(PASSWORD_OPEN_SEARCH) + .userBase(DN_PEOPLE_TEST_ORG) + .userSearch(USER_SEARCH) + .usernameAttribute(USERNAME_ATTRIBUTE) + .penTrustedCasFilePath(TEST_CERTIFICATES.getRootCertificate().getAbsolutePath()) + .build() + ) + ) + ) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER) + .build(); - @ClassRule - public static RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster); + @ClassRule + public static RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster); - @Rule - public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); + @Rule + public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); - @Test - public void shouldAuthenticateUserWithLdap_positive() { - try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { - TestRestClient.HttpResponse response = client.getAuthInfo(); + @Test + public void shouldAuthenticateUserWithLdap_positive() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { + TestRestClient.HttpResponse response = client.getAuthInfo(); - response.assertStatusCode(200); - } - } + response.assertStatusCode(200); + } + } - @Test - public void shouldAuthenticateUserWithLdap_negativeWhenIncorrectPassword() { - try (TestRestClient client = cluster.getRestClient(USER_SPOCK, "incorrect password")) { - TestRestClient.HttpResponse response = client.getAuthInfo(); + @Test + public void shouldAuthenticateUserWithLdap_negativeWhenIncorrectPassword() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, "incorrect password")) { + TestRestClient.HttpResponse response = client.getAuthInfo(); - response.assertStatusCode(401); - String expectedStackTraceFragment = "Unable to bind as user '".concat(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) - .concat("' because the provided password was incorrect."); - logsRule.assertThatStackTraceContain(expectedStackTraceFragment); - } - } + response.assertStatusCode(401); + String expectedStackTraceFragment = "Unable to bind as user '".concat(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) + .concat("' because the provided password was incorrect."); + logsRule.assertThatStackTraceContain(expectedStackTraceFragment); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java index be17b6c4cf..a27f0912e9 100644 --- a/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java +++ b/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java @@ -90,310 +90,325 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class LdapTlsAuthenticationTest { - private static final String SONG_INDEX_NAME = "song_lyrics"; - - private static final String HEADER_NAME_IMPERSONATE = "opendistro_security_impersonate_as"; - - private static final String PERSONAL_INDEX_NAME_SPOCK = "personal-" + USER_SPOCK; - private static final String PERSONAL_INDEX_NAME_KIRK = "personal-" + USER_KIRK; - - private static final String POINTER_BACKEND_ROLES = "/backend_roles"; - private static final String POINTER_ROLES = "/roles"; - private static final String POINTER_USERNAME = "/user_name"; - private static final String POINTER_ERROR_REASON = "/error/reason"; - - private static final String SONG_ID_1 = "l0001"; - private static final String SONG_ID_2 = "l0002"; - private static final String SONG_ID_3 = "l0003"; - - private static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS); - - private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); - - private static final Role ROLE_INDEX_ADMINISTRATOR = new Role("index_administrator").indexPermissions("*").on("*"); - private static final Role ROLE_PERSONAL_INDEX_ACCESS = new Role("personal_index_access").indexPermissions("*").on("personal-${attr.ldap.uid}"); - - private static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer(TEST_CERTIFICATES.getRootCertificateData(), - TEST_CERTIFICATES.getLdapCertificateData(), LDIF_DATA); - - private static final Map USER_IMPERSONATION_CONFIGURATION = Map.of( - "plugins.security.authcz.rest_impersonation_user." + USER_KIRK, List.of(USER_SPOCK) - ); - - private static final LocalCluster cluster = new LocalCluster.Builder() - .testCertificates(TEST_CERTIFICATES) - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) - .nodeSettings(USER_IMPERSONATION_CONFIGURATION) - .authc(new AuthcDomain("ldap", BASIC_AUTH_DOMAIN_ORDER + 1, true) - .httpAuthenticator(new HttpAuthenticator("basic").challenge(false)) - .backend(new AuthenticationBackend("ldap") - .config(() -> LdapAuthenticationConfigBuilder.config() - // this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used - .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapTlsPort())) - .enableSsl(true) - .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) - .password(PASSWORD_OPEN_SEARCH) - .userBase(DN_PEOPLE_TEST_ORG) - .userSearch(USER_SEARCH) - .usernameAttribute(USERNAME_ATTRIBUTE) - .penTrustedCasFilePath(TEST_CERTIFICATES.getRootCertificate().getAbsolutePath()) - .build()))) - .authc(AUTHC_HTTPBASIC_INTERNAL) - .users(ADMIN_USER) - .roles(ROLE_INDEX_ADMINISTRATOR, ROLE_PERSONAL_INDEX_ACCESS) - .rolesMapping( - new RolesMapping(ROLE_INDEX_ADMINISTRATOR).backendRoles(CN_GROUP_ADMIN), - new RolesMapping(ROLE_PERSONAL_INDEX_ACCESS).backendRoles(CN_GROUP_CREW) - ) - .authz(new AuthzDomain("ldap_roles").httpEnabled(true).transportEnabled(true) - .authorizationBackend(new AuthorizationBackend("ldap") - .config(() -> new LdapAuthorizationConfigBuilder() - .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapTlsPort())) - .enableSsl(true) - .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) - .password(PASSWORD_OPEN_SEARCH) - .userBase(DN_PEOPLE_TEST_ORG) - .userSearch(USER_SEARCH) - .usernameAttribute(USERNAME_ATTRIBUTE) - .penTrustedCasFilePath(TEST_CERTIFICATES.getRootCertificate().getAbsolutePath()) - .roleBase(DN_GROUPS_TEST_ORG) - .roleSearch("(uniqueMember={0})") - .userRoleAttribute(null) - .userRoleName("disabled") - .roleName("cn") - .resolveNestedRoles(true) - .build()))) - .build(); - - @ClassRule - public static final RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster); - - @Rule - public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); - - @BeforeClass - public static void createTestData() { - try(Client client = cluster.getInternalNodeClient()){ - client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); - client.prepareIndex(PERSONAL_INDEX_NAME_SPOCK).setId(SONG_ID_2).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1].asMap()).get(); - client.prepareIndex(PERSONAL_INDEX_NAME_KIRK).setId(SONG_ID_3).setRefreshPolicy(IMMEDIATE).setSource(SONGS[2].asMap()).get(); - } - } - - @Test - public void shouldAuthenticateUserWithLdap_positiveSpockUser() { - try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - String username = response.getTextFromJsonBody(POINTER_USERNAME); - assertThat(username, equalTo(USER_SPOCK)); - } - } - - @Test - public void shouldAuthenticateUserWithLdap_positiveKirkUser() { - try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - String username = response.getTextFromJsonBody(POINTER_USERNAME); - assertThat(username, equalTo(USER_KIRK)); - } - } - - @Test - public void shouldAuthenticateUserWithLdap_negativeWhenIncorrectPassword() { - try (TestRestClient client = cluster.getRestClient(USER_SPOCK, "incorrect password")) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - String expectedStackTraceFragment = "Unable to bind as user '".concat(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) - .concat("' because the provided password was incorrect."); - logsRule.assertThatStackTraceContain(expectedStackTraceFragment); - } - } - - @Test - public void shouldAuthenticateUserWithLdap_negativeWhenIncorrectUsername() { - final String username = "invalid-user-name"; - try (TestRestClient client = cluster.getRestClient(username, PASSWORD_SPOCK)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - logsRule.assertThatStackTraceContain(String.format("No user %s found", username)); - } - } - - @Test - public void shouldAuthenticateUserWithLdap_negativeWhenUserDoesNotExist() { - final String username = "doesNotExist"; - try (TestRestClient client = cluster.getRestClient(username, "password")) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(401); - logsRule.assertThatStackTraceContain(String.format("No user %s found", username)); - } - } - - @Test - public void shouldResolveUserRolesAgainstLdapBackend_positiveSpockUser() { - try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); - assertThat(backendRoles, contains(CN_GROUP_CREW)); - assertThat(response.getTextArrayFromJsonBody(POINTER_ROLES), contains(ROLE_PERSONAL_INDEX_ACCESS.getName())); - } - } - - @Test - public void shouldResolveUserRolesAgainstLdapBackend_positiveKirkUser() { - try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) { - - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - assertThat(response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES), contains(CN_GROUP_ADMIN)); - assertThat(response.getTextArrayFromJsonBody(POINTER_ROLES), contains(ROLE_INDEX_ADMINISTRATOR.getName())); - } - } - - @Test - public void shouldPerformAuthorizationAgainstLdapToAccessIndex_positive() throws IOException { - try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_KIRK, PASSWORD_KIRK)) { - SearchRequest request = queryStringQueryRequest(SONG_INDEX_NAME, "*"); - - SearchResponse searchResponse = client.search(request, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_1)); - } - } - - @Test - public void shouldPerformAuthorizationAgainstLdapToAccessIndex_negative() throws IOException { - try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_LEONARD, PASSWORD_LEONARD)) { - SearchRequest request = queryStringQueryRequest(SONG_INDEX_NAME, "*"); - - assertThatThrownBy(() -> client.search(request, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldResolveUserAttributesLoadedFromLdap_positive() throws IOException { - try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_SPOCK, PASSWORD_SPOCK)) { - SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_SPOCK, "*"); - - SearchResponse searchResponse = client.search(request, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, PERSONAL_INDEX_NAME_SPOCK, SONG_ID_2)); - } - } - - @Test - public void shouldResolveUserAttributesLoadedFromLdap_negative() throws IOException { - try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_SPOCK, PASSWORD_SPOCK)) { - SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_KIRK, "*"); - - assertThatThrownBy(() -> client.search(request, DEFAULT), statusException(FORBIDDEN)); - } - } - - @Test - public void shouldResolveNestedGroups_positive() { - try (TestRestClient client = cluster.getRestClient(USER_JEAN, PASSWORD_JEAN)) { - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); - assertThat(backendRoles, hasSize(2)); - //CN_GROUP_CREW is retrieved recursively: cn=Jean,ou=people,o=test.org -> cn=bridge,ou=groups,o=test.org -> cn=crew,ou=groups,o=test.org - assertThat(backendRoles, containsInAnyOrder(CN_GROUP_CREW, CN_GROUP_BRIDGE)); - assertThat(response.getTextArrayFromJsonBody(POINTER_ROLES), contains(ROLE_PERSONAL_INDEX_ACCESS.getName())); - } - } - - @Test - public void shouldResolveNestedGroups_negative() { - try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) { - HttpResponse response = client.getAuthInfo(); - - response.assertStatusCode(200); - List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); - assertThat(backendRoles, not(containsInAnyOrder(CN_GROUP_CREW))); - } - } - - @Test - public void shouldImpersonateUser_positive() { - try(TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)){ - - HttpResponse response = client.getAuthInfo(new BasicHeader(HEADER_NAME_IMPERSONATE, USER_SPOCK)); - - response.assertStatusCode(200); - assertThat(response.getTextFromJsonBody(POINTER_USERNAME), equalTo(USER_SPOCK)); - List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); - assertThat(backendRoles, hasSize(1)); - assertThat(backendRoles, contains(CN_GROUP_CREW)); - } - } - - @Test - public void shouldImpersonateUser_negativeJean() { - try(TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)){ - - HttpResponse response = client.getAuthInfo(new BasicHeader(HEADER_NAME_IMPERSONATE, USER_JEAN)); - - response.assertStatusCode(403); - String expectedMessage = String.format("'%s' is not allowed to impersonate as '%s'", USER_KIRK, USER_JEAN); - assertThat(response.getTextFromJsonBody(POINTER_ERROR_REASON), equalTo(expectedMessage)); - } - } - - @Test - public void shouldImpersonateUser_negativeKirk() { - try(TestRestClient client = cluster.getRestClient(USER_JEAN, PASSWORD_JEAN)){ - - HttpResponse response = client.getAuthInfo(new BasicHeader(HEADER_NAME_IMPERSONATE, USER_KIRK)); - - response.assertStatusCode(403); - String expectedMessage = String.format("'%s' is not allowed to impersonate as '%s'", USER_JEAN, USER_KIRK); - assertThat(response.getTextFromJsonBody(POINTER_ERROR_REASON), equalTo(expectedMessage)); - } - } - - @Test - public void shouldAccessImpersonatedUserPersonalIndex_positive() throws IOException { - BasicHeader impersonateHeader = new BasicHeader(HEADER_NAME_IMPERSONATE, USER_SPOCK); - try(RestHighLevelClient client = cluster.getRestHighLevelClient(USER_KIRK, PASSWORD_KIRK, impersonateHeader)){ - SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_SPOCK, "*"); - - SearchResponse searchResponse = client.search(request, DEFAULT); - - assertThat(searchResponse, isSuccessfulSearchResponse()); - assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); - assertThat(searchResponse, searchHitsContainDocumentWithId(0, PERSONAL_INDEX_NAME_SPOCK, SONG_ID_2)); - } - } - - @Test - public void shouldAccessImpersonatedUserPersonalIndex_negative() throws IOException { - BasicHeader impersonateHeader = new BasicHeader(HEADER_NAME_IMPERSONATE, USER_SPOCK); - try(RestHighLevelClient client = cluster.getRestHighLevelClient(USER_KIRK, PASSWORD_KIRK, impersonateHeader)){ - SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_KIRK, "*"); - - assertThatThrownBy(() -> client.search(request, DEFAULT), statusException(FORBIDDEN)); - } - } + private static final String SONG_INDEX_NAME = "song_lyrics"; + + private static final String HEADER_NAME_IMPERSONATE = "opendistro_security_impersonate_as"; + + private static final String PERSONAL_INDEX_NAME_SPOCK = "personal-" + USER_SPOCK; + private static final String PERSONAL_INDEX_NAME_KIRK = "personal-" + USER_KIRK; + + private static final String POINTER_BACKEND_ROLES = "/backend_roles"; + private static final String POINTER_ROLES = "/roles"; + private static final String POINTER_USERNAME = "/user_name"; + private static final String POINTER_ERROR_REASON = "/error/reason"; + + private static final String SONG_ID_1 = "l0001"; + private static final String SONG_ID_2 = "l0002"; + private static final String SONG_ID_3 = "l0003"; + + private static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS); + + private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); + + private static final Role ROLE_INDEX_ADMINISTRATOR = new Role("index_administrator").indexPermissions("*").on("*"); + private static final Role ROLE_PERSONAL_INDEX_ACCESS = new Role("personal_index_access").indexPermissions("*") + .on("personal-${attr.ldap.uid}"); + + private static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer( + TEST_CERTIFICATES.getRootCertificateData(), + TEST_CERTIFICATES.getLdapCertificateData(), + LDIF_DATA + ); + + private static final Map USER_IMPERSONATION_CONFIGURATION = Map.of( + "plugins.security.authcz.rest_impersonation_user." + USER_KIRK, + List.of(USER_SPOCK) + ); + + private static final LocalCluster cluster = new LocalCluster.Builder().testCertificates(TEST_CERTIFICATES) + .clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .nodeSettings(USER_IMPERSONATION_CONFIGURATION) + .authc( + new AuthcDomain("ldap", BASIC_AUTH_DOMAIN_ORDER + 1, true).httpAuthenticator(new HttpAuthenticator("basic").challenge(false)) + .backend( + new AuthenticationBackend("ldap").config( + () -> LdapAuthenticationConfigBuilder.config() + // this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used + .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapTlsPort())) + .enableSsl(true) + .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) + .password(PASSWORD_OPEN_SEARCH) + .userBase(DN_PEOPLE_TEST_ORG) + .userSearch(USER_SEARCH) + .usernameAttribute(USERNAME_ATTRIBUTE) + .penTrustedCasFilePath(TEST_CERTIFICATES.getRootCertificate().getAbsolutePath()) + .build() + ) + ) + ) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER) + .roles(ROLE_INDEX_ADMINISTRATOR, ROLE_PERSONAL_INDEX_ACCESS) + .rolesMapping( + new RolesMapping(ROLE_INDEX_ADMINISTRATOR).backendRoles(CN_GROUP_ADMIN), + new RolesMapping(ROLE_PERSONAL_INDEX_ACCESS).backendRoles(CN_GROUP_CREW) + ) + .authz( + new AuthzDomain("ldap_roles").httpEnabled(true) + .transportEnabled(true) + .authorizationBackend( + new AuthorizationBackend("ldap").config( + () -> new LdapAuthorizationConfigBuilder().hosts(List.of("localhost:" + embeddedLDAPServer.getLdapTlsPort())) + .enableSsl(true) + .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) + .password(PASSWORD_OPEN_SEARCH) + .userBase(DN_PEOPLE_TEST_ORG) + .userSearch(USER_SEARCH) + .usernameAttribute(USERNAME_ATTRIBUTE) + .penTrustedCasFilePath(TEST_CERTIFICATES.getRootCertificate().getAbsolutePath()) + .roleBase(DN_GROUPS_TEST_ORG) + .roleSearch("(uniqueMember={0})") + .userRoleAttribute(null) + .userRoleName("disabled") + .roleName("cn") + .resolveNestedRoles(true) + .build() + ) + ) + ) + .build(); + + @ClassRule + public static final RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster); + + @Rule + public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); + + @BeforeClass + public static void createTestData() { + try (Client client = cluster.getInternalNodeClient()) { + client.prepareIndex(SONG_INDEX_NAME).setId(SONG_ID_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get(); + client.prepareIndex(PERSONAL_INDEX_NAME_SPOCK).setId(SONG_ID_2).setRefreshPolicy(IMMEDIATE).setSource(SONGS[1].asMap()).get(); + client.prepareIndex(PERSONAL_INDEX_NAME_KIRK).setId(SONG_ID_3).setRefreshPolicy(IMMEDIATE).setSource(SONGS[2].asMap()).get(); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_positiveSpockUser() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USER_SPOCK)); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_positiveKirkUser() { + try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat(username, equalTo(USER_KIRK)); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_negativeWhenIncorrectPassword() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, "incorrect password")) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + String expectedStackTraceFragment = "Unable to bind as user '".concat(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG) + .concat("' because the provided password was incorrect."); + logsRule.assertThatStackTraceContain(expectedStackTraceFragment); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_negativeWhenIncorrectUsername() { + final String username = "invalid-user-name"; + try (TestRestClient client = cluster.getRestClient(username, PASSWORD_SPOCK)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + logsRule.assertThatStackTraceContain(String.format("No user %s found", username)); + } + } + + @Test + public void shouldAuthenticateUserWithLdap_negativeWhenUserDoesNotExist() { + final String username = "doesNotExist"; + try (TestRestClient client = cluster.getRestClient(username, "password")) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(401); + logsRule.assertThatStackTraceContain(String.format("No user %s found", username)); + } + } + + @Test + public void shouldResolveUserRolesAgainstLdapBackend_positiveSpockUser() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, contains(CN_GROUP_CREW)); + assertThat(response.getTextArrayFromJsonBody(POINTER_ROLES), contains(ROLE_PERSONAL_INDEX_ACCESS.getName())); + } + } + + @Test + public void shouldResolveUserRolesAgainstLdapBackend_positiveKirkUser() { + try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) { + + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + assertThat(response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES), contains(CN_GROUP_ADMIN)); + assertThat(response.getTextArrayFromJsonBody(POINTER_ROLES), contains(ROLE_INDEX_ADMINISTRATOR.getName())); + } + } + + @Test + public void shouldPerformAuthorizationAgainstLdapToAccessIndex_positive() throws IOException { + try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_KIRK, PASSWORD_KIRK)) { + SearchRequest request = queryStringQueryRequest(SONG_INDEX_NAME, "*"); + + SearchResponse searchResponse = client.search(request, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, SONG_INDEX_NAME, SONG_ID_1)); + } + } + + @Test + public void shouldPerformAuthorizationAgainstLdapToAccessIndex_negative() throws IOException { + try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_LEONARD, PASSWORD_LEONARD)) { + SearchRequest request = queryStringQueryRequest(SONG_INDEX_NAME, "*"); + + assertThatThrownBy(() -> client.search(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldResolveUserAttributesLoadedFromLdap_positive() throws IOException { + try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_SPOCK, PASSWORD_SPOCK)) { + SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_SPOCK, "*"); + + SearchResponse searchResponse = client.search(request, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, PERSONAL_INDEX_NAME_SPOCK, SONG_ID_2)); + } + } + + @Test + public void shouldResolveUserAttributesLoadedFromLdap_negative() throws IOException { + try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_SPOCK, PASSWORD_SPOCK)) { + SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_KIRK, "*"); + + assertThatThrownBy(() -> client.search(request, DEFAULT), statusException(FORBIDDEN)); + } + } + + @Test + public void shouldResolveNestedGroups_positive() { + try (TestRestClient client = cluster.getRestClient(USER_JEAN, PASSWORD_JEAN)) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(2)); + // CN_GROUP_CREW is retrieved recursively: cn=Jean,ou=people,o=test.org -> cn=bridge,ou=groups,o=test.org -> + // cn=crew,ou=groups,o=test.org + assertThat(backendRoles, containsInAnyOrder(CN_GROUP_CREW, CN_GROUP_BRIDGE)); + assertThat(response.getTextArrayFromJsonBody(POINTER_ROLES), contains(ROLE_PERSONAL_INDEX_ACCESS.getName())); + } + } + + @Test + public void shouldResolveNestedGroups_negative() { + try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) { + HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, not(containsInAnyOrder(CN_GROUP_CREW))); + } + } + + @Test + public void shouldImpersonateUser_positive() { + try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) { + + HttpResponse response = client.getAuthInfo(new BasicHeader(HEADER_NAME_IMPERSONATE, USER_SPOCK)); + + response.assertStatusCode(200); + assertThat(response.getTextFromJsonBody(POINTER_USERNAME), equalTo(USER_SPOCK)); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, contains(CN_GROUP_CREW)); + } + } + + @Test + public void shouldImpersonateUser_negativeJean() { + try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) { + + HttpResponse response = client.getAuthInfo(new BasicHeader(HEADER_NAME_IMPERSONATE, USER_JEAN)); + + response.assertStatusCode(403); + String expectedMessage = String.format("'%s' is not allowed to impersonate as '%s'", USER_KIRK, USER_JEAN); + assertThat(response.getTextFromJsonBody(POINTER_ERROR_REASON), equalTo(expectedMessage)); + } + } + + @Test + public void shouldImpersonateUser_negativeKirk() { + try (TestRestClient client = cluster.getRestClient(USER_JEAN, PASSWORD_JEAN)) { + + HttpResponse response = client.getAuthInfo(new BasicHeader(HEADER_NAME_IMPERSONATE, USER_KIRK)); + + response.assertStatusCode(403); + String expectedMessage = String.format("'%s' is not allowed to impersonate as '%s'", USER_JEAN, USER_KIRK); + assertThat(response.getTextFromJsonBody(POINTER_ERROR_REASON), equalTo(expectedMessage)); + } + } + + @Test + public void shouldAccessImpersonatedUserPersonalIndex_positive() throws IOException { + BasicHeader impersonateHeader = new BasicHeader(HEADER_NAME_IMPERSONATE, USER_SPOCK); + try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_KIRK, PASSWORD_KIRK, impersonateHeader)) { + SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_SPOCK, "*"); + + SearchResponse searchResponse = client.search(request, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, PERSONAL_INDEX_NAME_SPOCK, SONG_ID_2)); + } + } + + @Test + public void shouldAccessImpersonatedUserPersonalIndex_negative() throws IOException { + BasicHeader impersonateHeader = new BasicHeader(HEADER_NAME_IMPERSONATE, USER_SPOCK); + try (RestHighLevelClient client = cluster.getRestHighLevelClient(USER_KIRK, PASSWORD_KIRK, impersonateHeader)) { + SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_KIRK, "*"); + + assertThatThrownBy(() -> client.search(request, DEFAULT), statusException(FORBIDDEN)); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/ProxyAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/ProxyAuthenticationTest.java index be8a68fb9d..8d9ede8e5a 100644 --- a/src/integrationTest/java/org/opensearch/security/http/ProxyAuthenticationTest.java +++ b/src/integrationTest/java/org/opensearch/security/http/ProxyAuthenticationTest.java @@ -33,89 +33,96 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class ProxyAuthenticationTest extends CommonProxyAuthenticationTests { - private static final Map PROXY_AUTHENTICATOR_CONFIG = Map.of( - "user_header", HEADER_PROXY_USER, - "roles_header", HEADER_PROXY_ROLES - ); - - @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) - .xff(new XffConfig(true).internalProxiesRegexp("127\\.0\\.0\\.10")) - .authc(new AuthcDomain("proxy_auth_domain", -5, true) - .httpAuthenticator(new HttpAuthenticator("proxy").challenge(false).config(PROXY_AUTHENTICATOR_CONFIG)) - .backend(new AuthenticationBackend("noop"))) - .authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_ADMIN).roles(ROLE_ALL_INDEX_SEARCH, ROLE_PERSONAL_INDEX_SEARCH) - .rolesMapping(ROLES_MAPPING_CAPTAIN, ROLES_MAPPING_FIRST_MATE).build(); - - @Override - protected LocalCluster getCluster() { - return cluster; - } - - @Test - @Override - public void shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured() { - super.shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured(); - } - - @Test - @Override - public void shouldAuthenticateWithProxy_positiveUserKirk() throws IOException { - super.shouldAuthenticateWithProxy_positiveUserKirk(); - } - - @Test - @Override - public void shouldAuthenticateWithProxy_positiveUserSpock() throws IOException { - super.shouldAuthenticateWithProxy_positiveUserSpock(); - } - - @Test - @Override - public void shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing() throws IOException { - super.shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing(); - } - - @Test - @Override - public void shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing() throws IOException { - super.shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing(); - } - - @Test - @Override - public void shouldAuthenticateWithProxyWhenRolesHeaderIsMissing() throws IOException { - super.shouldAuthenticateWithProxyWhenRolesHeaderIsMissing(); - } - - @Test - @Override - public void shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy() throws IOException { - super.shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy(); - } - - @Test - @Override - public void shouldRetrieveEmptyListOfRoles() throws IOException { - super.shouldRetrieveEmptyListOfRoles(); - } - - @Test - @Override - public void shouldRetrieveSingleRoleFirstMate() throws IOException { - super.shouldRetrieveSingleRoleFirstMate(); - } - - @Test - @Override - public void shouldRetrieveSingleRoleCaptain() throws IOException { - super.shouldRetrieveSingleRoleCaptain(); - } - - @Test - @Override - public void shouldRetrieveMultipleRoles() throws IOException { - super.shouldRetrieveMultipleRoles(); - } + private static final Map PROXY_AUTHENTICATOR_CONFIG = Map.of( + "user_header", + HEADER_PROXY_USER, + "roles_header", + HEADER_PROXY_ROLES + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .xff(new XffConfig(true).internalProxiesRegexp("127\\.0\\.0\\.10")) + .authc( + new AuthcDomain("proxy_auth_domain", -5, true).httpAuthenticator( + new HttpAuthenticator("proxy").challenge(false).config(PROXY_AUTHENTICATOR_CONFIG) + ).backend(new AuthenticationBackend("noop")) + ) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(USER_ADMIN) + .roles(ROLE_ALL_INDEX_SEARCH, ROLE_PERSONAL_INDEX_SEARCH) + .rolesMapping(ROLES_MAPPING_CAPTAIN, ROLES_MAPPING_FIRST_MATE) + .build(); + + @Override + protected LocalCluster getCluster() { + return cluster; + } + + @Test + @Override + public void shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured() { + super.shouldAuthenticateWithBasicAuthWhenProxyAuthenticationIsConfigured(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_positiveUserKirk() throws IOException { + super.shouldAuthenticateWithProxy_positiveUserKirk(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_positiveUserSpock() throws IOException { + super.shouldAuthenticateWithProxy_positiveUserSpock(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing() throws IOException { + super.shouldAuthenticateWithProxy_negativeWhenXffHeaderIsMissing(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing() throws IOException { + super.shouldAuthenticateWithProxy_negativeWhenUserNameHeaderIsMissing(); + } + + @Test + @Override + public void shouldAuthenticateWithProxyWhenRolesHeaderIsMissing() throws IOException { + super.shouldAuthenticateWithProxyWhenRolesHeaderIsMissing(); + } + + @Test + @Override + public void shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy() throws IOException { + super.shouldAuthenticateWithProxy_negativeWhenRequestWasNotSendByProxy(); + } + + @Test + @Override + public void shouldRetrieveEmptyListOfRoles() throws IOException { + super.shouldRetrieveEmptyListOfRoles(); + } + + @Test + @Override + public void shouldRetrieveSingleRoleFirstMate() throws IOException { + super.shouldRetrieveSingleRoleFirstMate(); + } + + @Test + @Override + public void shouldRetrieveSingleRoleCaptain() throws IOException { + super.shouldRetrieveSingleRoleCaptain(); + } + + @Test + @Override + public void shouldRetrieveMultipleRoles() throws IOException { + super.shouldRetrieveMultipleRoles(); + } } diff --git a/src/integrationTest/java/org/opensearch/security/http/UntrustedLdapServerCertificateTest.java b/src/integrationTest/java/org/opensearch/security/http/UntrustedLdapServerCertificateTest.java index 87a55f4757..10e3f0853f 100644 --- a/src/integrationTest/java/org/opensearch/security/http/UntrustedLdapServerCertificateTest.java +++ b/src/integrationTest/java/org/opensearch/security/http/UntrustedLdapServerCertificateTest.java @@ -50,48 +50,55 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class UntrustedLdapServerCertificateTest { - private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); - private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); + private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); - public static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer(TEST_CERTIFICATES.getRootCertificateData(), - TEST_CERTIFICATES.createSelfSignedCertificate("CN=untrusted"), LDIF_DATA); + public static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer( + TEST_CERTIFICATES.getRootCertificateData(), + TEST_CERTIFICATES.createSelfSignedCertificate("CN=untrusted"), + LDIF_DATA + ); - public static LocalCluster cluster = new LocalCluster.Builder() - .testCertificates(TEST_CERTIFICATES) - .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) - .authc(new AuthcDomain("ldap", BASIC_AUTH_DOMAIN_ORDER + 1, true) - .httpAuthenticator(new HttpAuthenticator("basic").challenge(false)) - .backend(new AuthenticationBackend("ldap") - .config(() -> LdapAuthenticationConfigBuilder.config() - // this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used - .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapTlsPort())) - .enableSsl(true) - .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) - .password(PASSWORD_OPEN_SEARCH) - .userBase(DN_PEOPLE_TEST_ORG) - .userSearch(USER_SEARCH) - .usernameAttribute(USERNAME_ATTRIBUTE) - .penTrustedCasFilePath(TEST_CERTIFICATES.getRootCertificate().getAbsolutePath()) - .build()))) - .authc(AUTHC_HTTPBASIC_INTERNAL) - .users(ADMIN_USER) - .build(); + public static LocalCluster cluster = new LocalCluster.Builder().testCertificates(TEST_CERTIFICATES) + .clusterManager(ClusterManager.SINGLENODE) + .anonymousAuth(false) + .authc( + new AuthcDomain("ldap", BASIC_AUTH_DOMAIN_ORDER + 1, true).httpAuthenticator(new HttpAuthenticator("basic").challenge(false)) + .backend( + new AuthenticationBackend("ldap").config( + () -> LdapAuthenticationConfigBuilder.config() + // this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used + .hosts(List.of("localhost:" + embeddedLDAPServer.getLdapTlsPort())) + .enableSsl(true) + .bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG) + .password(PASSWORD_OPEN_SEARCH) + .userBase(DN_PEOPLE_TEST_ORG) + .userSearch(USER_SEARCH) + .usernameAttribute(USERNAME_ATTRIBUTE) + .penTrustedCasFilePath(TEST_CERTIFICATES.getRootCertificate().getAbsolutePath()) + .build() + ) + ) + ) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER) + .build(); - @ClassRule - public static RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster); + @ClassRule + public static RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster); - @Rule - public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); + @Rule + public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend"); - @Test - public void shouldNotAuthenticateUserWithLdap() { - try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { - TestRestClient.HttpResponse response = client.getAuthInfo(); + @Test + public void shouldNotAuthenticateUserWithLdap() { + try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) { + TestRestClient.HttpResponse response = client.getAuthInfo(); - response.assertStatusCode(401); - } - logsRule.assertThatStackTraceContain("javax.net.ssl.SSLHandshakeException"); - } + response.assertStatusCode(401); + } + logsRule.assertThatStackTraceContain("javax.net.ssl.SSLHandshakeException"); + } } diff --git a/src/integrationTest/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java b/src/integrationTest/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java index 9fd3765ea6..a896376d4d 100644 --- a/src/integrationTest/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java +++ b/src/integrationTest/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java @@ -36,36 +36,36 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class PrivilegesEvaluatorTest { - protected final static TestSecurityConfig.User NEGATIVE_LOOKAHEAD = new TestSecurityConfig.User( - "negative_lookahead_user") - .roles(new Role("negative_lookahead_role").indexPermissions("read").on("/^(?!t.*).*/") - .clusterPermissions("cluster_composite_ops")); + protected final static TestSecurityConfig.User NEGATIVE_LOOKAHEAD = new TestSecurityConfig.User("negative_lookahead_user").roles( + new Role("negative_lookahead_role").indexPermissions("read").on("/^(?!t.*).*/").clusterPermissions("cluster_composite_ops") + ); - protected final static TestSecurityConfig.User NEGATED_REGEX = new TestSecurityConfig.User("negated_regex_user") - .roles(new Role("negated_regex_role").indexPermissions("read").on("/^[a-z].*/") - .clusterPermissions("cluster_composite_ops")); + protected final static TestSecurityConfig.User NEGATED_REGEX = new TestSecurityConfig.User("negated_regex_user").roles( + new Role("negated_regex_role").indexPermissions("read").on("/^[a-z].*/").clusterPermissions("cluster_composite_ops") + ); - @ClassRule - public static LocalCluster cluster = new LocalCluster.Builder() - .clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS).authc(AUTHC_HTTPBASIC_INTERNAL) - .users(NEGATIVE_LOOKAHEAD, NEGATED_REGEX).build(); + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(NEGATIVE_LOOKAHEAD, NEGATED_REGEX) + .build(); - @Test - public void testNegativeLookaheadPattern() throws Exception { + @Test + public void testNegativeLookaheadPattern() throws Exception { - try (TestRestClient client = cluster.getRestClient(NEGATIVE_LOOKAHEAD)) { - assertThat(client.get("*/_search").getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - assertThat(client.get("r*/_search").getStatusCode(), equalTo(HttpStatus.SC_OK)); - } - } + try (TestRestClient client = cluster.getRestClient(NEGATIVE_LOOKAHEAD)) { + assertThat(client.get("*/_search").getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + assertThat(client.get("r*/_search").getStatusCode(), equalTo(HttpStatus.SC_OK)); + } + } - @Test - public void testRegexPattern() throws Exception { + @Test + public void testRegexPattern() throws Exception { - try (TestRestClient client = cluster.getRestClient(NEGATED_REGEX)) { - assertThat(client.get("*/_search").getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); - assertThat(client.get("r*/_search").getStatusCode(), equalTo(HttpStatus.SC_OK)); - } + try (TestRestClient client = cluster.getRestClient(NEGATED_REGEX)) { + assertThat(client.get("*/_search").getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + assertThat(client.get("r*/_search").getStatusCode(), equalTo(HttpStatus.SC_OK)); + } - } + } } From c808692b72b749319d823d82abca84cafa8b8f56 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Thu, 15 Jun 2023 23:32:10 -0400 Subject: [PATCH 205/369] Format everything (#2866) --- build.gradle | 51 +- gradle/formatting.gradle | 111 +- .../security/DefaultObjectMapper.java | 38 +- .../security/OpenSearchSecurityPlugin.java | 1242 ++++++++++--- .../ConfigUpdateNodeResponse.java | 4 +- .../configupdate/ConfigUpdateRequest.java | 4 +- .../ConfigUpdateRequestBuilder.java | 6 +- .../configupdate/ConfigUpdateResponse.java | 20 +- .../TransportConfigUpdateAction.java | 45 +- .../action/whoami/TransportWhoAmIAction.java | 26 +- .../action/whoami/WhoAmIRequestBuilder.java | 3 +- .../action/whoami/WhoAmIResponse.java | 3 +- .../security/auditlog/AuditLog.java | 22 +- .../auditlog/AuditLogSslExceptionHandler.java | 42 +- .../security/auditlog/NullAuditLog.java | 32 +- .../security/auditlog/config/AuditConfig.java | 237 ++- .../auditlog/config/ThreadPoolConfig.java | 9 +- .../auditlog/impl/AbstractAuditLog.java | 440 +++-- .../security/auditlog/impl/AuditCategory.java | 9 +- .../security/auditlog/impl/AuditLogImpl.java | 332 ++-- .../security/auditlog/impl/AuditMessage.java | 154 +- .../auditlog/impl/RequestResolver.java | 299 ++-- .../auditlog/routing/AsyncStoragePool.java | 121 +- .../auditlog/routing/AuditMessageRouter.java | 25 +- .../security/auditlog/sink/AuditLogSink.java | 74 +- .../auditlog/sink/ExternalOpenSearchSink.java | 281 +-- .../auditlog/sink/InternalOpenSearchSink.java | 97 +- .../security/auditlog/sink/KafkaSink.java | 108 +- .../security/auditlog/sink/Log4JSink.java | 8 +- .../security/auditlog/sink/NoopSink.java | 2 +- .../security/auditlog/sink/SinkProvider.java | 308 ++-- .../security/auditlog/sink/WebhookSink.java | 701 ++++---- .../opensearch/security/auth/AuthDomain.java | 11 +- .../security/auth/BackendRegistry.java | 310 ++-- .../opensearch/security/auth/Destroyable.java | 2 +- .../security/auth/RolesInjector.java | 20 +- .../security/auth/UserInjector.java | 6 +- .../auth/blocking/ClientBlockRegistry.java | 2 + .../HeapBasedClientBlockRegistry.java | 20 +- .../InternalAuthenticationBackend.java | 43 +- .../auth/limiting/AbstractRateLimiter.java | 14 +- .../limiting/AddressBasedRateLimiter.java | 5 +- .../security/compliance/ComplianceConfig.java | 253 +-- .../ComplianceIndexingOperationListener.java | 2 +- ...mplianceIndexingOperationListenerImpl.java | 37 +- .../compliance/FieldReadCallback.java | 54 +- .../security/configuration/AdminDNs.java | 30 +- .../configuration/ClusterInfoHolder.java | 10 +- .../security/configuration/CompatConfig.java | 24 +- .../configuration/ConfigCallback.java | 3 + .../ConfigurationLoaderSecurity7.java | 118 +- .../ConfigurationRepository.java | 215 ++- .../DlsFilterLevelActionHandler.java | 146 +- .../configuration/DlsFlsFilterLeafReader.java | 215 ++- .../configuration/DlsFlsRequestValve.java | 19 +- .../configuration/DlsFlsValveImpl.java | 202 ++- .../configuration/DlsQueryParser.java | 52 +- .../configuration/EmptyFilterLeafReader.java | 1 + .../security/configuration/MaskedField.java | 81 +- .../PrivilegesInterceptorImpl.java | 84 +- .../security/configuration/Salt.java | 15 +- .../SecurityFlsDlsIndexSearcherWrapper.java | 74 +- .../SecurityIndexSearcherWrapper.java | 30 +- .../dlic/rest/api/AbstractApiAction.java | 1124 ++++++------ .../dlic/rest/api/AccountApiAction.java | 78 +- .../dlic/rest/api/ActionGroupsApiAction.java | 220 +-- .../dlic/rest/api/AllowlistApiAction.java | 71 +- .../dlic/rest/api/AuditApiAction.java | 53 +- .../rest/api/AuthTokenProcessorAction.java | 128 +- .../dlic/rest/api/FlushCacheApiAction.java | 209 +-- .../dlic/rest/api/InternalUsersApiAction.java | 106 +- .../dlic/rest/api/MigrateApiAction.java | 178 +- .../rest/api/MultiTenancyConfigApiAction.java | 138 +- .../dlic/rest/api/NodesDnApiAction.java | 32 +- .../rest/api/PatchableResourceApiAction.java | 110 +- .../dlic/rest/api/PermissionsInfoAction.java | 131 +- .../api/RestApiAdminPrivilegesEvaluator.java | 106 +- .../rest/api/RestApiPrivilegesEvaluator.java | 811 +++++---- .../dlic/rest/api/RolesApiAction.java | 111 +- .../dlic/rest/api/RolesMappingApiAction.java | 213 ++- .../dlic/rest/api/SecurityConfigAction.java | 59 +- .../dlic/rest/api/SecurityRestApiActions.java | 321 +++- .../dlic/rest/api/SecuritySSLCertsAction.java | 164 +- .../dlic/rest/api/TenantsApiAction.java | 30 +- .../dlic/rest/api/ValidateApiAction.java | 71 +- .../dlic/rest/api/WhitelistApiAction.java | 40 +- .../security/dlic/rest/support/Utils.java | 33 +- .../AbstractConfigurationValidator.java | 45 +- .../rest/validation/ActionGroupValidator.java | 24 +- .../dlic/rest/validation/AuditValidator.java | 37 +- .../rest/validation/CredentialsValidator.java | 11 +- .../validation/InternalUsersValidator.java | 9 +- .../MultiTenancyConfigValidator.java | 1 - .../dlic/rest/validation/NoOpValidator.java | 6 +- .../rest/validation/PasswordValidator.java | 72 +- .../validation/RolesMappingValidator.java | 34 +- .../dlic/rest/validation/RolesValidator.java | 26 +- .../validation/SecurityConfigValidator.java | 10 +- .../dlic/rest/validation/TenantValidator.java | 8 +- .../security/filter/SecurityFilter.java | 276 ++- .../security/filter/SecurityRestFilter.java | 45 +- .../security/http/HTTPBasicAuthenticator.java | 2 +- .../http/HTTPClientCertAuthenticator.java | 8 +- .../security/http/HTTPProxyAuthenticator.java | 13 +- .../security/http/RemoteIpDetector.java | 58 +- .../http/SecurityHttpServerTransport.java | 28 +- .../SecurityNonSslHttpServerTransport.java | 14 +- .../opensearch/security/http/XFFResolver.java | 33 +- .../proxy/HTTPExtendedProxyAuthenticator.java | 18 +- .../security/httpclient/HttpClient.java | 109 +- .../privileges/PitPrivilegesEvaluator.java | 65 +- .../privileges/PrivilegesEvaluator.java | 265 +-- .../PrivilegesEvaluatorResponse.java | 22 +- .../privileges/PrivilegesInterceptor.java | 19 +- .../ProtectedIndexAccessEvaluator.java | 48 +- .../SecurityIndexAccessEvaluator.java | 72 +- .../privileges/SnapshotRestoreEvaluator.java | 21 +- .../privileges/TermsAggregationEvaluator.java | 90 +- .../queries/QueryBuilderTraverser.java | 17 +- .../resolver/IndexResolverReplacer.java | 325 ++-- .../security/rest/DashboardsInfoAction.java | 34 +- .../rest/SecurityConfigUpdateAction.java | 25 +- .../security/rest/SecurityHealthAction.java | 14 +- .../security/rest/SecurityInfoAction.java | 64 +- .../security/rest/SecurityWhoAmIAction.java | 167 +- .../security/rest/TenantInfoAction.java | 114 +- .../security/securityconf/ConfigModel.java | 3 +- .../security/securityconf/ConfigModelV6.java | 537 +++--- .../security/securityconf/ConfigModelV7.java | 609 ++++--- .../securityconf/DynamicConfigFactory.java | 235 ++- .../securityconf/DynamicConfigModel.java | 25 +- .../securityconf/DynamicConfigModelV6.java | 158 +- .../securityconf/DynamicConfigModelV7.java | 159 +- .../securityconf/EvaluatedDlsFlsConfig.java | 29 +- .../security/securityconf/Hashed.java | 2 + .../security/securityconf/Hideable.java | 1 + .../securityconf/InternalUsersModel.java | 5 + .../security/securityconf/Migration.java | 80 +- .../security/securityconf/RoleMappings.java | 4 +- .../security/securityconf/SecurityRoles.java | 40 +- .../impl/AllowlistingSettings.java | 45 +- .../security/securityconf/impl/CType.java | 6 +- .../security/securityconf/impl/Meta.java | 4 +- .../impl/SecurityDynamicConfiguration.java | 104 +- .../impl/WhitelistingSettings.java | 45 +- .../securityconf/impl/v6/ActionGroupsV6.java | 9 +- .../securityconf/impl/v6/ConfigV6.java | 110 +- .../securityconf/impl/v6/InternalUserV6.java | 188 +- .../securityconf/impl/v6/RoleMappingsV6.java | 28 +- .../security/securityconf/impl/v6/RoleV6.java | 16 +- .../securityconf/impl/v7/ActionGroupsV7.java | 36 +- .../securityconf/impl/v7/ConfigV7.java | 155 +- .../securityconf/impl/v7/InternalUserV7.java | 284 +-- .../securityconf/impl/v7/RoleMappingsV7.java | 42 +- .../security/securityconf/impl/v7/RoleV7.java | 78 +- .../securityconf/impl/v7/TenantV7.java | 9 +- .../security/setting/DeprecatedSettings.java | 7 +- .../setting/OpensearchDynamicSetting.java | 9 +- .../setting/TransportPassiveAuthSetting.java | 10 +- .../security/ssl/DefaultSecurityKeyStore.java | 634 ++++--- .../ssl/ExternalSecurityKeyStore.java | 22 +- .../ssl/OpenSearchSecuritySSLPlugin.java | 455 +++-- .../security/ssl/SecureSSLSettings.java | 21 +- .../security/ssl/SecurityKeyStore.java | 6 + .../security/ssl/SslExceptionHandler.java | 6 +- .../SecuritySSLNettyHttpServerTransport.java | 22 +- .../ssl/http/netty/ValidatingDispatcher.java | 13 +- .../ssl/rest/SecuritySSLInfoAction.java | 41 +- .../transport/DefaultPrincipalExtractor.java | 9 +- .../ssl/transport/DualModeSSLHandler.java | 1 - .../security/ssl/transport/SSLConfig.java | 15 +- .../transport/SecuritySSLNettyTransport.java | 82 +- .../transport/SecuritySSLRequestHandler.java | 83 +- .../SecuritySSLTransportInterceptor.java | 19 +- .../security/ssl/util/CertFileProps.java | 55 +- .../security/ssl/util/CertFromFile.java | 7 +- .../security/ssl/util/CertFromKeystore.java | 32 +- .../security/ssl/util/CertFromTruststore.java | 6 +- .../ssl/util/CertificateValidator.java | 99 +- .../security/ssl/util/ExceptionUtils.java | 23 +- .../ssl/util/SSLCertificateHelper.java | 44 +- .../security/ssl/util/SSLConfigConstants.java | 167 +- .../ssl/util/SSLConnectionTestUtil.java | 29 +- .../security/ssl/util/SSLRequestHelper.java | 83 +- .../opensearch/security/ssl/util/TLSUtil.java | 6 +- .../opensearch/security/ssl/util/Utils.java | 7 +- .../security/support/Base64Helper.java | 18 +- .../security/support/ConfigConstants.java | 175 +- .../security/support/ConfigHelper.java | 64 +- .../GuardedSearchOperationWrapper.java | 1 + .../security/support/HTTPHelper.java | 33 +- .../security/support/HeaderHelper.java | 4 +- .../opensearch/security/support/MapUtils.java | 10 +- .../security/support/ModuleInfo.java | 240 ++- .../security/support/ModuleType.java | 187 +- .../security/support/PemKeyReader.java | 127 +- .../security/support/ReflectionHelper.java | 15 +- .../support/ReflectiveAttributeAccessors.java | 2 - .../security/support/SecurityJsonNode.java | 12 +- .../security/support/SecuritySettings.java | 17 +- .../security/support/SecurityUtils.java | 80 +- .../support/SnapshotRestoreHelper.java | 8 +- .../security/support/SourceFieldsContext.java | 42 +- .../security/support/WildcardMatcher.java | 30 +- .../security/tools/AuditConfigMigrater.java | 54 +- .../org/opensearch/security/tools/Hasher.java | 20 +- .../opensearch/security/tools/Migrater.java | 113 +- .../security/tools/SecurityAdmin.java | 1547 ++++++++++------- .../DefaultInterClusterRequestEvaluator.java | 35 +- .../InterClusterRequestEvaluator.java | 8 +- .../transport/OIDClusterRequestEvaluator.java | 8 +- .../transport/SecurityInterceptor.java | 148 +- .../transport/SecurityRequestHandler.java | 254 ++- .../security/user/AuthCredentials.java | 39 +- .../org/opensearch/security/user/User.java | 51 +- .../opensearch/security/user/UserService.java | 67 +- .../ratetracking/HeapBasedRateTracker.java | 37 +- .../ratetracking/SingleTryRateTracker.java | 3 +- 218 files changed, 13406 insertions(+), 8840 deletions(-) diff --git a/build.gradle b/build.gradle index 59d9107d1d..08e20f81b2 100644 --- a/build.gradle +++ b/build.gradle @@ -70,56 +70,7 @@ apply plugin: 'opensearch.opensearchplugin' apply plugin: 'opensearch.pluginzip' apply plugin: 'opensearch.rest-test' apply plugin: 'opensearch.testclusters' -// apply from: 'gradle/formatting.gradle' - -spotless { - java { - // Normally this isn't necessary, but we have Java sources in - // non-standard places - target '**/com/amazon/dlic/**/*.java' - target '**/com/amazon/security/**/*.java' - target '**/test/**/*.java' - target '**/integrationTest/**/*.java' - - removeUnusedImports() - eclipse().configFile rootProject.file('formatter/formatterConfig.xml') - trimTrailingWhitespace() - endWithNewline(); - - // note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports - importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') - - custom 'Refuse wildcard imports', { - // Wildcard imports can't be resolved; fail the build - if (it =~ /\s+import .*\*;/) { - throw new AssertionError("Do not use wildcard imports. 'spotlessApply' cannot resolve this issue.") - } - } - - // See DEVELOPER_GUIDE.md for details of when to enable this. - if (System.getProperty('spotless.paddedcell') != null) { - paddedCell() - } - } - format 'misc', { - target '*.md', '*.gradle', '**/*.json', '**/*.yaml', '**/*.yml', '**/*.svg' - - trimTrailingWhitespace() - endWithNewline() - } - format('javaFoo', JavaExtension) { - - importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') - target '**/*.java' - targetExclude '**/com/amazon/dlic/**/*.java' - targetExclude '**/com/amazon/security/**/*.java' - targetExclude '**/test/**/*.java' - targetExclude '**/integrationTest/**/*.java' - - trimTrailingWhitespace() - endWithNewline(); - } -} +apply from: 'gradle/formatting.gradle' licenseFile = rootProject.file('LICENSE.txt') noticeFile = rootProject.file('NOTICE.txt') diff --git a/gradle/formatting.gradle b/gradle/formatting.gradle index 40ae51afb1..de52b51c83 100644 --- a/gradle/formatting.gradle +++ b/gradle/formatting.gradle @@ -1,95 +1,26 @@ -/* -* 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. -*/ - -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License 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. - */ - -import org.opensearch.gradle.BuildPlugin - -/* - * This script plugin configures formatting for Java source using Spotless - * for Gradle. Since the act of formatting existing source can interfere - * with developers' workflows, we don't automatically format all code - * (yet). Instead, we maintain a list of projects that are excluded from - * formatting, until we reach a point where we can comfortably format them - * in one go without too much disruption. - * - * Any new sub-projects must not be added to the exclusions list! - * - * To perform a reformat, run: - * - * ./gradlew spotlessApply - * - * To check the current format, run: - * - * ./gradlew spotlessJavaCheck - * - * This is also carried out by the `precommit` task. - * - * For more about Spotless, see: - * - * https://github.com/diffplug/spotless/tree/master/plugin-gradle - */ - -org.opensearch.gradle.BuildPlugin { - plugins.withType(BuildPlugin).whenPluginAdded { - project.apply plugin: "com.diffplug.spotless" - - spotless { - java { - // Normally this isn't necessary, but we have Java sources in - // non-standard places - target '**/*.java' - - removeUnusedImports() - eclipse().configFile rootProject.file('buildSrc/formatterConfig.xml') - trimTrailingWhitespace() - endWithNewline() - - custom 'Refuse wildcard imports', { - // Wildcard imports can't be resolved; fail the build - if (it =~ /\s+import .*\*;/) { - throw new AssertionError("Do not use wildcard imports. 'spotlessApply' cannot resolve this issue.") - } - } - - // See DEVELOPER_GUIDE.md for details of when to enable this. - if (System.getProperty('spotless.paddedcell') != null) { - paddedCell() - } - } - format 'misc', { - target '*.md', '*.gradle', '**/*.yaml', '**/*.yml', '**/*.svg' - - trimTrailingWhitespace() - endWithNewline() +allprojects { + project.apply plugin: "com.diffplug.spotless" + spotless { + java { + // Normally this isn't necessary, but we have Java sources in + // non-standard places + target '**/*.java' + + removeUnusedImports() + eclipse().configFile rootProject.file('formatter/formatterConfig.xml') + trimTrailingWhitespace() + endWithNewline(); + + // See DEVELOPER_GUIDE.md for details of when to enable this. + if (System.getProperty('spotless.paddedcell') != null) { + paddedCell() } } + format 'misc', { + target '*.md', '*.gradle', '**/*.json', '**/*.yaml', '**/*.yml', '**/*.svg' - precommit.dependsOn 'spotlessJavaCheck' + trimTrailingWhitespace() + endWithNewline() + } } } diff --git a/src/main/java/org/opensearch/security/DefaultObjectMapper.java b/src/main/java/org/opensearch/security/DefaultObjectMapper.java index 774af04bfa..fb3385629b 100644 --- a/src/main/java/org/opensearch/security/DefaultObjectMapper.java +++ b/src/main/java/org/opensearch/security/DefaultObjectMapper.java @@ -57,7 +57,7 @@ public class DefaultObjectMapper { static { objectMapper.setSerializationInclusion(Include.NON_NULL); - //objectMapper.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); + // objectMapper.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); objectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION); defaulOmittingObjectMapper.setSerializationInclusion(Include.NON_DEFAULT); defaulOmittingObjectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION); @@ -75,24 +75,31 @@ public static boolean getOrDefault(Map properties, String key, b if (value == null) { return defaultValue; } else if (value instanceof Boolean) { - return (boolean)value; + return (boolean) value; } else if (value instanceof String) { - String text = ((String)value).trim(); + String text = ((String) value).trim(); if ("true".equals(text) || "True".equals(text)) { return true; } if ("false".equals(text) || "False".equals(text)) { return false; } - throw InvalidFormatException.from(null, - "Cannot deserialize value of type 'boolean' from String \"" + text + "\": only \"true\" or \"false\" recognized)", - null, Boolean.class); + throw InvalidFormatException.from( + null, + "Cannot deserialize value of type 'boolean' from String \"" + text + "\": only \"true\" or \"false\" recognized)", + null, + Boolean.class + ); } - throw MismatchedInputException.from(null, Boolean.class, "Cannot deserialize instance of 'boolean' out of '" + value + "' (Property: " + key + ")"); + throw MismatchedInputException.from( + null, + Boolean.class, + "Cannot deserialize instance of 'boolean' out of '" + value + "' (Property: " + key + ")" + ); } public static T getOrDefault(Map properties, String key, T defaultValue) { - T value = (T)properties.get(key); + T value = (T) properties.get(key); return value != null ? value : defaultValue; } @@ -172,7 +179,7 @@ public static String writeValueAsString(Object value, boolean omitDefaults) thro return AccessController.doPrivileged(new PrivilegedExceptionAction() { @Override public String run() throws Exception { - return (omitDefaults?defaulOmittingObjectMapper:objectMapper).writeValueAsString(value); + return (omitDefaults ? defaulOmittingObjectMapper : objectMapper).writeValueAsString(value); } }); } catch (final PrivilegedActionException e) { @@ -229,12 +236,11 @@ public static TypeFactory getTypeFactory() { } public static Set getFields(Class cls) { - return objectMapper - .getSerializationConfig() - .introspect(getTypeFactory().constructType(cls)) - .findProperties() - .stream() - .map(BeanPropertyDefinition::getName) - .collect(ImmutableSet.toImmutableSet()); + return objectMapper.getSerializationConfig() + .introspect(getTypeFactory().constructType(cls)) + .findProperties() + .stream() + .map(BeanPropertyDefinition::getName) + .collect(ImmutableSet.toImmutableSet()); } } diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 89e8ec31ac..e4ca1050d6 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -244,7 +244,8 @@ public void close() throws IOException { private final SslExceptionHandler evaluateSslExceptionHandler() { if (client || disabled || SSLConfig.isSslOnlyMode()) { - return new SslExceptionHandler(){}; + return new SslExceptionHandler() { + }; } return Objects.requireNonNull(sslExceptionHandler); @@ -274,7 +275,9 @@ public OpenSearchSecurityPlugin(final Settings settings, final Path configPath) if (disabled) { this.sslCertReloadEnabled = false; - log.warn("OpenSearch Security plugin installed but disabled. This can expose your configuration (including passwords) to the public."); + log.warn( + "OpenSearch Security plugin installed but disabled. This can expose your configuration (including passwords) to the public." + ); return; } @@ -284,7 +287,6 @@ public OpenSearchSecurityPlugin(final Settings settings, final Path configPath) return; } - demoCertHashes.add("54a92508de7a39d06242a0ffbf59414d7eb478633c719e6af03938daf6de8a1a"); demoCertHashes.add("742e4659c79d7cad89ea86aab70aea490f23bbfc7e72abd5f0a5d3fb4c84d212"); demoCertHashes.add("db1264612891406639ecd25c894f256b7c5a6b7e1d9054cbe37b77acd2ddd913"); @@ -294,7 +296,7 @@ public OpenSearchSecurityPlugin(final Settings settings, final Path configPath) demoCertHashes.add("7a355f42c90e7543a267fbe3976c02f619036f5a34ce712995a22b342d83c3ce"); demoCertHashes.add("a9b5eca1399ec8518081c0d4a21a34eec4589087ce64c04fb01a488f9ad8edc9"); - //new certs 04/2018 + // new certs 04/2018 demoCertHashes.add("d14aefe70a592d7a29e14f3ff89c3d0070c99e87d21776aa07d333ee877e758f"); demoCertHashes.add("54a70016e0837a2b0c5658d1032d7ca32e432c62c55f01a2bf5adcb69a0a7ba9"); demoCertHashes.add("bdc141ab2272c779d0f242b79063152c49e1b06a2af05e0fd90d505f2b44d5f5"); @@ -310,7 +312,7 @@ public OpenSearchSecurityPlugin(final Settings settings, final Path configPath) AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { - if(Security.getProvider("BC") == null) { + if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } return null; @@ -322,18 +324,18 @@ public Object run() { deprecationLogger.deprecate("Setting {} is ignored.", advancedModulesEnabledKey); } - log.info("Clustername: {}", settings.get("cluster.name","opensearch")); + log.info("Clustername: {}", settings.get("cluster.name", "opensearch")); if (!transportSSLEnabled && !SSLConfig.isSslOnlyMode()) { - throw new IllegalStateException(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED+" must be set to 'true'"); + throw new IllegalStateException(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED + " must be set to 'true'"); } - if(!client) { + if (!client) { final List filesWithWrongPermissions = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); - if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { + if (Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().filter(p -> checkFilePermissions(p)).collect(Collectors.toList()); } catch (Exception e) { @@ -346,9 +348,9 @@ public List run() { } }); - if(filesWithWrongPermissions != null && filesWithWrongPermissions.size() > 0) { - for(final Path p: filesWithWrongPermissions) { - if(Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) { + if (filesWithWrongPermissions != null && filesWithWrongPermissions.size() > 0) { + for (final Path p : filesWithWrongPermissions) { + if (Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) { log.warn("Directory {} has insecure file permissions (should be 0700)", p); } else { log.warn("File {} has insecure file permissions (should be 0600)", p); @@ -357,13 +359,13 @@ public List run() { } } - if(!client && !settings.getAsBoolean(ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES, false)) { - //check for demo certificates + if (!client && !settings.getAsBoolean(ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES, false)) { + // check for demo certificates final List files = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); - if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { + if (Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().map(p -> sha256(p)).collect(Collectors.toList()); } catch (Exception e) { @@ -376,11 +378,13 @@ public List run() { } }); - if(files != null) { + if (files != null) { demoCertHashes.retainAll(files); - if(!demoCertHashes.isEmpty()) { - log.error("Demo certificates found but "+ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES+" is set to false."); - throw new RuntimeException("Demo certificates found "+demoCertHashes); + if (!demoCertHashes.isEmpty()) { + log.error( + "Demo certificates found but " + ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES + " is set to false." + ); + throw new RuntimeException("Demo certificates found " + demoCertHashes); } } else { throw new RuntimeException("Unable to look for demo certificates"); @@ -391,22 +395,22 @@ public List run() { private String sha256(Path p) { - if(!Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS)) { + if (!Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS)) { return ""; } - if(!Files.isReadable(p)) { - log.debug("Unreadable file "+p+" found"); + if (!Files.isReadable(p)) { + log.debug("Unreadable file " + p + " found"); return ""; } try { MessageDigest digester = MessageDigest.getInstance("SHA256"); final String hash = org.bouncycastle.util.encoders.Hex.toHexString(digester.digest(Files.readAllBytes(p))); - log.debug(hash +" :: "+p); + log.debug(hash + " :: " + p); return hash; } catch (Exception e) { - throw new OpenSearchSecurityException("Unable to digest file "+p, e); + throw new OpenSearchSecurityException("Unable to digest file " + p, e); } } @@ -416,79 +420,133 @@ private boolean checkFilePermissions(final Path p) { return false; } - Set perms; try { perms = Files.getPosixFilePermissions(p, LinkOption.NOFOLLOW_LINKS); } catch (Exception e) { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("Cannot determine posix file permissions for {} due to {}", p, e); } - //ignore, can happen on windows + // ignore, can happen on windows return false; } - if(Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) { + if (Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) { if (perms.contains(PosixFilePermission.OTHERS_EXECUTE)) { // no x for others must be set return true; } } else { - if (perms.contains(PosixFilePermission.OWNER_EXECUTE) || perms.contains(PosixFilePermission.GROUP_EXECUTE) - || perms.contains(PosixFilePermission.OTHERS_EXECUTE)) { + if (perms.contains(PosixFilePermission.OWNER_EXECUTE) + || perms.contains(PosixFilePermission.GROUP_EXECUTE) + || perms.contains(PosixFilePermission.OTHERS_EXECUTE)) { // no x must be set return true; } } - if (perms.contains(PosixFilePermission.OTHERS_READ) || perms.contains(PosixFilePermission.OTHERS_WRITE)) { // no permissions for "others" allowed return true; } - //if (perms.contains(PosixFilePermission.GROUP_READ) || perms.contains(PosixFilePermission.GROUP_WRITE)) { - // // no permissions for "group" allowed - // return true; - //} + // if (perms.contains(PosixFilePermission.GROUP_READ) || perms.contains(PosixFilePermission.GROUP_WRITE)) { + // // no permissions for "group" allowed + // return true; + // } return false; } - @Override - public List getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings, - IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter, - IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster) { + public List getRestHandlers( + Settings settings, + RestController restController, + ClusterSettings clusterSettings, + IndexScopedSettings indexScopedSettings, + SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier nodesInCluster + ) { final List handlers = new ArrayList(1); if (!client && !disabled) { - handlers.addAll(super.getRestHandlers(settings, restController, clusterSettings, indexScopedSettings, settingsFilter, indexNameExpressionResolver, nodesInCluster)); + handlers.addAll( + super.getRestHandlers( + settings, + restController, + clusterSettings, + indexScopedSettings, + settingsFilter, + indexNameExpressionResolver, + nodesInCluster + ) + ); - if(!SSLConfig.isSslOnlyMode()) { - handlers.add(new SecurityInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool))); + if (!SSLConfig.isSslOnlyMode()) { + handlers.add( + new SecurityInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool)) + ); handlers.add(new SecurityHealthAction(settings, restController, Objects.requireNonNull(backendRegistry))); - handlers.add(new DashboardsInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool))); - handlers.add(new TenantInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool), - Objects.requireNonNull(cs), Objects.requireNonNull(adminDns), Objects.requireNonNull(cr))); - handlers.add(new SecurityConfigUpdateAction(settings, restController, Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); - handlers.add(new SecurityWhoAmIAction(settings, restController, Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); + handlers.add( + new DashboardsInfoAction( + settings, + restController, + Objects.requireNonNull(evaluator), + Objects.requireNonNull(threadPool) + ) + ); + handlers.add( + new TenantInfoAction( + settings, + restController, + Objects.requireNonNull(evaluator), + Objects.requireNonNull(threadPool), + Objects.requireNonNull(cs), + Objects.requireNonNull(adminDns), + Objects.requireNonNull(cr) + ) + ); + handlers.add( + new SecurityConfigUpdateAction( + settings, + restController, + Objects.requireNonNull(threadPool), + adminDns, + configPath, + principalExtractor + ) + ); + handlers.add( + new SecurityWhoAmIAction( + settings, + restController, + Objects.requireNonNull(threadPool), + adminDns, + configPath, + principalExtractor + ) + ); handlers.addAll( - SecurityRestApiActions.getHandler( - settings, - configPath, - restController, - localClient, - adminDns, - cr, cs, principalExtractor, - evaluator, - threadPool, - Objects.requireNonNull(auditLog), sks, - Objects.requireNonNull(userService), - sslCertReloadEnabled) + SecurityRestApiActions.getHandler( + settings, + configPath, + restController, + localClient, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + Objects.requireNonNull(auditLog), + sks, + Objects.requireNonNull(userService), + sslCertReloadEnabled + ) ); log.debug("Added {} rest handler(s)", handlers.size()); } @@ -500,7 +558,7 @@ public List getRestHandlers(Settings settings, RestController restC @Override public UnaryOperator getRestHandlerWrapper(final ThreadContext threadContext) { - if(client || disabled || SSLConfig.isSslOnlyMode()) { + if (client || disabled || SSLConfig.isSslOnlyMode()) { return (rh) -> rh; } @@ -510,7 +568,7 @@ public UnaryOperator getRestHandlerWrapper(final ThreadContext thre @Override public List> getActions() { List> actions = new ArrayList<>(1); - if(!disabled && !SSLConfig.isSslOnlyMode()) { + if (!disabled && !SSLConfig.isSslOnlyMode()) { actions.add(new ActionHandler<>(ConfigUpdateAction.INSTANCE, TransportConfigUpdateAction.class)); actions.add(new ActionHandler<>(WhoAmIAction.INSTANCE, TransportWhoAmIAction.class)); } @@ -519,7 +577,7 @@ public UnaryOperator getRestHandlerWrapper(final ThreadContext thre @Override public void onIndexModule(IndexModule indexModule) { - //called for every index! + // called for every index! if (!disabled && !client && !SSLConfig.isSslOnlyMode()) { log.debug("Handle auditLog {} for onIndexModule() of index {}", auditLog.getClass(), indexModule.getIndex().getName()); @@ -527,8 +585,19 @@ public void onIndexModule(IndexModule indexModule) { final ComplianceIndexingOperationListener ciol = new ComplianceIndexingOperationListenerImpl(auditLog); indexModule.addIndexOperationListener(ciol); - indexModule.setReaderWrapper(indexService -> new SecurityFlsDlsIndexSearcherWrapper(indexService, settings, adminDns, cs, auditLog, ciol, evaluator, salt)); - indexModule.forceQueryCacheProvider((indexSettings,nodeCache)->new QueryCache() { + indexModule.setReaderWrapper( + indexService -> new SecurityFlsDlsIndexSearcherWrapper( + indexService, + settings, + adminDns, + cs, + auditLog, + ciol, + evaluator, + salt + ) + ); + indexModule.forceQueryCacheProvider((indexSettings, nodeCache) -> new QueryCache() { @Override public Index index() { @@ -547,17 +616,21 @@ public void clear(String reason) { @Override public Weight doCache(Weight weight, QueryCachingPolicy policy) { - final Map> allowedFlsFields = (Map>) HeaderHelper.deserializeSafeFromHeader(threadPool.getThreadContext(), - ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER); + final Map> allowedFlsFields = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadPool.getThreadContext(), + ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER + ); - if(SecurityUtils.evalMap(allowedFlsFields, index().getName()) != null) { + if (SecurityUtils.evalMap(allowedFlsFields, index().getName()) != null) { return weight; } else { - final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader(threadPool.getThreadContext(), - ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER); + final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadPool.getThreadContext(), + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + ); - if(SecurityUtils.evalMap(maskedFieldsMap, index().getName()) != null) { + if (SecurityUtils.evalMap(maskedFieldsMap, index().getName()) != null) { return weight; } else { return nodeCache.doCache(weight, policy); @@ -577,28 +650,34 @@ public void onPreQueryPhase(SearchContext context) { @Override public void onNewReaderContext(ReaderContext readerContext) { final boolean interClusterRequest = HeaderHelper.isInterClusterRequest(threadPool.getThreadContext()); - if (Origin.LOCAL.toString().equals(threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) - && (interClusterRequest || HeaderHelper.isDirectRequest(threadPool.getThreadContext())) + if (Origin.LOCAL.toString() + .equals(threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) + && (interClusterRequest || HeaderHelper.isDirectRequest(threadPool.getThreadContext())) ) { readerContext.putInContext("_opendistro_security_scroll_auth_local", Boolean.TRUE); } else { - readerContext.putInContext("_opendistro_security_scroll_auth", threadPool.getThreadContext() - .getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER)); + readerContext.putInContext( + "_opendistro_security_scroll_auth", + threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER) + ); } } @Override public void onNewScrollContext(ReaderContext readerContext) { final boolean interClusterRequest = HeaderHelper.isInterClusterRequest(threadPool.getThreadContext()); - if (Origin.LOCAL.toString().equals(threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) - && (interClusterRequest || HeaderHelper.isDirectRequest(threadPool.getThreadContext())) + if (Origin.LOCAL.toString() + .equals(threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) + && (interClusterRequest || HeaderHelper.isDirectRequest(threadPool.getThreadContext())) ) { readerContext.putInContext("_opendistro_security_scroll_auth_local", Boolean.TRUE); } else { - readerContext.putInContext("_opendistro_security_scroll_auth", threadPool.getThreadContext() - .getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER)); + readerContext.putInContext( + "_opendistro_security_scroll_auth", + threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER) + ); } } @@ -609,8 +688,7 @@ public void validateReaderContext(ReaderContext readerContext, TransportRequest final Object _user = readerContext.getFromContext("_opendistro_security_scroll_auth"); if (_user != null && (_user instanceof User)) { final User scrollUser = (User) _user; - final User currentUser = threadPool.getThreadContext() - .getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User currentUser = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); if (!scrollUser.equals(currentUser)) { auditLog.logMissingPrivileges(SearchScrollAction.NAME, transportRequest, null); log.error("Wrong user {} in reader context, expected {}", scrollUser, currentUser); @@ -631,8 +709,10 @@ public void onQueryPhase(SearchContext searchContext, long tookInNanos) { return; } - final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader(threadPool.getThreadContext(), - ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER); + final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadPool.getThreadContext(), + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + ); final String maskedEval = SecurityUtils.evalMap(maskedFieldsMap, indexModule.getIndex().getName()); if (maskedEval != null) { final Set mf = maskedFieldsMap.get(maskedEval); @@ -662,8 +742,12 @@ public List getTransportInterceptors(NamedWriteableRegistr interceptors.add(new TransportInterceptor() { @Override - public TransportRequestHandler interceptHandler(String action, String executor, - boolean forceExecution, TransportRequestHandler actualHandler) { + public TransportRequestHandler interceptHandler( + String action, + String executor, + boolean forceExecution, + TransportRequestHandler actualHandler + ) { return new TransportRequestHandler() { @@ -681,8 +765,13 @@ public AsyncSender interceptSender(AsyncSender sender) { return new AsyncSender() { @Override - public void sendRequest(Connection connection, String action, - TransportRequest request, TransportRequestOptions options, TransportResponseHandler handler) { + public void sendRequest( + Connection connection, + String action, + TransportRequest request, + TransportRequestOptions options, + TransportResponseHandler handler + ) { si.sendRequestDecorate(sender, connection, action, request, options, handler); } }; @@ -694,73 +783,148 @@ public void sendRequest(Connection connection, Str } @Override - public Map> getTransports(Settings settings, ThreadPool threadPool, PageCacheRecycler pageCacheRecycler, - CircuitBreakerService circuitBreakerService, NamedWriteableRegistry namedWriteableRegistry, NetworkService networkService) { + public Map> getTransports( + Settings settings, + ThreadPool threadPool, + PageCacheRecycler pageCacheRecycler, + CircuitBreakerService circuitBreakerService, + NamedWriteableRegistry namedWriteableRegistry, + NetworkService networkService + ) { Map> transports = new HashMap>(); - if(SSLConfig.isSslOnlyMode()) { - return super.getTransports(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService); + if (SSLConfig.isSslOnlyMode()) { + return super.getTransports( + settings, + threadPool, + pageCacheRecycler, + circuitBreakerService, + namedWriteableRegistry, + networkService + ); } if (transportSSLEnabled) { - transports.put("org.opensearch.security.ssl.http.netty.SecuritySSLNettyTransport", - () -> new SecuritySSLNettyTransport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, - namedWriteableRegistry, circuitBreakerService, sks, evaluateSslExceptionHandler(), sharedGroupFactory, SSLConfig)); + transports.put( + "org.opensearch.security.ssl.http.netty.SecuritySSLNettyTransport", + () -> new SecuritySSLNettyTransport( + settings, + Version.CURRENT, + threadPool, + networkService, + pageCacheRecycler, + namedWriteableRegistry, + circuitBreakerService, + sks, + evaluateSslExceptionHandler(), + sharedGroupFactory, + SSLConfig + ) + ); } return transports; } @Override - public Map> getHttpTransports(Settings settings, ThreadPool threadPool, BigArrays bigArrays, - PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, - NetworkService networkService, Dispatcher dispatcher, ClusterSettings clusterSettings) { + public Map> getHttpTransports( + Settings settings, + ThreadPool threadPool, + BigArrays bigArrays, + PageCacheRecycler pageCacheRecycler, + CircuitBreakerService circuitBreakerService, + NamedXContentRegistry xContentRegistry, + NetworkService networkService, + Dispatcher dispatcher, + ClusterSettings clusterSettings + ) { - if(SSLConfig.isSslOnlyMode()) { - return super.getHttpTransports(settings, threadPool, bigArrays, pageCacheRecycler, circuitBreakerService, xContentRegistry, - networkService, dispatcher, clusterSettings); + if (SSLConfig.isSslOnlyMode()) { + return super.getHttpTransports( + settings, + threadPool, + bigArrays, + pageCacheRecycler, + circuitBreakerService, + xContentRegistry, + networkService, + dispatcher, + clusterSettings + ); } - if(!disabled) { + if (!disabled) { if (!client && httpSSLEnabled) { - final ValidatingDispatcher validatingDispatcher = new ValidatingDispatcher(threadPool.getThreadContext(), dispatcher, - settings, configPath, evaluateSslExceptionHandler()); - //TODO close odshst - final SecurityHttpServerTransport odshst = new SecurityHttpServerTransport(settings, networkService, bigArrays, - threadPool, sks, evaluateSslExceptionHandler(), xContentRegistry, validatingDispatcher, clusterSettings, sharedGroupFactory); + final ValidatingDispatcher validatingDispatcher = new ValidatingDispatcher( + threadPool.getThreadContext(), + dispatcher, + settings, + configPath, + evaluateSslExceptionHandler() + ); + // TODO close odshst + final SecurityHttpServerTransport odshst = new SecurityHttpServerTransport( + settings, + networkService, + bigArrays, + threadPool, + sks, + evaluateSslExceptionHandler(), + xContentRegistry, + validatingDispatcher, + clusterSettings, + sharedGroupFactory + ); - return Collections.singletonMap("org.opensearch.security.http.SecurityHttpServerTransport", - () -> odshst); + return Collections.singletonMap("org.opensearch.security.http.SecurityHttpServerTransport", () -> odshst); } else if (!client) { - return Collections.singletonMap("org.opensearch.security.http.SecurityHttpServerTransport", - () -> new SecurityNonSslHttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory)); + return Collections.singletonMap( + "org.opensearch.security.http.SecurityHttpServerTransport", + () -> new SecurityNonSslHttpServerTransport( + settings, + networkService, + bigArrays, + threadPool, + xContentRegistry, + dispatcher, + clusterSettings, + sharedGroupFactory + ) + ); } } return Collections.emptyMap(); } - - @Override - public Collection createComponents(Client localClient, ClusterService clusterService, ThreadPool threadPool, - ResourceWatcherService resourceWatcherService, ScriptService scriptService, NamedXContentRegistry xContentRegistry, - Environment environment, NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry, - IndexNameExpressionResolver indexNameExpressionResolver, Supplier repositoriesServiceSupplier) { + public Collection createComponents( + Client localClient, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { SSLConfig.registerClusterSettingsChangeListener(clusterService.getClusterSettings()); - if(SSLConfig.isSslOnlyMode()) { + if (SSLConfig.isSslOnlyMode()) { return super.createComponents( - localClient, - clusterService, - threadPool, - resourceWatcherService, - scriptService, - xContentRegistry, - environment, - nodeEnvironment, - namedWriteableRegistry, - indexNameExpressionResolver, - repositoriesServiceSupplier + localClient, + clusterService, + threadPool, + resourceWatcherService, + scriptService, + xContentRegistry, + environment, + nodeEnvironment, + namedWriteableRegistry, + indexNameExpressionResolver, + repositoriesServiceSupplier ); } @@ -774,7 +938,7 @@ public Collection createComponents(Client localClient, ClusterService cl return components; } - //Register opensearch dynamic settings + // Register opensearch dynamic settings transportPassiveAuthSetting.registerClusterSettingsChangeListener(clusterService.getClusterSettings()); final ClusterInfoHolder cih = new ClusterInfoHolder(); @@ -787,8 +951,10 @@ public Collection createComponents(Client localClient, ClusterService cl final String DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS = DefaultInterClusterRequestEvaluator.class.getName(); InterClusterRequestEvaluator interClusterRequestEvaluator = new DefaultInterClusterRequestEvaluator(settings); - final String className = settings.get(ConfigConstants.SECURITY_INTERCLUSTER_REQUEST_EVALUATOR_CLASS, - DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS); + final String className = settings.get( + ConfigConstants.SECURITY_INTERCLUSTER_REQUEST_EVALUATOR_CLASS, + DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS + ); log.debug("Using {} as intercluster request evaluator class", className); if (!DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS.equals(className)) { interClusterRequestEvaluator = ReflectionHelper.instantiateInterClusterRequestEvaluator(className, settings); @@ -802,7 +968,14 @@ public Collection createComponents(Client localClient, ClusterService cl auditLog = new NullAuditLog(); privilegesInterceptor = new PrivilegesInterceptor(resolver, clusterService, localClient, threadPool); } else { - dlsFlsValve = new DlsFlsValveImpl(settings, localClient, clusterService, resolver, xContentRegistry, threadPool.getThreadContext()); + dlsFlsValve = new DlsFlsValveImpl( + settings, + localClient, + clusterService, + resolver, + xContentRegistry, + threadPool.getThreadContext() + ); auditLog = new AuditLogImpl(settings, configPath, localClient, threadPool, resolver, clusterService, environment); privilegesInterceptor = new PrivilegesInterceptorImpl(resolver, clusterService, localClient, threadPool); } @@ -822,21 +995,39 @@ public Collection createComponents(Client localClient, ClusterService cl // DLS-FLS is enabled if not client and not disabled and not SSL only. final boolean dlsFlsEnabled = !SSLConfig.isSslOnlyMode(); - evaluator = new PrivilegesEvaluator(clusterService, threadPool, cr, resolver, auditLog, - settings, privilegesInterceptor, cih, irr, dlsFlsEnabled, namedXContentRegistry.get()); + evaluator = new PrivilegesEvaluator( + clusterService, + threadPool, + cr, + resolver, + auditLog, + settings, + privilegesInterceptor, + cih, + irr, + dlsFlsEnabled, + namedXContentRegistry.get() + ); sf = new SecurityFilter(settings, evaluator, adminDns, dlsFlsValve, auditLog, threadPool, cs, compatConfig, irr, xffResolver); final String principalExtractorClass = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PRINCIPAL_EXTRACTOR_CLASS, null); - if(principalExtractorClass == null) { + if (principalExtractorClass == null) { principalExtractor = new DefaultPrincipalExtractor(); } else { principalExtractor = ReflectionHelper.instantiatePrincipalExtractor(principalExtractorClass); } - securityRestHandler = new SecurityRestFilter(backendRegistry, auditLog, threadPool, - principalExtractor, settings, configPath, compatConfig); + securityRestHandler = new SecurityRestFilter( + backendRegistry, + auditLog, + threadPool, + principalExtractor, + settings, + configPath, + compatConfig + ); final DynamicConfigFactory dcf = new DynamicConfigFactory(cr, settings, configPath, localClient, threadPool, cih); dcf.registerDCFListener(backendRegistry); @@ -852,12 +1043,24 @@ public Collection createComponents(Client localClient, ClusterService cl cr.setDynamicConfigFactory(dcf); - si = new SecurityInterceptor(settings, threadPool, backendRegistry, auditLog, principalExtractor, - interClusterRequestEvaluator, cs, Objects.requireNonNull(sslExceptionHandler), Objects.requireNonNull(cih), SSLConfig); + si = new SecurityInterceptor( + settings, + threadPool, + backendRegistry, + auditLog, + principalExtractor, + interClusterRequestEvaluator, + cs, + Objects.requireNonNull(sslExceptionHandler), + Objects.requireNonNull(cih), + SSLConfig + ); components.add(principalExtractor); - // NOTE: We need to create DefaultInterClusterRequestEvaluator before creating ConfigurationRepository since the latter requires security index to be accessible which means - // communciation with other nodes is already up. However for the communication to be up, there needs to be trusted nodes_dn. Hence the base values from opensearch.yml + // NOTE: We need to create DefaultInterClusterRequestEvaluator before creating ConfigurationRepository since the latter requires + // security index to be accessible which means + // communciation with other nodes is already up. However for the communication to be up, there needs to be trusted nodes_dn. Hence + // the base values from opensearch.yml // is used to first establish trust between same cluster nodes and there after dynamic config is loaded if enabled. if (DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS.equals(className)) { DefaultInterClusterRequestEvaluator e = (DefaultInterClusterRequestEvaluator) interClusterRequestEvaluator; @@ -873,7 +1076,6 @@ public Collection createComponents(Client localClient, ClusterService cl components.add(dcf); components.add(userService); - return components; } @@ -881,7 +1083,7 @@ public Collection createComponents(Client localClient, ClusterService cl @Override public Settings additionalSettings() { - if(disabled) { + if (disabled) { return Settings.EMPTY; } @@ -889,12 +1091,13 @@ public Settings additionalSettings() { builder.put(super.additionalSettings()); - if(!SSLConfig.isSslOnlyMode()){ + if (!SSLConfig.isSslOnlyMode()) { builder.put(NetworkModule.TRANSPORT_TYPE_KEY, "org.opensearch.security.ssl.http.netty.SecuritySSLNettyTransport"); builder.put(NetworkModule.HTTP_TYPE_KEY, "org.opensearch.security.http.SecurityHttpServerTransport"); } return builder.build(); } + @Override public List> getSettings() { List> settings = new ArrayList>(); @@ -907,80 +1110,246 @@ public List> getSettings() { settings.add(SecuritySettings.LEGACY_OPENDISTRO_SSL_DUAL_MODE_SETTING); // Protected index settings - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_DEFAULT, Property.NodeScope, Property.Filtered, Property.Final)); - settings.add(Setting.listSetting(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_DEFAULT, Function.identity(), Property.NodeScope, Property.Filtered, Property.Final)); - settings.add(Setting.listSetting(ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_DEFAULT, Function.identity(), Property.NodeScope, Property.Filtered, Property.Final)); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, + ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_DEFAULT, + Property.NodeScope, + Property.Filtered, + Property.Final + ) + ); + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, + ConfigConstants.SECURITY_PROTECTED_INDICES_DEFAULT, + Function.identity(), + Property.NodeScope, + Property.Filtered, + Property.Final + ) + ); + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, + ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_DEFAULT, + Function.identity(), + Property.NodeScope, + Property.Filtered, + Property.Final + ) + ); // System index settings - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_DEFAULT, Property.NodeScope, Property.Filtered, Property.Final)); - settings.add(Setting.listSetting(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, ConfigConstants.SECURITY_SYSTEM_INDICES_DEFAULT, Function.identity(), Property.NodeScope, Property.Filtered, Property.Final)); - - if(!SSLConfig.isSslOnlyMode()) { - settings.add(Setting.listSetting(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, + ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_DEFAULT, + Property.NodeScope, + Property.Filtered, + Property.Final + ) + ); + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, + ConfigConstants.SECURITY_SYSTEM_INDICES_DEFAULT, + Function.identity(), + Property.NodeScope, + Property.Filtered, + Property.Final + ) + ); + + if (!SSLConfig.isSslOnlyMode()) { + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here settings.add(Setting.simpleString(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, Property.NodeScope, Property.Filtered)); - settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_IMPERSONATION_DN+".", Property.NodeScope)); //not filtered here + settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_IMPERSONATION_DN + ".", Property.NodeScope)); // not filtered + // here settings.add(Setting.simpleString(ConfigConstants.SECURITY_CERT_OID, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_CERT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS, Property.NodeScope, Property.Filtered)); - settings.add(Setting.listSetting(ConfigConstants.SECURITY_NODES_DN, Collections.emptyList(), Function.identity(), Property.NodeScope));//not filtered here + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_CERT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.listSetting(ConfigConstants.SECURITY_NODES_DN, Collections.emptyList(), Function.identity(), Property.NodeScope) + );// not filtered here - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, false, Property.NodeScope));//not filtered here + settings.add(Setting.boolSetting(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, false, Property.NodeScope));// not + // filtered + // here - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, ConfigConstants.SECURITY_DEFAULT_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, - Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, ConfigConstants.SECURITY_DEFAULT_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, - Property.NodeScope, Property.Filtered)); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, + ConfigConstants.SECURITY_DEFAULT_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, + ConfigConstants.SECURITY_DEFAULT_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, + Property.NodeScope, + Property.Filtered + ) + ); settings.add(Setting.boolSetting(ConfigConstants.SECURITY_DISABLED, false, Property.NodeScope, Property.Filtered)); settings.add(Setting.intSetting(ConfigConstants.SECURITY_CACHE_TTL_MINUTES, 60, 0, Property.NodeScope, Property.Filtered)); - //Security - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_ADVANCED_MODULES_ENABLED, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".", Property.NodeScope)); //not filtered here + // Security + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_ADVANCED_MODULES_ENABLED, true, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES, false, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, false, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, + true, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, false, Property.NodeScope, Property.Filtered) + ); + settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".", Property.NodeScope)); // not + // filtered + // here settings.add(Setting.simpleString(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_DISABLE_ENVVAR_REPLACEMENT, false, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_DISABLE_ENVVAR_REPLACEMENT, false, Property.NodeScope, Property.Filtered) + ); // Security - Audit settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT, Property.NodeScope, Property.Filtered)); settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_ROUTES + ".", Property.NodeScope)); - settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + ".", Property.NodeScope)); + settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + ".", Property.NodeScope)); settings.add(Setting.intSetting(ConfigConstants.SECURITY_AUDIT_THREADPOOL_SIZE, 10, Property.NodeScope, Property.Filtered)); - settings.add(Setting.intSetting(ConfigConstants.SECURITY_AUDIT_THREADPOOL_MAX_QUEUE_LEN, 100*1000, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.intSetting( + ConfigConstants.SECURITY_AUDIT_THREADPOOL_MAX_QUEUE_LEN, + 100 * 1000, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, true, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES, true, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true, Property.NodeScope, Property.Filtered) + ); final List disabledCategories = new ArrayList(2); disabledCategories.add("AUTHENTICATED"); disabledCategories.add("GRANTED_PRIVILEGES"); - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, disabledCategories, Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, disabledCategories, Function.identity(), Property.NodeScope)); //not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, + disabledCategories, + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, + disabledCategories, + Function.identity(), + Property.NodeScope + ) + ); // not filtered here final List ignoredUsers = new ArrayList(2); ignoredUsers.add("kibanaserver"); - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, ignoredUsers, Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS, true, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, + ignoredUsers, + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.boolSetting( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS, + true, + Property.NodeScope, + Property.Filtered + ) + ); - final BiFunction> boolSettingNodeScopeFiltered = (String keyWithNamespace, Boolean value) -> Setting.boolSetting(keyWithNamespace, value, Property.NodeScope, Property.Filtered); + final BiFunction> boolSettingNodeScopeFiltered = ( + String keyWithNamespace, + Boolean value) -> Setting.boolSetting(keyWithNamespace, value, Property.NodeScope, Property.Filtered); Arrays.stream(FilterEntries.values()).map(filterEntry -> { - switch(filterEntry) { + switch (filterEntry) { case DISABLE_REST_CATEGORIES: case DISABLE_TRANSPORT_CATEGORIES: - return Setting.listSetting(filterEntry.getKeyWithNamespace(), disabledCategories, Function.identity(), Property.NodeScope); + return Setting.listSetting( + filterEntry.getKeyWithNamespace(), + disabledCategories, + Function.identity(), + Property.NodeScope + ); case IGNORE_REQUESTS: - return Setting.listSetting(filterEntry.getKeyWithNamespace(), Collections.emptyList(), Function.identity(), Property.NodeScope); + return Setting.listSetting( + filterEntry.getKeyWithNamespace(), + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ); case IGNORE_USERS: - return Setting.listSetting(filterEntry.getKeyWithNamespace(), ignoredUsers, Function.identity(), Property.NodeScope); + return Setting.listSetting( + filterEntry.getKeyWithNamespace(), + ignoredUsers, + Function.identity(), + Property.NodeScope + ); // All boolean settings with default of true case ENABLE_REST: case ENABLE_TRANSPORT: @@ -995,100 +1364,416 @@ public List> getSettings() { } }).forEach(settings::add); - // Security - Audit - Sink - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, + Property.NodeScope, + Property.Filtered + ) + ); // External OpenSearch - settings.add(Setting.listSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_HTTP_ENDPOINTS, Lists.newArrayList("localhost:9200"), Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PASSWORD, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_VERIFY_HOSTNAMES, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL_CLIENT_AUTH, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_CONTENT, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_CONTENT, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_PASSWORD, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_CONTENT, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_JKS_CERT_ALIAS, Property.NodeScope, Property.Filtered)); - settings.add(Setting.listSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_CIPHERS, Collections.emptyList(), Function.identity(), Property.NodeScope));//not filtered here - settings.add(Setting.listSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_PROTOCOLS, Collections.emptyList(), Function.identity(), Property.NodeScope));//not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_HTTP_ENDPOINTS, + Lists.newArrayList("localhost:9200"), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PASSWORD, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_VERIFY_HOSTNAMES, + true, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL_CLIENT_AUTH, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_CONTENT, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_CONTENT, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_PASSWORD, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_CONTENT, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_JKS_CERT_ALIAS, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_CIPHERS, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + );// not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_PROTOCOLS, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + );// not filtered here // Webhooks - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_URL, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_FORMAT, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_SSL_VERIFY, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_URL, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_FORMAT, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_SSL_VERIFY, + true, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, + Property.NodeScope, + Property.Filtered + ) + ); // Log4j - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_LOG4J_LOGGER_NAME, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_LOG4J_LEVEL, Property.NodeScope, Property.Filtered)); - + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_LOG4J_LOGGER_NAME, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_LOG4J_LEVEL, + Property.NodeScope, + Property.Filtered + ) + ); // Kerberos settings.add(Setting.simpleString(ConfigConstants.SECURITY_KERBEROS_KRB5_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_KERBEROS_ACCEPTOR_KEYTAB_FILEPATH, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.simpleString(ConfigConstants.SECURITY_KERBEROS_ACCEPTOR_KEYTAB_FILEPATH, Property.NodeScope, Property.Filtered) + ); settings.add(Setting.simpleString(ConfigConstants.SECURITY_KERBEROS_ACCEPTOR_PRINCIPAL, Property.NodeScope, Property.Filtered)); - // OpenSearch Security - REST API - settings.add(Setting.listSetting(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here settings.add(Setting.groupSetting(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + ".", Property.NodeScope)); settings.add(Setting.boolSetting(ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.simpleString(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, + Property.NodeScope, + Property.Filtered + ) + ); settings.add( - Setting.intSetting( - ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, - -1, -1, Property.NodeScope, Property.Filtered) + Setting.intSetting(ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, -1, -1, Property.NodeScope, Property.Filtered) ); settings.add( - Setting.simpleString( - ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, - PasswordValidator.ScoreStrength.STRONG.name(), - PasswordValidator.ScoreStrength::fromConfiguration, - Property.NodeScope, Property.Filtered - ) + Setting.simpleString( + ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, + PasswordValidator.ScoreStrength.STRONG.name(), + PasswordValidator.ScoreStrength::fromConfiguration, + Property.NodeScope, + Property.Filtered + ) ); // Compliance - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_COMPLIANCE_DISABLE_ANONYMOUS_AUTHENTICATION, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.listSetting(ConfigConstants.SECURITY_COMPLIANCE_IMMUTABLE_INDICES, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.boolSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_COMPLIANCE_DISABLE_ANONYMOUS_AUTHENTICATION, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_COMPLIANCE_IMMUTABLE_INDICES, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here settings.add(Setting.simpleString(ConfigConstants.SECURITY_COMPLIANCE_SALT, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, false, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, + false, + Property.NodeScope, + Property.Filtered + ) + ); settings.add(transportPassiveAuthSetting.getDynamicSetting()); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_FILTER_SECURITYINDEX_FROM_ALL_REQUESTS, false, Property.NodeScope, - Property.Filtered)); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_FILTER_SECURITYINDEX_FROM_ALL_REQUESTS, + false, + Property.NodeScope, + Property.Filtered + ) + ); - //compat - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY, false, Property.NodeScope, Property.Filtered)); + // compat + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY, + false, + Property.NodeScope, + Property.Filtered + ) + ); // system integration - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_ADMIN_USER_ENABLED, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_ALLOW_NOW_IN_DLS, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG, false, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_UNSUPPORTED_INJECT_ADMIN_USER_ENABLED, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_ALLOW_NOW_IN_DLS, false, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES, true, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, false, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG, + false, + Property.NodeScope, + Property.Filtered + ) + ); } return settings; @@ -1098,7 +1783,7 @@ public List> getSettings() { public List getSettingsFilter() { List settingsFilter = new ArrayList<>(); - if(disabled) { + if (disabled) { return settingsFilter; } settingsFilter.add("opendistro_security.*"); @@ -1109,16 +1794,16 @@ public List getSettingsFilter() { @Override public void onNodeStarted() { log.info("Node started"); - if(!SSLConfig.isSslOnlyMode() && !client && !disabled) { + if (!SSLConfig.isSslOnlyMode() && !client && !disabled) { cr.initOnNodeStart(); } final Set securityModules = ReflectionHelper.getModulesLoaded(); log.info("{} OpenSearch Security modules loaded so far: {}", securityModules.size(), securityModules); } - //below is a hack because it seems not possible to access RepositoriesService from a non guice class - //the way of how deguice is organized is really a mess - hope this can be fixed in later versions - //TODO check if this could be removed + // below is a hack because it seems not possible to access RepositoriesService from a non guice class + // the way of how deguice is organized is really a mess - hope this can be fixed in later versions + // TODO check if this could be removed @Override public Collection> getGuiceServiceClasses() { @@ -1138,8 +1823,10 @@ public Function> getFieldFilter() { if (threadPool == null) { return field -> true; } - final Map> allowedFlsFields = (Map>) HeaderHelper - .deserializeSafeFromHeader(threadPool.getThreadContext(), ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER); + final Map> allowedFlsFields = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadPool.getThreadContext(), + ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER + ); final String eval = SecurityUtils.evalMap(allowedFlsFields, index); @@ -1151,7 +1838,6 @@ public Function> getFieldFilter() { final Set includesSet = new HashSet<>(includesExcludes.size()); final Set excludesSet = new HashSet<>(includesExcludes.size()); - for (final String incExc : includesExcludes) { final char firstChar = incExc.charAt(0); @@ -1175,14 +1861,17 @@ public Function> getFieldFilter() { @Override public Collection getSystemIndexDescriptors(Settings settings) { - final String indexPattern = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + final String indexPattern = settings.get( + ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + ); final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(indexPattern, "Security index"); return Collections.singletonList(systemIndexDescriptor); } private static String handleKeyword(final String field) { - if(field != null && field.endsWith(KEYWORD)) { - return field.substring(0, field.length()-KEYWORD.length()); + if (field != null && field.endsWith(KEYWORD)) { + return field.substring(0, field.length() - KEYWORD.length()); } return field; } @@ -1198,8 +1887,13 @@ public static class GuiceHolder implements LifecycleComponent { private static ExtensionsManager extensionsManager; @Inject - public GuiceHolder(final RepositoriesService repositoriesService, - final TransportService remoteClusterService, IndicesService indicesService, PitService pitService, ExtensionsManager extensionsManager) { + public GuiceHolder( + final RepositoriesService repositoriesService, + final TransportService remoteClusterService, + IndicesService indicesService, + PitService pitService, + ExtensionsManager extensionsManager + ) { GuiceHolder.repositoriesService = repositoriesService; GuiceHolder.remoteClusterService = remoteClusterService.getRemoteClusterService(); GuiceHolder.indicesService = indicesService; @@ -1220,16 +1914,18 @@ public static IndicesService getIndicesService() { return indicesService; } - public static PitService getPitService() { return pitService; } + public static PitService getPitService() { + return pitService; + } // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions - public static ExtensionsManager getExtensionsManager() { return extensionsManager; } + public static ExtensionsManager getExtensionsManager() { + return extensionsManager; + } // CS-ENFORCE-SINGLE - @Override - public void close() { - } + public void close() {} @Override public State lifecycleState() { @@ -1237,20 +1933,16 @@ public State lifecycleState() { } @Override - public void addLifecycleListener(LifecycleListener listener) { - } + public void addLifecycleListener(LifecycleListener listener) {} @Override - public void removeLifecycleListener(LifecycleListener listener) { - } + public void removeLifecycleListener(LifecycleListener listener) {} @Override - public void start() { - } + public void start() {} @Override - public void stop() { - } + public void stop() {} } } diff --git a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java index 0e6944e9c4..0ac4459102 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java @@ -58,7 +58,7 @@ public static ConfigUpdateNodeResponse readNodeResponse(StreamInput in) throws I } public String[] getUpdatedConfigTypes() { - return updatedConfigTypes==null?null:Arrays.copyOf(updatedConfigTypes, updatedConfigTypes.length); + return updatedConfigTypes == null ? null : Arrays.copyOf(updatedConfigTypes, updatedConfigTypes.length); } public String getMessage() { @@ -81,7 +81,7 @@ public String toString() { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); builder.field("updated_config_types", updatedConfigTypes); - builder.field("updated_config_size", updatedConfigTypes == null ? 0: updatedConfigTypes.length); + builder.field("updated_config_size", updatedConfigTypes == null ? 0 : updatedConfigTypes.length); builder.field("message", message); builder.endObject(); return builder; diff --git a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequest.java b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequest.java index aeb92fa057..5310c497a2 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequest.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequest.java @@ -47,8 +47,8 @@ public ConfigUpdateRequest() { } public ConfigUpdateRequest(String[] configTypes) { - this(); - setConfigTypes(configTypes); + this(); + setConfigTypes(configTypes); } @Override diff --git a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequestBuilder.java b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequestBuilder.java index d33c0fa7a6..edfe1f10bb 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequestBuilder.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequestBuilder.java @@ -30,8 +30,10 @@ import org.opensearch.action.support.nodes.NodesOperationRequestBuilder; import org.opensearch.client.OpenSearchClient; -public class ConfigUpdateRequestBuilder extends -NodesOperationRequestBuilder { +public class ConfigUpdateRequestBuilder extends NodesOperationRequestBuilder< + ConfigUpdateRequest, + ConfigUpdateResponse, + ConfigUpdateRequestBuilder> { protected ConfigUpdateRequestBuilder(OpenSearchClient client, ActionType action) { super(client, action, new ConfigUpdateRequest()); diff --git a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java index 3a57ca4144..fd3176c016 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java @@ -57,15 +57,15 @@ public void writeNodesTo(final StreamOutput out, List out.writeList(nodes); } - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject("configupdate_response"); - builder.field("nodes", getNodesMap()); - builder.field("node_size", getNodes().size()); - builder.field("has_failures", hasFailures()); - builder.field("failures_size", failures().size()); - builder.endObject(); + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject("configupdate_response"); + builder.field("nodes", getNodesMap()); + builder.field("node_size", getNodes().size()); + builder.field("has_failures", hasFailures()); + builder.field("failures_size", failures().size()); + builder.endObject(); - return builder; - } + return builder; + } } diff --git a/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java b/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java index c5c60cf8a6..9a8cc22624 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java +++ b/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java @@ -49,9 +49,11 @@ import org.opensearch.transport.TransportRequest; import org.opensearch.transport.TransportService; -public class TransportConfigUpdateAction -extends -TransportNodesAction { +public class TransportConfigUpdateAction extends TransportNodesAction< + ConfigUpdateRequest, + ConfigUpdateResponse, + TransportConfigUpdateAction.NodeConfigUpdateRequest, + ConfigUpdateNodeResponse> { protected Logger logger = LogManager.getLogger(getClass()); private final Provider backendRegistry; @@ -59,13 +61,27 @@ public class TransportConfigUpdateAction private DynamicConfigFactory dynamicConfigFactory; @Inject - public TransportConfigUpdateAction(final Settings settings, - final ThreadPool threadPool, final ClusterService clusterService, final TransportService transportService, - final ConfigurationRepository configurationRepository, final ActionFilters actionFilters, - Provider backendRegistry, DynamicConfigFactory dynamicConfigFactory) { - super(ConfigUpdateAction.NAME, threadPool, clusterService, transportService, actionFilters, - ConfigUpdateRequest::new, TransportConfigUpdateAction.NodeConfigUpdateRequest::new, - ThreadPool.Names.MANAGEMENT, ConfigUpdateNodeResponse.class); + public TransportConfigUpdateAction( + final Settings settings, + final ThreadPool threadPool, + final ClusterService clusterService, + final TransportService transportService, + final ConfigurationRepository configurationRepository, + final ActionFilters actionFilters, + Provider backendRegistry, + DynamicConfigFactory dynamicConfigFactory + ) { + super( + ConfigUpdateAction.NAME, + threadPool, + clusterService, + transportService, + actionFilters, + ConfigUpdateRequest::new, + TransportConfigUpdateAction.NodeConfigUpdateRequest::new, + ThreadPool.Names.MANAGEMENT, + ConfigUpdateNodeResponse.class + ); this.configurationRepository = configurationRepository; this.backendRegistry = backendRegistry; @@ -76,7 +92,7 @@ public static class NodeConfigUpdateRequest extends TransportRequest { ConfigUpdateRequest request; - public NodeConfigUpdateRequest(StreamInput in) throws IOException{ + public NodeConfigUpdateRequest(StreamInput in) throws IOException { super(in); request = new ConfigUpdateRequest(in); } @@ -98,8 +114,11 @@ protected ConfigUpdateNodeResponse newNodeResponse(StreamInput in) throws IOExce } @Override - protected ConfigUpdateResponse newResponse(ConfigUpdateRequest request, List responses, - List failures) { + protected ConfigUpdateResponse newResponse( + ConfigUpdateRequest request, + List responses, + List failures + ) { return new ConfigUpdateResponse(this.clusterService.getClusterName(), responses, failures); } diff --git a/src/main/java/org/opensearch/security/action/whoami/TransportWhoAmIAction.java b/src/main/java/org/opensearch/security/action/whoami/TransportWhoAmIAction.java index 901800f0a2..bd3ecf46a2 100644 --- a/src/main/java/org/opensearch/security/action/whoami/TransportWhoAmIAction.java +++ b/src/main/java/org/opensearch/security/action/whoami/TransportWhoAmIAction.java @@ -40,17 +40,20 @@ import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.TransportService; -public class TransportWhoAmIAction -extends -HandledTransportAction { +public class TransportWhoAmIAction extends HandledTransportAction { private final AdminDNs adminDNs; private final ThreadPool threadPool; @Inject - public TransportWhoAmIAction(final Settings settings, - final ThreadPool threadPool, final ClusterService clusterService, final TransportService transportService, - final AdminDNs adminDNs, final ActionFilters actionFilters) { + public TransportWhoAmIAction( + final Settings settings, + final ThreadPool threadPool, + final ClusterService clusterService, + final TransportService transportService, + final AdminDNs adminDNs, + final ActionFilters actionFilters + ) { super(WhoAmIAction.NAME, transportService, actionFilters, WhoAmIRequest::new); @@ -58,15 +61,16 @@ public TransportWhoAmIAction(final Settings settings, this.threadPool = threadPool; } - @Override protected void doExecute(Task task, WhoAmIRequest request, ActionListener listener) { final User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - final String dn = user==null?threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL):user.getName(); + final String dn = user == null + ? threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL) + : user.getName(); final boolean isAdmin = adminDNs.isAdminDN(dn); - final boolean isAuthenticated = isAdmin?true: user != null; - final boolean isNodeCertificateRequest = HeaderHelper.isInterClusterRequest(threadPool.getThreadContext()) || - HeaderHelper.isTrustedClusterRequest(threadPool.getThreadContext()); + final boolean isAuthenticated = isAdmin ? true : user != null; + final boolean isNodeCertificateRequest = HeaderHelper.isInterClusterRequest(threadPool.getThreadContext()) + || HeaderHelper.isTrustedClusterRequest(threadPool.getThreadContext()); listener.onResponse(new WhoAmIResponse(dn, isAdmin, isAuthenticated, isNodeCertificateRequest)); diff --git a/src/main/java/org/opensearch/security/action/whoami/WhoAmIRequestBuilder.java b/src/main/java/org/opensearch/security/action/whoami/WhoAmIRequestBuilder.java index 7e22cc000d..229ba57ed9 100644 --- a/src/main/java/org/opensearch/security/action/whoami/WhoAmIRequestBuilder.java +++ b/src/main/java/org/opensearch/security/action/whoami/WhoAmIRequestBuilder.java @@ -32,8 +32,7 @@ import org.opensearch.client.ClusterAdminClient; import org.opensearch.client.OpenSearchClient; -public class WhoAmIRequestBuilder extends -ActionRequestBuilder { +public class WhoAmIRequestBuilder extends ActionRequestBuilder { public WhoAmIRequestBuilder(final ClusterAdminClient client) throws IOException { this(client, WhoAmIAction.INSTANCE); } diff --git a/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java b/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java index 2b25344f76..3e9d74fe25 100644 --- a/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java +++ b/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java @@ -50,7 +50,6 @@ public WhoAmIResponse(String dn, boolean isAdmin, boolean isAuthenticated, boole this.isNodeCertificateRequest = isNodeCertificateRequest; } - public WhoAmIResponse() { super(); this.dn = null; @@ -106,6 +105,6 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws @Override public String toString() { - return Strings.toString(XContentType.JSON,this, true, true); + return Strings.toString(XContentType.JSON, this, true, true); } } diff --git a/src/main/java/org/opensearch/security/auditlog/AuditLog.java b/src/main/java/org/opensearch/security/auditlog/AuditLog.java index 128944e387..3ac7e095aa 100644 --- a/src/main/java/org/opensearch/security/auditlog/AuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/AuditLog.java @@ -43,30 +43,38 @@ public interface AuditLog extends Closeable { - //login + // login void logFailedLogin(String effectiveUser, boolean securityadmin, String initiatingUser, RestRequest request); + void logSucceededLogin(String effectiveUser, boolean securityadmin, String initiatingUser, RestRequest request); - //privs + // privs void logMissingPrivileges(String privilege, String effectiveUser, RestRequest request); + void logGrantedPrivileges(String effectiveUser, RestRequest request); + void logMissingPrivileges(String privilege, TransportRequest request, Task task); + void logGrantedPrivileges(String privilege, TransportRequest request, Task task); // index event requests void logIndexEvent(String privilege, TransportRequest request, Task task); - //spoof + // spoof void logBadHeaders(TransportRequest request, String action, Task task); + void logBadHeaders(RestRequest request); void logSecurityIndexAttempt(TransportRequest request, String action, Task task); void logSSLException(TransportRequest request, Throwable t, String action, Task task); + void logSSLException(RestRequest request, Throwable t); void logDocumentRead(String index, String id, ShardId shardId, Map fieldNameValues); + void logDocumentWritten(ShardId shardId, GetResult originalIndex, Index currentIndex, IndexResult result); + void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult result); // compliance config @@ -76,10 +84,14 @@ public interface AuditLog extends Closeable { void setConfig(AuditConfig auditConfig); public enum Origin { - REST, TRANSPORT, LOCAL + REST, + TRANSPORT, + LOCAL } public enum Operation { - CREATE, UPDATE, DELETE + CREATE, + UPDATE, + DELETE } } diff --git a/src/main/java/org/opensearch/security/auditlog/AuditLogSslExceptionHandler.java b/src/main/java/org/opensearch/security/auditlog/AuditLogSslExceptionHandler.java index 3421bc4a4f..942f06804f 100644 --- a/src/main/java/org/opensearch/security/auditlog/AuditLogSslExceptionHandler.java +++ b/src/main/java/org/opensearch/security/auditlog/AuditLogSslExceptionHandler.java @@ -32,7 +32,7 @@ import org.opensearch.tasks.Task; import org.opensearch.transport.TransportRequest; -public class AuditLogSslExceptionHandler implements SslExceptionHandler{ +public class AuditLogSslExceptionHandler implements SslExceptionHandler { private final AuditLog auditLog; @@ -44,14 +44,14 @@ public AuditLogSslExceptionHandler(final AuditLog auditLog) { @Override public void logError(Throwable t, RestRequest request, int type) { switch (type) { - case 0: - auditLog.logSSLException(request, t); - break; - case 1: - auditLog.logBadHeaders(request); - break; - default: - break; + case 0: + auditLog.logSSLException(request, t); + break; + case 1: + auditLog.logBadHeaders(request); + break; + default: + break; } } @@ -67,18 +67,18 @@ public void logError(Throwable t, boolean isRest) { @Override public void logError(Throwable t, TransportRequest request, String action, Task task, int type) { switch (type) { - case 0: - if(t instanceof OpenSearchException) { - auditLog.logMissingPrivileges(action, request, task); - } else { - auditLog.logSSLException(request, t, action, task); - } - break; - case 1: - auditLog.logBadHeaders(request, action, task); - break; - default: - break; + case 0: + if (t instanceof OpenSearchException) { + auditLog.logMissingPrivileges(action, request, task); + } else { + auditLog.logSSLException(request, t, action, task); + } + break; + case 1: + auditLog.logBadHeaders(request, action, task); + break; + default: + break; } } diff --git a/src/main/java/org/opensearch/security/auditlog/NullAuditLog.java b/src/main/java/org/opensearch/security/auditlog/NullAuditLog.java index ced4d0aae6..7fe3324d2e 100644 --- a/src/main/java/org/opensearch/security/auditlog/NullAuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/NullAuditLog.java @@ -45,82 +45,82 @@ public class NullAuditLog implements AuditLog { @Override public void close() throws IOException { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logFailedLogin(String effectiveUser, boolean securityadmin, String initiatingUser, RestRequest request) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logSucceededLogin(String effectiveUser, boolean securityadmin, String initiatingUser, RestRequest request) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logMissingPrivileges(String privilege, TransportRequest request, Task task) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logGrantedPrivileges(String privilege, TransportRequest request, Task task) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logIndexEvent(String privilege, TransportRequest request, Task task) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logBadHeaders(TransportRequest request, String action, Task task) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logBadHeaders(RestRequest request) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logSecurityIndexAttempt(TransportRequest request, String action, Task task) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logSSLException(TransportRequest request, Throwable t, String action, Task task) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logSSLException(RestRequest request, Throwable t) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logMissingPrivileges(String privilege, String effectiveUser, RestRequest request) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logGrantedPrivileges(String effectiveUser, RestRequest request) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logDocumentRead(String index, String id, ShardId shardId, Map fieldNameValues) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logDocumentWritten(ShardId shardId, GetResult originalIndex, Index currentIndex, IndexResult result) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult result) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override diff --git a/src/main/java/org/opensearch/security/auditlog/config/AuditConfig.java b/src/main/java/org/opensearch/security/auditlog/config/AuditConfig.java index 1152abe89e..f6f9a42e87 100644 --- a/src/main/java/org/opensearch/security/auditlog/config/AuditConfig.java +++ b/src/main/java/org/opensearch/security/auditlog/config/AuditConfig.java @@ -108,10 +108,7 @@ public ComplianceConfig getCompliance() { } @VisibleForTesting - public AuditConfig( - final boolean auditLogEnabled, - final Filter filter, - final ComplianceConfig compliance) { + public AuditConfig(final boolean auditLogEnabled, final Filter filter, final ComplianceConfig compliance) { this.auditLogEnabled = auditLogEnabled; this.filter = filter != null ? filter : Filter.DEFAULT; this.compliance = compliance != null ? compliance : ComplianceConfig.DEFAULT; @@ -147,16 +144,18 @@ public static class Filter { private final Set disabledTransportCategories; @VisibleForTesting - Filter(final boolean isRestApiAuditEnabled, - final boolean isTransportApiAuditEnabled, - final boolean resolveBulkRequests, - final boolean logRequestBody, - final boolean resolveIndices, - final boolean excludeSensitiveHeaders, - final Set ignoredAuditUsers, - final Set ignoredAuditRequests, - final Set disabledRestCategories, - final Set disabledTransportCategories) { + Filter( + final boolean isRestApiAuditEnabled, + final boolean isTransportApiAuditEnabled, + final boolean resolveBulkRequests, + final boolean logRequestBody, + final boolean resolveIndices, + final boolean excludeSensitiveHeaders, + final Set ignoredAuditUsers, + final Set ignoredAuditRequests, + final Set disabledRestCategories, + final Set disabledTransportCategories + ) { this.isRestApiAuditEnabled = isRestApiAuditEnabled; this.isTransportApiAuditEnabled = isTransportApiAuditEnabled; this.resolveBulkRequests = resolveBulkRequests; @@ -179,22 +178,29 @@ public enum FilterEntries { RESOLVE_INDICES("resolve_indices", ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES), EXCLUDE_SENSITIVE_HEADERS("exclude_sensitive_headers", ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS), DISABLE_REST_CATEGORIES("disabled_rest_categories", ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES), - DISABLE_TRANSPORT_CATEGORIES("disabled_transport_categories", ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES), + DISABLE_TRANSPORT_CATEGORIES( + "disabled_transport_categories", + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES + ), IGNORE_USERS("ignore_users", ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS), IGNORE_REQUESTS("ignore_requests", ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS); private final String key; private final String legacyKeyWithNamespace; + FilterEntries(final String entryKey, final String legacyKeyWithNamespace) { this.key = entryKey; this.legacyKeyWithNamespace = legacyKeyWithNamespace; } + public String getKey() { return this.key; } + public String getKeyWithNamespace() { - return SECURITY_AUDIT_CONFIG_DEFAULT + "."+ this.key; + return SECURITY_AUDIT_CONFIG_DEFAULT + "." + this.key; } + public String getLegacyKeyWithNamespace() { return this.legacyKeyWithNamespace; } @@ -204,7 +210,14 @@ public String getLegacyKeyWithNamespace() { @VisibleForTesting public static Filter from(Map properties) throws JsonProcessingException { if (!FIELDS.containsAll(properties.keySet())) { - throw new UnrecognizedPropertyException(null, "Unrecognized field(s) present in the input data for audit filter config", null, Filter.class, null, null); + throw new UnrecognizedPropertyException( + null, + "Unrecognized field(s) present in the input data for audit filter config", + null, + Filter.class, + null, + null + ); } final boolean isRestApiAuditEnabled = getOrDefault(properties, FilterEntries.ENABLE_REST.getKey(), true); @@ -213,22 +226,39 @@ public static Filter from(Map properties) throws JsonProcessingE final boolean logRequestBody = getOrDefault(properties, FilterEntries.LOG_REQUEST_BODY.getKey(), true); final boolean resolveIndices = getOrDefault(properties, FilterEntries.RESOLVE_INDICES.getKey(), true); final boolean excludeSensitiveHeaders = getOrDefault(properties, FilterEntries.EXCLUDE_SENSITIVE_HEADERS.getKey(), true); - final Set disabledRestCategories = AuditCategory.parse(getOrDefault(properties, FilterEntries.DISABLE_REST_CATEGORIES.getKey(), ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT)); - final Set disabledTransportCategories = AuditCategory.parse(getOrDefault(properties, FilterEntries.DISABLE_TRANSPORT_CATEGORIES.getKey(), ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT)); - final Set ignoredAuditUsers = ImmutableSet.copyOf(getOrDefault(properties, FilterEntries.IGNORE_USERS.getKey(), DEFAULT_IGNORED_USERS)); - final Set ignoreAuditRequests = ImmutableSet.copyOf(getOrDefault(properties, FilterEntries.IGNORE_REQUESTS.getKey(), Collections.emptyList())); + final Set disabledRestCategories = AuditCategory.parse( + getOrDefault( + properties, + FilterEntries.DISABLE_REST_CATEGORIES.getKey(), + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT + ) + ); + final Set disabledTransportCategories = AuditCategory.parse( + getOrDefault( + properties, + FilterEntries.DISABLE_TRANSPORT_CATEGORIES.getKey(), + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT + ) + ); + final Set ignoredAuditUsers = ImmutableSet.copyOf( + getOrDefault(properties, FilterEntries.IGNORE_USERS.getKey(), DEFAULT_IGNORED_USERS) + ); + final Set ignoreAuditRequests = ImmutableSet.copyOf( + getOrDefault(properties, FilterEntries.IGNORE_REQUESTS.getKey(), Collections.emptyList()) + ); return new Filter( - isRestApiAuditEnabled, - isTransportAuditEnabled, - resolveBulkRequests, - logRequestBody, - resolveIndices, - excludeSensitiveHeaders, - ignoredAuditUsers, - ignoreAuditRequests, - disabledRestCategories, - disabledTransportCategories); + isRestApiAuditEnabled, + isTransportAuditEnabled, + resolveBulkRequests, + logRequestBody, + resolveIndices, + excludeSensitiveHeaders, + ignoredAuditUsers, + ignoreAuditRequests, + disabledRestCategories, + disabledTransportCategories + ); } @@ -244,34 +274,52 @@ public static Filter from(Settings settings) { final boolean logRequestBody = fromSettingBoolean(settings, FilterEntries.LOG_REQUEST_BODY, true); final boolean resolveIndices = fromSettingBoolean(settings, FilterEntries.RESOLVE_INDICES, true); final boolean excludeSensitiveHeaders = fromSettingBoolean(settings, FilterEntries.EXCLUDE_SENSITIVE_HEADERS, true); - final Set disabledRestCategories = AuditCategory.parse(fromSettingStringSet(settings, FilterEntries.DISABLE_REST_CATEGORIES, ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT)); - final Set disabledTransportCategories = AuditCategory.parse(fromSettingStringSet(settings, FilterEntries.DISABLE_TRANSPORT_CATEGORIES, ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT)); + final Set disabledRestCategories = AuditCategory.parse( + fromSettingStringSet( + settings, + FilterEntries.DISABLE_REST_CATEGORIES, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT + ) + ); + final Set disabledTransportCategories = AuditCategory.parse( + fromSettingStringSet( + settings, + FilterEntries.DISABLE_TRANSPORT_CATEGORIES, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT + ) + ); final Set ignoredAuditUsers = fromSettingStringSet(settings, FilterEntries.IGNORE_USERS, DEFAULT_IGNORED_USERS); final Set ignoreAuditRequests = fromSettingStringSet(settings, FilterEntries.IGNORE_REQUESTS, Collections.emptyList()); - return new Filter(isRestApiAuditEnabled, - isTransportAuditEnabled, - resolveBulkRequests, - logRequestBody, - resolveIndices, - excludeSensitiveHeaders, - ignoredAuditUsers, - ignoreAuditRequests, - disabledRestCategories, - disabledTransportCategories); + return new Filter( + isRestApiAuditEnabled, + isTransportAuditEnabled, + resolveBulkRequests, + logRequestBody, + resolveIndices, + excludeSensitiveHeaders, + ignoredAuditUsers, + ignoreAuditRequests, + disabledRestCategories, + disabledTransportCategories + ); } static boolean fromSettingBoolean(final Settings settings, FilterEntries filterEntry, final boolean defaultValue) { - return settings.getAsBoolean(filterEntry.getKeyWithNamespace(), settings.getAsBoolean(filterEntry.getLegacyKeyWithNamespace(), defaultValue)); + return settings.getAsBoolean( + filterEntry.getKeyWithNamespace(), + settings.getAsBoolean(filterEntry.getLegacyKeyWithNamespace(), defaultValue) + ); } static Set fromSettingStringSet(final Settings settings, FilterEntries filterEntry, final List defaultValue) { final String defaultDetectorValue = "__DEFAULT_DETECTION__"; final Set stringSetOfKey = ConfigConstants.getSettingAsSet( - settings, - filterEntry.getKeyWithNamespace(), - ImmutableList.of(defaultDetectorValue), - true); + settings, + filterEntry.getKeyWithNamespace(), + ImmutableList.of(defaultDetectorValue), + true + ); final boolean foundDefault = stringSetOfKey.stream().anyMatch(defaultDetectorValue::equals); if (!foundDefault) { @@ -279,11 +327,7 @@ static Set fromSettingStringSet(final Settings settings, FilterEntries f } // Fallback to the legacy keyname - return ConfigConstants.getSettingAsSet( - settings, - filterEntry.getLegacyKeyWithNamespace(), - defaultValue, - true); + return ConfigConstants.getSettingAsSet(settings, filterEntry.getLegacyKeyWithNamespace(), defaultValue, true); } /** @@ -400,18 +444,28 @@ public void log(Logger logger) { @Override public String toString() { - return "Filter{" + - "isRestApiAuditEnabled=" + isRestApiAuditEnabled + - ", disabledRestCategories=" + disabledRestCategories + - ", isTransportApiAuditEnabled=" + isTransportApiAuditEnabled + - ", disabledTransportCategories=" + disabledTransportCategories + - ", resolveBulkRequests=" + resolveBulkRequests + - ", logRequestBody=" + logRequestBody + - ", resolveIndices=" + resolveIndices + - ", excludeSensitiveHeaders=" + excludeSensitiveHeaders + - ", ignoredAuditUsers=" + ignoredAuditUsersMatcher + - ", ignoreAuditRequests=" + ignoredAuditRequestsMatcher + - '}'; + return "Filter{" + + "isRestApiAuditEnabled=" + + isRestApiAuditEnabled + + ", disabledRestCategories=" + + disabledRestCategories + + ", isTransportApiAuditEnabled=" + + isTransportApiAuditEnabled + + ", disabledTransportCategories=" + + disabledTransportCategories + + ", resolveBulkRequests=" + + resolveBulkRequests + + ", logRequestBody=" + + logRequestBody + + ", resolveIndices=" + + resolveIndices + + ", excludeSensitiveHeaders=" + + excludeSensitiveHeaders + + ", ignoredAuditUsers=" + + ignoredAuditUsersMatcher + + ", ignoreAuditRequests=" + + ignoredAuditRequestsMatcher + + '}'; } } @@ -419,39 +473,36 @@ public String toString() { * List of keys that are deprecated */ public static final List DEPRECATED_KEYS = ImmutableList.of( - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, - ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, + ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES ); public static Set getDeprecatedKeys(final Settings settings) { - return AuditConfig.DEPRECATED_KEYS - .stream() - .filter(settings::hasValue) - .collect(Collectors.toSet()); + return AuditConfig.DEPRECATED_KEYS.stream().filter(settings::hasValue).collect(Collectors.toSet()); } public static final Set FIELD_PATHS = Sets.union( - Utils.generateFieldResourcePaths(AuditConfig.FIELDS, "/"), - Sets.union( - Utils.generateFieldResourcePaths(Filter.FIELDS, "/audit/"), - Utils.generateFieldResourcePaths(ComplianceConfig.FIELDS, "/compliance/") - ) + Utils.generateFieldResourcePaths(AuditConfig.FIELDS, "/"), + Sets.union( + Utils.generateFieldResourcePaths(Filter.FIELDS, "/audit/"), + Utils.generateFieldResourcePaths(ComplianceConfig.FIELDS, "/compliance/") + ) ); } diff --git a/src/main/java/org/opensearch/security/auditlog/config/ThreadPoolConfig.java b/src/main/java/org/opensearch/security/auditlog/config/ThreadPoolConfig.java index a0ef7937ff..a8c44e0cee 100644 --- a/src/main/java/org/opensearch/security/auditlog/config/ThreadPoolConfig.java +++ b/src/main/java/org/opensearch/security/auditlog/config/ThreadPoolConfig.java @@ -27,7 +27,9 @@ public ThreadPoolConfig(int threadPoolSize, int threadPoolMaxQueueLen) { } if (threadPoolMaxQueueLen <= 0) { - throw new IllegalArgumentException("Incorrect thread pool queue length: " + threadPoolMaxQueueLen + " configured for audit logging."); + throw new IllegalArgumentException( + "Incorrect thread pool queue length: " + threadPoolMaxQueueLen + " configured for audit logging." + ); } this.threadPoolSize = threadPoolSize; @@ -44,7 +46,10 @@ public int getThreadPoolMaxQueueLen() { public static ThreadPoolConfig getConfig(Settings settings) { int threadPoolSize = settings.getAsInt(ConfigConstants.SECURITY_AUDIT_THREADPOOL_SIZE, DEFAULT_THREAD_POOL_SIZE); - int threadPoolMaxQueueLen = settings.getAsInt(ConfigConstants.SECURITY_AUDIT_THREADPOOL_MAX_QUEUE_LEN, DEFAULT_THREAD_POOL_MAX_QUEUE_LEN); + int threadPoolMaxQueueLen = settings.getAsInt( + ConfigConstants.SECURITY_AUDIT_THREADPOOL_MAX_QUEUE_LEN, + DEFAULT_THREAD_POOL_MAX_QUEUE_LEN + ); return new ThreadPoolConfig(threadPoolSize, threadPoolMaxQueueLen); } 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 e4aa062641..e14d5b17a9 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java @@ -75,7 +75,6 @@ import static org.opensearch.core.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; - public abstract class AbstractAuditLog implements AuditLog { protected final Logger log = LogManager.getLogger(this.getClass()); @@ -100,13 +99,22 @@ public abstract class AbstractAuditLog implements AuditLog { writeClasses.add(DeleteRequest.class.getSimpleName()); } - protected AbstractAuditLog(Settings settings, final ThreadPool threadPool, final IndexNameExpressionResolver resolver, final ClusterService clusterService, final Environment environment) { + protected AbstractAuditLog( + Settings settings, + final ThreadPool threadPool, + final IndexNameExpressionResolver resolver, + final ClusterService clusterService, + final Environment environment + ) { super(); this.threadPool = threadPool; this.settings = settings; this.resolver = resolver; this.clusterService = clusterService; - this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + this.securityIndex = settings.get( + ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + ); this.environment = environment; } @@ -133,7 +141,7 @@ public ComplianceConfig getComplianceConfig() { @Override public void logFailedLogin(String effectiveUser, boolean securityadmin, String initiatingUser, RestRequest request) { - if(!checkRestFilter(AuditCategory.FAILED_LOGIN, effectiveUser, request)) { + if (!checkRestFilter(AuditCategory.FAILED_LOGIN, effectiveUser, request)) { return; } @@ -151,7 +159,7 @@ public void logFailedLogin(String effectiveUser, boolean securityadmin, String i @Override public void logSucceededLogin(String effectiveUser, boolean securityadmin, String initiatingUser, RestRequest request) { - if(!checkRestFilter(AuditCategory.AUTHENTICATED, effectiveUser, request)) { + if (!checkRestFilter(AuditCategory.AUTHENTICATED, effectiveUser, request)) { return; } @@ -167,7 +175,7 @@ public void logSucceededLogin(String effectiveUser, boolean securityadmin, Strin @Override public void logMissingPrivileges(String privilege, String effectiveUser, RestRequest request) { - if(!checkRestFilter(AuditCategory.MISSING_PRIVILEGES, effectiveUser, request)) { + if (!checkRestFilter(AuditCategory.MISSING_PRIVILEGES, effectiveUser, request)) { return; } @@ -181,7 +189,7 @@ public void logMissingPrivileges(String privilege, String effectiveUser, RestReq @Override public void logGrantedPrivileges(String effectiveUser, RestRequest request) { - if(!checkRestFilter(AuditCategory.GRANTED_PRIVILEGES, effectiveUser, request)) { + if (!checkRestFilter(AuditCategory.GRANTED_PRIVILEGES, effectiveUser, request)) { return; } @@ -196,14 +204,35 @@ public void logGrantedPrivileges(String effectiveUser, RestRequest request) { public void logMissingPrivileges(String privilege, TransportRequest request, Task task) { final String action = null; - if(!checkTransportFilter(AuditCategory.MISSING_PRIVILEGES, privilege, getUser(), request)) { + if (!checkTransportFilter(AuditCategory.MISSING_PRIVILEGES, privilege, getUser(), request)) { return; } final TransportAddress remoteAddress = getRemoteAddress(); - final List msgs = RequestResolver.resolve(AuditCategory.MISSING_PRIVILEGES, getOrigin(), action, privilege, getUser(), null, null, remoteAddress, request, getThreadContextHeaders(), task, resolver, clusterService, settings, auditConfigFilter.shouldLogRequestBody(), auditConfigFilter.shouldResolveIndices(), auditConfigFilter.shouldResolveBulkRequests(), securityIndex, auditConfigFilter.shouldExcludeSensitiveHeaders(), null); - - for(AuditMessage msg: msgs) { + final List msgs = RequestResolver.resolve( + AuditCategory.MISSING_PRIVILEGES, + getOrigin(), + action, + privilege, + getUser(), + null, + null, + remoteAddress, + request, + getThreadContextHeaders(), + task, + resolver, + clusterService, + settings, + auditConfigFilter.shouldLogRequestBody(), + auditConfigFilter.shouldResolveIndices(), + auditConfigFilter.shouldResolveBulkRequests(), + securityIndex, + auditConfigFilter.shouldExcludeSensitiveHeaders(), + null + ); + + for (AuditMessage msg : msgs) { save(msg); } } @@ -212,21 +241,42 @@ public void logMissingPrivileges(String privilege, TransportRequest request, Tas public void logGrantedPrivileges(String privilege, TransportRequest request, Task task) { final String action = null; - if(!checkTransportFilter(AuditCategory.GRANTED_PRIVILEGES, privilege, getUser(), request)) { + if (!checkTransportFilter(AuditCategory.GRANTED_PRIVILEGES, privilege, getUser(), request)) { return; } final TransportAddress remoteAddress = getRemoteAddress(); - final List msgs = RequestResolver.resolve(AuditCategory.GRANTED_PRIVILEGES, getOrigin(), action, privilege, getUser(), null, null, remoteAddress, request, getThreadContextHeaders(), task, resolver, clusterService, settings, auditConfigFilter.shouldLogRequestBody(), auditConfigFilter.shouldResolveIndices(), auditConfigFilter.shouldResolveBulkRequests(), securityIndex, auditConfigFilter.shouldExcludeSensitiveHeaders(), null); - - for(AuditMessage msg: msgs) { + final List msgs = RequestResolver.resolve( + AuditCategory.GRANTED_PRIVILEGES, + getOrigin(), + action, + privilege, + getUser(), + null, + null, + remoteAddress, + request, + getThreadContextHeaders(), + task, + resolver, + clusterService, + settings, + auditConfigFilter.shouldLogRequestBody(), + auditConfigFilter.shouldResolveIndices(), + auditConfigFilter.shouldResolveBulkRequests(), + securityIndex, + auditConfigFilter.shouldExcludeSensitiveHeaders(), + null + ); + + for (AuditMessage msg : msgs) { save(msg); } } @Override public void logIndexEvent(String privilege, TransportRequest request, Task task) { - if(!checkTransportFilter(AuditCategory.INDEX_EVENT, privilege, getUser(), request)) { + if (!checkTransportFilter(AuditCategory.INDEX_EVENT, privilege, getUser(), request)) { return; } // log only cluster admin action @@ -234,7 +284,28 @@ public void logIndexEvent(String privilege, TransportRequest request, Task task) return; } final TransportAddress remoteAddress = getRemoteAddress(); - final List msgs = RequestResolver.resolve(AuditCategory.INDEX_EVENT, getOrigin(), null, privilege, getUser(), null, null, remoteAddress, request, getThreadContextHeaders(), task, resolver, clusterService, settings, auditConfigFilter.shouldLogRequestBody(), auditConfigFilter.shouldResolveIndices(), auditConfigFilter.shouldResolveBulkRequests(), securityIndex, auditConfigFilter.shouldExcludeSensitiveHeaders(), null); + final List msgs = RequestResolver.resolve( + AuditCategory.INDEX_EVENT, + getOrigin(), + null, + privilege, + getUser(), + null, + null, + remoteAddress, + request, + getThreadContextHeaders(), + task, + resolver, + clusterService, + settings, + auditConfigFilter.shouldLogRequestBody(), + auditConfigFilter.shouldResolveIndices(), + auditConfigFilter.shouldResolveBulkRequests(), + securityIndex, + auditConfigFilter.shouldExcludeSensitiveHeaders(), + null + ); msgs.forEach(this::save); } @@ -242,14 +313,35 @@ public void logIndexEvent(String privilege, TransportRequest request, Task task) @Override public void logBadHeaders(TransportRequest request, String action, Task task) { - if(!checkTransportFilter(AuditCategory.BAD_HEADERS, action, getUser(), request)) { + if (!checkTransportFilter(AuditCategory.BAD_HEADERS, action, getUser(), request)) { return; } final TransportAddress remoteAddress = getRemoteAddress(); - final List msgs = RequestResolver.resolve(AuditCategory.BAD_HEADERS, getOrigin(), action, null, getUser(), null, null, remoteAddress, request, getThreadContextHeaders(), task, resolver, clusterService, settings, auditConfigFilter.shouldLogRequestBody(), auditConfigFilter.shouldResolveIndices(), auditConfigFilter.shouldResolveBulkRequests(), securityIndex, auditConfigFilter.shouldExcludeSensitiveHeaders(), null); - - for(AuditMessage msg: msgs) { + final List msgs = RequestResolver.resolve( + AuditCategory.BAD_HEADERS, + getOrigin(), + action, + null, + getUser(), + null, + null, + remoteAddress, + request, + getThreadContextHeaders(), + task, + resolver, + clusterService, + settings, + auditConfigFilter.shouldLogRequestBody(), + auditConfigFilter.shouldResolveIndices(), + auditConfigFilter.shouldResolveBulkRequests(), + securityIndex, + auditConfigFilter.shouldExcludeSensitiveHeaders(), + null + ); + + for (AuditMessage msg : msgs) { save(msg); } } @@ -257,7 +349,7 @@ public void logBadHeaders(TransportRequest request, String action, Task task) { @Override public void logBadHeaders(RestRequest request) { - if(!checkRestFilter(AuditCategory.BAD_HEADERS, getUser(), request)) { + if (!checkRestFilter(AuditCategory.BAD_HEADERS, getUser(), request)) { return; } @@ -273,14 +365,35 @@ public void logBadHeaders(RestRequest request) { @Override public void logSecurityIndexAttempt(TransportRequest request, String action, Task task) { - if(!checkTransportFilter(AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT, action, getUser(), request)) { + if (!checkTransportFilter(AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT, action, getUser(), request)) { return; } final TransportAddress remoteAddress = getRemoteAddress(); - final List msgs = RequestResolver.resolve(AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT, getOrigin(), action, null, getUser(), false, null, remoteAddress, request, getThreadContextHeaders(), task, resolver, clusterService, settings, auditConfigFilter.shouldLogRequestBody(), auditConfigFilter.shouldResolveIndices(), auditConfigFilter.shouldResolveBulkRequests(), securityIndex, auditConfigFilter.shouldExcludeSensitiveHeaders(), null); - - for(AuditMessage msg: msgs) { + final List msgs = RequestResolver.resolve( + AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT, + getOrigin(), + action, + null, + getUser(), + false, + null, + remoteAddress, + request, + getThreadContextHeaders(), + task, + resolver, + clusterService, + settings, + auditConfigFilter.shouldLogRequestBody(), + auditConfigFilter.shouldResolveIndices(), + auditConfigFilter.shouldResolveBulkRequests(), + securityIndex, + auditConfigFilter.shouldExcludeSensitiveHeaders(), + null + ); + + for (AuditMessage msg : msgs) { save(msg); } } @@ -288,16 +401,36 @@ public void logSecurityIndexAttempt(TransportRequest request, String action, Tas @Override public void logSSLException(TransportRequest request, Throwable t, String action, Task task) { - if(!checkTransportFilter(AuditCategory.SSL_EXCEPTION, action, getUser(), request)) { + if (!checkTransportFilter(AuditCategory.SSL_EXCEPTION, action, getUser(), request)) { return; } final TransportAddress remoteAddress = getRemoteAddress(); - final List msgs = RequestResolver.resolve(AuditCategory.SSL_EXCEPTION, Origin.TRANSPORT, action, null, getUser(), false, null, remoteAddress, request, - getThreadContextHeaders(), task, resolver, clusterService, settings, auditConfigFilter.shouldLogRequestBody(), auditConfigFilter.shouldResolveIndices(), auditConfigFilter.shouldResolveBulkRequests(), securityIndex, auditConfigFilter.shouldExcludeSensitiveHeaders(), t); - - for(AuditMessage msg: msgs) { + final List msgs = RequestResolver.resolve( + AuditCategory.SSL_EXCEPTION, + Origin.TRANSPORT, + action, + null, + getUser(), + false, + null, + remoteAddress, + request, + getThreadContextHeaders(), + task, + resolver, + clusterService, + settings, + auditConfigFilter.shouldLogRequestBody(), + auditConfigFilter.shouldResolveIndices(), + auditConfigFilter.shouldResolveBulkRequests(), + securityIndex, + auditConfigFilter.shouldExcludeSensitiveHeaders(), + t + ); + + for (AuditMessage msg : msgs) { save(msg); } } @@ -305,7 +438,7 @@ public void logSSLException(TransportRequest request, Throwable t, String action @Override public void logSSLException(RestRequest request, Throwable t) { - if(!checkRestFilter(AuditCategory.SSL_EXCEPTION, getUser(), request)) { + if (!checkRestFilter(AuditCategory.SSL_EXCEPTION, getUser(), request)) { return; } @@ -322,36 +455,39 @@ public void logSSLException(RestRequest request, Throwable t) { @Override public void logDocumentRead(String index, String id, ShardId shardId, Map fieldNameValues) { final ComplianceConfig complianceConfig = getComplianceConfig(); - if(complianceConfig == null || !complianceConfig.readHistoryEnabledForIndex(index)) { + if (complianceConfig == null || !complianceConfig.readHistoryEnabledForIndex(index)) { return; } - final String initiatingRequestClass = threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_INITIAL_ACTION_CLASS_HEADER); + final String initiatingRequestClass = threadPool.getThreadContext() + .getHeader(ConfigConstants.OPENDISTRO_SECURITY_INITIAL_ACTION_CLASS_HEADER); - if(initiatingRequestClass != null && writeClasses.contains(initiatingRequestClass)) { + if (initiatingRequestClass != null && writeClasses.contains(initiatingRequestClass)) { return; } - AuditCategory category = securityIndex.equals(index)? AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ: AuditCategory.COMPLIANCE_DOC_READ; + AuditCategory category = securityIndex.equals(index) + ? AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ + : AuditCategory.COMPLIANCE_DOC_READ; String effectiveUser = getUser(); - if(!checkComplianceFilter(category, effectiveUser, getOrigin(), complianceConfig)) { + if (!checkComplianceFilter(category, effectiveUser, getOrigin(), complianceConfig)) { return; } - if(fieldNameValues != null && !fieldNameValues.isEmpty()) { + if (fieldNameValues != null && !fieldNameValues.isEmpty()) { AuditMessage msg = new AuditMessage(category, clusterService, getOrigin(), null); TransportAddress remoteAddress = getRemoteAddress(); msg.addRemoteAddress(remoteAddress); msg.addEffectiveUser(effectiveUser); - msg.addIndices(new String[]{index}); - msg.addResolvedIndices(new String[]{index}); + msg.addIndices(new String[] { index }); + msg.addResolvedIndices(new String[] { index }); msg.addShardId(shardId); - //msg.addIsAdminDn(securityadmin); + // msg.addIsAdminDn(securityadmin); msg.addId(id); try { - if(complianceConfig.shouldLogReadMetadataOnly()) { + if (complianceConfig.shouldLogReadMetadataOnly()) { try { XContentBuilder builder = XContentBuilder.builder(JsonXContent.jsonXContent); builder.startObject(); @@ -363,10 +499,19 @@ public void logDocumentRead(String index, String id, ShardId shardId, Map map = fieldNameValues.entrySet().stream() - .collect(Collectors.toMap(entry -> "id", entry -> new String(BaseEncoding.base64().decode(((Entry) entry).getValue()), StandardCharsets.UTF_8))); + Map map = fieldNameValues.entrySet() + .stream() + .collect( + Collectors.toMap( + entry -> "id", + entry -> new String( + BaseEncoding.base64().decode(((Entry) entry).getValue()), + StandardCharsets.UTF_8 + ) + ) + ); msg.addSecurityConfigMapToRequestBody(Utils.convertJsonToxToStructuredMap(map.get("id")), id); } catch (Exception e) { msg.addSecurityConfigMapToRequestBody(fieldNameValues, id); @@ -376,7 +521,7 @@ public void logDocumentRead(String index, String id, ShardId shardId, Map(XContentType.JSON, currentIndex.source()), id); - } + if (!complianceConfig.shouldLogWriteMetadataOnly()) { + if (securityIndex.equals(shardId.getIndexName())) { + // current source, normally not null or empty + 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), StandardCharsets.UTF_8), + id + ); + } else { + msg.addSecurityConfigTupleToRequestBody( + new Tuple(XContentType.JSON, currentIndex.source()), + id + ); + } } catch (Exception e) { log.error(e.toString()); } - //if we want to have msg.ComplianceWritePreviousSource we need to do the same as above + // if we want to have msg.ComplianceWritePreviousSource we need to do the same as above } else { - //previous source, can be null if document is a new one - //msg.ComplianceWritePreviousSource(new Tuple(XContentType.JSON, originalResult.internalSourceRef())); + // previous source, can be null if document is a new one + // msg.ComplianceWritePreviousSource(new Tuple(XContentType.JSON, + // originalResult.internalSourceRef())); - //current source, normally not null or empty + // current source, normally not null or empty msg.addTupleToRequestBody(new Tuple(XContentType.JSON, currentIndex.source())); } } - save(msg); } @@ -488,7 +670,9 @@ public void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult resu String effectiveUser = getUser(); final ComplianceConfig complianceConfig = getComplianceConfig(); - if (complianceConfig == null || !complianceConfig.isEnabled() || !checkComplianceFilter(AuditCategory.COMPLIANCE_DOC_WRITE, effectiveUser, getOrigin(), complianceConfig)) { + if (complianceConfig == null + || !complianceConfig.isEnabled() + || !checkComplianceFilter(AuditCategory.COMPLIANCE_DOC_WRITE, effectiveUser, getOrigin(), complianceConfig)) { return; } @@ -496,8 +680,8 @@ public void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult resu TransportAddress remoteAddress = getRemoteAddress(); msg.addRemoteAddress(remoteAddress); msg.addEffectiveUser(effectiveUser); - msg.addIndices(new String[]{shardId.getIndexName()}); - msg.addResolvedIndices(new String[]{shardId.getIndexName()}); + msg.addIndices(new String[] { shardId.getIndexName() }); + msg.addResolvedIndices(new String[] { shardId.getIndexName() }); msg.addId(delete.id()); msg.addShardId(shardId); msg.addComplianceDocVersion(result.getVersion()); @@ -509,7 +693,11 @@ public void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult resu protected void logExternalConfig() { final ComplianceConfig complianceConfig = getComplianceConfig(); - if (complianceConfig == null || !complianceConfig.isEnabled() || !complianceConfig.shouldLogExternalConfig() || !checkComplianceFilter(AuditCategory.COMPLIANCE_EXTERNAL_CONFIG, null, getOrigin(), complianceConfig) || externalConfigLogged.getAndSet(true)) { + if (complianceConfig == null + || !complianceConfig.isEnabled() + || !complianceConfig.shouldLogExternalConfig() + || !checkComplianceFilter(AuditCategory.COMPLIANCE_EXTERNAL_CONFIG, null, getOrigin(), complianceConfig) + || externalConfigLogged.getAndSet(true)) { return; } @@ -536,7 +724,7 @@ public Map run() { } }); - final String sha256 = DigestUtils.sha256Hex(configAsMap.toString()+envAsMap.toString()+propsAsMap.toString()); + final String sha256 = DigestUtils.sha256Hex(configAsMap.toString() + envAsMap.toString() + propsAsMap.toString()); AuditMessage msg = new AuditMessage(AuditCategory.COMPLIANCE_EXTERNAL_CONFIG, clusterService, null, null); try (XContentBuilder builder = XContentBuilder.builder(XContentType.JSON.xContent())) { @@ -551,50 +739,54 @@ public Map run() { builder.close(); msg.addUnescapedJsonToRequestBody(Strings.toString(builder)); } catch (Exception e) { - log.error("Unable to build message",e); + log.error("Unable to build message", e); } Map paths = new HashMap(); - for(String key: settings.keySet()) { - if(key.startsWith("opendistro_security") && - (key.contains("filepath") || key.contains("file_path"))) { + for (String key : settings.keySet()) { + if (key.startsWith("opendistro_security") && (key.contains("filepath") || key.contains("file_path"))) { String value = settings.get(key); - if(value != null && !value.isEmpty()) { - Path path = value.startsWith("/")?Paths.get(value):environment.configDir().resolve(value); + if (value != null && !value.isEmpty()) { + Path path = value.startsWith("/") ? Paths.get(value) : environment.configDir().resolve(value); paths.put(key, path); } } } msg.addFileInfos(paths); - save(msg); } private Origin getOrigin() { String origin = (String) threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN); - if(origin == null && threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER) != null) { + if (origin == null && threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER) != null) { origin = threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER); } - return origin == null?null:Origin.valueOf(origin); + return origin == null ? null : Origin.valueOf(origin); } private TransportAddress getRemoteAddress() { TransportAddress address = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); - if(address == null && threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER) != null) { - address = new TransportAddress((InetSocketAddress) Base64Helper.deserializeObject(threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER))); + if (address == null && threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER) != null) { + address = new TransportAddress( + (InetSocketAddress) Base64Helper.deserializeObject( + threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER) + ) + ); } return address; } private String getUser() { User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - if(user == null && threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER) != null) { - user = (User) Base64Helper.deserializeObject(threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER)); + if (user == null && threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER) != null) { + user = (User) Base64Helper.deserializeObject( + threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER) + ); } - return user==null?null:user.getName(); + return user == null ? null : user.getName(); } private Map getThreadContextHeaders() { @@ -605,15 +797,20 @@ private Map getThreadContextHeaders() { boolean checkTransportFilter(final AuditCategory category, final String action, final String effectiveUser, TransportRequest request) { final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { - log.trace("Check category:{}, action:{}, effectiveUser:{}, request:{}", category, action, effectiveUser, request==null?null:request.getClass().getSimpleName()); + log.trace( + "Check category:{}, action:{}, effectiveUser:{}, request:{}", + category, + action, + effectiveUser, + request == null ? null : request.getClass().getSimpleName() + ); } - if (!auditConfigFilter.isTransportApiAuditEnabled()) { return false; } - //skip internals + // skip internals if (action != null && action.startsWith("internal:")) { return false; } @@ -627,10 +824,12 @@ boolean checkTransportFilter(final AuditCategory category, final String action, return false; } - if (request != null && (auditConfigFilter.isRequestAuditDisabled(action) || auditConfigFilter.isRequestAuditDisabled(request.getClass().getSimpleName()))) { + if (request != null + && (auditConfigFilter.isRequestAuditDisabled(action) + || auditConfigFilter.isRequestAuditDisabled(request.getClass().getSimpleName()))) { if (isTraceEnabled) { - log.trace("Skipped audit log message because request {} is ignored", action+"#"+request.getClass().getSimpleName()); + log.trace("Skipped audit log message because request {} is ignored", action + "#" + request.getClass().getSimpleName()); } return false; @@ -645,29 +844,33 @@ boolean checkTransportFilter(final AuditCategory category, final String action, return false; } - - //skip internal:* - //check transport audit enabled - //check category enabled - //check action - //check ignoreAuditUsers + // skip internal:* + // check transport audit enabled + // check category enabled + // check action + // check ignoreAuditUsers } - private boolean checkComplianceFilter(final AuditCategory category, final String effectiveUser, Origin origin, ComplianceConfig complianceConfig) { + private boolean checkComplianceFilter( + final AuditCategory category, + final String effectiveUser, + Origin origin, + ComplianceConfig complianceConfig + ) { final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { log.trace("Check for COMPLIANCE category:{}, effectiveUser:{}, origin: {}", category, effectiveUser, origin); } - if(origin == Origin.LOCAL && effectiveUser == null && category != AuditCategory.COMPLIANCE_EXTERNAL_CONFIG) { + if (origin == Origin.LOCAL && effectiveUser == null && category != AuditCategory.COMPLIANCE_EXTERNAL_CONFIG) { if (isTraceEnabled) { log.trace("Skipped compliance log message because of null user and local origin"); } return false; } - if(category == AuditCategory.COMPLIANCE_DOC_READ || category == AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ) { + if (category == AuditCategory.COMPLIANCE_DOC_READ || category == AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ) { if (effectiveUser != null && complianceConfig.isComplianceReadAuditDisabled(effectiveUser)) { @@ -678,7 +881,7 @@ private boolean checkComplianceFilter(final AuditCategory category, final String } } - if(category == AuditCategory.COMPLIANCE_DOC_WRITE || category == AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE) { + if (category == AuditCategory.COMPLIANCE_DOC_WRITE || category == AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE) { if (effectiveUser != null && complianceConfig.isComplianceWriteAuditDisabled(effectiveUser)) { if (isTraceEnabled) { @@ -695,7 +898,12 @@ private boolean checkComplianceFilter(final AuditCategory category, final String boolean checkRestFilter(final AuditCategory category, final String effectiveUser, RestRequest request) { final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { - log.trace("Check for REST category:{}, effectiveUser:{}, request:{}", category, effectiveUser, request==null?null:request.path()); + log.trace( + "Check for REST category:{}, effectiveUser:{}, request:{}", + category, + effectiveUser, + request == null ? null : request.path() + ); } if (!auditConfigFilter.isRestApiAuditEnabled()) { @@ -729,13 +937,11 @@ boolean checkRestFilter(final AuditCategory category, final String effectiveUser return false; } - - //check rest audit enabled - //check category enabled - //check action - //check ignoreAuditUsers + // check rest audit enabled + // check category enabled + // check action + // check ignoreAuditUsers } - protected abstract void save(final AuditMessage msg); } diff --git a/src/main/java/org/opensearch/security/auditlog/impl/AuditCategory.java b/src/main/java/org/opensearch/security/auditlog/impl/AuditCategory.java index 20f8b4e777..caf6938b14 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AuditCategory.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AuditCategory.java @@ -33,13 +33,8 @@ public enum AuditCategory { COMPLIANCE_INTERNAL_CONFIG_WRITE; public static Set parse(final Collection categories) { - if (categories.isEmpty()) - return Collections.emptySet(); + if (categories.isEmpty()) return Collections.emptySet(); - return categories - .stream() - .map(String::toUpperCase) - .map(AuditCategory::valueOf) - .collect(ImmutableSet.toImmutableSet()); + return categories.stream().map(String::toUpperCase).map(AuditCategory::valueOf).collect(ImmutableSet.toImmutableSet()); } } diff --git a/src/main/java/org/opensearch/security/auditlog/impl/AuditLogImpl.java b/src/main/java/org/opensearch/security/auditlog/impl/AuditLogImpl.java index be5068fc14..a09c6a694e 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AuditLogImpl.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AuditLogImpl.java @@ -40,54 +40,58 @@ public final class AuditLogImpl extends AbstractAuditLog { - private final AuditMessageRouter messageRouter; - private final Settings settings; - private final boolean messageRouterEnabled; - private volatile boolean enabled; - private final Thread shutdownHook; - - public AuditLogImpl(final Settings settings, - final Path configPath, - final Client clientProvider, - final ThreadPool threadPool, - final IndexNameExpressionResolver resolver, - final ClusterService clusterService) { - this(settings, configPath, clientProvider, threadPool, resolver, clusterService, null); - } + private final AuditMessageRouter messageRouter; + private final Settings settings; + private final boolean messageRouterEnabled; + private volatile boolean enabled; + private final Thread shutdownHook; + + public AuditLogImpl( + final Settings settings, + final Path configPath, + final Client clientProvider, + final ThreadPool threadPool, + final IndexNameExpressionResolver resolver, + final ClusterService clusterService + ) { + this(settings, configPath, clientProvider, threadPool, resolver, clusterService, null); + } @SuppressWarnings("removal") - public AuditLogImpl(final Settings settings, - final Path configPath, - final Client clientProvider, - final ThreadPool threadPool, - final IndexNameExpressionResolver resolver, - final ClusterService clusterService, - final Environment environment) { - super(settings, threadPool, resolver, clusterService, environment); - this.settings = settings; - this.messageRouter = new AuditMessageRouter(settings, clientProvider, threadPool, configPath); - this.messageRouterEnabled = this.messageRouter.isEnabled(); - - log.info("Message routing enabled: {}", this.messageRouterEnabled); - - SpecialPermission.check(); - shutdownHook = AccessController.doPrivileged((PrivilegedAction) this::addShutdownHook); - log.debug("Shutdown hook {} registered", shutdownHook); - } - - @Subscribe - public void setConfig(final AuditConfig auditConfig) { - enabled = auditConfig.isEnabled() && messageRouterEnabled; - onAuditConfigFilterChanged(auditConfig.getFilter()); - onComplianceConfigChanged(auditConfig.getCompliance()); - } - - @Override - protected void enableRoutes() { - if (messageRouterEnabled) { - messageRouter.enableRoutes(settings); - } - } + public AuditLogImpl( + final Settings settings, + final Path configPath, + final Client clientProvider, + final ThreadPool threadPool, + final IndexNameExpressionResolver resolver, + final ClusterService clusterService, + final Environment environment + ) { + super(settings, threadPool, resolver, clusterService, environment); + this.settings = settings; + this.messageRouter = new AuditMessageRouter(settings, clientProvider, threadPool, configPath); + this.messageRouterEnabled = this.messageRouter.isEnabled(); + + log.info("Message routing enabled: {}", this.messageRouterEnabled); + + SpecialPermission.check(); + shutdownHook = AccessController.doPrivileged((PrivilegedAction) this::addShutdownHook); + log.debug("Shutdown hook {} registered", shutdownHook); + } + + @Subscribe + public void setConfig(final AuditConfig auditConfig) { + enabled = auditConfig.isEnabled() && messageRouterEnabled; + onAuditConfigFilterChanged(auditConfig.getFilter()); + onComplianceConfigChanged(auditConfig.getCompliance()); + } + + @Override + protected void enableRoutes() { + if (messageRouterEnabled) { + messageRouter.enableRoutes(settings); + } + } private Thread addShutdownHook() { Thread shutdownHook = new Thread(() -> messageRouter.close()); @@ -119,123 +123,123 @@ public void close() throws IOException { } } - @Override - protected void save(final AuditMessage msg) { - if (enabled) { - messageRouter.route(msg); - } - } - - @Override - public void logFailedLogin(String effectiveUser, boolean securityAdmin, String initiatingUser, RestRequest request) { - if (enabled) { - super.logFailedLogin(effectiveUser, securityAdmin, initiatingUser, request); - } - } - - @Override - public void logSucceededLogin(String effectiveUser, boolean securityAdmin, String initiatingUser, RestRequest request) { - if (enabled) { - super.logSucceededLogin(effectiveUser, securityAdmin, initiatingUser, request); - } - } - - @Override - public void logMissingPrivileges(String privilege, String effectiveUser, RestRequest request) { - if (enabled) { - super.logMissingPrivileges(privilege, effectiveUser, request); - } - } - - @Override - public void logGrantedPrivileges(String effectiveUser, RestRequest request) { - if (enabled) { - super.logGrantedPrivileges(effectiveUser, request); - } - } - - @Override - public void logMissingPrivileges(String privilege, TransportRequest request, Task task) { - if (enabled) { - super.logMissingPrivileges(privilege, request, task); - } - } - - @Override - public void logGrantedPrivileges(String privilege, TransportRequest request, Task task) { - if (enabled) { - super.logGrantedPrivileges(privilege, request, task); - } - } - - @Override - public void logIndexEvent(String privilege, TransportRequest request, Task task) { - if (enabled) { - super.logIndexEvent(privilege, request, task); - } - } - - @Override - public void logBadHeaders(TransportRequest request, String action, Task task) { - if (enabled) { - super.logBadHeaders(request, action, task); - } - } - - @Override - public void logBadHeaders(RestRequest request) { - if (enabled) { - super.logBadHeaders(request); - } - } - - @Override - public void logSecurityIndexAttempt (TransportRequest request, String action, Task task) { - if (enabled) { - super.logSecurityIndexAttempt(request, action, task); - } - } - - @Override - public void logSSLException(TransportRequest request, Throwable t, String action, Task task) { - if (enabled) { - super.logSSLException(request, t, action, task); - } - } - - @Override - public void logSSLException(RestRequest request, Throwable t) { - if (enabled) { - super.logSSLException(request, t); - } - } - - @Override - public void logDocumentRead(String index, String id, ShardId shardId, Map fieldNameValues) { - if (enabled) { - super.logDocumentRead(index, id, shardId, fieldNameValues); - } - } - - @Override - public void logDocumentWritten(ShardId shardId, GetResult originalResult, Index currentIndex, IndexResult result) { - if (enabled) { - super.logDocumentWritten(shardId, originalResult, currentIndex, result); - } - } - - @Override - public void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult result) { - if (enabled) { - super.logDocumentDeleted(shardId, delete, result); - } - } - - @Override - protected void logExternalConfig() { - if (enabled) { - super.logExternalConfig(); - } - } + @Override + protected void save(final AuditMessage msg) { + if (enabled) { + messageRouter.route(msg); + } + } + + @Override + public void logFailedLogin(String effectiveUser, boolean securityAdmin, String initiatingUser, RestRequest request) { + if (enabled) { + super.logFailedLogin(effectiveUser, securityAdmin, initiatingUser, request); + } + } + + @Override + public void logSucceededLogin(String effectiveUser, boolean securityAdmin, String initiatingUser, RestRequest request) { + if (enabled) { + super.logSucceededLogin(effectiveUser, securityAdmin, initiatingUser, request); + } + } + + @Override + public void logMissingPrivileges(String privilege, String effectiveUser, RestRequest request) { + if (enabled) { + super.logMissingPrivileges(privilege, effectiveUser, request); + } + } + + @Override + public void logGrantedPrivileges(String effectiveUser, RestRequest request) { + if (enabled) { + super.logGrantedPrivileges(effectiveUser, request); + } + } + + @Override + public void logMissingPrivileges(String privilege, TransportRequest request, Task task) { + if (enabled) { + super.logMissingPrivileges(privilege, request, task); + } + } + + @Override + public void logGrantedPrivileges(String privilege, TransportRequest request, Task task) { + if (enabled) { + super.logGrantedPrivileges(privilege, request, task); + } + } + + @Override + public void logIndexEvent(String privilege, TransportRequest request, Task task) { + if (enabled) { + super.logIndexEvent(privilege, request, task); + } + } + + @Override + public void logBadHeaders(TransportRequest request, String action, Task task) { + if (enabled) { + super.logBadHeaders(request, action, task); + } + } + + @Override + public void logBadHeaders(RestRequest request) { + if (enabled) { + super.logBadHeaders(request); + } + } + + @Override + public void logSecurityIndexAttempt(TransportRequest request, String action, Task task) { + if (enabled) { + super.logSecurityIndexAttempt(request, action, task); + } + } + + @Override + public void logSSLException(TransportRequest request, Throwable t, String action, Task task) { + if (enabled) { + super.logSSLException(request, t, action, task); + } + } + + @Override + public void logSSLException(RestRequest request, Throwable t) { + if (enabled) { + super.logSSLException(request, t); + } + } + + @Override + public void logDocumentRead(String index, String id, ShardId shardId, Map fieldNameValues) { + if (enabled) { + super.logDocumentRead(index, id, shardId, fieldNameValues); + } + } + + @Override + public void logDocumentWritten(ShardId shardId, GetResult originalResult, Index currentIndex, IndexResult result) { + if (enabled) { + super.logDocumentWritten(shardId, originalResult, currentIndex, result); + } + } + + @Override + public void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult result) { + if (enabled) { + super.logDocumentDeleted(shardId, delete, result); + } + } + + @Override + protected void logExternalConfig() { + if (enabled) { + super.logExternalConfig(); + } + } } 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 8931d44690..9b1d70d41c 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java @@ -55,13 +55,14 @@ public final class AuditMessage { - //clustername and cluster uuid + // clustername and cluster uuid private static final WildcardMatcher AUTHORIZATION_HEADER = WildcardMatcher.from("Authorization", false); private static final String SENSITIVE_KEY = "password"; private static final String SENSITIVE_REPLACEMENT_VALUE = "__SENSITIVE__"; - private static final Pattern SENSITIVE_PATHS = - Pattern.compile( "/(" + LEGACY_OPENDISTRO_PREFIX + "|" + PLUGINS_PREFIX + ")/api/(account.*|internalusers.*|user.*)"); + private static final Pattern SENSITIVE_PATHS = Pattern.compile( + "/(" + LEGACY_OPENDISTRO_PREFIX + "|" + PLUGINS_PREFIX + ")/api/(account.*|internalusers.*|user.*)" + ); @VisibleForTesting public static final Pattern BCRYPT_HASH = Pattern.compile("\\$2[ayb]\\$.{56}"); @@ -93,8 +94,8 @@ public final class AuditMessage { public static final String TRANSPORT_REQUEST_HEADERS = "audit_transport_headers"; public static final String ID = "audit_trace_doc_id"; - //public static final String TYPES = "audit_trace_doc_types"; - //public static final String SOURCE = "audit_trace_doc_source"; + // public static final String TYPES = "audit_trace_doc_types"; + // public static final String SOURCE = "audit_trace_doc_source"; public static final String INDICES = "audit_trace_indices"; public static final String SHARD_ID = "audit_trace_shard_id"; public static final String RESOLVED_INDICES = "audit_trace_resolved_indices"; @@ -111,8 +112,8 @@ public final class AuditMessage { public static final String COMPLIANCE_DIFF_CONTENT = "audit_compliance_diff_content"; public static final String COMPLIANCE_FILE_INFOS = "audit_compliance_file_infos"; - //public static final String COMPLIANCE_DIFF_STORED_IS_NOOP = "audit_compliance_diff_stored_is_noop"; - //public static final String COMPLIANCE_STORED_FIELDS_CONTENT = "audit_compliance_stored_fields_content"; + // public static final String COMPLIANCE_DIFF_STORED_IS_NOOP = "audit_compliance_diff_stored_is_noop"; + // public static final String COMPLIANCE_STORED_FIELDS_CONTENT = "audit_compliance_stored_fields_content"; public static final String REQUEST_LAYER = "audit_request_layer"; @@ -135,11 +136,11 @@ public AuditMessage(final AuditCategory msgCategory, final ClusterService cluste auditInfo.put(NODE_NAME, Objects.requireNonNull(clusterService).localNode().getName()); auditInfo.put(CLUSTER_NAME, Objects.requireNonNull(clusterService).getClusterName().value()); - if(origin != null) { + if (origin != null) { auditInfo.put(ORIGIN, origin); } - if(layer != null) { + if (layer != null) { auditInfo.put(REQUEST_LAYER, layer); } } @@ -197,25 +198,25 @@ void addSecurityConfigWriteDiffSource(final String diff, final String id) { addComplianceWriteDiffSource(redactSecurityConfigContent(diff, id)); } -// public void addComplianceWriteStoredFields0(String diff) { -// if (diff != null && !diff.isEmpty()) { -// auditInfo.put(COMPLIANCE_STORED_FIELDS_CONTENT, diff); -// //auditInfo.put(COMPLIANCE_DIFF_STORED_IS_NOOP, false); -// } -// } + // public void addComplianceWriteStoredFields0(String diff) { + // if (diff != null && !diff.isEmpty()) { + // auditInfo.put(COMPLIANCE_STORED_FIELDS_CONTENT, diff); + // //auditInfo.put(COMPLIANCE_DIFF_STORED_IS_NOOP, false); + // } + // } public void addTupleToRequestBody(Tuple xContentTuple) { if (xContentTuple != null) { try { auditInfo.put(REQUEST_BODY, XContentHelper.convertToJson(xContentTuple.v2(), false, xContentTuple.v1())); } catch (Exception e) { - auditInfo.put(REQUEST_BODY, "ERROR: Unable to convert to json because of "+e.toString()); + auditInfo.put(REQUEST_BODY, "ERROR: Unable to convert to json because of " + e.toString()); } } } public void addMapToRequestBody(Map map) { - if(map != null) { + if (map != null) { auditInfo.put(REQUEST_BODY, Utils.convertStructuredMapToJson(map)); } } @@ -289,10 +290,10 @@ public void addType(String type) { public void addFileInfos(Map paths) { if (paths != null && !paths.isEmpty()) { List infos = new ArrayList<>(); - for(Entry path: paths.entrySet()) { + for (Entry path : paths.entrySet()) { try { - if(Files.isReadable(path.getValue())) { + if (Files.isReadable(path.getValue())) { final String chcksm = DigestUtils.sha256Hex(Files.readAllBytes(path.getValue())); FileTime lm = Files.getLastModifiedTime(path.getValue(), LinkOption.NOFOLLOW_LINKS); Map innerInfos = new HashMap<>(); @@ -303,7 +304,7 @@ public void addFileInfos(Map paths) { infos.add(innerInfos); } } catch (Throwable e) { - //ignore non readable files + // ignore non readable files } } auditInfo.put(COMPLIANCE_FILE_INFOS, infos); @@ -330,29 +331,29 @@ public void addResolvedIndices(String[] resolvedIndices) { } public void addTaskId(long id) { - auditInfo.put(TASK_ID, auditInfo.get(NODE_ID)+":"+id); + auditInfo.put(TASK_ID, auditInfo.get(NODE_ID) + ":" + id); } public void addShardId(ShardId id) { - if(id != null) { + if (id != null) { auditInfo.put(SHARD_ID, id.getId()); } - } + } public void addTaskParentId(String id) { - if(id != null) { + if (id != null) { auditInfo.put(TASK_PARENT_ID, id); } } - public void addRestParams(Map params) { - if(params != null && !params.isEmpty()) { + public void addRestParams(Map params) { + if (params != null && !params.isEmpty()) { auditInfo.put(REST_REQUEST_PARAMS, new HashMap<>(params)); } } - public void addRestHeaders(Map> headers, boolean excludeSensitiveHeaders) { - if(headers != null && !headers.isEmpty()) { + public void addRestHeaders(Map> headers, boolean excludeSensitiveHeaders) { + if (headers != null && !headers.isEmpty()) { final Map> headersClone = new HashMap<>(headers); if (excludeSensitiveHeaders) { headersClone.keySet().removeIf(AUTHORIZATION_HEADER); @@ -377,10 +378,11 @@ void addRestRequestInfo(final RestRequest request, final AuditConfig.Filter filt if (filter.shouldLogRequestBody() && request.hasContentOrSourceParam()) { try { final Tuple xContentTuple = request.contentOrSourceParam(); - final String requestBody = XContentHelper.convertToJson(xContentTuple.v2(), false, xContentTuple.v1()); - if (path != null && requestBody != null - && SENSITIVE_PATHS.matcher(path).matches() - && requestBody.contains(SENSITIVE_KEY)) { + final String requestBody = XContentHelper.convertToJson(xContentTuple.v2(), false, xContentTuple.v1()); + if (path != null + && requestBody != null + && SENSITIVE_PATHS.matcher(path).matches() + && requestBody.contains(SENSITIVE_KEY)) { auditInfo.put(REQUEST_BODY, SENSITIVE_REPLACEMENT_VALUE); } else { auditInfo.put(REQUEST_BODY, requestBody); @@ -392,8 +394,8 @@ void addRestRequestInfo(final RestRequest request, final AuditConfig.Filter filt } } - public void addTransportHeaders(Map headers, boolean excludeSensitiveHeaders) { - if(headers != null && !headers.isEmpty()) { + public void addTransportHeaders(Map headers, boolean excludeSensitiveHeaders) { + if (headers != null && !headers.isEmpty()) { final Map headersClone = new HashMap<>(headers); if (excludeSensitiveHeaders) { headersClone.keySet().removeIf(AUTHORIZATION_HEADER); @@ -403,7 +405,7 @@ public void addTransportHeaders(Map headers, boolean excludeSensi } public void addComplianceOperation(Operation op) { - if(op != null) { + if (op != null) { auditInfo.put(COMPLIANCE_OPERATION, op); } } @@ -413,7 +415,7 @@ public void addComplianceDocVersion(long version) { } public Map getAsMap() { - return new HashMap<>(this.auditInfo); + return new HashMap<>(this.auditInfo); } public String getInitiatingUser() { @@ -432,9 +434,9 @@ public RestRequest.Method getRequestMethod() { return (RestRequest.Method) this.auditInfo.get(REST_REQUEST_METHOD); } - public AuditCategory getCategory() { - return msgCategory; - } + public AuditCategory getCategory() { + return msgCategory; + } public Origin getOrigin() { return (Origin) this.auditInfo.get(ORIGIN); @@ -460,14 +462,14 @@ public String getDocId() { return (String) this.auditInfo.get(ID); } - @Override - public String toString() { - try { - return org.opensearch.common.Strings.toString(JsonXContent.contentBuilder().map(getAsMap())); - } catch (final IOException e) { - throw ExceptionsHelper.convertToOpenSearchException(e); - } - } + @Override + public String toString() { + try { + return org.opensearch.common.Strings.toString(JsonXContent.contentBuilder().map(getAsMap())); + } catch (final IOException e) { + throw ExceptionsHelper.convertToOpenSearchException(e); + } + } public String toPrettyString() { try { @@ -477,34 +479,34 @@ public String toPrettyString() { } } - public String toText() { - StringBuilder builder = new StringBuilder(); - for (Entry entry : getAsMap().entrySet()) { - addIfNonEmpty(builder, entry.getKey(), stringOrNull(entry.getValue())); - } - return builder.toString(); - } - - public final String toJson() { - return this.toString(); - } - - public String toUrlParameters() { - URIBuilder builder = new URIBuilder(); - for (Entry entry : getAsMap().entrySet()) { - builder.addParameter(entry.getKey(), stringOrNull(entry.getValue())); - } - return builder.toString(); - } - - protected static void addIfNonEmpty(StringBuilder builder, String key, String value) { - if (!Strings.isEmpty(value)) { - if (builder.length() > 0) { - builder.append("\n"); - } - builder.append(key).append(": ").append(value); - } - } + public String toText() { + StringBuilder builder = new StringBuilder(); + for (Entry entry : getAsMap().entrySet()) { + addIfNonEmpty(builder, entry.getKey(), stringOrNull(entry.getValue())); + } + return builder.toString(); + } + + public final String toJson() { + return this.toString(); + } + + public String toUrlParameters() { + URIBuilder builder = new URIBuilder(); + for (Entry entry : getAsMap().entrySet()) { + builder.addParameter(entry.getKey(), stringOrNull(entry.getValue())); + } + return builder.toString(); + } + + protected static void addIfNonEmpty(StringBuilder builder, String key, String value) { + if (!Strings.isEmpty(value)) { + if (builder.length() > 0) { + builder.append("\n"); + } + builder.append(key).append(": ").append(value); + } + } private String currentTime() { DateTime dt = new DateTime(DateTimeZone.UTC); @@ -517,7 +519,7 @@ private String formatTime(long epoch) { } protected String stringOrNull(Object object) { - if(object == null) { + if (object == null) { return null; } diff --git a/src/main/java/org/opensearch/security/auditlog/impl/RequestResolver.java b/src/main/java/org/opensearch/security/auditlog/impl/RequestResolver.java index 5b4881d1eb..ecf7a2bd36 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/RequestResolver.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/RequestResolver.java @@ -64,54 +64,56 @@ public final class RequestResolver { private static final Logger log = LogManager.getLogger(RequestResolver.class); public static List resolve( - final AuditCategory category, - final Origin origin, - final String action, - final String privilege, - final String effectiveUser, - final Boolean securityadmin, - final String initiatingUser, - final TransportAddress remoteAddress, - final TransportRequest request, - final Map headers, - final Task task, - final IndexNameExpressionResolver resolver, - final ClusterService cs, - final Settings settings, - final boolean logRequestBody, - final boolean resolveIndices, - final boolean resolveBulk, - final String securityIndex, - final boolean excludeSensitiveHeaders, - final Throwable exception) { - - if(resolveBulk && request instanceof BulkShardRequest) { + final AuditCategory category, + final Origin origin, + final String action, + final String privilege, + final String effectiveUser, + final Boolean securityadmin, + final String initiatingUser, + final TransportAddress remoteAddress, + final TransportRequest request, + final Map headers, + final Task task, + final IndexNameExpressionResolver resolver, + final ClusterService cs, + final Settings settings, + final boolean logRequestBody, + final boolean resolveIndices, + final boolean resolveBulk, + final String securityIndex, + final boolean excludeSensitiveHeaders, + final Throwable exception + ) { + + if (resolveBulk && request instanceof BulkShardRequest) { final BulkItemRequest[] innerRequests = ((BulkShardRequest) request).items(); final List messages = new ArrayList(innerRequests.length); - for(BulkItemRequest ar: innerRequests) { + for (BulkItemRequest ar : innerRequests) { final DocWriteRequest innerRequest = ar.request(); final AuditMessage msg = resolveInner( - category, - effectiveUser, - securityadmin, - initiatingUser, - remoteAddress, - action, - privilege, - origin, - innerRequest, - headers, - task, - resolver, - cs, - settings, - logRequestBody, - resolveIndices, - securityIndex, - excludeSensitiveHeaders, - exception); - msg.addShardId(((BulkShardRequest) request).shardId()); + category, + effectiveUser, + securityadmin, + initiatingUser, + remoteAddress, + action, + privilege, + origin, + innerRequest, + headers, + task, + resolver, + cs, + settings, + logRequestBody, + resolveIndices, + securityIndex, + excludeSensitiveHeaders, + exception + ); + msg.addShardId(((BulkShardRequest) request).shardId()); messages.add(msg); } @@ -119,17 +121,18 @@ public static List resolve( return messages; } - if(request instanceof BulkShardRequest) { + if (request instanceof BulkShardRequest) { - if(category != AuditCategory.FAILED_LOGIN - && category != AuditCategory.MISSING_PRIVILEGES - && category != AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT) { + if (category != AuditCategory.FAILED_LOGIN + && category != AuditCategory.MISSING_PRIVILEGES + && category != AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT) { return Collections.emptyList(); } } - return Collections.singletonList(resolveInner( + return Collections.singletonList( + resolveInner( category, effectiveUser, securityadmin, @@ -148,29 +151,32 @@ public static List resolve( resolveIndices, securityIndex, excludeSensitiveHeaders, - exception)); + exception + ) + ); } - - private static AuditMessage resolveInner(final AuditCategory category, - final String effectiveUser, - final Boolean securityadmin, - final String initiatingUser, - final TransportAddress remoteAddress, - final String action, - final String priv, - final Origin origin, - final Object request, - final Map headers, - final Task task, - final IndexNameExpressionResolver resolver, - final ClusterService cs, - final Settings settings, - final boolean logRequestBody, - final boolean resolveIndices, - final String securityIndex, - final boolean excludeSensitiveHeaders, - final Throwable exception) { + private static AuditMessage resolveInner( + final AuditCategory category, + final String effectiveUser, + final Boolean securityadmin, + final String initiatingUser, + final TransportAddress remoteAddress, + final String action, + final String priv, + final Origin origin, + final Object request, + final Map headers, + final Task task, + final IndexNameExpressionResolver resolver, + final ClusterService cs, + final Settings settings, + final boolean logRequestBody, + final boolean resolveIndices, + final String securityIndex, + final boolean excludeSensitiveHeaders, + final Throwable exception + ) { final AuditMessage msg = new AuditMessage(category, cs, origin, Origin.TRANSPORT); msg.addInitiatingUser(initiatingUser); @@ -178,11 +184,11 @@ private static AuditMessage resolveInner(final AuditCategory category, msg.addRemoteAddress(remoteAddress); msg.addAction(action); - if(request != null) { + if (request != null) { msg.addRequestType(request.getClass().getSimpleName()); } - if(securityadmin != null) { + if (securityadmin != null) { msg.addIsAdminDn(securityadmin); } @@ -190,14 +196,14 @@ private static AuditMessage resolveInner(final AuditCategory category, msg.addPrivilege(priv); msg.addTransportHeaders(headers, excludeSensitiveHeaders); - if(task != null) { + if (task != null) { msg.addTaskId(task.getId()); - if(task.getParentTaskId() != null && task.getParentTaskId().isSet()) { + if (task.getParentTaskId() != null && task.getParentTaskId().isSet()) { msg.addTaskParentId(task.getParentTaskId().toString()); } } - //attempt to resolve indices/types/id/source + // attempt to resolve indices/types/id/source if (request instanceof MultiGetRequest.Item) { final MultiGetRequest.Item item = (MultiGetRequest.Item) request; final String[] indices = arrayOrEmpty(item.indices()); @@ -221,7 +227,7 @@ private static AuditMessage resolveInner(final AuditCategory category, } else if (request instanceof DeleteIndexRequest) { final DeleteIndexRequest dir = (DeleteIndexRequest) request; final String[] indices = arrayOrEmpty(dir.indices()); - //dir id alle id's beim schreiben protokolloieren + // dir id alle id's beim schreiben protokolloieren addIndicesSourceSafe(msg, indices, resolver, cs, null, null, settings, resolveIndices, logRequestBody, false, securityIndex); } else if (request instanceof IndexRequest) { final IndexRequest ir = (IndexRequest) request; @@ -229,7 +235,19 @@ private static AuditMessage resolveInner(final AuditCategory category, final String id = ir.id(); msg.addShardId(ir.shardId()); msg.addId(id); - addIndicesSourceSafe(msg, indices, resolver, cs, ir.getContentType(), ir.source(), settings, resolveIndices, logRequestBody, true, securityIndex); + addIndicesSourceSafe( + msg, + indices, + resolver, + cs, + ir.getContentType(), + ir.source(), + settings, + resolveIndices, + logRequestBody, + true, + securityIndex + ); } else if (request instanceof DeleteRequest) { final DeleteRequest dr = (DeleteRequest) request; final String[] indices = arrayOrEmpty(dr.indices()); @@ -243,10 +261,10 @@ private static AuditMessage resolveInner(final AuditCategory category, final String id = ur.id(); msg.addId(id); addIndicesSourceSafe(msg, indices, resolver, cs, null, null, settings, resolveIndices, logRequestBody, false, securityIndex); - if(logRequestBody) { + if (logRequestBody) { if (ur.doc() != null) { - msg.addTupleToRequestBody(ur.doc() == null ? null :convertSource(ur.doc().getContentType(), ur.doc().source())); + msg.addTupleToRequestBody(ur.doc() == null ? null : convertSource(ur.doc().getContentType(), ur.doc().source())); } if (ur.script() != null) { @@ -263,10 +281,22 @@ private static AuditMessage resolveInner(final AuditCategory category, final SearchRequest sr = (SearchRequest) request; final String[] indices = arrayOrEmpty(sr.indices()); - Map sourceAsMap = sr.source() == null? null:Utils.convertJsonToxToStructuredMap(sr.source()); - addIndicesSourceSafe(msg, indices, resolver, cs, XContentType.JSON, sourceAsMap, settings, resolveIndices, logRequestBody, false, securityIndex); + Map sourceAsMap = sr.source() == null ? null : Utils.convertJsonToxToStructuredMap(sr.source()); + addIndicesSourceSafe( + msg, + indices, + resolver, + cs, + XContentType.JSON, + sourceAsMap, + settings, + resolveIndices, + logRequestBody, + false, + securityIndex + ); } else if (request instanceof ClusterUpdateSettingsRequest) { - if(logRequestBody) { + if (logRequestBody) { final ClusterUpdateSettingsRequest cusr = (ClusterUpdateSettingsRequest) request; final Settings persistentSettings = cusr.persistentSettings(); final Settings transientSettings = cusr.transientSettings(); @@ -276,31 +306,42 @@ private static AuditMessage resolveInner(final AuditCategory category, builder = XContentFactory.jsonBuilder(); builder.startObject(); - if(persistentSettings != null) { + if (persistentSettings != null) { builder.field("persistent_settings", Utils.convertJsonToxToStructuredMap(persistentSettings)); } - if(transientSettings != null) { + if (transientSettings != null) { builder.field("transient_settings", Utils.convertJsonToxToStructuredMap(persistentSettings)); } builder.endObject(); - msg.addUnescapedJsonToRequestBody(builder == null?null:Strings.toString(builder)); + msg.addUnescapedJsonToRequestBody(builder == null ? null : Strings.toString(builder)); } catch (IOException e) { log.error(e.toString()); } finally { - if(builder != null) { + if (builder != null) { builder.close(); } } - - } + } } else if (request instanceof ReindexRequest) { final IndexRequest ir = ((ReindexRequest) request).getDestination(); final String[] indices = arrayOrEmpty(ir.indices()); final String id = ir.id(); msg.addShardId(ir.shardId()); msg.addId(id); - addIndicesSourceSafe(msg, indices, resolver, cs, ir.getContentType(), ir.source(), settings, resolveIndices, logRequestBody, true, securityIndex); + addIndicesSourceSafe( + msg, + indices, + resolver, + cs, + ir.getContentType(), + ir.source(), + settings, + resolveIndices, + logRequestBody, + true, + securityIndex + ); } else if (request instanceof DeleteByQueryRequest) { final DeleteByQueryRequest ir = (DeleteByQueryRequest) request; final String[] indices = arrayOrEmpty(ir.indices()); @@ -315,18 +356,18 @@ private static AuditMessage resolveInner(final AuditCategory category, String[] indices = new String[0]; msg.addIndices(indices); - if(ci != null) { - indices = new String[]{ci.getName()}; + if (ci != null) { + indices = new String[] { ci.getName() }; } - if(logRequestBody) { + if (logRequestBody) { msg.addUnescapedJsonToRequestBody(pr.source()); } - if(resolveIndices) { + if (resolveIndices) { msg.addResolvedIndices(indices); } - } else if (request instanceof IndicesRequest) { //less specific + } else if (request instanceof IndicesRequest) { // less specific final IndicesRequest ir = (IndicesRequest) request; final String[] indices = arrayOrEmpty(ir.indices()); addIndicesSourceSafe(msg, indices, resolver, cs, null, null, settings, resolveIndices, logRequestBody, false, securityIndex); @@ -335,66 +376,70 @@ private static AuditMessage resolveInner(final AuditCategory category, return msg; } - private static void addIndicesSourceSafe(final AuditMessage msg, - final String[] indices, - final IndexNameExpressionResolver resolver, - final ClusterService cs, - final XContentType xContentType, - final Object source, - final Settings settings, - boolean resolveIndices, - final boolean addSource, - final boolean sourceIsSensitive, - final String securityIndex) { - - if(addSource) { + private static void addIndicesSourceSafe( + final AuditMessage msg, + final String[] indices, + final IndexNameExpressionResolver resolver, + final ClusterService cs, + final XContentType xContentType, + final Object source, + final Settings settings, + boolean resolveIndices, + final boolean addSource, + final boolean sourceIsSensitive, + final String securityIndex + ) { + + if (addSource) { resolveIndices = true; } - final String[] _indices = indices == null?new String[0]:indices; + final String[] _indices = indices == null ? new String[0] : indices; msg.addIndices(_indices); final Set allIndices; - if(resolveIndices) { - final String[] resolvedIndices = (resolver==null)?new String[0]:resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), indices); + if (resolveIndices) { + final String[] resolvedIndices = (resolver == null) + ? new String[0] + : resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), indices); msg.addResolvedIndices(resolvedIndices); - allIndices = new HashSet(resolvedIndices.length+_indices.length); + allIndices = new HashSet(resolvedIndices.length + _indices.length); allIndices.addAll(Arrays.asList(_indices)); allIndices.addAll(Arrays.asList(resolvedIndices)); - if(allIndices.contains("_all")) { - allIndices.add("*"); //TODO: maybe replace allIndices instead of add? + if (allIndices.contains("_all")) { + allIndices.add("*"); // TODO: maybe replace allIndices instead of add? } } else { allIndices = new HashSet(_indices.length); allIndices.addAll(Arrays.asList(_indices)); - if(allIndices.contains("_all")) { - allIndices.add("*"); //TODO: maybe replace allIndices instead of add? + if (allIndices.contains("_all")) { + allIndices.add("*"); // TODO: maybe replace allIndices instead of add? } } final WildcardMatcher allIndicesMatcher = WildcardMatcher.from(allIndices); - if(addSource) { - if(sourceIsSensitive && source != null) { - if(!allIndicesMatcher.test(securityIndex)) { - if(source instanceof BytesReference) { - msg.addTupleToRequestBody(convertSource(xContentType, (BytesReference) source)); + if (addSource) { + if (sourceIsSensitive && source != null) { + if (!allIndicesMatcher.test(securityIndex)) { + if (source instanceof BytesReference) { + msg.addTupleToRequestBody(convertSource(xContentType, (BytesReference) source)); } else { msg.addMapToRequestBody((Map) source); } } - } else if(source != null) { - if(source instanceof BytesReference) { + } else if (source != null) { + if (source instanceof BytesReference) { msg.addTupleToRequestBody(convertSource(xContentType, (BytesReference) source)); - } else { - msg.addMapToRequestBody((Map) source); - } + } else { + msg.addMapToRequestBody((Map) source); + } } } } private static Tuple convertSource(XContentType type, BytesReference bytes) { - if(type == null) { + if (type == null) { type = XContentType.JSON; } @@ -402,11 +447,11 @@ private static Tuple convertSource(XContentType ty } private static String[] arrayOrEmpty(String[] array) { - if(array == null) { + if (array == null) { return new String[0]; } - if(array.length == 1 && array[0] == null) { + if (array.length == 1 && array[0] == null) { return new String[0]; } diff --git a/src/main/java/org/opensearch/security/auditlog/routing/AsyncStoragePool.java b/src/main/java/org/opensearch/security/auditlog/routing/AsyncStoragePool.java index bbea998b8d..494d67aba6 100644 --- a/src/main/java/org/opensearch/security/auditlog/routing/AsyncStoragePool.java +++ b/src/main/java/org/opensearch/security/auditlog/routing/AsyncStoragePool.java @@ -24,68 +24,75 @@ import org.opensearch.security.auditlog.sink.AuditLogSink; public class AsyncStoragePool { - private static final Logger log = LogManager.getLogger(AsyncStoragePool.class); - private final ExecutorService pool; - private final ThreadPoolConfig threadPoolConfig; + private static final Logger log = LogManager.getLogger(AsyncStoragePool.class); + private final ExecutorService pool; + private final ThreadPoolConfig threadPoolConfig; - public AsyncStoragePool(final ThreadPoolConfig threadPoolConfig) { - this.threadPoolConfig = threadPoolConfig; - this.pool = createExecutor(threadPoolConfig); - } + public AsyncStoragePool(final ThreadPoolConfig threadPoolConfig) { + this.threadPoolConfig = threadPoolConfig; + this.pool = createExecutor(threadPoolConfig); + } - public ThreadPoolConfig getConfig() { - return this.threadPoolConfig; - } + public ThreadPoolConfig getConfig() { + return this.threadPoolConfig; + } - public void submit(AuditMessage message, AuditLogSink sink) { - try { - pool.submit(() -> { - sink.store(message); - if (log.isTraceEnabled()) { - log.trace("stored on delegate {} asynchronously", sink.getClass().getSimpleName()); - } - }); - } catch (Exception ex) { - log.error("Could not submit audit message {} to thread pool for delegate '{}' due to '{}'", message, sink.getClass().getSimpleName(), ex.getMessage()); - if (sink.getFallbackSink() != null) { - sink.getFallbackSink().store(message); - } - } - } + public void submit(AuditMessage message, AuditLogSink sink) { + try { + pool.submit(() -> { + sink.store(message); + if (log.isTraceEnabled()) { + log.trace("stored on delegate {} asynchronously", sink.getClass().getSimpleName()); + } + }); + } catch (Exception ex) { + log.error( + "Could not submit audit message {} to thread pool for delegate '{}' due to '{}'", + message, + sink.getClass().getSimpleName(), + ex.getMessage() + ); + if (sink.getFallbackSink() != null) { + sink.getFallbackSink().store(message); + } + } + } - private static ThreadPoolExecutor createExecutor(final ThreadPoolConfig config) { - if (log.isDebugEnabled()) { - log.debug("Create new executor with threadPoolSize: {} and maxQueueLen: {}", - config.getThreadPoolSize(), - config.getThreadPoolMaxQueueLen()); - } - return new ThreadPoolExecutor( - config.getThreadPoolSize(), - config.getThreadPoolSize(), - 0L, - TimeUnit.MILLISECONDS, - new LinkedBlockingQueue<>(config.getThreadPoolMaxQueueLen())); - } + private static ThreadPoolExecutor createExecutor(final ThreadPoolConfig config) { + if (log.isDebugEnabled()) { + log.debug( + "Create new executor with threadPoolSize: {} and maxQueueLen: {}", + config.getThreadPoolSize(), + config.getThreadPoolMaxQueueLen() + ); + } + return new ThreadPoolExecutor( + config.getThreadPoolSize(), + config.getThreadPoolSize(), + 0L, + TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(config.getThreadPoolMaxQueueLen()) + ); + } - public void close() { + public void close() { - if (pool != null) { - pool.shutdown(); // Disable new tasks from being submitted + if (pool != null) { + pool.shutdown(); // Disable new tasks from being submitted - try { - // Wait a while for existing tasks to terminate - if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { - pool.shutdownNow(); // Cancel currently executing tasks - // Wait a while for tasks to respond to being cancelled - if (!pool.awaitTermination(60, TimeUnit.SECONDS)) - log.error("Pool did not terminate"); - } - } catch (InterruptedException ie) { - // (Re-)Cancel if current thread also interrupted - pool.shutdownNow(); - // Preserve interrupt status - Thread.currentThread().interrupt(); - } - } - } + try { + // Wait a while for existing tasks to terminate + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { + pool.shutdownNow(); // Cancel currently executing tasks + // Wait a while for tasks to respond to being cancelled + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) log.error("Pool did not terminate"); + } + } catch (InterruptedException ie) { + // (Re-)Cancel if current thread also interrupted + pool.shutdownNow(); + // Preserve interrupt status + Thread.currentThread().interrupt(); + } + } + } } diff --git a/src/main/java/org/opensearch/security/auditlog/routing/AuditMessageRouter.java b/src/main/java/org/opensearch/security/auditlog/routing/AuditMessageRouter.java index a6a6f29f23..af120daf35 100644 --- a/src/main/java/org/opensearch/security/auditlog/routing/AuditMessageRouter.java +++ b/src/main/java/org/opensearch/security/auditlog/routing/AuditMessageRouter.java @@ -106,9 +106,12 @@ public final void enableRoutes(Settings settings) { if (categorySinks != null) { return; } - Map routesConfiguration = Utils.convertJsonToxToStructuredMap(settings.getAsSettings(ConfigConstants.SECURITY_AUDIT_CONFIG_ROUTES)); + Map routesConfiguration = Utils.convertJsonToxToStructuredMap( + settings.getAsSettings(ConfigConstants.SECURITY_AUDIT_CONFIG_ROUTES) + ); EnumSet presentAuditCategory = EnumSet.noneOf(AuditCategory.class); - categorySinks = routesConfiguration.entrySet().stream() + categorySinks = routesConfiguration.entrySet() + .stream() .peek(entry -> log.trace("Setting up routes for endpoint {}, configuration is {}", entry.getKey(), entry.getValue())) .map(entry -> { String categoryName = entry.getKey(); @@ -116,9 +119,16 @@ public final void enableRoutes(Settings settings) { // first set up all configured routes. We do it this way so category names are case insensitive // and we can warn if a non-existing category has been detected. AuditCategory auditCategory = AuditCategory.valueOf(categoryName.toUpperCase()); - return Maps.immutableEntry(auditCategory, createSinksForCategory(auditCategory, (Map>)entry.getValue())); + return Maps.immutableEntry( + auditCategory, + createSinksForCategory(auditCategory, (Map>) entry.getValue()) + ); } catch (IllegalArgumentException e) { - log.error("Invalid category '{}' found in routing configuration. Must be one of: {}", categoryName, AuditCategory.values()); + log.error( + "Invalid category '{}' found in routing configuration. Must be one of: {}", + categoryName, + AuditCategory.values() + ); return null; } }) @@ -138,12 +148,7 @@ public final void enableRoutes(Settings settings) { } return false; }) - .collect( - Maps.toImmutableEnumMap( - Map.Entry::getKey, - Map.Entry::getValue - ) - ); + .collect(Maps.toImmutableEnumMap(Map.Entry::getKey, Map.Entry::getValue)); // for all non-configured categories we automatically set up the default endpoint log.warn("No endpoint configured for categories {}, using default endpoint", EnumSet.complementOf(presentAuditCategory)); diff --git a/src/main/java/org/opensearch/security/auditlog/sink/AuditLogSink.java b/src/main/java/org/opensearch/security/auditlog/sink/AuditLogSink.java index 2f2c4da5b0..a482b81c29 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/AuditLogSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/AuditLogSink.java @@ -38,7 +38,7 @@ public abstract class AuditLogSink { protected AuditLogSink(String name, Settings settings, String settingsPrefix, AuditLogSink fallbackSink) { this.name = name.toLowerCase(); - this.settings = Objects.requireNonNull(settings); + this.settings = Objects.requireNonNull(settings); this.settingsPrefix = settingsPrefix; this.fallbackSink = fallbackSink; @@ -51,34 +51,34 @@ public boolean isHandlingBackpressure() { } public String getName() { - return name; + return name; } public AuditLogSink getFallbackSink() { - return fallbackSink; + return fallbackSink; } public final void store(AuditMessage msg) { - if (!doStoreWithRetry(msg) && !fallbackSink.doStoreWithRetry(msg)) { - System.err.println(msg.toPrettyString()); - } + if (!doStoreWithRetry(msg) && !fallbackSink.doStoreWithRetry(msg)) { + System.err.println(msg.toPrettyString()); + } } private boolean doStoreWithRetry(AuditMessage msg) { - //retryCount of 0 means no retry (which is: try exactly once) - delayMs is ignored - //retryCount of 1 means: try and if this fails wait delayMs and try once again + // retryCount of 0 means no retry (which is: try exactly once) - delayMs is ignored + // retryCount of 1 means: try and if this fails wait delayMs and try once again - if(doStore(msg)) { + if (doStore(msg)) { return true; } final boolean isDebugEnabled = log.isDebugEnabled(); - for(int i=0; i DEFAULT_TLS_PROTOCOLS = Arrays.asList(new String[] { "TLSv1.2", "TLSv1.1"}); - // config in opensearch.yml - private final String index; - private final String type; - private final HttpClient client; - private List servers; - private DateTimeFormatter indexPattern; + private static final List DEFAULT_TLS_PROTOCOLS = Arrays.asList(new String[] { "TLSv1.2", "TLSv1.1" }); + // config in opensearch.yml + private final String index; + private final String type; + private final HttpClient client; + private List servers; + private DateTimeFormatter indexPattern; static final String PKCS12 = "PKCS12"; - public ExternalOpenSearchSink(final String name, final Settings settings, final String settingPrefix, final Path configPath, AuditLogSink fallbackSink) throws Exception { - - super(name, settings, settingPrefix, fallbackSink); - Settings sinkSettings = settings.getAsSettings(settingPrefix); - servers = sinkSettings.getAsList(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_HTTP_ENDPOINTS); - if (servers == null || servers.size() == 0) { - log.error("No http endpoints configured for external OpenSearch endpoint '{}', falling back to localhost.", name); - servers = Collections.singletonList("localhost:9200"); - } + public ExternalOpenSearchSink( + final String name, + final Settings settings, + final String settingPrefix, + final Path configPath, + AuditLogSink fallbackSink + ) throws Exception { + + super(name, settings, settingPrefix, fallbackSink); + Settings sinkSettings = settings.getAsSettings(settingPrefix); + servers = sinkSettings.getAsList(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_HTTP_ENDPOINTS); + if (servers == null || servers.size() == 0) { + log.error("No http endpoints configured for external OpenSearch endpoint '{}', falling back to localhost.", name); + servers = Collections.singletonList("localhost:9200"); + } - this.index = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, "'security-auditlog-'YYYY.MM.dd"); + this.index = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, "'security-auditlog-'YYYY.MM.dd"); - try { + try { this.indexPattern = DateTimeFormat.forPattern(index); } catch (IllegalArgumentException e) { - log.debug("Unable to parse index pattern due to {}. " - + "If you have no date pattern configured you can safely ignore this message", e.getMessage()); + log.debug( + "Unable to parse index pattern due to {}. " + "If you have no date pattern configured you can safely ignore this message", + e.getMessage() + ); } - this.type = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, null); - final boolean verifyHostnames = sinkSettings.getAsBoolean(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_VERIFY_HOSTNAMES, true); - final boolean enableSsl = sinkSettings.getAsBoolean(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, false); - final boolean enableSslClientAuth = sinkSettings.getAsBoolean(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL_CLIENT_AUTH , ConfigConstants.OPENDISTRO_SECURITY_AUDIT_SSL_ENABLE_SSL_CLIENT_AUTH_DEFAULT); - final String user = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME); - final String password = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PASSWORD); - - final HttpClientBuilder builder = HttpClient.builder(servers.toArray(new String[0])); - - if (enableSsl) { - - final boolean pem = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, null) != null - || sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_CONTENT, null) != null; - - KeyStore effectiveTruststore; - KeyStore effectiveKeystore; - char[] effectiveKeyPassword; - String effectiveKeyAlias; - - final boolean isDebugEnabled = log.isDebugEnabled(); - - if(pem) { - X509Certificate[] trustCertificates = PemKeyReader.loadCertificatesFromStream(PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_CONTENT, sinkSettings)); - - if(trustCertificates == null) { - String path = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH); - trustCertificates = PemKeyReader.loadCertificatesFromFile(PemKeyReader.resolve(path, ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, settings, configPath, true)); + this.type = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, null); + final boolean verifyHostnames = sinkSettings.getAsBoolean( + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_VERIFY_HOSTNAMES, + true + ); + final boolean enableSsl = sinkSettings.getAsBoolean(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, false); + final boolean enableSslClientAuth = sinkSettings.getAsBoolean( + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL_CLIENT_AUTH, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_SSL_ENABLE_SSL_CLIENT_AUTH_DEFAULT + ); + final String user = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME); + final String password = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PASSWORD); + + final HttpClientBuilder builder = HttpClient.builder(servers.toArray(new String[0])); + + if (enableSsl) { + + final boolean pem = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, null) != null + || sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_CONTENT, null) != null; + + KeyStore effectiveTruststore; + KeyStore effectiveKeystore; + char[] effectiveKeyPassword; + String effectiveKeyAlias; + + final boolean isDebugEnabled = log.isDebugEnabled(); + + if (pem) { + X509Certificate[] trustCertificates = PemKeyReader.loadCertificatesFromStream( + PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_CONTENT, sinkSettings) + ); + + if (trustCertificates == null) { + String path = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH); + trustCertificates = PemKeyReader.loadCertificatesFromFile( + PemKeyReader.resolve( + path, + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, + settings, + configPath, + true + ) + ); } - //for client authentication - X509Certificate[] authenticationCertificate = PemKeyReader.loadCertificatesFromStream(PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_CONTENT, sinkSettings)); - - if(authenticationCertificate == null) { - String path = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH); - authenticationCertificate = PemKeyReader.loadCertificatesFromFile(PemKeyReader.resolve(path, ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH, settings, configPath, enableSslClientAuth)); + // for client authentication + X509Certificate[] authenticationCertificate = PemKeyReader.loadCertificatesFromStream( + PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_CONTENT, sinkSettings) + ); + + if (authenticationCertificate == null) { + String path = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH); + authenticationCertificate = PemKeyReader.loadCertificatesFromFile( + PemKeyReader.resolve( + path, + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH, + settings, + configPath, + enableSslClientAuth + ) + ); } - PrivateKey authenticationKey = PemKeyReader.loadKeyFromStream(sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_PASSWORD), PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_CONTENT, sinkSettings)); - - if(authenticationKey == null) { - String path = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH); - authenticationKey = PemKeyReader.loadKeyFromFile(sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_PASSWORD), PemKeyReader.resolve(path, ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH, settings, configPath, enableSslClientAuth)); + PrivateKey authenticationKey = PemKeyReader.loadKeyFromStream( + sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_PASSWORD), + PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_CONTENT, sinkSettings) + ); + + if (authenticationKey == null) { + String path = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH); + authenticationKey = PemKeyReader.loadKeyFromFile( + sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_PASSWORD), + PemKeyReader.resolve( + path, + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH, + settings, + configPath, + enableSslClientAuth + ) + ); } effectiveKeyPassword = PemKeyReader.randomChars(12); effectiveKeyAlias = "al"; effectiveTruststore = PemKeyReader.toTruststore(effectiveKeyAlias, trustCertificates); - effectiveKeystore = PemKeyReader.toKeystore(effectiveKeyAlias, effectiveKeyPassword, authenticationCertificate, authenticationKey); + effectiveKeystore = PemKeyReader.toKeystore( + effectiveKeyAlias, + effectiveKeyPassword, + authenticationCertificate, + authenticationKey + ); if (isDebugEnabled) { - log.debug("Use PEM to secure communication with auditlog server (client auth is {})", authenticationKey!=null); + log.debug("Use PEM to secure communication with auditlog server (client auth is {})", authenticationKey != null); } } else { - final KeyStore trustStore = PemKeyReader.loadKeyStore(PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, configPath, true) - , SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings) - , settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); - - //for client authentication - final KeyStore keyStore = PemKeyReader.loadKeyStore(PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, settings, configPath, enableSslClientAuth) - , SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD) - , settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE)); - final String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD); - effectiveKeyPassword = keyStorePassword==null||keyStorePassword.isEmpty()?null:keyStorePassword.toCharArray(); + final KeyStore trustStore = PemKeyReader.loadKeyStore( + PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, configPath, true), + SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE) + ); + + // for client authentication + final KeyStore keyStore = PemKeyReader.loadKeyStore( + PemKeyReader.resolve( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + settings, + configPath, + enableSslClientAuth + ), + SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE) + ); + final String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting( + settings, + SSLConfigConstants.DEFAULT_STORE_PASSWORD + ); + effectiveKeyPassword = keyStorePassword == null || keyStorePassword.isEmpty() ? null : keyStorePassword.toCharArray(); effectiveKeyAlias = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_JKS_CERT_ALIAS, null); - if(enableSslClientAuth && effectiveKeyAlias == null) { - throw new IllegalArgumentException(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_JKS_CERT_ALIAS+" not given"); + if (enableSslClientAuth && effectiveKeyAlias == null) { + throw new IllegalArgumentException(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_JKS_CERT_ALIAS + " not given"); } effectiveTruststore = trustStore; effectiveKeystore = keyStore; if (isDebugEnabled) { - log.debug("Use Trust-/Keystore to secure communication with LDAP server (client auth is {})", keyStore!=null); - log.debug("keyStoreAlias: {}", effectiveKeyAlias); + log.debug("Use Trust-/Keystore to secure communication with LDAP server (client auth is {})", keyStore != null); + log.debug("keyStoreAlias: {}", effectiveKeyAlias); } } - final List enabledCipherSuites = sinkSettings.getAsList(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_CIPHERS, null); - final List enabledProtocols = sinkSettings.getAsList(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_PROTOCOLS, DEFAULT_TLS_PROTOCOLS); + final List enabledCipherSuites = sinkSettings.getAsList( + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_CIPHERS, + null + ); + final List enabledProtocols = sinkSettings.getAsList( + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_PROTOCOLS, + DEFAULT_TLS_PROTOCOLS + ); - builder.setSupportedCipherSuites(enabledCipherSuites==null?null:enabledCipherSuites.toArray(new String[0])); + builder.setSupportedCipherSuites(enabledCipherSuites == null ? null : enabledCipherSuites.toArray(new String[0])); builder.setSupportedProtocols(enabledProtocols.toArray(new String[0])); - builder.enableSsl(effectiveTruststore, verifyHostnames); //trust all aliases + builder.enableSsl(effectiveTruststore, verifyHostnames); // trust all aliases if (enableSslClientAuth) { builder.setPkiCredentials(effectiveKeystore, effectiveKeyPassword, effectiveKeyAlias); } - } - - if (user != null && password != null) { - builder.setBasicCredentials(user, password); - } - - client = builder.build(); - } - - @Override - public void close() throws IOException { - if (client != null) { - client.close(); - } - } - - public boolean doStore(final AuditMessage msg) { - try { - boolean successful = client.index(msg.toString(), getExpandedIndexName(indexPattern, index), type, true); - if (!successful) { - log.error("Unable to send audit log {} to one of these servers: {}", msg, servers); - } - return successful; - } catch (Exception e) { - log.error("Unable to send audit log {} due to", msg, e); - return false; - } - } + } + + if (user != null && password != null) { + builder.setBasicCredentials(user, password); + } + + client = builder.build(); + } + + @Override + public void close() throws IOException { + if (client != null) { + client.close(); + } + } + + public boolean doStore(final AuditMessage msg) { + try { + boolean successful = client.index(msg.toString(), getExpandedIndexName(indexPattern, index), type, true); + if (!successful) { + log.error("Unable to send audit log {} to one of these servers: {}", msg, servers); + } + return successful; + } catch (Exception e) { + log.error("Unable to send audit log {} due to", msg, e); + return false; + } + } } diff --git a/src/main/java/org/opensearch/security/auditlog/sink/InternalOpenSearchSink.java b/src/main/java/org/opensearch/security/auditlog/sink/InternalOpenSearchSink.java index 9b7be332b6..4a4af8d1fe 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/InternalOpenSearchSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/InternalOpenSearchSink.java @@ -30,53 +30,68 @@ public final class InternalOpenSearchSink extends AuditLogSink { - private final Client clientProvider; - final String index; - final String type; - private DateTimeFormatter indexPattern; - private final ThreadPool threadPool; + private final Client clientProvider; + final String index; + final String type; + private DateTimeFormatter indexPattern; + private final ThreadPool threadPool; - public InternalOpenSearchSink(final String name, final Settings settings, final String settingsPrefix, final Path configPath, final Client clientProvider, ThreadPool threadPool, AuditLogSink fallbackSink) { - super(name, settings, settingsPrefix, fallbackSink); - this.clientProvider = clientProvider; - Settings sinkSettings = getSinkSettings(settingsPrefix); + public InternalOpenSearchSink( + final String name, + final Settings settings, + final String settingsPrefix, + final Path configPath, + final Client clientProvider, + ThreadPool threadPool, + AuditLogSink fallbackSink + ) { + super(name, settings, settingsPrefix, fallbackSink); + this.clientProvider = clientProvider; + Settings sinkSettings = getSinkSettings(settingsPrefix); - this.index = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, "'security-auditlog-'YYYY.MM.dd"); - this.type = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, null); + this.index = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, "'security-auditlog-'YYYY.MM.dd"); + this.type = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, null); - this.threadPool = threadPool; - try { - this.indexPattern = DateTimeFormat.forPattern(index); - } catch (IllegalArgumentException e) { - log.debug("Unable to parse index pattern due to {}. " + "If you have no date pattern configured you can safely ignore this message", e.getMessage()); - } - } + this.threadPool = threadPool; + try { + this.indexPattern = DateTimeFormat.forPattern(index); + } catch (IllegalArgumentException e) { + log.debug( + "Unable to parse index pattern due to {}. " + "If you have no date pattern configured you can safely ignore this message", + e.getMessage() + ); + } + } - @Override - public void close() throws IOException { + @Override + public void close() throws IOException { - } + } - public boolean doStore(final AuditMessage msg) { + public boolean doStore(final AuditMessage msg) { - if (Boolean.parseBoolean((String) HeaderHelper.getSafeFromHeader(threadPool.getThreadContext(), ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER))) { - if (log.isTraceEnabled()) { - log.trace("audit log of audit log will not be executed"); - } - return true; - } + if (Boolean.parseBoolean( + (String) HeaderHelper.getSafeFromHeader(threadPool.getThreadContext(), ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER) + )) { + if (log.isTraceEnabled()) { + log.trace("audit log of audit log will not be executed"); + } + return true; + } - try (StoredContext ctx = threadPool.getThreadContext().stashContext()) { - try { - final IndexRequestBuilder irb = clientProvider.prepareIndex(getExpandedIndexName(indexPattern, index)).setRefreshPolicy(RefreshPolicy.IMMEDIATE).setSource(msg.getAsMap()); - threadPool.getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); - irb.setTimeout(TimeValue.timeValueMinutes(1)); - irb.execute().actionGet(); - return true; - } catch (final Exception e) { - log.error("Unable to index audit log {} due to", msg, e); - return false; - } - } - } + try (StoredContext ctx = threadPool.getThreadContext().stashContext()) { + try { + final IndexRequestBuilder irb = clientProvider.prepareIndex(getExpandedIndexName(indexPattern, index)) + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .setSource(msg.getAsMap()); + threadPool.getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); + irb.setTimeout(TimeValue.timeValueMinutes(1)); + irb.execute().actionGet(); + return true; + } catch (final Exception e) { + log.error("Unable to index audit log {} due to", msg, e); + return false; + } + } + } } diff --git a/src/main/java/org/opensearch/security/auditlog/sink/KafkaSink.java b/src/main/java/org/opensearch/security/auditlog/sink/KafkaSink.java index a94a17ff03..e67ed66549 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/KafkaSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/KafkaSink.java @@ -32,41 +32,41 @@ public class KafkaSink extends AuditLogSink { - private final String[] mandatoryProperties = new String []{"bootstrap_servers","topic_name"}; - private boolean valid = true; - private Producer producer; - private String topicName; + private final String[] mandatoryProperties = new String[] { "bootstrap_servers", "topic_name" }; + private boolean valid = true; + private Producer producer; + private String topicName; @SuppressWarnings("removal") - public KafkaSink(final String name, final Settings settings, final String settingsPrefix, AuditLogSink fallbackSink) { - super(name, settings, settingsPrefix, fallbackSink); + public KafkaSink(final String name, final Settings settings, final String settingsPrefix, AuditLogSink fallbackSink) { + super(name, settings, settingsPrefix, fallbackSink); - Settings sinkSettings = settings.getAsSettings(settingsPrefix); - checkMandatorySinkSettings(sinkSettings); + Settings sinkSettings = settings.getAsSettings(settingsPrefix); + checkMandatorySinkSettings(sinkSettings); - if (!valid) { - log.error("Failed to configure Kafka producer, please check the logfile."); - return; - } + if (!valid) { + log.error("Failed to configure Kafka producer, please check the logfile."); + return; + } final Properties producerProps = new Properties(); - for(String key: sinkSettings.names()) { - if(!key.equals("topic_name")) { + for (String key : sinkSettings.names()) { + if (!key.equals("topic_name")) { producerProps.put(key.replace('_', '.'), sinkSettings.get(key)); } } - producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, LongSerializer.class.getName()); - producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); - topicName = sinkSettings.get("topic_name"); + producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, LongSerializer.class.getName()); + producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); + topicName = sinkSettings.get("topic_name"); - //map path of - //ssl.keystore.location - //ssl.truststore.location - //sasl.kerberos.kinit.cmd + // map path of + // ssl.keystore.location + // ssl.truststore.location + // sasl.kerberos.kinit.cmd - final SecurityManager sm = System.getSecurityManager(); + final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new SpecialPermission()); @@ -84,49 +84,49 @@ public KafkaProducer run() throws Exception { this.valid = false; } - } + } - @Override - protected boolean doStore(AuditMessage msg) { - if (!valid || producer == null) { - return false; - } + @Override + protected boolean doStore(AuditMessage msg) { + if (!valid || producer == null) { + return false; + } - ProducerRecord data = new ProducerRecord(topicName, msg.toJson()); - producer.send(data, new Callback() { + ProducerRecord data = new ProducerRecord(topicName, msg.toJson()); + producer.send(data, new Callback() { @Override public void onCompletion(RecordMetadata metadata, Exception exception) { - if(exception == null) { - //log trace? - } else { - log.error("Could not store message on Kafka topic {}", topicName, exception); - fallbackSink.store(msg); - } + if (exception == null) { + // log trace? + } else { + log.error("Could not store message on Kafka topic {}", topicName, exception); + fallbackSink.store(msg); + } } }); - return true; - } - - @Override - public boolean isHandlingBackpressure() { - return true; - } - - private void checkMandatorySinkSettings(Settings sinkSettings) { - for(String mandatory: mandatoryProperties) { - String value = sinkSettings.get(mandatory); - if (value == null || value.length() == 0) { - log.error("No value for {} provided in configuration, this endpoint will not work.", value); - this.valid = false; - } - } - } + return true; + } + + @Override + public boolean isHandlingBackpressure() { + return true; + } + + private void checkMandatorySinkSettings(Settings sinkSettings) { + for (String mandatory : mandatoryProperties) { + String value = sinkSettings.get(mandatory); + if (value == null || value.length() == 0) { + log.error("No value for {} provided in configuration, this endpoint will not work.", value); + this.valid = false; + } + } + } @Override public void close() throws IOException { - if(producer != null) { + if (producer != null) { valid = false; producer.close(); } diff --git a/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java b/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java index 8794b90f41..f01043fa21 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java @@ -27,18 +27,18 @@ public final class Log4JSink extends AuditLogSink { public Log4JSink(final String name, final Settings settings, final String settingsPrefix, AuditLogSink fallbackSink) { super(name, settings, settingsPrefix, fallbackSink); - loggerName = settings.get( settingsPrefix + ".log4j.logger_name","sgaudit"); + loggerName = settings.get(settingsPrefix + ".log4j.logger_name", "sgaudit"); auditLogger = LogManager.getLogger(loggerName); - logLevel = Level.toLevel(settings.get(settingsPrefix + ".log4j.level","INFO").toUpperCase()); + logLevel = Level.toLevel(settings.get(settingsPrefix + ".log4j.level", "INFO").toUpperCase()); enabled = auditLogger.isEnabled(logLevel); } public boolean isHandlingBackpressure() { - return !enabled; //no submit to thread pool if not enabled + return !enabled; // no submit to thread pool if not enabled } public boolean doStore(final AuditMessage msg) { - if(enabled) { + if (enabled) { auditLogger.log(logLevel, msg.toJson()); } return true; diff --git a/src/main/java/org/opensearch/security/auditlog/sink/NoopSink.java b/src/main/java/org/opensearch/security/auditlog/sink/NoopSink.java index 740e7a4459..2981fa995d 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/NoopSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/NoopSink.java @@ -27,7 +27,7 @@ public boolean isHandlingBackpressure() { @Override public boolean doStore(final AuditMessage msg) { - //do nothing + // do nothing return true; } diff --git a/src/main/java/org/opensearch/security/auditlog/sink/SinkProvider.java b/src/main/java/org/opensearch/security/auditlog/sink/SinkProvider.java index 270d106d2d..894c9162dd 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/SinkProvider.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/SinkProvider.java @@ -27,147 +27,171 @@ public class SinkProvider { - protected final Logger log = LogManager.getLogger(this.getClass()); - private static final String FALLBACKSINK_NAME = "fallback"; - private static final String DEFAULTSINK_NAME = "default"; - private final Client clientProvider; - private final ThreadPool threadPool; - private final Path configPath; - private final Settings settings; - final Map allSinks = new HashMap<>(); - AuditLogSink defaultSink; - AuditLogSink fallbackSink; - - public SinkProvider(final Settings settings, final Client clientProvider, ThreadPool threadPool, final Path configPath) { - this.settings = settings; - this.clientProvider = clientProvider; - this.threadPool = threadPool; - this.configPath = configPath; - - // fall back sink, make sure we don't lose messages - String fallbackConfigPrefix = ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + "." + FALLBACKSINK_NAME; - Settings fallbackSinkSettings = settings.getAsSettings(fallbackConfigPrefix); - if(!fallbackSinkSettings.isEmpty()) { - this.fallbackSink = createSink(FALLBACKSINK_NAME, fallbackSinkSettings.get("type"), settings, fallbackConfigPrefix+".config"); - } - - // make sure we always have a fallback to write to - if (this.fallbackSink == null) { - this.fallbackSink = new DebugSink(FALLBACKSINK_NAME, settings, null); - } - - allSinks.put(FALLBACKSINK_NAME, this.fallbackSink); - - // create default sink - defaultSink = this.createSink(DEFAULTSINK_NAME, settings.get(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT), settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT); - if (defaultSink == null) { - log.error("Default endpoint could not be created, auditlog will not work properly."); - return; - } - - allSinks.put(DEFAULTSINK_NAME, defaultSink); - - // create all other sinks - Map sinkSettingsMap = Utils.convertJsonToxToStructuredMap(settings.getAsSettings(ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS)); - - for (Entry sinkEntry : sinkSettingsMap.entrySet()) { - String sinkName = sinkEntry.getKey(); - // do not create fallback twice - if(sinkName.equalsIgnoreCase(FALLBACKSINK_NAME)) { - continue; - } - String type = settings.getAsSettings(ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + "." + sinkName).get("type"); - if (type == null) { - log.error("No type defined for endpoint {}.", sinkName); - continue; - } - AuditLogSink sink = createSink(sinkName, type, this.settings, ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + "." + sinkName + ".config"); - if (sink == null) { - log.error("Endpoint '{}' could not be created, check log file for further information.", sinkName); - continue; - } - allSinks.put(sinkName.toLowerCase(), sink); - if (log.isDebugEnabled()) { - log.debug("sink '{}' created successfully.", sinkName); - } - } - } - - public AuditLogSink getSink(String sinkName) { - return allSinks.get(sinkName.toLowerCase()); - } - - public AuditLogSink getDefaultSink() { - return defaultSink; - } - - public void close() { - for (AuditLogSink sink : allSinks.values()) { - close(sink); - } - } - - protected void close(AuditLogSink sink) { - try { - log.info("Closing {}", sink.getClass().getSimpleName()); - sink.close(); - } catch (Exception ex) { - log.info("Could not close sink '{}' due to '{}'", sink.getClass().getSimpleName(), ex.getMessage()); - } - } - - private final AuditLogSink createSink(final String name, final String type, final Settings settings, final String settingsPrefix) { - AuditLogSink sink = null; - if (type != null) { - switch (type.toLowerCase()) { - case "internal_opensearch": - sink = new InternalOpenSearchSink(name, settings, settingsPrefix, configPath, clientProvider, threadPool, fallbackSink); - break; - case "external_opensearch": - try { - sink = new ExternalOpenSearchSink(name, settings, settingsPrefix, configPath, fallbackSink); - } catch (Exception e) { - log.error("Audit logging unavailable: Unable to setup HttpOpenSearchAuditLog due to", e); - } - break; - case "webhook": - try { - sink = new WebhookSink(name, settings, settingsPrefix, configPath, fallbackSink); - } catch (Exception e1) { - log.error("Audit logging unavailable: Unable to setup WebhookAuditLog due to", e1); - } - break; - case "debug": - sink = new DebugSink(name, settings, fallbackSink); - break; - case "noop": - sink = new NoopSink(name, settings, fallbackSink); - break; - case "log4j": - sink = new Log4JSink(name, settings, settingsPrefix, fallbackSink); - break; - case "kafka": - sink = new KafkaSink(name, settings, settingsPrefix, fallbackSink); - break; - default: - try { - Class delegateClass = Class.forName(type); - if (AuditLogSink.class.isAssignableFrom(delegateClass)) { - try { - sink = (AuditLogSink) delegateClass.getConstructor(String.class, Settings.class, String.class, Path.class, Client.class, ThreadPool.class, AuditLogSink.class).newInstance(name, settings, settingsPrefix, configPath, - clientProvider, threadPool, fallbackSink); - } catch (Throwable e) { - sink = (AuditLogSink) delegateClass.getConstructor(String.class, Settings.class, String.class, AuditLogSink.class).newInstance(name, settings, settingsPrefix, fallbackSink); - } - } else { - log.error("Audit logging unavailable: '{}' is not a subclass of {}", type, AuditLogSink.class.getSimpleName()); - } - } catch (Throwable e) { // we need really catch a Throwable here! - log.error("Audit logging unavailable: Cannot instantiate object of class {} due to ", type, e); - } - } - } - return sink; - } + protected final Logger log = LogManager.getLogger(this.getClass()); + private static final String FALLBACKSINK_NAME = "fallback"; + private static final String DEFAULTSINK_NAME = "default"; + private final Client clientProvider; + private final ThreadPool threadPool; + private final Path configPath; + private final Settings settings; + final Map allSinks = new HashMap<>(); + AuditLogSink defaultSink; + AuditLogSink fallbackSink; + + public SinkProvider(final Settings settings, final Client clientProvider, ThreadPool threadPool, final Path configPath) { + this.settings = settings; + this.clientProvider = clientProvider; + this.threadPool = threadPool; + this.configPath = configPath; + + // fall back sink, make sure we don't lose messages + String fallbackConfigPrefix = ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + "." + FALLBACKSINK_NAME; + Settings fallbackSinkSettings = settings.getAsSettings(fallbackConfigPrefix); + if (!fallbackSinkSettings.isEmpty()) { + this.fallbackSink = createSink(FALLBACKSINK_NAME, fallbackSinkSettings.get("type"), settings, fallbackConfigPrefix + ".config"); + } + + // make sure we always have a fallback to write to + if (this.fallbackSink == null) { + this.fallbackSink = new DebugSink(FALLBACKSINK_NAME, settings, null); + } + + allSinks.put(FALLBACKSINK_NAME, this.fallbackSink); + + // create default sink + defaultSink = this.createSink( + DEFAULTSINK_NAME, + settings.get(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT), + settings, + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT + ); + if (defaultSink == null) { + log.error("Default endpoint could not be created, auditlog will not work properly."); + return; + } + + allSinks.put(DEFAULTSINK_NAME, defaultSink); + + // create all other sinks + Map sinkSettingsMap = Utils.convertJsonToxToStructuredMap( + settings.getAsSettings(ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS) + ); + + for (Entry sinkEntry : sinkSettingsMap.entrySet()) { + String sinkName = sinkEntry.getKey(); + // do not create fallback twice + if (sinkName.equalsIgnoreCase(FALLBACKSINK_NAME)) { + continue; + } + String type = settings.getAsSettings(ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + "." + sinkName).get("type"); + if (type == null) { + log.error("No type defined for endpoint {}.", sinkName); + continue; + } + AuditLogSink sink = createSink( + sinkName, + type, + this.settings, + ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + "." + sinkName + ".config" + ); + if (sink == null) { + log.error("Endpoint '{}' could not be created, check log file for further information.", sinkName); + continue; + } + allSinks.put(sinkName.toLowerCase(), sink); + if (log.isDebugEnabled()) { + log.debug("sink '{}' created successfully.", sinkName); + } + } + } + + public AuditLogSink getSink(String sinkName) { + return allSinks.get(sinkName.toLowerCase()); + } + + public AuditLogSink getDefaultSink() { + return defaultSink; + } + + public void close() { + for (AuditLogSink sink : allSinks.values()) { + close(sink); + } + } + + protected void close(AuditLogSink sink) { + try { + log.info("Closing {}", sink.getClass().getSimpleName()); + sink.close(); + } catch (Exception ex) { + log.info("Could not close sink '{}' due to '{}'", sink.getClass().getSimpleName(), ex.getMessage()); + } + } + + private final AuditLogSink createSink(final String name, final String type, final Settings settings, final String settingsPrefix) { + AuditLogSink sink = null; + if (type != null) { + switch (type.toLowerCase()) { + case "internal_opensearch": + sink = new InternalOpenSearchSink(name, settings, settingsPrefix, configPath, clientProvider, threadPool, fallbackSink); + break; + case "external_opensearch": + try { + sink = new ExternalOpenSearchSink(name, settings, settingsPrefix, configPath, fallbackSink); + } catch (Exception e) { + log.error("Audit logging unavailable: Unable to setup HttpOpenSearchAuditLog due to", e); + } + break; + case "webhook": + try { + sink = new WebhookSink(name, settings, settingsPrefix, configPath, fallbackSink); + } catch (Exception e1) { + log.error("Audit logging unavailable: Unable to setup WebhookAuditLog due to", e1); + } + break; + case "debug": + sink = new DebugSink(name, settings, fallbackSink); + break; + case "noop": + sink = new NoopSink(name, settings, fallbackSink); + break; + case "log4j": + sink = new Log4JSink(name, settings, settingsPrefix, fallbackSink); + break; + case "kafka": + sink = new KafkaSink(name, settings, settingsPrefix, fallbackSink); + break; + default: + try { + Class delegateClass = Class.forName(type); + if (AuditLogSink.class.isAssignableFrom(delegateClass)) { + try { + sink = (AuditLogSink) delegateClass.getConstructor( + String.class, + Settings.class, + String.class, + Path.class, + Client.class, + ThreadPool.class, + AuditLogSink.class + ).newInstance(name, settings, settingsPrefix, configPath, clientProvider, threadPool, fallbackSink); + } catch (Throwable e) { + sink = (AuditLogSink) delegateClass.getConstructor( + String.class, + Settings.class, + String.class, + AuditLogSink.class + ).newInstance(name, settings, settingsPrefix, fallbackSink); + } + } else { + log.error("Audit logging unavailable: '{}' is not a subclass of {}", type, AuditLogSink.class.getSimpleName()); + } + } catch (Throwable e) { // we need really catch a Throwable here! + log.error("Audit logging unavailable: Cannot instantiate object of class {} due to ", type, e); + } + } + } + return sink; + } } diff --git a/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java b/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java index 083df01bbd..78780fb8e1 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java @@ -54,301 +54,320 @@ public class WebhookSink extends AuditLogSink { - /* HttpClient is thread safe */ - private final CloseableHttpClient httpClient; - - String webhookUrl = null; - WebhookFormat webhookFormat = null; - final boolean verifySSL; - final KeyStore effectiveTruststore; - - public WebhookSink(final String name, final Settings settings, final String settingsPrefix, final Path configPath, AuditLogSink fallbackSink) throws Exception { - super(name, settings, settingsPrefix, fallbackSink); - - Settings sinkSettings = settings.getAsSettings(settingsPrefix); - - this.effectiveTruststore = getEffectiveKeyStore(configPath); - - final String webhookUrl = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_URL); - final String format = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_FORMAT); - - verifySSL = sinkSettings.getAsBoolean(ConfigConstants.SECURITY_AUDIT_WEBHOOK_SSL_VERIFY, true); - httpClient = getHttpClient(); - - if(httpClient == null) { - log.error("Could not create HttpClient, audit log not available."); - return; - } - - if (Strings.isEmpty(webhookUrl)) { - log.error("plugins.security.audit.config.webhook.url not provided, webhook audit log will not work"); - return; - } else { - try { - // Sanity - check URL validity - new URL(webhookUrl); - this.webhookUrl = webhookUrl; - } catch (MalformedURLException ex) { - log.error("URL {} is invalid, webhook audit log will not work.", webhookUrl, ex); - } - } - - if (Strings.isEmpty(format)) { - log.warn("plugins.security.audit.config.webhook.format not provided, falling back to 'text'"); - webhookFormat = WebhookFormat.TEXT; - } else { - try { - webhookFormat = WebhookFormat.valueOf(format.toUpperCase()); - } catch (Exception ex) { - log.error("Could not find WebhookFormat for type {}, falling back to 'text'", format, ex); - webhookFormat = WebhookFormat.TEXT; - } - } - } - - @Override + /* HttpClient is thread safe */ + private final CloseableHttpClient httpClient; + + String webhookUrl = null; + WebhookFormat webhookFormat = null; + final boolean verifySSL; + final KeyStore effectiveTruststore; + + public WebhookSink( + final String name, + final Settings settings, + final String settingsPrefix, + final Path configPath, + AuditLogSink fallbackSink + ) throws Exception { + super(name, settings, settingsPrefix, fallbackSink); + + Settings sinkSettings = settings.getAsSettings(settingsPrefix); + + this.effectiveTruststore = getEffectiveKeyStore(configPath); + + final String webhookUrl = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_URL); + final String format = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_FORMAT); + + verifySSL = sinkSettings.getAsBoolean(ConfigConstants.SECURITY_AUDIT_WEBHOOK_SSL_VERIFY, true); + httpClient = getHttpClient(); + + if (httpClient == null) { + log.error("Could not create HttpClient, audit log not available."); + return; + } + + if (Strings.isEmpty(webhookUrl)) { + log.error("plugins.security.audit.config.webhook.url not provided, webhook audit log will not work"); + return; + } else { + try { + // Sanity - check URL validity + new URL(webhookUrl); + this.webhookUrl = webhookUrl; + } catch (MalformedURLException ex) { + log.error("URL {} is invalid, webhook audit log will not work.", webhookUrl, ex); + } + } + + if (Strings.isEmpty(format)) { + log.warn("plugins.security.audit.config.webhook.format not provided, falling back to 'text'"); + webhookFormat = WebhookFormat.TEXT; + } else { + try { + webhookFormat = WebhookFormat.valueOf(format.toUpperCase()); + } catch (Exception ex) { + log.error("Could not find WebhookFormat for type {}, falling back to 'text'", format, ex); + webhookFormat = WebhookFormat.TEXT; + } + } + } + + @Override @SuppressWarnings("removal") - public boolean doStore(AuditMessage msg) { - if (Strings.isEmpty(webhookUrl)) { - log.debug("Webhook URL is null"); - return false; - } - if (msg == null) { - log.debug("Message is null"); - return true; - } - - return AccessController.doPrivileged(new PrivilegedAction() { - - @Override - public Boolean run() { - boolean success = false; - try { - switch (webhookFormat.method) { - case POST: - success = post(msg); - break; - case GET: - success =get(msg); - break; - default: - log.error("Http Method '{}' defined in WebhookFormat '{}' not implemented yet", webhookFormat.method.name(), - webhookFormat.name()); - } - // log something in case endpoint is not reachable or did not return 200 - if (!success) { - log.error(msg.toString()); - } - return success; - } catch(Throwable t) { - log.error("Uncaught exception while trying to log message.", t); - log.error(msg.toString()); - return false; - } - } - }); - } + public boolean doStore(AuditMessage msg) { + if (Strings.isEmpty(webhookUrl)) { + log.debug("Webhook URL is null"); + return false; + } + if (msg == null) { + log.debug("Message is null"); + return true; + } + + return AccessController.doPrivileged(new PrivilegedAction() { + + @Override + public Boolean run() { + boolean success = false; + try { + switch (webhookFormat.method) { + case POST: + success = post(msg); + break; + case GET: + success = get(msg); + break; + default: + log.error( + "Http Method '{}' defined in WebhookFormat '{}' not implemented yet", + webhookFormat.method.name(), + webhookFormat.name() + ); + } + // log something in case endpoint is not reachable or did not return 200 + if (!success) { + log.error(msg.toString()); + } + return success; + } catch (Throwable t) { + log.error("Uncaught exception while trying to log message.", t); + log.error(msg.toString()); + return false; + } + } + }); + } @Override public void close() throws IOException { - if(httpClient != null) { - httpClient.close(); + if (httpClient != null) { + httpClient.close(); } } + /** + * Transforms an {@link AuditMessage} to JSON. By default, all fields are + * included in the JSON string. This method can be overridden by subclasses + * if a specific JSON format is needed. + * + * @param msg the AuditMessage to transform + * @return the JSON string + */ + protected String formatJson(final AuditMessage msg) { + return msg.toJson(); + } - /** - * Transforms an {@link AuditMessage} to JSON. By default, all fields are - * included in the JSON string. This method can be overridden by subclasses - * if a specific JSON format is needed. - * - * @param msg the AuditMessage to transform - * @return the JSON string - */ - protected String formatJson(final AuditMessage msg) { - return msg.toJson(); - } - - /** - * Transforms an {@link AuditMessage} to plain text. This method can be overridden - * by subclasses if a specific text format is needed. - * - * @param msg the AuditMessage to transform - * @return the text string - */ - protected String formatText(AuditMessage msg) { - return msg.toText(); - } - - /** - * Transforms an {@link AuditMessage} to Slack format. - * The default implementation returns - *

-	 * {
-	 *   "text": ""
-	 * }
-	 * 
- *

- * Can be overridden by subclasses if a more specific format is needed. - * - * @param msg the AuditMessage to transform - * @return the Slack formatted JSON string - */ - protected String formatSlack(AuditMessage msg) { - return "{\"text\": \"" + msg.toText() + "\"}"; - } - - /** - * Transforms an {@link AuditMessage} to a query parameter String. - * Used by {@link WebhookFormat#URL_PARAMETER_GET} and - * Used by {@link WebhookFormat#URL_PARAMETER_POST}. Can be overridden by - * subclasses if a specific format is needed. - * - * @param msg the AuditMessage to transform - * @return the query parameter string - */ - protected String formatUrlParameters(AuditMessage msg) { - return msg.toUrlParameters(); - } - - boolean get(AuditMessage msg) { - switch (webhookFormat) { - case URL_PARAMETER_GET: - return doGet(webhookUrl + formatUrlParameters(msg)); - default: - log.error("WebhookFormat '{}' not implemented yet", webhookFormat.name()); - return false; - } - } - - protected boolean doGet(String url) { - HttpGet httpGet = new HttpGet(url); - CloseableHttpResponse serverResponse = null; - try { - serverResponse = httpClient.execute(httpGet); - int responseCode = serverResponse.getCode(); - if (responseCode != HttpStatus.SC_OK) { - log.error("Cannot GET to webhook URL '{}', server returned status {}", webhookUrl, responseCode); - return false; - } - return true; - } catch (Throwable e) { - log.error("Cannot GET to webhook URL '{}'", webhookUrl, e); - return false; - } finally { - try { - if (serverResponse != null) { - serverResponse.close(); - } - } catch (IOException e) { - log.error("Cannot close server response", e); - } - } - } - - boolean post(AuditMessage msg) { - - String payload; - String url = webhookUrl; - - switch (webhookFormat) { - case JSON: - payload = formatJson(msg); - break; - case TEXT: - payload = formatText(msg); - break; - case SLACK: - payload = "{\"text\": \"" + msg.toText() + "\"}"; - break; - case URL_PARAMETER_POST: - payload = ""; - url = webhookUrl + formatUrlParameters(msg); - break; - default: - log.error("WebhookFormat '{}' not implemented yet", webhookFormat.name()); - return false; - } - - return doPost(url, payload); - - } - - protected boolean doPost(String url, String payload) { - - HttpPost postRequest = new HttpPost(url); - - StringEntity input = new StringEntity(payload, webhookFormat.contentType.withCharset(StandardCharsets.UTF_8)); - postRequest.setEntity(input); - - CloseableHttpResponse serverResponse = null; - try { - serverResponse = httpClient.execute(postRequest); - int responseCode = serverResponse.getCode(); - if (responseCode != HttpStatus.SC_OK) { - log.error("Cannot POST to webhook URL '{}', server returned status {}", webhookUrl, responseCode); - return false; - } - return true; - } catch (Throwable e) { - log.error("Cannot POST to webhook URL '{}' due to '{}'", webhookUrl, e.getMessage(), e); - return false; - } finally { - try { - if (serverResponse != null) { - serverResponse.close(); - } - } catch (IOException e) { - log.error("Cannot close server response", e); - } - } - } + /** + * Transforms an {@link AuditMessage} to plain text. This method can be overridden + * by subclasses if a specific text format is needed. + * + * @param msg the AuditMessage to transform + * @return the text string + */ + protected String formatText(AuditMessage msg) { + return msg.toText(); + } - @SuppressWarnings("removal") - private KeyStore getEffectiveKeyStore(final Path configPath) { + /** + * Transforms an {@link AuditMessage} to Slack format. + * The default implementation returns + *

+     * {
+     *   "text": ""
+     * }
+     * 
+ *

+ * Can be overridden by subclasses if a more specific format is needed. + * + * @param msg the AuditMessage to transform + * @return the Slack formatted JSON string + */ + protected String formatSlack(AuditMessage msg) { + return "{\"text\": \"" + msg.toText() + "\"}"; + } - return AccessController.doPrivileged(new PrivilegedAction() { + /** + * Transforms an {@link AuditMessage} to a query parameter String. + * Used by {@link WebhookFormat#URL_PARAMETER_GET} and + * Used by {@link WebhookFormat#URL_PARAMETER_POST}. Can be overridden by + * subclasses if a specific format is needed. + * + * @param msg the AuditMessage to transform + * @return the query parameter string + */ + protected String formatUrlParameters(AuditMessage msg) { + return msg.toUrlParameters(); + } - @Override - public KeyStore run() { - try { - Settings sinkSettings = settings.getAsSettings(settingsPrefix); + boolean get(AuditMessage msg) { + switch (webhookFormat) { + case URL_PARAMETER_GET: + return doGet(webhookUrl + formatUrlParameters(msg)); + default: + log.error("WebhookFormat '{}' not implemented yet", webhookFormat.name()); + return false; + } + } - final boolean pem = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH, null) != null - || sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, null) != null; + protected boolean doGet(String url) { + HttpGet httpGet = new HttpGet(url); + CloseableHttpResponse serverResponse = null; + try { + serverResponse = httpClient.execute(httpGet); + int responseCode = serverResponse.getCode(); + if (responseCode != HttpStatus.SC_OK) { + log.error("Cannot GET to webhook URL '{}', server returned status {}", webhookUrl, responseCode); + return false; + } + return true; + } catch (Throwable e) { + log.error("Cannot GET to webhook URL '{}'", webhookUrl, e); + return false; + } finally { + try { + if (serverResponse != null) { + serverResponse.close(); + } + } catch (IOException e) { + log.error("Cannot close server response", e); + } + } + } - if(pem) { - X509Certificate[] trustCertificates = PemKeyReader.loadCertificatesFromStream(PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, sinkSettings)); + boolean post(AuditMessage msg) { + + String payload; + String url = webhookUrl; + + switch (webhookFormat) { + case JSON: + payload = formatJson(msg); + break; + case TEXT: + payload = formatText(msg); + break; + case SLACK: + payload = "{\"text\": \"" + msg.toText() + "\"}"; + break; + case URL_PARAMETER_POST: + payload = ""; + url = webhookUrl + formatUrlParameters(msg); + break; + default: + log.error("WebhookFormat '{}' not implemented yet", webhookFormat.name()); + return false; + } + + return doPost(url, payload); - if(trustCertificates == null) { - String fullPath = settingsPrefix + "." + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH; - trustCertificates = PemKeyReader.loadCertificatesFromFile(PemKeyReader.resolve(fullPath, settings, configPath, false)); - } + } - return PemKeyReader.toTruststore("alw", trustCertificates); + protected boolean doPost(String url, String payload) { + HttpPost postRequest = new HttpPost(url); - } else { - return PemKeyReader.loadKeyStore(PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, configPath, false) - , SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings) - , settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); - } - } catch(Exception ex) { - log.error("Could not load key material. Make sure your certificates are located relative to the config directory", ex); - return null; - } - } - }); - } + StringEntity input = new StringEntity(payload, webhookFormat.contentType.withCharset(StandardCharsets.UTF_8)); + postRequest.setEntity(input); - CloseableHttpClient getHttpClient() { + CloseableHttpResponse serverResponse = null; + try { + serverResponse = httpClient.execute(postRequest); + int responseCode = serverResponse.getCode(); + if (responseCode != HttpStatus.SC_OK) { + log.error("Cannot POST to webhook URL '{}', server returned status {}", webhookUrl, responseCode); + return false; + } + return true; + } catch (Throwable e) { + log.error("Cannot POST to webhook URL '{}' due to '{}'", webhookUrl, e.getMessage(), e); + return false; + } finally { + try { + if (serverResponse != null) { + serverResponse.close(); + } + } catch (IOException e) { + log.error("Cannot close server response", e); + } + } + } + + @SuppressWarnings("removal") + private KeyStore getEffectiveKeyStore(final Path configPath) { + + return AccessController.doPrivileged(new PrivilegedAction() { + + @Override + public KeyStore run() { + try { + Settings sinkSettings = settings.getAsSettings(settingsPrefix); + + final boolean pem = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH, null) != null + || sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, null) != null; + + if (pem) { + X509Certificate[] trustCertificates = PemKeyReader.loadCertificatesFromStream( + PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, sinkSettings) + ); + + if (trustCertificates == null) { + String fullPath = settingsPrefix + "." + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH; + trustCertificates = PemKeyReader.loadCertificatesFromFile( + PemKeyReader.resolve(fullPath, settings, configPath, false) + ); + } + + return PemKeyReader.toTruststore("alw", trustCertificates); + + } else { + return PemKeyReader.loadKeyStore( + PemKeyReader.resolve( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + settings, + configPath, + false + ), + SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE) + ); + } + } catch (Exception ex) { + log.error("Could not load key material. Make sure your certificates are located relative to the config directory", ex); + return null; + } + } + }); + } + + CloseableHttpClient getHttpClient() { // TODO: set a timeout until we have a proper way to deal with back pressure int timeout = 5; RequestConfig config = RequestConfig.custom() - .setConnectTimeout(timeout, TimeUnit.SECONDS) - .setConnectionRequestTimeout(timeout, TimeUnit.SECONDS).build(); + .setConnectTimeout(timeout, TimeUnit.SECONDS) + .setConnectionRequestTimeout(timeout, TimeUnit.SECONDS) + .build(); final TrustStrategy trustAllStrategy = new TrustStrategy() { @Override @@ -357,76 +376,74 @@ public boolean isTrusted(X509Certificate[] chain, String authType) { } }; - try { - - HttpClientBuilder hcb = HttpClients.custom().setDefaultRequestConfig(config); - if(!verifySSL) { - SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(trustAllStrategy).build(); - final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, null, null, - NoopHostnameVerifier.INSTANCE); - - final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslsf) - .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeout, TimeUnit.SECONDS).build()) - .build(); - hcb.setConnectionManager(cm); - return hcb.build(); - } - - if(effectiveTruststore == null) { - return HttpClients.custom() - .setDefaultRequestConfig(config) - .build(); - } - SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(effectiveTruststore, null).build(); - final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, null, null, - new DefaultHostnameVerifier()); - - final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslsf) - .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeout, TimeUnit.SECONDS).build()) - .build(); - hcb.setConnectionManager(cm); - - return hcb.build(); - - - } catch(Exception ex) { - log.error("Could not create HTTPClient due to {}, audit log not available.", ex.getMessage(), ex); - return null; - } - } - - public static enum WebhookFormat { - URL_PARAMETER_GET(HttpMethod.GET, ContentType.TEXT_PLAIN), - URL_PARAMETER_POST(HttpMethod.POST, ContentType.TEXT_PLAIN), - TEXT(HttpMethod.POST, ContentType.TEXT_PLAIN), - JSON(HttpMethod.POST, ContentType.APPLICATION_JSON), - SLACK(HttpMethod.POST, ContentType.APPLICATION_JSON); - - private HttpMethod method; - private ContentType contentType; - - private WebhookFormat(HttpMethod method, ContentType contentType) { - this.method = method; - this.contentType = contentType; - } - - HttpMethod getMethod() { - return method; - } - - ContentType getContentType() { - return contentType; - } - - - } - - private static enum HttpMethod { - GET, - POST; - } + try { + + HttpClientBuilder hcb = HttpClients.custom().setDefaultRequestConfig(config); + if (!verifySSL) { + SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(trustAllStrategy).build(); + final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( + sslContext, + null, + null, + NoopHostnameVerifier.INSTANCE + ); + + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslsf) + .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeout, TimeUnit.SECONDS).build()) + .build(); + hcb.setConnectionManager(cm); + return hcb.build(); + } + + if (effectiveTruststore == null) { + return HttpClients.custom().setDefaultRequestConfig(config).build(); + } + SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(effectiveTruststore, null).build(); + final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, null, null, new DefaultHostnameVerifier()); + + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslsf) + .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeout, TimeUnit.SECONDS).build()) + .build(); + hcb.setConnectionManager(cm); + + return hcb.build(); + } catch (Exception ex) { + log.error("Could not create HTTPClient due to {}, audit log not available.", ex.getMessage(), ex); + return null; + } + } + + public static enum WebhookFormat { + URL_PARAMETER_GET(HttpMethod.GET, ContentType.TEXT_PLAIN), + URL_PARAMETER_POST(HttpMethod.POST, ContentType.TEXT_PLAIN), + TEXT(HttpMethod.POST, ContentType.TEXT_PLAIN), + JSON(HttpMethod.POST, ContentType.APPLICATION_JSON), + SLACK(HttpMethod.POST, ContentType.APPLICATION_JSON); + + private HttpMethod method; + private ContentType contentType; + + private WebhookFormat(HttpMethod method, ContentType contentType) { + this.method = method; + this.contentType = contentType; + } + + HttpMethod getMethod() { + return method; + } + + ContentType getContentType() { + return contentType; + } + + } + + private static enum HttpMethod { + GET, + POST; + } } diff --git a/src/main/java/org/opensearch/security/auth/AuthDomain.java b/src/main/java/org/opensearch/security/auth/AuthDomain.java index effa29adf3..decad5f068 100644 --- a/src/main/java/org/opensearch/security/auth/AuthDomain.java +++ b/src/main/java/org/opensearch/security/auth/AuthDomain.java @@ -61,8 +61,15 @@ public int getOrder() { @Override public String toString() { - return "AuthDomain [backend=" + backend + ", httpAuthenticator=" + httpAuthenticator + ", order=" + order + ", challenge=" - + challenge + "]"; + return "AuthDomain [backend=" + + backend + + ", httpAuthenticator=" + + httpAuthenticator + + ", order=" + + order + + ", challenge=" + + challenge + + "]"; } @Override diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index 51e93978bd..0a287d19f5 100644 --- a/src/main/java/org/opensearch/security/auth/BackendRegistry.java +++ b/src/main/java/org/opensearch/security/auth/BackendRegistry.java @@ -26,7 +26,6 @@ package org.opensearch.security.auth; - import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Collection; @@ -85,44 +84,55 @@ public class BackendRegistry { private final XFFResolver xffResolver; private volatile boolean anonymousAuthEnabled = false; private final Settings opensearchSettings; - //private final InternalAuthenticationBackend iab; + // private final InternalAuthenticationBackend iab; private final AuditLog auditLog; private final ThreadPool threadPool; private final UserInjector userInjector; private final int ttlInMin; - private Cache userCache; //rest standard - private Cache restImpersonationCache; //used for rest impersonation + private Cache userCache; // rest standard + private Cache restImpersonationCache; // used for rest impersonation private Cache> restRoleCache; // private void createCaches() { - userCache = CacheBuilder.newBuilder().expireAfterWrite(ttlInMin, TimeUnit.MINUTES) - .removalListener(new RemovalListener() { - @Override - public void onRemoval(RemovalNotification notification) { - log.debug("Clear user cache for {} due to {}", notification.getKey().getUsername(), notification.getCause()); - } - }).build(); + userCache = CacheBuilder.newBuilder() + .expireAfterWrite(ttlInMin, TimeUnit.MINUTES) + .removalListener(new RemovalListener() { + @Override + public void onRemoval(RemovalNotification notification) { + log.debug("Clear user cache for {} due to {}", notification.getKey().getUsername(), notification.getCause()); + } + }) + .build(); - restImpersonationCache = CacheBuilder.newBuilder().expireAfterWrite(ttlInMin, TimeUnit.MINUTES) - .removalListener(new RemovalListener() { - @Override - public void onRemoval(RemovalNotification notification) { - log.debug("Clear user cache for {} due to {}", notification.getKey(), notification.getCause()); - } - }).build(); + restImpersonationCache = CacheBuilder.newBuilder() + .expireAfterWrite(ttlInMin, TimeUnit.MINUTES) + .removalListener(new RemovalListener() { + @Override + public void onRemoval(RemovalNotification notification) { + log.debug("Clear user cache for {} due to {}", notification.getKey(), notification.getCause()); + } + }) + .build(); - restRoleCache = CacheBuilder.newBuilder().expireAfterWrite(ttlInMin, TimeUnit.MINUTES) - .removalListener(new RemovalListener>() { - @Override - public void onRemoval(RemovalNotification> notification) { - log.debug("Clear user cache for {} due to {}", notification.getKey(), notification.getCause()); - } - }).build(); + restRoleCache = CacheBuilder.newBuilder() + .expireAfterWrite(ttlInMin, TimeUnit.MINUTES) + .removalListener(new RemovalListener>() { + @Override + public void onRemoval(RemovalNotification> notification) { + log.debug("Clear user cache for {} due to {}", notification.getKey(), notification.getCause()); + } + }) + .build(); } - public BackendRegistry(final Settings settings, final AdminDNs adminDns, - final XFFResolver xffResolver, final AuditLog auditLog, final ThreadPool threadPool) { + public BackendRegistry( + final Settings settings, + final AdminDNs adminDns, + final XFFResolver xffResolver, + final AuditLog auditLog, + final ThreadPool threadPool + ) { this.adminDns = adminDns; this.opensearchSettings = settings; this.xffResolver = xffResolver; @@ -130,11 +140,10 @@ public BackendRegistry(final Settings settings, final AdminDNs adminDns, this.threadPool = threadPool; this.userInjector = new UserInjector(settings, threadPool, auditLog, xffResolver); - this.ttlInMin = settings.getAsInt(ConfigConstants.SECURITY_CACHE_TTL_MINUTES, 60); // This is going to be defined in the opensearch.yml, so it's best suited to be initialized once. - this.injectedUserEnabled = opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED,false); + this.injectedUserEnabled = opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false); createCaches(); } @@ -153,8 +162,8 @@ public void invalidateCache() { public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { invalidateCache(); - anonymousAuthEnabled = dcm.isAnonymousAuthenticationEnabled()//config.dynamic.http.anonymous_auth_enabled - && !opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_COMPLIANCE_DISABLE_ANONYMOUS_AUTHENTICATION, false); + anonymousAuthEnabled = dcm.isAnonymousAuthenticationEnabled()// config.dynamic.http.anonymous_auth_enabled + && !opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_COMPLIANCE_DISABLE_ANONYMOUS_AUTHENTICATION, false); restAuthDomains = Collections.unmodifiableSortedSet(dcm.getRestAuthDomains()); restAuthorizers = Collections.unmodifiableSet(dcm.getRestAuthorizers()); @@ -164,8 +173,8 @@ public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { ipClientBlockRegistries = dcm.getIpClientBlockRegistries(); authBackendClientBlockRegistries = dcm.getAuthBackendClientBlockRegistries(); - //OpenSearch Security no default authc - initialized = !restAuthDomains.isEmpty() || anonymousAuthEnabled || injectedUserEnabled; + // OpenSearch Security no default authc + initialized = !restAuthDomains.isEmpty() || anonymousAuthEnabled || injectedUserEnabled; } /** @@ -177,7 +186,8 @@ public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { */ public boolean authenticate(final RestRequest request, final RestChannel channel, final ThreadContext threadContext) { final boolean isDebugEnabled = log.isDebugEnabled(); - if (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress && isBlocked(((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress())) { + if (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress + && isBlocked(((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress())) { if (isDebugEnabled) { log.debug("Rejecting REST request because of blocked address: {}", request.getHttpChannel().getRemoteAddress()); } @@ -189,8 +199,8 @@ public boolean authenticate(final RestRequest request, final RestChannel channel final String sslPrincipal = (String) threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PRINCIPAL); - if(adminDns.isAdminDN(sslPrincipal)) { - //PKI authenticated REST call + if (adminDns.isAdminDN(sslPrincipal)) { + // PKI authenticated REST call threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, new User(sslPrincipal)); auditLog.logSucceededLogin(sslPrincipal, true, null, request); return true; @@ -203,8 +213,7 @@ public boolean authenticate(final RestRequest request, final RestChannel channel if (!isInitialized()) { log.error("Not yet initialized (you may need to run securityadmin)"); - channel.sendResponse(new BytesRestResponse(RestStatus.SERVICE_UNAVAILABLE, - "OpenSearch Security not initialized.")); + channel.sendResponse(new BytesRestResponse(RestStatus.SERVICE_UNAVAILABLE, "OpenSearch Security not initialized.")); return false; } @@ -212,7 +221,7 @@ public boolean authenticate(final RestRequest request, final RestChannel channel final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { log.trace("Rest authentication request from {} [original: {}]", remoteAddress, request.getHttpChannel().getRemoteAddress()); - } + } threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, remoteAddress); @@ -224,15 +233,20 @@ public boolean authenticate(final RestRequest request, final RestChannel channel HTTPAuthenticator firstChallengingHttpAuthenticator = null; - //loop over all http/rest auth domains - for (final AuthDomain authDomain: restAuthDomains) { + // loop over all http/rest auth domains + for (final AuthDomain authDomain : restAuthDomains) { if (isDebugEnabled) { - log.debug("Check authdomain for rest {}/{} or {} in total", authDomain.getBackend().getType(), authDomain.getOrder(), restAuthDomains.size()); + log.debug( + "Check authdomain for rest {}/{} or {} in total", + authDomain.getBackend().getType(), + authDomain.getOrder(), + restAuthDomains.size() + ); } final HTTPAuthenticator httpAuthenticator = authDomain.getHttpAuthenticator(); - if(authDomain.isChallenge() && firstChallengingHttpAuthenticator == null) { + if (authDomain.isChallenge() && firstChallengingHttpAuthenticator == null) { firstChallengingHttpAuthenticator = httpAuthenticator; } @@ -260,17 +274,17 @@ public boolean authenticate(final RestRequest request, final RestChannel channel authCredenetials = ac; if (ac == null) { - //no credentials found in request - if(anonymousAuthEnabled) { + // no credentials found in request + if (anonymousAuthEnabled) { continue; } - if(authDomain.isChallenge() && httpAuthenticator.reRequestAuthentication(channel, null)) { + if (authDomain.isChallenge() && httpAuthenticator.reRequestAuthentication(channel, null)) { auditLog.logFailedLogin("", false, null, request); log.warn("No 'Authorization' header, send 401 and 'WWW-Authenticate Basic'"); return false; } else { - //no reRequest possible + // no reRequest possible if (isTraceEnabled) { log.trace("No 'Authorization' header, send 403"); } @@ -279,39 +293,54 @@ public boolean authenticate(final RestRequest request, final RestChannel channel } else { org.apache.logging.log4j.ThreadContext.put("user", ac.getUsername()); if (!ac.isComplete()) { - //credentials found in request but we need another client challenge - if(httpAuthenticator.reRequestAuthentication(channel, ac)) { - //auditLog.logFailedLogin(ac.getUsername()+" ", request); --noauditlog + // credentials found in request but we need another client challenge + if (httpAuthenticator.reRequestAuthentication(channel, ac)) { + // auditLog.logFailedLogin(ac.getUsername()+" ", request); --noauditlog return false; } else { - //no reRequest possible + // no reRequest possible continue; } } } - //http completed + // http completed authenticatedUser = authcz(userCache, restRoleCache, ac, authDomain.getBackend(), restAuthorizers); - if(authenticatedUser == null) { + if (authenticatedUser == null) { if (isDebugEnabled) { - log.debug("Cannot authenticate rest user {} (or add roles) with authdomain {}/{} of {}, try next", ac.getUsername(), authDomain.getBackend().getType(), authDomain.getOrder(), restAuthDomains); + log.debug( + "Cannot authenticate rest user {} (or add roles) with authdomain {}/{} of {}, try next", + ac.getUsername(), + authDomain.getBackend().getType(), + authDomain.getOrder(), + restAuthDomains + ); } - for (AuthFailureListener authFailureListener : this.authBackendFailureListeners.get(authDomain.getBackend().getClass().getName())) { + for (AuthFailureListener authFailureListener : this.authBackendFailureListeners.get( + authDomain.getBackend().getClass().getName() + )) { authFailureListener.onAuthFailure( - (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress) ? ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress() - : null, - ac, request); + (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress) + ? ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress() + : null, + ac, + request + ); } continue; } - if(adminDns.isAdmin(authenticatedUser)) { + if (adminDns.isAdmin(authenticatedUser)) { log.error("Cannot authenticate rest user because admin user is not permitted to login via HTTP"); auditLog.logFailedLogin(authenticatedUser.getName(), true, null, request); - channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, - "Cannot authenticate user because admin user is not permitted to login via HTTP")); + channel.sendResponse( + new BytesRestResponse( + RestStatus.FORBIDDEN, + "Cannot authenticate user because admin user is not permitted to login via HTTP" + ) + ); return false; } @@ -325,19 +354,26 @@ public boolean authenticate(final RestRequest request, final RestChannel channel authenticatedUser.setRequestedTenant(tenant); authenticated = true; break; - }//end looping auth domains + }// end looping auth domains - if(authenticated) { + if (authenticated) { final User impersonatedUser = impersonate(request, authenticatedUser); - threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, impersonatedUser==null?authenticatedUser:impersonatedUser); - auditLog.logSucceededLogin((impersonatedUser == null ? authenticatedUser : impersonatedUser).getName(), false, - authenticatedUser.getName(), request); + threadContext.putTransient( + ConfigConstants.OPENDISTRO_SECURITY_USER, + impersonatedUser == null ? authenticatedUser : impersonatedUser + ); + auditLog.logSucceededLogin( + (impersonatedUser == null ? authenticatedUser : impersonatedUser).getName(), + false, + authenticatedUser.getName(), + request + ); } else { if (isDebugEnabled) { log.debug("User still not authenticated after checking {} auth domains", restAuthDomains.size()); } - if(authCredenetials == null && anonymousAuthEnabled) { + if (authCredenetials == null && anonymousAuthEnabled) { final String tenant = Utils.coalesce(request.header("securitytenant"), request.header("security_tenant")); User anonymousUser = new User(User.ANONYMOUS.getName(), new HashSet(User.ANONYMOUS.getRoles()), null); anonymousUser.setRequestedTenant(tenant); @@ -350,26 +386,33 @@ public boolean authenticate(final RestRequest request, final RestChannel channel return true; } - if(firstChallengingHttpAuthenticator != null) { + if (firstChallengingHttpAuthenticator != null) { if (isDebugEnabled) { log.debug("Rerequest with {}", firstChallengingHttpAuthenticator.getClass()); } - if(firstChallengingHttpAuthenticator.reRequestAuthentication(channel, null)) { + if (firstChallengingHttpAuthenticator.reRequestAuthentication(channel, null)) { if (isDebugEnabled) { log.debug("Rerequest {} failed", firstChallengingHttpAuthenticator.getClass()); } - log.warn("Authentication finally failed for {} from {}", authCredenetials == null ? null:authCredenetials.getUsername(), remoteAddress); - auditLog.logFailedLogin(authCredenetials == null ? null:authCredenetials.getUsername(), false, null, request); + log.warn( + "Authentication finally failed for {} from {}", + authCredenetials == null ? null : authCredenetials.getUsername(), + remoteAddress + ); + auditLog.logFailedLogin(authCredenetials == null ? null : authCredenetials.getUsername(), false, null, request); return false; } } - log.warn("Authentication finally failed for {} from {}", authCredenetials == null ? null : authCredenetials.getUsername(), - remoteAddress); - auditLog.logFailedLogin(authCredenetials == null ? null:authCredenetials.getUsername(), false, null, request); + log.warn( + "Authentication finally failed for {} from {}", + authCredenetials == null ? null : authCredenetials.getUsername(), + remoteAddress + ); + auditLog.logFailedLogin(authCredenetials == null ? null : authCredenetials.getUsername(), false, null, request); notifyIpAuthFailureListeners(request, authCredenetials); @@ -382,8 +425,12 @@ public boolean authenticate(final RestRequest request, final RestChannel channel private void notifyIpAuthFailureListeners(RestRequest request, AuthCredentials authCredentials) { notifyIpAuthFailureListeners( - (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress) ? ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress() : null, - authCredentials, request); + (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress) + ? ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress() + : null, + authCredentials, + request + ); } private void notifyIpAuthFailureListeners(InetAddress remoteAddress, AuthCredentials authCredentials, Object request) { @@ -397,9 +444,13 @@ private void notifyIpAuthFailureListeners(InetAddress remoteAddress, AuthCredent * * @return null if user cannot b authenticated */ - private User checkExistsAndAuthz(final Cache cache, final User user, final AuthenticationBackend authenticationBackend, - final Set authorizers) { - if(user == null) { + private User checkExistsAndAuthz( + final Cache cache, + final User user, + final AuthenticationBackend authenticationBackend, + final Set authorizers + ) { + if (user == null) { return null; } @@ -407,14 +458,18 @@ private User checkExistsAndAuthz(final Cache cache, final User use final boolean isTraceEnabled = log.isTraceEnabled(); try { - return cache.get(user.getName(), new Callable() { //no cache miss in case of noop + return cache.get(user.getName(), new Callable() { // no cache miss in case of noop @Override public User call() throws Exception { if (isTraceEnabled) { - log.trace("Credentials for user {} not cached, return from {} backend directly", user.getName(), authenticationBackend.getType()); + log.trace( + "Credentials for user {} not cached, return from {} backend directly", + user.getName(), + authenticationBackend.getType() + ); } - if(authenticationBackend.exists(user)) { - authz(user, null, authorizers); //no role cache because no miss here in case of noop + if (authenticationBackend.exists(user)) { + authz(user, null, authorizers); // no role cache because no miss here in case of noop return user; } @@ -431,23 +486,24 @@ public User call() throws Exception { return null; } } + private void authz(User authenticatedUser, Cache> roleCache, final Set authorizers) { - if(authenticatedUser == null) { + if (authenticatedUser == null) { return; } - if(roleCache != null) { + if (roleCache != null) { final Set cachedBackendRoles = roleCache.getIfPresent(authenticatedUser); - if(cachedBackendRoles != null) { + if (cachedBackendRoles != null) { authenticatedUser.addRoles(new HashSet(cachedBackendRoles)); return; } } - if(authorizers == null || authorizers.isEmpty()) { + if (authorizers == null || authorizers.isEmpty()) { return; } @@ -455,7 +511,11 @@ private void authz(User authenticatedUser, Cache> roleCache, f for (final AuthorizationBackend ab : authorizers) { try { if (isTraceEnabled) { - log.trace("Backend roles for {} not cached, return from {} backend directly", authenticatedUser.getName(), ab.getType()); + log.trace( + "Backend roles for {} not cached, return from {} backend directly", + authenticatedUser.getName(), + ab.getType() + ); } ab.fillRoles(authenticatedUser, new AuthCredentials(authenticatedUser.getName())); } catch (Exception e) { @@ -463,7 +523,7 @@ private void authz(User authenticatedUser, Cache> roleCache, f } } - if(roleCache != null) { + if (roleCache != null) { roleCache.put(authenticatedUser, new HashSet(authenticatedUser.getRoles())); } } @@ -473,17 +533,22 @@ private void authz(User authenticatedUser, Cache> roleCache, f * * @return null if user cannot b authenticated */ - private User authcz(final Cache cache, Cache> roleCache, final AuthCredentials ac, - final AuthenticationBackend authBackend, final Set authorizers) { - if(ac == null) { + private User authcz( + final Cache cache, + Cache> roleCache, + final AuthCredentials ac, + final AuthenticationBackend authBackend, + final Set authorizers + ) { + if (ac == null) { return null; } try { - //noop backend configured and no authorizers - //that mean authc and authz was completely done via HTTP (like JWT or PKI) - if(authBackend.getClass() == NoOpAuthenticationBackend.class && authorizers.isEmpty()) { - //no cache + // noop backend configured and no authorizers + // that mean authc and authz was completely done via HTTP (like JWT or PKI) + if (authBackend.getClass() == NoOpAuthenticationBackend.class && authorizers.isEmpty()) { + // no cache return authBackend.authenticate(ac); } @@ -491,7 +556,11 @@ private User authcz(final Cache cache, Cache injectUserAndRoles(TransportRequest transportRequest, String String[] strs = injectedUserAndRoles.split("\\|"); if (strs.length == 0) { - log.error("Roles injected string malformed, could not extract parts. User string was '{}.'" + - " Roles injection failed.", injectedUserAndRoles); + log.error( + "Roles injected string malformed, could not extract parts. User string was '{}.'" + " Roles injection failed.", + injectedUserAndRoles + ); return null; } @@ -71,16 +73,20 @@ public Set injectUserAndRoles(TransportRequest transportRequest, String } Set roles = ImmutableSet.copyOf(strs[1].split(",")); - if(user != null && roles != null) { + if (user != null && roles != null) { addUser(user, transportRequest, action, task, ctx); } return roles; } - private void addUser(final User user, final TransportRequest transportRequest, - final String action, final Task task, final ThreadContext threadContext) { - if(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER) != null) - return; + private void addUser( + final User user, + final TransportRequest transportRequest, + final String action, + final Task task, + final ThreadContext threadContext + ) { + if (threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER) != null) return; threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); } diff --git a/src/main/java/org/opensearch/security/auth/UserInjector.java b/src/main/java/org/opensearch/security/auth/UserInjector.java index 9253023eb3..9ce040c485 100644 --- a/src/main/java/org/opensearch/security/auth/UserInjector.java +++ b/src/main/java/org/opensearch/security/auth/UserInjector.java @@ -172,16 +172,16 @@ public InjectedUser getInjectedUser() { return injectedUser; } - boolean injectUser(RestRequest request) { InjectedUser injectedUser = getInjectedUser(); - if(injectedUser == null) { + if (injectedUser == null) { return false; } // Set remote address into the thread context if (injectedUser.getTransportAddress() != null) { - threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, injectedUser.getTransportAddress()); + threadPool.getThreadContext() + .putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, injectedUser.getTransportAddress()); } else { threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, xffResolver.resolve(request)); } diff --git a/src/main/java/org/opensearch/security/auth/blocking/ClientBlockRegistry.java b/src/main/java/org/opensearch/security/auth/blocking/ClientBlockRegistry.java index e74c3ad70a..801dafc75d 100644 --- a/src/main/java/org/opensearch/security/auth/blocking/ClientBlockRegistry.java +++ b/src/main/java/org/opensearch/security/auth/blocking/ClientBlockRegistry.java @@ -20,6 +20,8 @@ public interface ClientBlockRegistry { boolean isBlocked(ClientIdType clientId); + void block(ClientIdType clientId); + Class getClientIdType(); } diff --git a/src/main/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistry.java b/src/main/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistry.java index 450dda54db..334073118a 100644 --- a/src/main/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistry.java +++ b/src/main/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistry.java @@ -35,15 +35,19 @@ public class HeapBasedClientBlockRegistry implements ClientBlockRe public HeapBasedClientBlockRegistry(long expiryMs, int maxEntries, Class clientIdType) { this.clientIdType = clientIdType; - this.cache = CacheBuilder.newBuilder().expireAfterWrite(expiryMs, TimeUnit.MILLISECONDS).maximumSize(maxEntries).concurrencyLevel(4) - .removalListener(new RemovalListener() { - @Override - public void onRemoval(RemovalNotification notification) { - if (log.isInfoEnabled()) { - log.info("Unblocking " + notification.getKey()); - } + this.cache = CacheBuilder.newBuilder() + .expireAfterWrite(expiryMs, TimeUnit.MILLISECONDS) + .maximumSize(maxEntries) + .concurrencyLevel(4) + .removalListener(new RemovalListener() { + @Override + public void onRemoval(RemovalNotification notification) { + if (log.isInfoEnabled()) { + log.info("Unblocking " + notification.getKey()); } - }).build(); + } + }) + .build(); } @Override diff --git a/src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java b/src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java index adc49c8735..98443a2902 100644 --- a/src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java +++ b/src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java @@ -52,27 +52,27 @@ public class InternalAuthenticationBackend implements AuthenticationBackend, Aut @Override public boolean exists(User user) { - if(user == null || internalUsersModel == null) { + if (user == null || internalUsersModel == null) { return false; } final boolean exists = internalUsersModel.exists(user.getName()); - if(exists) { + if (exists) { user.addRoles(internalUsersModel.getBackenRoles(user.getName())); - //FIX https://github.com/opendistro-for-elasticsearch/security/pull/23 - //Credits to @turettn + // FIX https://github.com/opendistro-for-elasticsearch/security/pull/23 + // Credits to @turettn final Map customAttributes = internalUsersModel.getAttributes(user.getName()); Map attributeMap = new HashMap<>(); - if(customAttributes != null) { - for(Entry attributeEntry: customAttributes.entrySet()) { - attributeMap.put("attr.internal."+attributeEntry.getKey(), attributeEntry.getValue()); + if (customAttributes != null) { + for (Entry attributeEntry : customAttributes.entrySet()) { + attributeMap.put("attr.internal." + attributeEntry.getKey(), attributeEntry.getValue()); } } final List securityRoles = internalUsersModel.getSecurityRoles(user.getName()); - if(securityRoles != null) { + if (securityRoles != null) { user.addSecurityRoles(securityRoles); } @@ -104,10 +104,11 @@ public User authenticate(final AuthCredentials credentials) { final byte[] password; String hash; - if(!internalUsersModel.exists(credentials.getUsername())) { + if (!internalUsersModel.exists(credentials.getUsername())) { userExists = false; password = credentials.getPassword(); - hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2"; // Ensure the same cryptographic complexity for users not found and invalid password + hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2"; // Ensure the same cryptographic complexity for users not + // found and invalid password } else { userExists = true; password = credentials.getPassword(); @@ -123,22 +124,22 @@ public User authenticate(final AuthCredentials credentials) { char[] array = new char[buf.limit()]; buf.get(array); - Arrays.fill(password, (byte)0); + Arrays.fill(password, (byte) 0); try { if (passwordMatchesHash(hash, array) && userExists) { final List roles = internalUsersModel.getBackenRoles(credentials.getUsername()); final Map customAttributes = internalUsersModel.getAttributes(credentials.getUsername()); - if(customAttributes != null) { - for(Entry attributeName: customAttributes.entrySet()) { - credentials.addAttribute("attr.internal."+attributeName.getKey(), attributeName.getValue()); + if (customAttributes != null) { + for (Entry attributeName : customAttributes.entrySet()) { + credentials.addAttribute("attr.internal." + attributeName.getKey(), attributeName.getValue()); } } final User user = new User(credentials.getUsername(), roles, credentials); final List securityRoles = internalUsersModel.getSecurityRoles(credentials.getUsername()); - if(securityRoles != null) { + if (securityRoles != null) { user.addSecurityRoles(securityRoles); } return user; @@ -149,7 +150,7 @@ public User authenticate(final AuthCredentials credentials) { throw new OpenSearchSecurityException("password does not match"); } } finally { - Arrays.fill(wrap.array(), (byte)0); + Arrays.fill(wrap.array(), (byte) 0); Arrays.fill(buf.array(), '\0'); Arrays.fill(array, '\0'); } @@ -164,18 +165,19 @@ public String getType() { public void fillRoles(User user, AuthCredentials credentials) throws OpenSearchSecurityException { if (internalUsersModel == null) { - throw new OpenSearchSecurityException("Internal authentication backend not configured. May be OpenSearch Security is not initialized."); + throw new OpenSearchSecurityException( + "Internal authentication backend not configured. May be OpenSearch Security is not initialized." + ); } - if(exists(user)) { + if (exists(user)) { final List roles = internalUsersModel.getBackenRoles(user.getName()); - if(roles != null && !roles.isEmpty() && user != null) { + if (roles != null && !roles.isEmpty() && user != null) { user.addRoles(roles); } } - } @Subscribe @@ -183,5 +185,4 @@ public void onInternalUsersModelChanged(InternalUsersModel ium) { this.internalUsersModel = ium; } - } diff --git a/src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java b/src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java index a4d596b61d..0fc796d94f 100644 --- a/src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java +++ b/src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java @@ -32,10 +32,16 @@ public abstract class AbstractRateLimiter implements AuthFailureLi protected final RateTracker rateTracker; public AbstractRateLimiter(Settings settings, Path configPath, Class clientIdType) { - this.clientBlockRegistry = new HeapBasedClientBlockRegistry<>(settings.getAsInt("block_expiry_seconds", 60 * 10) * 1000, - settings.getAsInt("max_blocked_clients", 100_000), clientIdType); - this.rateTracker = RateTracker.create(settings.getAsInt("time_window_seconds", 60 * 60) * 1000, settings.getAsInt("allowed_tries", 10), - settings.getAsInt("max_tracked_clients", 100_000)); + this.clientBlockRegistry = new HeapBasedClientBlockRegistry<>( + settings.getAsInt("block_expiry_seconds", 60 * 10) * 1000, + settings.getAsInt("max_blocked_clients", 100_000), + clientIdType + ); + this.rateTracker = RateTracker.create( + settings.getAsInt("time_window_seconds", 60 * 60) * 1000, + settings.getAsInt("allowed_tries", 10), + settings.getAsInt("max_tracked_clients", 100_000) + ); } @Override diff --git a/src/main/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiter.java b/src/main/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiter.java index 35a6571f8f..876615870a 100644 --- a/src/main/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiter.java +++ b/src/main/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiter.java @@ -25,7 +25,10 @@ import org.opensearch.security.auth.blocking.ClientBlockRegistry; import org.opensearch.security.user.AuthCredentials; -public class AddressBasedRateLimiter extends AbstractRateLimiter implements AuthFailureListener, ClientBlockRegistry { +public class AddressBasedRateLimiter extends AbstractRateLimiter + implements + AuthFailureListener, + ClientBlockRegistry { public AddressBasedRateLimiter(Settings settings, Path configPath) { super(settings, configPath, InetAddress.class); diff --git a/src/main/java/org/opensearch/security/compliance/ComplianceConfig.java b/src/main/java/org/opensearch/security/compliance/ComplianceConfig.java index 4b0f3079f2..1d81479f37 100644 --- a/src/main/java/org/opensearch/security/compliance/ComplianceConfig.java +++ b/src/main/java/org/opensearch/security/compliance/ComplianceConfig.java @@ -105,19 +105,20 @@ public class ComplianceConfig { private final boolean enabled; private ComplianceConfig( - final boolean enabled, - final boolean logExternalConfig, - final boolean logInternalConfig, - final boolean logReadMetadataOnly, - final Map> watchedReadFields, - final Set ignoredComplianceUsersForRead, - final boolean logWriteMetadataOnly, - final boolean logDiffsForWrite, - final List watchedWriteIndicesPatterns, - final Set ignoredComplianceUsersForWrite, - final String securityIndex, - final String destinationType, - final String destinationIndex) { + final boolean enabled, + final boolean logExternalConfig, + final boolean logInternalConfig, + final boolean logReadMetadataOnly, + final Map> watchedReadFields, + final Set ignoredComplianceUsersForRead, + final boolean logWriteMetadataOnly, + final boolean logDiffsForWrite, + final List watchedWriteIndicesPatterns, + final Set ignoredComplianceUsersForWrite, + final String securityIndex, + final String destinationType, + final String destinationIndex + ) { this.enabled = enabled; this.logExternalConfig = logExternalConfig; this.logInternalConfig = logInternalConfig; @@ -133,22 +134,20 @@ private ComplianceConfig( this.watchedWriteIndicesPatterns = watchedWriteIndicesPatterns; this.ignoredComplianceUsersForWrite = ignoredComplianceUsersForWrite; - this.readEnabledFields = watchedReadFields.entrySet().stream() - .filter(entry -> !Strings.isNullOrEmpty(entry.getKey())) - .collect( - ImmutableMap.toImmutableMap( - entry -> WildcardMatcher.from(entry.getKey()), - entry -> ImmutableSet.copyOf(entry.getValue()) - ) - ); + this.readEnabledFields = watchedReadFields.entrySet() + .stream() + .filter(entry -> !Strings.isNullOrEmpty(entry.getKey())) + .collect( + ImmutableMap.toImmutableMap(entry -> WildcardMatcher.from(entry.getKey()), entry -> ImmutableSet.copyOf(entry.getValue())) + ); DateTimeFormatter auditLogPattern = null; String auditLogIndex = null; if (INTERNAL_OPENSEARCH.equalsIgnoreCase(destinationType)) { try { - auditLogPattern = DateTimeFormat.forPattern(destinationIndex); //throws IllegalArgumentException if no pattern + auditLogPattern = DateTimeFormat.forPattern(destinationIndex); // throws IllegalArgumentException if no pattern } catch (IllegalArgumentException e) { - //no pattern + // no pattern auditLogIndex = destinationIndex; } catch (Exception e) { log.error("Unable to check if auditlog index {} is part of compliance setup", destinationIndex, e); @@ -157,43 +156,45 @@ private ComplianceConfig( this.auditLogPattern = auditLogPattern; this.auditLogIndex = auditLogIndex; - this.readEnabledFieldsCache = CacheBuilder.newBuilder() - .maximumSize(CACHE_SIZE) - .build(new CacheLoader() { - @Override - public WildcardMatcher load(String index) throws Exception { - return WildcardMatcher.from(getFieldsForIndex(index)); - } - }); + this.readEnabledFieldsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).build(new CacheLoader() { + @Override + public WildcardMatcher load(String index) throws Exception { + return WildcardMatcher.from(getFieldsForIndex(index)); + } + }); } @VisibleForTesting public ComplianceConfig( - final boolean enabled, - final boolean logExternalConfig, - final boolean logInternalConfig, - final boolean logReadMetadataOnly, - final Map> watchedReadFields, - final Set ignoredComplianceUsersForRead, - final boolean logWriteMetadataOnly, - final boolean logDiffsForWrite, - final List watchedWriteIndicesPatterns, - final Set ignoredComplianceUsersForWrite, - Settings settings) { + final boolean enabled, + final boolean logExternalConfig, + final boolean logInternalConfig, + final boolean logReadMetadataOnly, + final Map> watchedReadFields, + final Set ignoredComplianceUsersForRead, + final boolean logWriteMetadataOnly, + final boolean logDiffsForWrite, + final List watchedWriteIndicesPatterns, + final Set ignoredComplianceUsersForWrite, + Settings settings + ) { this( - enabled, - logExternalConfig, - logInternalConfig, - logReadMetadataOnly, - watchedReadFields, - ignoredComplianceUsersForRead, - logWriteMetadataOnly, - logDiffsForWrite, - watchedWriteIndicesPatterns, - ignoredComplianceUsersForWrite, - settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX), - settings.get(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT, null), - settings.get(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, "'security-auditlog-'YYYY.MM.dd") + enabled, + logExternalConfig, + logInternalConfig, + logReadMetadataOnly, + watchedReadFields, + ignoredComplianceUsersForRead, + logWriteMetadataOnly, + logDiffsForWrite, + watchedWriteIndicesPatterns, + ignoredComplianceUsersForWrite, + settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX), + settings.get(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT, null), + settings.get( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, + "'security-auditlog-'YYYY.MM.dd" + ) ); } @@ -215,7 +216,14 @@ public void log(Logger logger) { @JsonCreator public static ComplianceConfig from(Map properties, @JacksonInject Settings settings) throws JsonProcessingException { if (!FIELDS.containsAll(properties.keySet())) { - throw new UnrecognizedPropertyException(null, "Invalid property present in the input data for compliance config", null, ComplianceConfig.class, null, null); + throw new UnrecognizedPropertyException( + null, + "Invalid property present in the input data for compliance config", + null, + ComplianceConfig.class, + null, + null + ); } final boolean enabled = getOrDefault(properties, "enabled", true); @@ -223,24 +231,28 @@ public static ComplianceConfig from(Map properties, @JacksonInje final boolean logInternalConfig = getOrDefault(properties, "internal_config", false); final boolean logReadMetadataOnly = getOrDefault(properties, "read_metadata_only", false); final Map> watchedReadFields = getOrDefault(properties, "read_watched_fields", Collections.emptyMap()); - final Set ignoredComplianceUsersForRead = ImmutableSet.copyOf(getOrDefault(properties, "read_ignore_users", AuditConfig.DEFAULT_IGNORED_USERS)); + final Set ignoredComplianceUsersForRead = ImmutableSet.copyOf( + getOrDefault(properties, "read_ignore_users", AuditConfig.DEFAULT_IGNORED_USERS) + ); final boolean logWriteMetadataOnly = getOrDefault(properties, "write_metadata_only", false); final boolean logDiffsForWrite = getOrDefault(properties, "write_log_diffs", false); final List watchedWriteIndicesPatterns = getOrDefault(properties, "write_watched_indices", Collections.emptyList()); - final Set ignoredComplianceUsersForWrite = ImmutableSet.copyOf(getOrDefault(properties, "write_ignore_users", AuditConfig.DEFAULT_IGNORED_USERS)); + final Set ignoredComplianceUsersForWrite = ImmutableSet.copyOf( + getOrDefault(properties, "write_ignore_users", AuditConfig.DEFAULT_IGNORED_USERS) + ); return new ComplianceConfig( - enabled, - logExternalConfig, - logInternalConfig, - logReadMetadataOnly, - watchedReadFields, - ignoredComplianceUsersForRead, - logWriteMetadataOnly, - logDiffsForWrite, - watchedWriteIndicesPatterns, - ignoredComplianceUsersForWrite, - settings + enabled, + logExternalConfig, + logInternalConfig, + logReadMetadataOnly, + watchedReadFields, + ignoredComplianceUsersForRead, + logWriteMetadataOnly, + logDiffsForWrite, + watchedWriteIndicesPatterns, + ignoredComplianceUsersForWrite, + settings ); } @@ -250,47 +262,71 @@ public static ComplianceConfig from(Map properties, @JacksonInje * @return compliance configuration */ public static ComplianceConfig from(Settings settings) { - final boolean logExternalConfig = settings.getAsBoolean(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false); + final boolean logExternalConfig = settings.getAsBoolean( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, + false + ); final boolean logInternalConfig = settings.getAsBoolean(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, false); - final boolean logReadMetadataOnly = settings.getAsBoolean(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, false); - final boolean logWriteMetadataOnly = settings.getAsBoolean(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, false); - final boolean logDiffsForWrite = settings.getAsBoolean(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, false); - final List watchedReadFields = settings.getAsList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, - Collections.emptyList(), false); - //plugins.security.compliance.pii_fields: - // - indexpattern,fieldpattern,fieldpattern,.... + final boolean logReadMetadataOnly = settings.getAsBoolean( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, + false + ); + final boolean logWriteMetadataOnly = settings.getAsBoolean( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, + false + ); + final boolean logDiffsForWrite = settings.getAsBoolean( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, + false + ); + final List watchedReadFields = settings.getAsList( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, + Collections.emptyList(), + false + ); + // plugins.security.compliance.pii_fields: + // - indexpattern,fieldpattern,fieldpattern,.... final Map> readEnabledFields = watchedReadFields.stream() - .map(watchedReadField -> watchedReadField.split(",")) - .filter(split -> split.length != 0 && !Strings.isNullOrEmpty(split[0])) - .collect(ImmutableMap.toImmutableMap( - split -> split[0], - split -> split.length == 1 ? - ImmutableList.of("*") : Arrays.stream(split).skip(1).collect(ImmutableList.toImmutableList()) - )); - final List watchedWriteIndices = settings.getAsList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, Collections.emptyList()); + .map(watchedReadField -> watchedReadField.split(",")) + .filter(split -> split.length != 0 && !Strings.isNullOrEmpty(split[0])) + .collect( + ImmutableMap.toImmutableMap( + split -> split[0], + split -> split.length == 1 + ? ImmutableList.of("*") + : Arrays.stream(split).skip(1).collect(ImmutableList.toImmutableList()) + ) + ); + final List watchedWriteIndices = settings.getAsList( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, + Collections.emptyList() + ); final Set ignoredComplianceUsersForRead = ConfigConstants.getSettingAsSet( - settings, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, - AuditConfig.DEFAULT_IGNORED_USERS, - false); + settings, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, + AuditConfig.DEFAULT_IGNORED_USERS, + false + ); final Set ignoredComplianceUsersForWrite = ConfigConstants.getSettingAsSet( - settings, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, - AuditConfig.DEFAULT_IGNORED_USERS, - false); + settings, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, + AuditConfig.DEFAULT_IGNORED_USERS, + false + ); return new ComplianceConfig( - true, - logExternalConfig, - logInternalConfig, - logReadMetadataOnly, - readEnabledFields, - ignoredComplianceUsersForRead, - logWriteMetadataOnly, - logDiffsForWrite, - watchedWriteIndices, - ignoredComplianceUsersForWrite, - settings); + true, + logExternalConfig, + logInternalConfig, + logReadMetadataOnly, + readEnabledFields, + ignoredComplianceUsersForRead, + logWriteMetadataOnly, + logDiffsForWrite, + watchedWriteIndices, + ignoredComplianceUsersForWrite, + settings + ); } /** @@ -415,10 +451,11 @@ private Set getFieldsForIndex(String index) { } } - return readEnabledFields.entrySet().stream() - .filter(entry -> entry.getKey().test(index)) - .flatMap(entry -> entry.getValue().stream()) - .collect(ImmutableSet.toImmutableSet()); + return readEnabledFields.entrySet() + .stream() + .filter(entry -> entry.getKey().test(index)) + .flatMap(entry -> entry.getValue().stream()) + .collect(ImmutableSet.toImmutableSet()); } /** diff --git a/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListener.java b/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListener.java index b7803a4836..3d31111407 100644 --- a/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListener.java +++ b/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListener.java @@ -37,6 +37,6 @@ public class ComplianceIndexingOperationListener implements IndexingOperationListener { public void setIs(IndexService is) { - //noop + // noop } } diff --git a/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListenerImpl.java b/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListenerImpl.java index c8834c9e31..cf369bb0cb 100644 --- a/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListenerImpl.java +++ b/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListenerImpl.java @@ -40,7 +40,7 @@ public ComplianceIndexingOperationListenerImpl(final AuditLog auditlog) { @Override public void setIs(final IndexService is) { - if(this.is != null) { + if (this.is != null) { throw new OpenSearchException("Index service already set"); } this.is = is; @@ -66,7 +66,9 @@ public void postDelete(final ShardId shardId, final Delete delete, final DeleteR final ComplianceConfig complianceConfig = auditlog.getComplianceConfig(); if (isLoggingWriteEnabled(complianceConfig, shardId.getIndexName())) { Objects.requireNonNull(is); - if(result.getFailure() == null && result.isFound() && delete.origin() == org.opensearch.index.engine.Engine.Operation.Origin.PRIMARY) { + if (result.getFailure() == null + && result.isFound() + && delete.origin() == org.opensearch.index.engine.Engine.Operation.Origin.PRIMARY) { auditlog.logDocumentDeleted(shardId, delete, result); } } @@ -83,15 +85,14 @@ public Index preIndex(final ShardId shardId, final Index index) { return index; } - if((shard = is.getShardOrNull(shardId.getId())) == null) { + if ((shard = is.getShardOrNull(shardId.getId())) == null) { return index; } if (shard.isReadAllowed()) { try { - final GetResult getResult = shard.getService().getForUpdate(index.id(), - index.getIfSeqNo(), index.getIfPrimaryTerm()); + final GetResult getResult = shard.getService().getForUpdate(index.id(), index.getIfSeqNo(), index.getIfPrimaryTerm()); if (getResult.isExists()) { threadContext.set(new Context(getResult)); @@ -113,7 +114,6 @@ public Index preIndex(final ShardId shardId, final Index index) { return index; } - @Override public void postIndex(final ShardId shardId, final Index index, final Exception ex) { if (isLoggingWriteDiffEnabled(auditlog.getComplianceConfig(), shardId.getIndexName())) { @@ -130,7 +130,7 @@ public void postIndex(ShardId shardId, Index index, IndexResult result) { if (complianceConfig.shouldLogDiffsForWrite()) { final Context context = threadContext.get(); - final GetResult previousContent = context==null?null:context.getGetResult(); + final GetResult previousContent = context == null ? null : context.getGetResult(); threadContext.remove(); Objects.requireNonNull(is); @@ -138,26 +138,31 @@ public void postIndex(ShardId shardId, Index index, IndexResult result) { return; } - if(is.getShardOrNull(shardId.getId()) == null) { + if (is.getShardOrNull(shardId.getId()) == null) { return; } - if(previousContent == null) { - //no previous content - if(!result.isCreated()) { - log.warn("No previous content and not created (its an update but do not find orig source) for {}/{}/{}", index.startTime(), shardId, index.id()); + if (previousContent == null) { + // no previous content + if (!result.isCreated()) { + log.warn( + "No previous content and not created (its an update but do not find orig source) for {}/{}/{}", + index.startTime(), + shardId, + index.id() + ); } - assert result.isCreated():"No previous content and not created"; + assert result.isCreated() : "No previous content and not created"; } else { - if(result.isCreated()) { + if (result.isCreated()) { log.warn("Previous content and created for {}/{}/{}", index.startTime(), shardId, index.id()); } - assert !result.isCreated():"Previous content and created"; + assert !result.isCreated() : "Previous content and created"; } auditlog.logDocumentWritten(shardId, previousContent, index, result); } else { - //no diffs + // no diffs if (result.getFailure() != null || index.origin() != org.opensearch.index.engine.Engine.Operation.Origin.PRIMARY) { return; } diff --git a/src/main/java/org/opensearch/security/compliance/FieldReadCallback.java b/src/main/java/org/opensearch/security/compliance/FieldReadCallback.java index ff507c2a73..73f536c2f8 100644 --- a/src/main/java/org/opensearch/security/compliance/FieldReadCallback.java +++ b/src/main/java/org/opensearch/security/compliance/FieldReadCallback.java @@ -46,8 +46,8 @@ public final class FieldReadCallback { private static final Logger log = LogManager.getLogger(FieldReadCallback.class); - //private final ThreadContext threadContext; - //private final ClusterService clusterService; + // private final ThreadContext threadContext; + // private final ClusterService clusterService; private final Index index; private final WildcardMatcher maskedFieldsMatcher; private final AuditLog auditLog; @@ -56,19 +56,24 @@ public final class FieldReadCallback { private Doc doc; private final ShardId shardId; - public FieldReadCallback(final ThreadContext threadContext, final IndexService indexService, - final ClusterService clusterService, final AuditLog auditLog, - final WildcardMatcher maskedFieldsMatcher, ShardId shardId) { + public FieldReadCallback( + final ThreadContext threadContext, + final IndexService indexService, + final ClusterService clusterService, + final AuditLog auditLog, + final WildcardMatcher maskedFieldsMatcher, + ShardId shardId + ) { super(); - //this.threadContext = Objects.requireNonNull(threadContext); - //this.clusterService = Objects.requireNonNull(clusterService); + // this.threadContext = Objects.requireNonNull(threadContext); + // this.clusterService = Objects.requireNonNull(clusterService); this.index = Objects.requireNonNull(indexService).index(); this.auditLog = auditLog; this.maskedFieldsMatcher = maskedFieldsMatcher; this.shardId = shardId; try { sfc = (SourceFieldsContext) HeaderHelper.deserializeSafeFromHeader(threadContext, "_opendistro_security_source_field_context"); - if(sfc != null && sfc.hasIncludesOrExcludes()) { + if (sfc != null && sfc.hasIncludesOrExcludes()) { if (log.isTraceEnabled()) { log.trace("_opendistro_security_source_field_context: {}", sfc); } @@ -76,39 +81,40 @@ public FieldReadCallback(final ThreadContext threadContext, final IndexService i filterFunction = XContentMapValues.filter(sfc.getIncludes(), sfc.getExcludes()); } } catch (Exception e) { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("Cannot deserialize _opendistro_security_source_field_context because of {}", e.toString()); } } } private boolean recordField(final String fieldName, boolean isStringField) { - return !(isStringField && maskedFieldsMatcher.test(fieldName)) && auditLog.getComplianceConfig().readHistoryEnabledForField(index.getName(), fieldName); + return !(isStringField && maskedFieldsMatcher.test(fieldName)) + && auditLog.getComplianceConfig().readHistoryEnabledForField(index.getName(), fieldName); } public void binaryFieldRead(final FieldInfo fieldInfo, byte[] fieldValue) { try { - if(!recordField(fieldInfo.name, false) && !fieldInfo.name.equals("_source") && !fieldInfo.name.equals("_id")) { + if (!recordField(fieldInfo.name, false) && !fieldInfo.name.equals("_source") && !fieldInfo.name.equals("_id")) { return; } - if(fieldInfo.name.equals("_source")) { + if (fieldInfo.name.equals("_source")) { - if(filterFunction != null) { + if (filterFunction != null) { final Map filteredSource = filterFunction.apply(Utils.byteArrayToMutableJsonMap(fieldValue)); fieldValue = Utils.jsonMapToByteArray(filteredSource); } Map filteredSource = new JsonFlattener(new String(fieldValue, StandardCharsets.UTF_8)).flattenAsMap(); - for(String k: filteredSource.keySet()) { - if(!recordField(k, filteredSource.get(k) instanceof String)) { + for (String k : filteredSource.keySet()) { + if (!recordField(k, filteredSource.get(k) instanceof String)) { continue; } fieldRead0(k, filteredSource.get(k)); } } else if (fieldInfo.name.equals("_id")) { fieldRead0(fieldInfo.name, Uid.decodeId(fieldValue)); - } else { + } else { fieldRead0(fieldInfo.name, new String(fieldValue, StandardCharsets.UTF_8)); } } catch (Exception e) { @@ -118,7 +124,7 @@ public void binaryFieldRead(final FieldInfo fieldInfo, byte[] fieldValue) { public void stringFieldRead(final FieldInfo fieldInfo, final String fieldValue) { try { - if(!recordField(fieldInfo.name, true)) { + if (!recordField(fieldInfo.name, true)) { return; } fieldRead0(fieldInfo.name, fieldValue); @@ -129,7 +135,7 @@ public void stringFieldRead(final FieldInfo fieldInfo, final String fieldValue) public void numericFieldRead(final FieldInfo fieldInfo, final Number fieldValue) { try { - if(!recordField(fieldInfo.name, false)) { + if (!recordField(fieldInfo.name, false)) { return; } fieldRead0(fieldInfo.name, fieldValue); @@ -139,15 +145,15 @@ public void numericFieldRead(final FieldInfo fieldInfo, final Number fieldValue) } private void fieldRead0(final String fieldName, final Object fieldValue) { - if(doc != null) { - if(fieldName.equals("_id")) { + if (doc != null) { + if (fieldName.equals("_id")) { doc.setId(fieldValue.toString()); } else { doc.addField(new Field(fieldName, fieldValue)); } } else { final String indexName = index.getName(); - if(fieldName.equals("_id")) { + if (fieldName.equals("_id")) { doc = new Doc(indexName, fieldValue.toString()); } else { doc = new Doc(indexName, null); @@ -157,12 +163,12 @@ private void fieldRead0(final String fieldName, final Object fieldValue) { } public void finished() { - if(doc == null) { + if (doc == null) { return; } try { Map f = new HashMap(); - for(Field fi: doc.fields) { + for (Field fi : doc.fields) { f.put(fi.fieldName, String.valueOf(fi.fieldValue)); } auditLog.logDocumentRead(doc.indexName, doc.id, shardId, f); @@ -202,11 +208,13 @@ public String toString() { private class Field { final String fieldName; final Object fieldValue; + public Field(String fieldName, Object fieldValue) { super(); this.fieldName = fieldName; this.fieldValue = fieldValue; } + @Override public String toString() { return "Field [fieldName=" + fieldName + ", fieldValue=" + fieldValue + "]"; diff --git a/src/main/java/org/opensearch/security/configuration/AdminDNs.java b/src/main/java/org/opensearch/security/configuration/AdminDNs.java index 72a4485e9f..204f277808 100644 --- a/src/main/java/org/opensearch/security/configuration/AdminDNs.java +++ b/src/main/java/org/opensearch/security/configuration/AdminDNs.java @@ -63,7 +63,7 @@ public AdminDNs(final Settings settings) { final List adminDnsA = settings.getAsList(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, Collections.emptyList()); - for (String dn:adminDnsA) { + for (String dn : adminDnsA) { try { log.debug("{} is registered as an admin dn", dn); adminDn.add(new LdapName(dn)); @@ -75,16 +75,17 @@ public AdminDNs(final Settings settings) { } adminUsernames.add(dn); } else { - log.error("Unable to parse admin dn {}",dn, e); + log.error("Unable to parse admin dn {}", dn, e); } } } - log.debug("Loaded {} admin DN's {}",adminDn.size(), adminDn); + log.debug("Loaded {} admin DN's {}", adminDn.size(), adminDn); - final Settings impersonationDns = settings.getByPrefix(ConfigConstants.SECURITY_AUTHCZ_IMPERSONATION_DN+"."); + final Settings impersonationDns = settings.getByPrefix(ConfigConstants.SECURITY_AUTHCZ_IMPERSONATION_DN + "."); - allowedDnsImpersonations = impersonationDns.keySet().stream() + allowedDnsImpersonations = impersonationDns.keySet() + .stream() .map(this::toLdapName) .filter(Objects::nonNull) .collect( @@ -96,17 +97,18 @@ public AdminDNs(final Settings settings) { log.debug("Loaded {} impersonation DN's {}", allowedDnsImpersonations.size(), allowedDnsImpersonations); - final Settings impersonationUsersRest = settings.getByPrefix(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+"."); + final Settings impersonationUsersRest = settings.getByPrefix(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + "."); - allowedRestImpersonations = impersonationUsersRest.keySet().stream() + allowedRestImpersonations = impersonationUsersRest.keySet() + .stream() .collect( ImmutableMap.toImmutableMap( Function.identity(), - user -> WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+"."+user)) + user -> WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + "." + user)) ) ); - log.debug("Loaded {} impersonation users for REST {}",allowedRestImpersonations.size(), allowedRestImpersonations); + log.debug("Loaded {} impersonation users for REST {}", allowedRestImpersonations.size(), allowedRestImpersonations); } private LdapName toLdapName(String dn) { @@ -132,17 +134,17 @@ public boolean isAdmin(User user) { public boolean isAdminDN(String dn) { - if(dn == null) return false; + if (dn == null) return false; try { return isAdminDN(new LdapName(dn)); } catch (InvalidNameException e) { - return false; + return false; } } private boolean isAdminDN(LdapName dn) { - if(dn == null) return false; + if (dn == null) return false; boolean isAdmin = adminDn.contains(dn); @@ -154,6 +156,8 @@ private boolean isAdminDN(LdapName dn) { } public boolean isRestImpersonationAllowed(final String originalUser, final String impersonated) { - return (originalUser != null) ? allowedRestImpersonations.getOrDefault(originalUser, WildcardMatcher.NONE).test(impersonated) : false; + return (originalUser != null) + ? allowedRestImpersonations.getOrDefault(originalUser, WildcardMatcher.NONE).test(impersonated) + : false; } } diff --git a/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java b/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java index 1c42321986..00101d9a73 100644 --- a/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java +++ b/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java @@ -43,7 +43,7 @@ public class ClusterInfoHolder implements ClusterStateListener { @Override public void clusterChanged(ClusterChangedEvent event) { - if(nodes == null || event.nodesChanged()) { + if (nodes == null || event.nodesChanged()) { nodes = event.state().nodes(); if (log.isDebugEnabled()) { log.debug("Cluster Info Holder now initialized for 'nodes'"); @@ -51,7 +51,7 @@ public void clusterChanged(ClusterChangedEvent event) { initialized = true; } - isLocalNodeElectedClusterManager = event.localNodeClusterManager()?Boolean.TRUE:Boolean.FALSE; + isLocalNodeElectedClusterManager = event.localNodeClusterManager() ? Boolean.TRUE : Boolean.FALSE; } public Boolean isLocalNodeElectedClusterManager() { @@ -63,13 +63,13 @@ public boolean isInitialized() { } public Boolean hasNode(DiscoveryNode node) { - if(nodes == null) { - if(log.isDebugEnabled()) { + if (nodes == null) { + if (log.isDebugEnabled()) { log.debug("Cluster Info Holder not initialized yet for 'nodes'"); } return null; } - return nodes.nodeExists(node)?Boolean.TRUE:Boolean.FALSE; + return nodes.nodeExists(node) ? Boolean.TRUE : Boolean.FALSE; } } diff --git a/src/main/java/org/opensearch/security/configuration/CompatConfig.java b/src/main/java/org/opensearch/security/configuration/CompatConfig.java index 48f91b10be..ec2a521afe 100644 --- a/src/main/java/org/opensearch/security/configuration/CompatConfig.java +++ b/src/main/java/org/opensearch/security/configuration/CompatConfig.java @@ -57,12 +57,15 @@ public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { log.debug("dynamicSecurityConfig updated?: {}", (dcm != null)); } - //true is default + // true is default public boolean restAuthEnabled() { - final boolean restInitiallyDisabled = staticSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY, false); + final boolean restInitiallyDisabled = staticSettings.getAsBoolean( + ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY, + false + ); final boolean isTraceEnabled = log.isTraceEnabled(); - if(restInitiallyDisabled) { - if(dcm == null) { + if (restInitiallyDisabled) { + if (dcm == null) { if (isTraceEnabled) { log.trace("dynamicSecurityConfig is null, initially static restDisabled"); } @@ -80,12 +83,15 @@ public boolean restAuthEnabled() { } - //true is default + // true is default public boolean transportInterClusterAuthEnabled() { - final boolean interClusterAuthInitiallyDisabled = staticSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY, false); + final boolean interClusterAuthInitiallyDisabled = staticSettings.getAsBoolean( + ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY, + false + ); final boolean isTraceEnabled = log.isTraceEnabled(); - if(interClusterAuthInitiallyDisabled) { - if(dcm == null) { + if (interClusterAuthInitiallyDisabled) { + if (dcm == null) { if (isTraceEnabled) { log.trace("dynamicSecurityConfig is null, initially static interClusterAuthDisabled"); } @@ -107,7 +113,7 @@ public boolean transportInterClusterAuthEnabled() { */ public boolean transportInterClusterPassiveAuthEnabled() { final boolean interClusterAuthInitiallyPassive = transportPassiveAuthSetting.getDynamicSettingValue(); - if(log.isTraceEnabled()) { + if (log.isTraceEnabled()) { log.trace("{} {}", SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY, interClusterAuthInitiallyPassive); } return interClusterAuthInitiallyPassive; diff --git a/src/main/java/org/opensearch/security/configuration/ConfigCallback.java b/src/main/java/org/opensearch/security/configuration/ConfigCallback.java index cb8fc1eedf..a3b4cff90d 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigCallback.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigCallback.java @@ -32,8 +32,11 @@ public interface ConfigCallback { void success(SecurityDynamicConfiguration dConf); + void noData(String id); + void singleFailure(Failure failure); + void failure(Throwable t); } diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java b/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java index 3019c76462..f7802fd10e 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java @@ -78,7 +78,10 @@ public class ConfigurationLoaderSecurity7 { super(); this.client = client; this.settings = settings; - this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + this.securityIndex = settings.get( + ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + ); this.cs = cs; log.debug("Index is: {}", securityIndex); } @@ -91,7 +94,8 @@ boolean isAuditConfigDocPresentInIndex() { return isAuditConfigDocPresentInIndex.get(); } - Map> load(final CType[] events, long timeout, TimeUnit timeUnit, boolean acceptInvalid) throws InterruptedException, TimeoutException { + Map> load(final CType[] events, long timeout, TimeUnit timeUnit, boolean acceptInvalid) + throws InterruptedException, TimeoutException { final CountDownLatch latch = new CountDownLatch(events.length); final Map> rs = new HashMap<>(events.length); final boolean isDebugEnabled = log.isDebugEnabled(); @@ -99,8 +103,13 @@ Map> load(final CType[] events, long time @Override public void success(SecurityDynamicConfiguration dConf) { - if(latch.getCount() <= 0) { - log.error("Latch already counted down (for {} of {}) (index={})", dConf.getCType().toLCString(), Arrays.toString(events), securityIndex); + if (latch.getCount() <= 0) { + log.error( + "Latch already counted down (for {} of {}) (index={})", + dConf.getCType().toLCString(), + Arrays.toString(events), + securityIndex + ); } // Audit configuration doc is available in the index. @@ -112,25 +121,39 @@ public void success(SecurityDynamicConfiguration dConf) { rs.put(dConf.getCType(), dConf); latch.countDown(); if (isDebugEnabled) { - log.debug("Received config for {} (of {}) with current latch value={}", dConf.getCType().toLCString(), Arrays.toString(events), latch.getCount()); + log.debug( + "Received config for {} (of {}) with current latch value={}", + dConf.getCType().toLCString(), + Arrays.toString(events), + latch.getCount() + ); } } @Override public void singleFailure(Failure failure) { - log.error("Failure {} retrieving configuration for {} (index={})", failure==null?null:failure.getMessage(), Arrays.toString(events), securityIndex); + log.error( + "Failure {} retrieving configuration for {} (index={})", + failure == null ? null : failure.getMessage(), + Arrays.toString(events), + securityIndex + ); } @Override public void noData(String id) { CType cType = CType.fromString(id); - // Since NODESDN is newly introduced data-type applying for existing clusters as well, we make it backward compatible by returning valid empty + // Since NODESDN is newly introduced data-type applying for existing clusters as well, we make it backward compatible by + // returning valid empty // SecurityDynamicConfiguration. // Same idea for new setting WHITELIST/ALLOWLIST if (cType == CType.NODESDN || cType == CType.WHITELIST || cType == CType.ALLOWLIST) { try { - SecurityDynamicConfiguration empty = ConfigHelper.createEmptySdc(cType, ConfigurationRepository.getDefaultConfigVersion()); + SecurityDynamicConfiguration empty = ConfigHelper.createEmptySdc( + cType, + ConfigurationRepository.getDefaultConfigVersion() + ); rs.put(cType, empty); latch.countDown(); return; @@ -139,12 +162,15 @@ public void noData(String id) { } } - if(cType == CType.AUDIT) { + if (cType == CType.AUDIT) { // Audit configuration doc is not available in the index. // Configuration cannot be hot-reloaded. isAuditConfigDocPresentInIndex.set(false); try { - SecurityDynamicConfiguration empty = ConfigHelper.createEmptySdc(cType, ConfigurationRepository.getDefaultConfigVersion()); + SecurityDynamicConfiguration empty = ConfigHelper.createEmptySdc( + cType, + ConfigurationRepository.getDefaultConfigVersion() + ); empty.putCObject("config", AuditConfig.from(settings)); rs.put(cType, empty); latch.countDown(); @@ -163,16 +189,26 @@ public void failure(Throwable t) { } }, acceptInvalid); - if(!latch.await(timeout, timeUnit)) { - //timeout - throw new TimeoutException("Timeout after "+timeout+""+timeUnit+" while retrieving configuration for "+Arrays.toString(events)+ "(index="+securityIndex+")"); + if (!latch.await(timeout, timeUnit)) { + // timeout + throw new TimeoutException( + "Timeout after " + + timeout + + "" + + timeUnit + + " while retrieving configuration for " + + Arrays.toString(events) + + "(index=" + + securityIndex + + ")" + ); } return rs; } void loadAsync(final CType[] events, final ConfigCallback callback, boolean acceptInvalid) { - if(events == null || events.length == 0) { + if (events == null || events.length == 0) { log.warn("No config events requested to load"); return; } @@ -193,28 +229,28 @@ public void onResponse(MultiGetResponse response) { MultiGetItemResponse[] responses = response.getResponses(); for (int i = 0; i < responses.length; i++) { MultiGetItemResponse singleResponse = responses[i]; - if(singleResponse != null && !singleResponse.isFailed()) { + if (singleResponse != null && !singleResponse.isFailed()) { GetResponse singleGetResponse = singleResponse.getResponse(); - if(singleGetResponse.isExists() && !singleGetResponse.isSourceEmpty()) { - //success + if (singleGetResponse.isExists() && !singleGetResponse.isSourceEmpty()) { + // success try { final SecurityDynamicConfiguration dConf = toConfig(singleGetResponse, acceptInvalid); - if(dConf != null) { + if (dConf != null) { callback.success(dConf.deepClone()); } else { - callback.failure(new Exception("Cannot parse settings for "+singleGetResponse.getId())); + callback.failure(new Exception("Cannot parse settings for " + singleGetResponse.getId())); } } catch (Exception e) { log.error(e.toString()); callback.failure(e); } } else { - //does not exist or empty source + // does not exist or empty source callback.noData(singleGetResponse.getId()); } } else { - //failure - callback.singleFailure(singleResponse==null?null:singleResponse.getFailure()); + // failure + callback.singleFailure(singleResponse == null ? null : singleResponse.getFailure()); } } } @@ -233,8 +269,6 @@ private SecurityDynamicConfiguration toConfig(GetResponse singleGetResponse, final long seqNo = singleGetResponse.getSeqNo(); final long primaryTerm = singleGetResponse.getPrimaryTerm(); - - if (ref == null || ref.length() == 0) { log.error("Empty or null byte reference for {}", id); return null; @@ -247,7 +281,7 @@ private SecurityDynamicConfiguration toConfig(GetResponse singleGetResponse, parser.nextToken(); parser.nextToken(); - if(!id.equals((parser.currentName()))) { + if (!id.equals((parser.currentName()))) { log.error("Cannot parse config for type {} because {}!={}", id, id, parser.currentName()); return null; } @@ -258,35 +292,47 @@ private SecurityDynamicConfiguration toConfig(GetResponse singleGetResponse, final JsonNode jsonNode = DefaultObjectMapper.readTree(jsonAsString); int configVersion = 1; - - - if(jsonNode.get("_meta") != null) { + if (jsonNode.get("_meta") != null) { assert jsonNode.get("_meta").get("type").asText().equals(id); configVersion = jsonNode.get("_meta").get("config_version").asInt(); } - if(log.isDebugEnabled()) { - log.debug("Load "+id+" with version "+configVersion); + if (log.isDebugEnabled()) { + log.debug("Load " + id + " with version " + configVersion); } if (CType.ACTIONGROUPS.toLCString().equals(id)) { try { - return SecurityDynamicConfiguration.fromJson(jsonAsString, CType.fromString(id), configVersion, seqNo, primaryTerm, acceptInvalid); + return SecurityDynamicConfiguration.fromJson( + jsonAsString, + CType.fromString(id), + configVersion, + seqNo, + primaryTerm, + acceptInvalid + ); } catch (Exception e) { - if(log.isDebugEnabled()) { - log.debug("Unable to load "+id+" with version "+configVersion+" - Try loading legacy format ..."); + if (log.isDebugEnabled()) { + log.debug("Unable to load " + id + " with version " + configVersion + " - Try loading legacy format ..."); } return SecurityDynamicConfiguration.fromJson(jsonAsString, CType.fromString(id), 0, seqNo, primaryTerm, acceptInvalid); } } - return SecurityDynamicConfiguration.fromJson(jsonAsString, CType.fromString(id), configVersion, seqNo, primaryTerm, acceptInvalid); + return SecurityDynamicConfiguration.fromJson( + jsonAsString, + CType.fromString(id), + configVersion, + seqNo, + primaryTerm, + acceptInvalid + ); } finally { - if(parser != null) { + if (parser != null) { try { parser.close(); } catch (IOException e) { - //ignore + // ignore } } } diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java index 31e2a7d16f..92ae22af01 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java @@ -95,9 +95,18 @@ public class ConfigurationRepository { private final AtomicBoolean installDefaultConfig = new AtomicBoolean(); private final boolean acceptInvalid; - private ConfigurationRepository(Settings settings, final Path configPath, ThreadPool threadPool, - Client client, ClusterService clusterService, AuditLog auditLog) { - this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + private ConfigurationRepository( + Settings settings, + final Path configPath, + ThreadPool threadPool, + Client client, + ClusterService clusterService, + AuditLog auditLog + ) { + this.securityIndex = settings.get( + ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + ); this.settings = settings; this.client = client; this.threadPool = threadPool; @@ -107,45 +116,90 @@ private ConfigurationRepository(Settings settings, final Path configPath, Thread this.acceptInvalid = settings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG, false); cl = new ConfigurationLoaderSecurity7(client, threadPool, settings, clusterService); - configCache = CacheBuilder - .newBuilder() - .build(); + configCache = CacheBuilder.newBuilder().build(); bgThread = new Thread(() -> { try { - LOGGER.info("Background init thread started. Install default config?: "+installDefaultConfig.get()); + LOGGER.info("Background init thread started. Install default config?: " + installDefaultConfig.get()); // wait for the cluster here until it will finish managed node election while (clusterService.state().blocks().hasGlobalBlockWithStatus(RestStatus.SERVICE_UNAVAILABLE)) { LOGGER.info("Wait for cluster to be available ..."); TimeUnit.SECONDS.sleep(1); } - if(installDefaultConfig.get()) { + if (installDefaultConfig.get()) { try { String lookupDir = System.getProperty("security.default_init.dir"); - final String cd = lookupDir != null? (lookupDir+"/") : new Environment(settings, configPath).configDir().toAbsolutePath().toString()+"/opensearch-security/"; - File confFile = new File(cd+"config.yml"); - if(confFile.exists()) { + final String cd = lookupDir != null + ? (lookupDir + "/") + : new Environment(settings, configPath).configDir().toAbsolutePath().toString() + "/opensearch-security/"; + File confFile = new File(cd + "config.yml"); + if (confFile.exists()) { final ThreadContext threadContext = threadPool.getThreadContext(); - try(StoredContext ctx = threadContext.stashContext()) { + try (StoredContext ctx = threadContext.stashContext()) { threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); createSecurityIndexIfAbsent(); waitForSecurityIndexToBeAtLeastYellow(); - ConfigHelper.uploadFile(client, cd+"config.yml", securityIndex, CType.CONFIG, DEFAULT_CONFIG_VERSION); - ConfigHelper.uploadFile(client, cd+"roles.yml", securityIndex, CType.ROLES, DEFAULT_CONFIG_VERSION); - ConfigHelper.uploadFile(client, cd+"roles_mapping.yml", securityIndex, CType.ROLESMAPPING, DEFAULT_CONFIG_VERSION); - ConfigHelper.uploadFile(client, cd+"internal_users.yml", securityIndex, CType.INTERNALUSERS, DEFAULT_CONFIG_VERSION); - ConfigHelper.uploadFile(client, cd+"action_groups.yml", securityIndex, CType.ACTIONGROUPS, DEFAULT_CONFIG_VERSION); - if(DEFAULT_CONFIG_VERSION == 2) { - ConfigHelper.uploadFile(client, cd+"tenants.yml", securityIndex, CType.TENANTS, DEFAULT_CONFIG_VERSION); + ConfigHelper.uploadFile(client, cd + "config.yml", securityIndex, CType.CONFIG, DEFAULT_CONFIG_VERSION); + ConfigHelper.uploadFile(client, cd + "roles.yml", securityIndex, CType.ROLES, DEFAULT_CONFIG_VERSION); + ConfigHelper.uploadFile( + client, + cd + "roles_mapping.yml", + securityIndex, + CType.ROLESMAPPING, + DEFAULT_CONFIG_VERSION + ); + ConfigHelper.uploadFile( + client, + cd + "internal_users.yml", + securityIndex, + CType.INTERNALUSERS, + DEFAULT_CONFIG_VERSION + ); + ConfigHelper.uploadFile( + client, + cd + "action_groups.yml", + securityIndex, + CType.ACTIONGROUPS, + DEFAULT_CONFIG_VERSION + ); + if (DEFAULT_CONFIG_VERSION == 2) { + ConfigHelper.uploadFile( + client, + cd + "tenants.yml", + securityIndex, + CType.TENANTS, + DEFAULT_CONFIG_VERSION + ); } final boolean populateEmptyIfFileMissing = true; - ConfigHelper.uploadFile(client, cd+"nodes_dn.yml", securityIndex, CType.NODESDN, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); - ConfigHelper.uploadFile(client, cd + "whitelist.yml", securityIndex, CType.WHITELIST, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); - ConfigHelper.uploadFile(client, cd + "allowlist.yml", securityIndex, CType.ALLOWLIST, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); + ConfigHelper.uploadFile( + client, + cd + "nodes_dn.yml", + securityIndex, + CType.NODESDN, + DEFAULT_CONFIG_VERSION, + populateEmptyIfFileMissing + ); + ConfigHelper.uploadFile( + client, + cd + "whitelist.yml", + securityIndex, + CType.WHITELIST, + DEFAULT_CONFIG_VERSION, + populateEmptyIfFileMissing + ); + ConfigHelper.uploadFile( + client, + cd + "allowlist.yml", + securityIndex, + CType.ALLOWLIST, + DEFAULT_CONFIG_VERSION, + populateEmptyIfFileMissing + ); // audit.yml is not packaged by default final String auditConfigPath = cd + "audit.yml"; @@ -161,7 +215,7 @@ private ConfigurationRepository(Settings settings, final Path configPath, Thread } } - while(!dynamicConfigFactory.isInitialized()) { + while (!dynamicConfigFactory.isInitialized()) { try { LOGGER.debug("Try to load config ..."); reloadConfiguration(Arrays.asList(CType.values())); @@ -180,7 +234,10 @@ private ConfigurationRepository(Settings settings, final Path configPath, Thread final Set deprecatedAuditKeysInSettings = AuditConfig.getDeprecatedKeys(settings); if (!deprecatedAuditKeysInSettings.isEmpty()) { - LOGGER.warn("Following keys {} are deprecated in opensearch settings. They will be removed in plugin v2.0.0.0", deprecatedAuditKeysInSettings); + LOGGER.warn( + "Following keys {} are deprecated in opensearch settings. They will be removed in plugin v2.0.0.0", + deprecatedAuditKeysInSettings + ); } final boolean isAuditConfigDocPresentInIndex = cl.isAuditConfigDocPresentInIndex(); if (isAuditConfigDocPresentInIndex) { @@ -189,14 +246,16 @@ private ConfigurationRepository(Settings settings, final Path configPath, Thread } LOGGER.info("Hot-reloading of audit configuration is enabled"); } else { - LOGGER.info("Hot-reloading of audit configuration is disabled. Using configuration with defaults from opensearch settings. Populate the configuration in index using audit.yml or securityadmin to enable it."); + LOGGER.info( + "Hot-reloading of audit configuration is disabled. Using configuration with defaults from opensearch settings. Populate the configuration in index using audit.yml or securityadmin to enable it." + ); auditLog.setConfig(AuditConfig.from(settings)); } LOGGER.info("Node '{}' initialized", clusterService.localNode().getName()); } catch (Exception e) { - LOGGER.error("Unexpected exception while initializing node "+e, e); + LOGGER.error("Unexpected exception while initializing node " + e, e); } }); @@ -204,17 +263,9 @@ private ConfigurationRepository(Settings settings, final Path configPath, Thread private boolean createSecurityIndexIfAbsent() { try { - final Map indexSettings = ImmutableMap.of( - "index.number_of_shards", 1, - "index.auto_expand_replicas", "0-all" - ); - final CreateIndexRequest createIndexRequest = new CreateIndexRequest(securityIndex) - .settings(indexSettings); - final boolean ok = client.admin() - .indices() - .create(createIndexRequest) - .actionGet() - .isAcknowledged(); + final Map indexSettings = ImmutableMap.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all"); + final CreateIndexRequest createIndexRequest = new CreateIndexRequest(securityIndex).settings(indexSettings); + final boolean ok = client.admin().indices().create(createIndexRequest).actionGet().isAcknowledged(); LOGGER.info("Index {} created?: {}", securityIndex, ok); return ok; } catch (ResourceAlreadyExistsException resourceAlreadyExistsException) { @@ -227,19 +278,24 @@ private void waitForSecurityIndexToBeAtLeastYellow() { LOGGER.info("Node started, try to initialize it. Wait for at least yellow cluster state...."); ClusterHealthResponse response = null; try { - response = client.admin().cluster().health(new ClusterHealthRequest(securityIndex) - .waitForActiveShards(1) - .waitForYellowStatus()).actionGet(); + response = client.admin() + .cluster() + .health(new ClusterHealthRequest(securityIndex).waitForActiveShards(1).waitForYellowStatus()) + .actionGet(); } catch (Exception e) { LOGGER.debug("Caught a {} but we just try again ...", e.toString()); } - while(response == null || response.isTimedOut() || response.getStatus() == ClusterHealthStatus.RED) { - LOGGER.debug("index '{}' not healthy yet, we try again ... (Reason: {})", securityIndex, response==null?"no response":(response.isTimedOut()?"timeout":"other, maybe red cluster")); + while (response == null || response.isTimedOut() || response.getStatus() == ClusterHealthStatus.RED) { + LOGGER.debug( + "index '{}' not healthy yet, we try again ... (Reason: {})", + securityIndex, + response == null ? "no response" : (response.isTimedOut() ? "timeout" : "other, maybe red cluster") + ); try { Thread.sleep(500); } catch (InterruptedException e) { - //ignore + // ignore Thread.currentThread().interrupt(); } try { @@ -256,13 +312,17 @@ public void initOnNodeStart() { LOGGER.info("Will attempt to create index {} and default configs if they are absent", securityIndex); installDefaultConfig.set(true); bgThread.start(); - } else if (settings.getAsBoolean(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, true)){ - LOGGER.info("Will not attempt to create index {} and default configs if they are absent. Use securityadmin to initialize cluster", - securityIndex); + } else if (settings.getAsBoolean(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, true)) { + LOGGER.info( + "Will not attempt to create index {} and default configs if they are absent. Use securityadmin to initialize cluster", + securityIndex + ); bgThread.start(); } else { - LOGGER.info("Will not attempt to create index {} and default configs if they are absent. Will not perform background initialization", - securityIndex); + LOGGER.info( + "Will not attempt to create index {} and default configs if they are absent. Will not perform background initialization", + securityIndex + ); } } catch (Throwable e2) { LOGGER.error("Error during node initialization: {}", e2, e2); @@ -274,9 +334,22 @@ public boolean isAuditHotReloadingEnabled() { return cl.isAuditConfigDocPresentInIndex(); } - public static ConfigurationRepository create(Settings settings, final Path configPath, final ThreadPool threadPool, - Client client, ClusterService clusterService, AuditLog auditLog) { - final ConfigurationRepository repository = new ConfigurationRepository(settings, configPath, threadPool, client, clusterService, auditLog); + public static ConfigurationRepository create( + Settings settings, + final Path configPath, + final ThreadPool threadPool, + Client client, + ClusterService clusterService, + AuditLog auditLog + ) { + final ConfigurationRepository repository = new ConfigurationRepository( + settings, + configPath, + threadPool, + client, + clusterService, + auditLog + ); return repository; } @@ -290,8 +363,8 @@ public void setDynamicConfigFactory(DynamicConfigFactory dynamicConfigFactory) { * @return can also return empty in case it was never loaded */ public SecurityDynamicConfiguration getConfiguration(CType configurationType) { - SecurityDynamicConfiguration conf= configCache.getIfPresent(configurationType); - if(conf != null) { + SecurityDynamicConfiguration conf = configCache.getIfPresent(configurationType); + if (conf != null) { return conf.deepClone(); } return SecurityDynamicConfiguration.empty(); @@ -316,7 +389,6 @@ public void reloadConfiguration(Collection configTypes) throws ConfigUpda } } - private void reloadConfiguration0(Collection configTypes, boolean acceptInvalid) { final Map> loaded = getConfigurationsFromIndex(configTypes, false, acceptInvalid); configCache.putAll(loaded); @@ -333,7 +405,7 @@ private synchronized void notifyAboutChanges(Map> getConfigurationsFromIndex(Collection configTypes, boolean logComplianceEvent) { + public Map> getConfigurationsFromIndex( + Collection configTypes, + boolean logComplianceEvent + ) { return getConfigurationsFromIndex(configTypes, logComplianceEvent, this.acceptInvalid); } - public Map> getConfigurationsFromIndex(Collection configTypes, boolean logComplianceEvent, boolean acceptInvalid) { + public Map> getConfigurationsFromIndex( + Collection configTypes, + boolean logComplianceEvent, + boolean acceptInvalid + ) { final ThreadContext threadContext = threadPool.getThreadContext(); final Map> retVal = new HashMap<>(); - try(StoredContext ctx = threadContext.stashContext()) { + try (StoredContext ctx = threadContext.stashContext()) { threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); IndexMetadata securityMetadata = clusterService.state().metadata().index(this.securityIndex); - MappingMetadata mappingMetadata = securityMetadata==null?null:securityMetadata.mapping(); + MappingMetadata mappingMetadata = securityMetadata == null ? null : securityMetadata.mapping(); if (securityMetadata != null && mappingMetadata != null) { - if("security".equals(mappingMetadata.type())) { + if ("security".equals(mappingMetadata.type())) { LOGGER.debug("security index exists and was created before ES 7 (legacy layout)"); } else { LOGGER.debug("security index exists and was created with ES 7 (new layout)"); } - retVal.putAll(validate(cl.load(configTypes.toArray(new CType[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size())); - + retVal.putAll( + validate(cl.load(configTypes.toArray(new CType[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size()) + ); } else { - //wait (and use new layout) + // wait (and use new layout) LOGGER.debug("security index not exists (yet)"); - retVal.putAll(validate(cl.load(configTypes.toArray(new CType[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size())); + retVal.putAll( + validate(cl.load(configTypes.toArray(new CType[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size()) + ); } } catch (Exception e) { @@ -389,9 +471,10 @@ public Map> getConfigurationsFromIndex(Co return retVal; } - private Map> validate(Map> conf, int expectedSize) throws InvalidConfigException { + private Map> validate(Map> conf, int expectedSize) + throws InvalidConfigException { - if(conf == null || conf.size() != expectedSize) { + if (conf == null || conf.size() != expectedSize) { throw new InvalidConfigException("Retrieved only partial configuration"); } diff --git a/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java b/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java index 4bb27a6cbb..fa1c4989e0 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java @@ -68,19 +68,32 @@ public class DlsFilterLevelActionHandler { private static final Logger log = LogManager.getLogger(DlsFilterLevelActionHandler.class); - private static final Function LOCAL_CLUSTER_ALIAS_GETTER = ReflectiveAttributeAccessors - .protectedObjectAttr("localClusterAlias", String.class); - - public static boolean handle(String action, ActionRequest request, ActionListener listener, EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, - Resolved resolved, Client nodeClient, ClusterService clusterService, IndicesService indicesService, - IndexNameExpressionResolver resolver, DlsQueryParser dlsQueryParser, ThreadContext threadContext) { + private static final Function LOCAL_CLUSTER_ALIAS_GETTER = ReflectiveAttributeAccessors.protectedObjectAttr( + "localClusterAlias", + String.class + ); + + public static boolean handle( + String action, + ActionRequest request, + ActionListener listener, + EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, + Resolved resolved, + Client nodeClient, + ClusterService clusterService, + IndicesService indicesService, + IndexNameExpressionResolver resolver, + DlsQueryParser dlsQueryParser, + ThreadContext threadContext + ) { if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_FILTER_LEVEL_DLS_DONE) != null) { return true; } - if (action.startsWith("cluster:") || action.startsWith("indices:admin/template/") - || action.startsWith("indices:admin/index_template/")) { + if (action.startsWith("cluster:") + || action.startsWith("indices:admin/template/") + || action.startsWith("indices:admin/index_template/")) { return true; } @@ -98,8 +111,19 @@ public static boolean handle(String action, ActionRequest request, ActionListene return true; } - return new DlsFilterLevelActionHandler(action, request, listener, evaluatedDlsFlsConfig, resolved, nodeClient, clusterService, indicesService, - resolver, dlsQueryParser, threadContext).handle(); + return new DlsFilterLevelActionHandler( + action, + request, + listener, + evaluatedDlsFlsConfig, + resolved, + nodeClient, + clusterService, + indicesService, + resolver, + dlsQueryParser, + threadContext + ).handle(); } private final String action; @@ -117,9 +141,19 @@ public static boolean handle(String action, ActionRequest request, ActionListene private BoolQueryBuilder filterLevelQueryBuilder; private DocumentAllowList documentAllowlist; - DlsFilterLevelActionHandler(String action, ActionRequest request, ActionListener listener, EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, - Resolved resolved, Client nodeClient, ClusterService clusterService, IndicesService indicesService, - IndexNameExpressionResolver resolver, DlsQueryParser dlsQueryParser, ThreadContext threadContext) { + DlsFilterLevelActionHandler( + String action, + ActionRequest request, + ActionListener listener, + EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, + Resolved resolved, + Client nodeClient, + ClusterService clusterService, + IndicesService indicesService, + IndexNameExpressionResolver resolver, + DlsQueryParser dlsQueryParser, + ThreadContext threadContext + ) { this.action = action; this.request = request; this.listener = listener; @@ -170,8 +204,11 @@ private boolean handle() { return handle((ClusterSearchShardsRequest) request, ctx); } else { log.error("Unsupported request type for filter level DLS: " + request); - listener.onFailure(new OpenSearchSecurityException( - "Unsupported request type for filter level DLS: " + action + "; " + request.getClass().getName())); + listener.onFailure( + new OpenSearchSecurityException( + "Unsupported request type for filter level DLS: " + action + "; " + request.getClass().getName() + ) + ); return false; } } @@ -230,7 +267,9 @@ private boolean handle(GetRequest getRequest, StoredContext ctx) { } SearchRequest searchRequest = new SearchRequest(getRequest.indices()); - BoolQueryBuilder query = QueryBuilders.boolQuery().must(QueryBuilders.idsQuery().addIds(getRequest.id())).must(filterLevelQueryBuilder); + BoolQueryBuilder query = QueryBuilders.boolQuery() + .must(QueryBuilders.idsQuery().addIds(getRequest.id())) + .must(filterLevelQueryBuilder); searchRequest.source(SearchSourceBuilder.searchSource().query(query)); nodeClient.search(searchRequest, new ActionListener() { @@ -247,8 +286,21 @@ public void onResponse(SearchResponse response) { if (hits == 1) { getListener.onResponse(new GetResponse(searchHitToGetResult(response.getHits().getAt(0)))); } else if (hits == 0) { - getListener.onResponse(new GetResponse(new GetResult(searchRequest.indices()[0], getRequest.id(), - SequenceNumbers.UNASSIGNED_SEQ_NO, SequenceNumbers.UNASSIGNED_PRIMARY_TERM, -1, false, null, null, null))); + getListener.onResponse( + new GetResponse( + new GetResult( + searchRequest.indices()[0], + getRequest.id(), + SequenceNumbers.UNASSIGNED_SEQ_NO, + SequenceNumbers.UNASSIGNED_PRIMARY_TERM, + -1, + false, + null, + null, + null + ) + ) + ); } else { log.error("Unexpected hit count " + hits + " in " + response); listener.onFailure(new OpenSearchSecurityException("Internal error when performing DLS")); @@ -274,8 +326,9 @@ private boolean handle(MultiGetRequest multiGetRequest, StoredContext ctx) { documentAllowlist.applyTo(threadContext); } - Map> idsGroupedByIndex = multiGetRequest.getItems().stream() - .collect(Collectors.groupingBy((item) -> item.index(), Collectors.mapping((item) -> item.id(), Collectors.toSet()))); + Map> idsGroupedByIndex = multiGetRequest.getItems() + .stream() + .collect(Collectors.groupingBy((item) -> item.index(), Collectors.mapping((item) -> item.id(), Collectors.toSet()))); Set indices = idsGroupedByIndex.keySet(); SearchRequest searchRequest = new SearchRequest(indices.toArray(new String[indices.size()])); @@ -283,14 +336,16 @@ private boolean handle(MultiGetRequest multiGetRequest, StoredContext ctx) { if (indices.size() == 1) { Set ids = idsGroupedByIndex.get(indices.iterator().next()); - query = QueryBuilders.boolQuery().must(QueryBuilders.idsQuery().addIds(ids.toArray(new String[ids.size()]))) - .must(filterLevelQueryBuilder); + query = QueryBuilders.boolQuery() + .must(QueryBuilders.idsQuery().addIds(ids.toArray(new String[ids.size()]))) + .must(filterLevelQueryBuilder); } else { BoolQueryBuilder mgetQuery = QueryBuilders.boolQuery().minimumShouldMatch(1); for (Map.Entry> entry : idsGroupedByIndex.entrySet()) { - BoolQueryBuilder indexQuery = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("_index", entry.getKey())) - .must(QueryBuilders.idsQuery().addIds(entry.getValue().toArray(new String[entry.getValue().size()]))); + BoolQueryBuilder indexQuery = QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery("_index", entry.getKey())) + .must(QueryBuilders.idsQuery().addIds(entry.getValue().toArray(new String[entry.getValue().size()]))); mgetQuery.should(indexQuery); } @@ -315,7 +370,9 @@ public void onResponse(SearchResponse response) { @SuppressWarnings("unchecked") ActionListener multiGetListener = (ActionListener) listener; - multiGetListener.onResponse(new MultiGetResponse(itemResponses.toArray(new MultiGetItemResponse[itemResponses.size()]))); + multiGetListener.onResponse( + new MultiGetResponse(itemResponses.toArray(new MultiGetItemResponse[itemResponses.size()])) + ); } catch (Exception e) { listener.onFailure(e); } @@ -332,8 +389,11 @@ public void onFailure(Exception e) { } private boolean handle(ClusterSearchShardsRequest request, StoredContext ctx) { - listener.onFailure(new OpenSearchSecurityException( - "Filter-level DLS via cross cluster search is not available for scrolling and minimize_roundtrips=true")); + listener.onFailure( + new OpenSearchSecurityException( + "Filter-level DLS via cross cluster search is not available for scrolling and minimize_roundtrips=true" + ) + ); return false; } @@ -373,9 +433,14 @@ private GetResult searchHitToGetResult(SearchHit hit) { } else { if (log.isWarnEnabled()) { - log.warn("Could not find IndexService for " + hit.getIndex() + "; assuming all fields as document fields." + log.warn( + "Could not find IndexService for " + + hit.getIndex() + + "; assuming all fields as document fields." + "This should not happen, however this should also not pose a big problem as ES mixes the fields again anyway.\n" - + "IndexMetadata: " + indexMetadata); + + "IndexMetadata: " + + indexMetadata + ); } documentFields = fields; @@ -383,8 +448,17 @@ private GetResult searchHitToGetResult(SearchHit hit) { } } - return new GetResult(hit.getIndex(), hit.getId(), hit.getSeqNo(), hit.getPrimaryTerm(), hit.getVersion(), true, hit.getSourceRef(), - documentFields, metadataFields); + return new GetResult( + hit.getIndex(), + hit.getId(), + hit.getSeqNo(), + hit.getPrimaryTerm(), + hit.getVersion(), + true, + hit.getSourceRef(), + documentFields, + metadataFields + ); } private boolean modifyQuery() throws IOException { @@ -441,11 +515,15 @@ private boolean modifyQuery(String localClusterAlias) throws IOException { dlsQueryBuilder.should(parsedDlsQuery); } else { // The original request referred to several indices. That's why we have to scope each query to the index it is meant for - dlsQueryBuilder.should(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("_index", prefixedIndex)).must(parsedDlsQuery)); + dlsQueryBuilder.should( + QueryBuilders.boolQuery().must(QueryBuilders.termQuery("_index", prefixedIndex)).must(parsedDlsQuery) + ); } - Set queryBuilders = QueryBuilderTraverser.findAll(parsedDlsQuery, - (q) -> (q instanceof TermsQueryBuilder) && ((TermsQueryBuilder) q).termsLookup() != null); + Set queryBuilders = QueryBuilderTraverser.findAll( + parsedDlsQuery, + (q) -> (q instanceof TermsQueryBuilder) && ((TermsQueryBuilder) q).termsLookup() != null + ); for (QueryBuilder queryBuilder : queryBuilders) { TermsQueryBuilder termsQueryBuilder = (TermsQueryBuilder) queryBuilder; diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java index d849e6d999..7100be35e5 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java @@ -85,7 +85,7 @@ import org.opensearch.security.support.SecurityUtils; import org.opensearch.security.support.WildcardMatcher; -class DlsFlsFilterLeafReader extends SequentialStoredFieldsLeafReader { +class DlsFlsFilterLeafReader extends SequentialStoredFieldsLeafReader { private static final String KEYWORD = ".keyword"; private static final String[] EMPTY_STRING_ARRAY = new String[0]; @@ -108,11 +108,18 @@ class DlsFlsFilterLeafReader extends SequentialStoredFieldsLeafReader { private DlsGetEvaluator dge = null; - - DlsFlsFilterLeafReader(final LeafReader delegate, final Set includesExcludes, - final Query dlsQuery, final IndexService indexService, final ThreadContext threadContext, - final ClusterService clusterService, - final AuditLog auditlog, final Set maskedFields, final ShardId shardId, final Salt salt) { + DlsFlsFilterLeafReader( + final LeafReader delegate, + final Set includesExcludes, + final Query dlsQuery, + final IndexService indexService, + final ThreadContext threadContext, + final ClusterService clusterService, + final AuditLog auditlog, + final Set maskedFields, + final ShardId shardId, + final Salt salt + ) { super(delegate); maskFields = (maskedFields != null && maskedFields.size() > 0); @@ -199,8 +206,6 @@ class DlsFlsFilterLeafReader extends SequentialStoredFieldsLeafReader { System.arraycopy(fa, 0, tmp, 0, i); this.flsFieldInfos = new FieldInfos(tmp); - - } else { this.includesSet = null; this.excludesSet = null; @@ -221,9 +226,9 @@ private class DlsGetEvaluator { private final boolean hasDeletions; public DlsGetEvaluator(final Query dlsQuery, final LeafReader in, boolean applyDlsHere) throws IOException { - if(dlsQuery != null && applyDlsHere) { - //borrowed from Apache Lucene (Copyright Apache Software Foundation (ASF)) - //https://github.com/apache/lucene-solr/blob/branch_6_3/lucene/misc/src/java/org/apache/lucene/index/PKIndexSplitter.java + if (dlsQuery != null && applyDlsHere) { + // borrowed from Apache Lucene (Copyright Apache Software Foundation (ASF)) + // https://github.com/apache/lucene-solr/blob/branch_6_3/lucene/misc/src/java/org/apache/lucene/index/PKIndexSplitter.java final IndexSearcher searcher = new IndexSearcher(DlsFlsFilterLeafReader.this); searcher.setQueryCache(null); final Weight preserveWeight = searcher.createWeight(dlsQuery, ScoreMode.COMPLETE_NO_SCORES, 1f); @@ -253,7 +258,7 @@ public DlsGetEvaluator(final Query dlsQuery, final LeafReader in, boolean applyD hasDeletions = true; } else { - //no dls or handled in a different place + // no dls or handled in a different place liveBits = in.getLiveDocs(); numDocs = in.numDocs(); readerCacheHelper = in.getReaderCacheHelper(); @@ -261,7 +266,7 @@ public DlsGetEvaluator(final Query dlsQuery, final LeafReader in, boolean applyD } } - //return null means no hidden docs + // return null means no hidden docs public Bits getLiveDocs() { return liveBits; } @@ -288,19 +293,18 @@ private MaskedFieldsMap(Map maskedFieldsMap) { public static MaskedFieldsMap extractMaskedFields(boolean maskFields, Set maskedFields, final Salt salt) { if (maskFields) { - return new MaskedFieldsMap(maskedFields.stream() - .map(mf -> new MaskedField(mf, salt)) - .collect(ImmutableMap.toImmutableMap(mf -> WildcardMatcher.from(mf.getName()), Function.identity()))); + return new MaskedFieldsMap( + maskedFields.stream() + .map(mf -> new MaskedField(mf, salt)) + .collect(ImmutableMap.toImmutableMap(mf -> WildcardMatcher.from(mf.getName()), Function.identity())) + ); } else { return new MaskedFieldsMap(Collections.emptyMap()); } } public Optional getMaskedField(String fieldName) { - return maskedFieldsMap.entrySet().stream() - .filter(entry -> entry.getKey().test(fieldName)) - .map(Map.Entry::getValue) - .findFirst(); + return maskedFieldsMap.entrySet().stream().filter(entry -> entry.getKey().test(fieldName)).map(Map.Entry::getValue).findFirst(); } public boolean anyMatch(String fieldName) { @@ -311,7 +315,6 @@ public WildcardMatcher getMatcher() { return WildcardMatcher.from(maskedFieldsMap.keySet()); } - } private static class DlsFlsSubReaderWrapper extends FilterDirectoryReader.SubReaderWrapper { @@ -326,10 +329,17 @@ private static class DlsFlsSubReaderWrapper extends FilterDirectoryReader.SubRea private final ShardId shardId; private final Salt salt; - public DlsFlsSubReaderWrapper(final Set includes, final Query dlsQuery, - final IndexService indexService, final ThreadContext threadContext, - final ClusterService clusterService, - final AuditLog auditlog, final Set maskedFields, ShardId shardId, final Salt salt) { + public DlsFlsSubReaderWrapper( + final Set includes, + final Query dlsQuery, + final IndexService indexService, + final ThreadContext threadContext, + final ClusterService clusterService, + final AuditLog auditlog, + final Set maskedFields, + ShardId shardId, + final Salt salt + ) { this.includes = includes; this.dlsQuery = dlsQuery; this.indexService = indexService; @@ -343,7 +353,18 @@ public DlsFlsSubReaderWrapper(final Set includes, final Query dlsQuery, @Override public LeafReader wrap(final LeafReader reader) { - return new DlsFlsFilterLeafReader(reader, includes, dlsQuery, indexService, threadContext, clusterService, auditlog, maskedFields, shardId, salt); + return new DlsFlsFilterLeafReader( + reader, + includes, + dlsQuery, + indexService, + threadContext, + clusterService, + auditlog, + maskedFields, + shardId, + salt + ); } } @@ -360,11 +381,32 @@ static class DlsFlsDirectoryReader extends FilterDirectoryReader { private final ShardId shardId; private final Salt salt; - public DlsFlsDirectoryReader(final DirectoryReader in, final Set includes, final Query dlsQuery, - final IndexService indexService, final ThreadContext threadContext, - final ClusterService clusterService, - final AuditLog auditlog, final Set maskedFields, ShardId shardId, final Salt salt) throws IOException { - super(in, new DlsFlsSubReaderWrapper(includes, dlsQuery, indexService, threadContext, clusterService, auditlog, maskedFields, shardId, salt)); + public DlsFlsDirectoryReader( + final DirectoryReader in, + final Set includes, + final Query dlsQuery, + final IndexService indexService, + final ThreadContext threadContext, + final ClusterService clusterService, + final AuditLog auditlog, + final Set maskedFields, + ShardId shardId, + final Salt salt + ) throws IOException { + super( + in, + new DlsFlsSubReaderWrapper( + includes, + dlsQuery, + indexService, + threadContext, + clusterService, + auditlog, + maskedFields, + shardId, + salt + ) + ); this.includes = includes; this.dlsQuery = dlsQuery; this.indexService = indexService; @@ -378,7 +420,18 @@ public DlsFlsDirectoryReader(final DirectoryReader in, final Set include @Override protected DirectoryReader doWrapDirectoryReader(final DirectoryReader in) throws IOException { - return new DlsFlsDirectoryReader(in, includes, dlsQuery, indexService, threadContext, clusterService, auditlog, maskedFields, shardId, salt); + return new DlsFlsDirectoryReader( + in, + includes, + dlsQuery, + indexService, + threadContext, + clusterService, + auditlog, + maskedFields, + shardId, + salt + ); } @Override @@ -467,7 +520,7 @@ private boolean isFls(final BytesRef termAsFiledName) { private boolean isFls(final String name) { - if(!flsEnabled) { + if (!flsEnabled) { return true; } @@ -477,7 +530,7 @@ private boolean isFls(final String name) { @Override public FieldInfos getFieldInfos() { - if(!flsEnabled) { + if (!flsEnabled) { return in.getFieldInfos(); } @@ -487,8 +540,14 @@ public FieldInfos getFieldInfos() { private class ComplianceAwareStoredFieldVisitor extends StoredFieldVisitor { private final StoredFieldVisitor delegate; - private FieldReadCallback fieldReadCallback = - new FieldReadCallback(threadContext, indexService, clusterService, auditlog, maskedFieldsMap.getMatcher(), shardId); + private FieldReadCallback fieldReadCallback = new FieldReadCallback( + threadContext, + indexService, + clusterService, + auditlog, + maskedFieldsMap.getMatcher(), + shardId + ); public ComplianceAwareStoredFieldVisitor(final StoredFieldVisitor delegate) { super(); @@ -501,7 +560,6 @@ public void binaryField(final FieldInfo fieldInfo, final byte[] value) throws IO delegate.binaryField(fieldInfo, value); } - @Override public Status needsField(final FieldInfo fieldInfo) throws IOException { return delegate.needsField(fieldInfo); @@ -584,7 +642,6 @@ public void binaryField(final FieldInfo fieldInfo, final byte[] value) throws IO } } - @Override public Status needsField(final FieldInfo fieldInfo) throws IOException { return isFls(fieldInfo.name) ? delegate.needsField(fieldInfo) : Status.NO; @@ -640,7 +697,11 @@ public void binaryField(final FieldInfo fieldInfo, final byte[] value) throws IO if (fieldInfo.name.equals("_source")) { final BytesReference bytesRef = new BytesArray(value); - final Tuple> bytesRefTuple = XContentHelper.convertToMap(bytesRef, false, XContentType.JSON); + final Tuple> bytesRefTuple = XContentHelper.convertToMap( + bytesRef, + false, + XContentType.JSON + ); Map filteredSource = bytesRefTuple.v2(); MapUtils.deepTraverseMap(filteredSource, HASH_CB); final XContentBuilder xBuilder = XContentBuilder.builder(bytesRefTuple.v1().xContent()).map(filteredSource); @@ -650,7 +711,6 @@ public void binaryField(final FieldInfo fieldInfo, final byte[] value) throws IO } } - @Override public Status needsField(final FieldInfo fieldInfo) throws IOException { return delegate.needsField(fieldInfo); @@ -745,7 +805,7 @@ public Fields getTermVectors(final int docID) throws IOException { @Override public Iterator iterator() { - return Iterators. filter(fields.iterator(), input -> isFls(input)); + return Iterators.filter(fields.iterator(), input -> isFls(input)); } @Override @@ -781,7 +841,7 @@ private BinaryDocValues wrapBinaryDocValues(final String field, final BinaryDocV final MaskedFieldsMap maskedFieldsMap; - if (binaryDocValues != null && ((maskedFieldsMap=getRuntimeMaskedFieldInfo()) != null)) { + if (binaryDocValues != null && ((maskedFieldsMap = getRuntimeMaskedFieldInfo()) != null)) { final MaskedField mf = maskedFieldsMap.getMaskedField(handleKeyword(field)).orElse(null); if (mf != null) { @@ -822,7 +882,6 @@ public BytesRef binaryValue() throws IOException { return binaryDocValues; } - @Override public SortedDocValues getSortedDocValues(final String field) throws IOException { return isFls(field) ? wrapSortedDocValues(field, in.getSortedDocValues(field)) : null; @@ -832,7 +891,7 @@ private SortedDocValues wrapSortedDocValues(final String field, final SortedDocV final MaskedFieldsMap maskedFieldsMap; - if (sortedDocValues != null && (maskedFieldsMap=getRuntimeMaskedFieldInfo())!=null) { + if (sortedDocValues != null && (maskedFieldsMap = getRuntimeMaskedFieldInfo()) != null) { final MaskedField mf = maskedFieldsMap.getMaskedField(handleKeyword(field)).orElse(null); if (mf != null) { @@ -843,7 +902,6 @@ public int lookupTerm(BytesRef key) throws IOException { return sortedDocValues.lookupTerm(key); } - @Override public TermsEnum termsEnum() throws IOException { return new MaskedTermsEnum(sortedDocValues.termsEnum(), mf); @@ -913,7 +971,6 @@ private SortedSetDocValues wrapSortedSetDocValues(final String field, final Sort final MaskedFieldsMap maskedFieldsMap; - if (sortedSetDocValues != null && ((maskedFieldsMap = getRuntimeMaskedFieldInfo()) != null)) { MaskedField mf = maskedFieldsMap.getMaskedField(handleKeyword(field)).orElse(null); @@ -1002,16 +1059,16 @@ public Terms terms(String field) throws IOException { private Terms wrapTerms(final String field, Terms terms) throws IOException { - if(terms == null) { + if (terms == null) { return null; } MaskedFieldsMap maskedFieldInfo = getRuntimeMaskedFieldInfo(); - if(maskedFieldInfo != null && maskedFieldInfo.anyMatch(handleKeyword(field))) { + if (maskedFieldInfo != null && maskedFieldInfo.anyMatch(handleKeyword(field))) { return null; } - if("_field_names".equals(field)) { + if ("_field_names".equals(field)) { return new FilteredTerms(terms); } return terms; @@ -1024,8 +1081,8 @@ public FilteredTermsEnum(TermsEnum delegate) { @Override public BytesRef next() throws IOException { - //wind forward in the sequence of terms until we reached the end or we find a allowed term(=field name) - //so that calling this method never return a term which is not allowed by fls rules + // wind forward in the sequence of terms until we reached the end or we find a allowed term(=field name) + // so that calling this method never return a term which is not allowed by fls rules for (BytesRef nextBytesRef = in.next(); nextBytesRef != null; nextBytesRef = in.next()) { if (!isFls((nextBytesRef))) { continue; @@ -1038,20 +1095,20 @@ public BytesRef next() throws IOException { @Override public SeekStatus seekCeil(BytesRef text) throws IOException { - //Get the current seek status for a given term in the original sequence of terms + // Get the current seek status for a given term in the original sequence of terms final SeekStatus delegateStatus = in.seekCeil(text); - //So delegateStatus here is either FOUND or NOT_FOUND - //check if the current term (=field name) is allowed - //If so just return current seek status + // So delegateStatus here is either FOUND or NOT_FOUND + // check if the current term (=field name) is allowed + // If so just return current seek status if (delegateStatus != SeekStatus.END && isFls((in.term()))) { return delegateStatus; } else if (delegateStatus == SeekStatus.END) { - //If we hit the end just return END + // If we hit the end just return END return SeekStatus.END; } else { - //If we are not at the end and the current term (=field name) is not allowed just check if - //we are at the end of the (filtered) iterator + // If we are not at the end and the current term (=field name) is not allowed just check if + // we are at the end of the (filtered) iterator if (this.next() != null) { return SeekStatus.NOT_FOUND; } else { @@ -1060,7 +1117,6 @@ public SeekStatus seekCeil(BytesRef text) throws IOException { } } - @Override public boolean seekExact(BytesRef term) throws IOException { return isFls(term) && in.seekExact(term); @@ -1079,12 +1135,15 @@ public long ord() throws IOException { private final class FilteredTerms extends FilterTerms { - //According to - //https://www.elastic.co/guide/en/elasticsearch/reference/6.8/mapping-field-names-field.html - //"The _field_names field used to index the names of every field in a document that contains any value other than null" - //"For fields which have either doc_values or norm enabled the exists query will still be available but will not use the _field_names field." - //That means if a field has no doc values (which is always the case for an analyzed string) and no norms we need to strip the non allowed fls fields - //from the _field_names field. They are stored as terms, so we need to create a FilterTerms implementation which skips the terms (=field names)not allowed by fls + // According to + // https://www.elastic.co/guide/en/elasticsearch/reference/6.8/mapping-field-names-field.html + // "The _field_names field used to index the names of every field in a document that contains any value other than null" + // "For fields which have either doc_values or norm enabled the exists query will still be available but will not use the + // _field_names field." + // That means if a field has no doc values (which is always the case for an analyzed string) and no norms we need to strip the non + // allowed fls fields + // from the _field_names field. They are stored as terms, so we need to create a FilterTerms implementation which skips the terms + // (=field names)not allowed by fls public FilteredTerms(Terms delegate) throws IOException { super(delegate); @@ -1123,13 +1182,15 @@ public boolean hasDeletions() { @SuppressWarnings("unchecked") private MaskedFieldsMap getRuntimeMaskedFieldInfo() { - final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader(threadContext, - ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER); + final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadContext, + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + ); final String maskedEval = SecurityUtils.evalMap(maskedFieldsMap, indexService.index().getName()); - if(maskedEval != null) { + if (maskedEval != null) { final Set mf = maskedFieldsMap.get(maskedEval); - if(mf != null && !mf.isEmpty()) { + if (mf != null && !mf.isEmpty()) { return MaskedFieldsMap.extractMaskedFields(true, mf, salt); } @@ -1139,8 +1200,8 @@ private MaskedFieldsMap getRuntimeMaskedFieldInfo() { } private String handleKeyword(final String field) { - if(field != null && field.endsWith(KEYWORD)) { - return field.substring(0, field.length()-KEYWORD.length()); + if (field != null && field.endsWith(KEYWORD)) { + return field.substring(0, field.length() - KEYWORD.length()); } return field; } @@ -1158,7 +1219,7 @@ public MaskedTermsEnum(TermsEnum delegate, MaskedField mf) { @Override public BytesRef next() throws IOException { - return delegate.next(); //no masking here + return delegate.next(); // no masking here } @Override @@ -1223,7 +1284,6 @@ public TermState termState() throws IOException { } - private String getRuntimeActionName() { return (String) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ACTION_NAME); } @@ -1233,16 +1293,15 @@ private boolean isSuggest() { } private boolean applyDlsHere() { - if(isSuggest()) { - //we need to apply it here + if (isSuggest()) { + // we need to apply it here return true; } - final String action = getRuntimeActionName(); assert action != null; - //we need to apply here if it is not a search request - //(a get for example) + // we need to apply here if it is not a search request + // (a get for example) return !action.startsWith("indices:data/read/search"); } } diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java b/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java index f5751efcae..9bce6564dc 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java @@ -37,7 +37,13 @@ public interface DlsFlsRequestValve { - boolean invoke(String action, ActionRequest request, ActionListener listener, EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, Resolved resolved); + boolean invoke( + String action, + ActionRequest request, + ActionListener listener, + EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, + Resolved resolved + ); void handleSearchContext(SearchContext context, ThreadPool threadPool, NamedXContentRegistry namedXContentRegistry); @@ -45,9 +51,14 @@ public interface DlsFlsRequestValve { public static class NoopDlsFlsRequestValve implements DlsFlsRequestValve { - @Override - public boolean invoke(String action, ActionRequest request, ActionListener listener, EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, - Resolved resolved) { + @Override + public boolean invoke( + String action, + ActionRequest request, + ActionListener listener, + EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, + Resolved resolved + ) { return true; } diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java index 947557d342..81fe9e255f 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java @@ -82,8 +82,8 @@ public class DlsFlsValveImpl implements DlsFlsRequestValve { - private static final String MAP_EXECUTION_HINT = "map"; - private static final Logger log = LogManager.getLogger(DlsFlsValveImpl.class); + private static final String MAP_EXECUTION_HINT = "map"; + private static final Logger log = LogManager.getLogger(DlsFlsValveImpl.class); private final Client nodeClient; private final ClusterService clusterService; @@ -92,8 +92,14 @@ public class DlsFlsValveImpl implements DlsFlsRequestValve { private final DlsQueryParser dlsQueryParser; private final IndexNameExpressionResolver resolver; - public DlsFlsValveImpl(Settings settings, Client nodeClient, ClusterService clusterService, IndexNameExpressionResolver resolver, - NamedXContentRegistry namedXContentRegistry, ThreadContext threadContext) { + public DlsFlsValveImpl( + Settings settings, + Client nodeClient, + ClusterService clusterService, + IndexNameExpressionResolver resolver, + NamedXContentRegistry namedXContentRegistry, + ThreadContext threadContext + ) { super(); this.nodeClient = nodeClient; this.clusterService = clusterService; @@ -109,12 +115,25 @@ public DlsFlsValveImpl(Settings settings, Client nodeClient, ClusterService clus * @param listener * @return false on error */ - public boolean invoke(String action, ActionRequest request, final ActionListener listener, EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, - final Resolved resolved) { + public boolean invoke( + String action, + ActionRequest request, + final ActionListener listener, + EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, + final Resolved resolved + ) { if (log.isDebugEnabled()) { - log.debug("DlsFlsValveImpl.invoke()\nrequest: " + request + "\nevaluatedDlsFlsConfig: " + evaluatedDlsFlsConfig + "\nresolved: " - + resolved + "\nmode: " + mode); + log.debug( + "DlsFlsValveImpl.invoke()\nrequest: " + + request + + "\nevaluatedDlsFlsConfig: " + + evaluatedDlsFlsConfig + + "\nresolved: " + + resolved + + "\nmode: " + + mode + ); } if (evaluatedDlsFlsConfig == null || evaluatedDlsFlsConfig.isEmpty()) { @@ -173,10 +192,10 @@ public boolean invoke(String action, ActionRequest request, final ActionListener SearchRequest searchRequest = ((SearchRequest) request); - //When we encounter a terms or sampler aggregation with masked fields activated we forcibly - //need to switch off global ordinals because field masking can break ordering + // When we encounter a terms or sampler aggregation with masked fields activated we forcibly + // need to switch off global ordinals because field masking can break ordering // CS-SUPPRESS-SINGLE: RegexpSingleline Ignore term inside of url - //https://www.elastic.co/guide/en/elasticsearch/reference/master/eager-global-ordinals.html#_avoiding_global_ordinal_loading + // https://www.elastic.co/guide/en/elasticsearch/reference/master/eager-global-ordinals.html#_avoiding_global_ordinal_loading // CS-ENFORCE-SINGLE if (evaluatedDlsFlsConfig.hasFieldMasking()) { @@ -197,8 +216,7 @@ public boolean invoke(String action, ActionRequest request, final ActionListener } } - if (!evaluatedDlsFlsConfig.hasFls() && !evaluatedDlsFlsConfig.hasDls() - && searchRequest.source().aggregations() != null) { + if (!evaluatedDlsFlsConfig.hasFls() && !evaluatedDlsFlsConfig.hasDls() && searchRequest.source().aggregations() != null) { boolean cacheable = true; @@ -224,8 +242,11 @@ public boolean invoke(String action, ActionRequest request, final ActionListener if (!cacheable) { searchRequest.requestCache(Boolean.FALSE); } else { - LogManager.getLogger("debuglogger").error("Shard requestcache enabled for " - + (searchRequest.source() == null ? "" : Strings.toString(XContentType.JSON, searchRequest.source()))); + LogManager.getLogger("debuglogger") + .error( + "Shard requestcache enabled for " + + (searchRequest.source() == null ? "" : Strings.toString(XContentType.JSON, searchRequest.source())) + ); } } else { @@ -241,7 +262,9 @@ public boolean invoke(String action, ActionRequest request, final ActionListener if (request instanceof BulkRequest) { for (DocWriteRequest inner : ((BulkRequest) request).requests()) { if (inner instanceof UpdateRequest) { - listener.onFailure(new OpenSearchSecurityException("Update is not supported when FLS or DLS or Fieldmasking is activated")); + listener.onFailure( + new OpenSearchSecurityException("Update is not supported when FLS or DLS or Fieldmasking is activated") + ); return false; } } @@ -250,7 +273,9 @@ public boolean invoke(String action, ActionRequest request, final ActionListener if (request instanceof BulkShardRequest) { for (BulkItemRequest inner : ((BulkShardRequest) request).items()) { if (inner.request() instanceof UpdateRequest) { - listener.onFailure(new OpenSearchSecurityException("Update is not supported when FLS or DLS or Fieldmasking is activated")); + listener.onFailure( + new OpenSearchSecurityException("Update is not supported when FLS or DLS or Fieldmasking is activated") + ); return false; } } @@ -261,9 +286,13 @@ public boolean invoke(String action, ActionRequest request, final ActionListener return false; } - if(action.contains("plugins/replication")) { - listener.onFailure(new OpenSearchSecurityException("Cross Cluster Replication is not supported when FLS or DLS or Fieldmasking is activated", - RestStatus.FORBIDDEN)); + if (action.contains("plugins/replication")) { + listener.onFailure( + new OpenSearchSecurityException( + "Cross Cluster Replication is not supported when FLS or DLS or Fieldmasking is activated", + RestStatus.FORBIDDEN + ) + ); return false; } @@ -292,8 +321,19 @@ public boolean invoke(String action, ActionRequest request, final ActionListener } if (doFilterLevelDls && filteredDlsFlsConfig.hasDls()) { - return DlsFilterLevelActionHandler.handle(action, request, listener, evaluatedDlsFlsConfig, resolved, nodeClient, clusterService, - OpenSearchSecurityPlugin.GuiceHolder.getIndicesService(), resolver, dlsQueryParser, threadContext); + return DlsFilterLevelActionHandler.handle( + action, + request, + listener, + evaluatedDlsFlsConfig, + resolved, + nodeClient, + clusterService, + OpenSearchSecurityPlugin.GuiceHolder.getIndicesService(), + resolver, + dlsQueryParser, + threadContext + ); } else { return true; } @@ -303,8 +343,10 @@ public boolean invoke(String action, ActionRequest request, final ActionListener public void handleSearchContext(SearchContext context, ThreadPool threadPool, NamedXContentRegistry namedXContentRegistry) { try { @SuppressWarnings("unchecked") - final Map> queries = (Map>) HeaderHelper.deserializeSafeFromHeader(threadPool.getThreadContext(), - ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER); + final Map> queries = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadPool.getThreadContext(), + ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER + ); final String dlsEval = SecurityUtils.evalMap(queries, context.indexShard().indexSettings().getIndex().getName()); @@ -319,8 +361,11 @@ public void handleSearchContext(SearchContext context, ThreadPool threadPool, Na final Set unparsedDlsQueries = queries.get(dlsEval); if (unparsedDlsQueries != null && !unparsedDlsQueries.isEmpty()) { - BooleanQuery.Builder queryBuilder = dlsQueryParser.parse(unparsedDlsQueries, context.getQueryShardContext(), - (q) -> new ConstantScoreQuery(q)); + BooleanQuery.Builder queryBuilder = dlsQueryParser.parse( + unparsedDlsQueries, + context.getQueryShardContext(), + (q) -> new ConstantScoreQuery(q) + ); queryBuilder.add(context.parsedQuery().query(), Occur.MUST); @@ -343,11 +388,11 @@ public void onQueryPhase(QuerySearchResult queryResult) { assert aggregations != null; queryResult.aggregations( - InternalAggregations.from( - StreamSupport.stream(aggregations.spliterator(), false) - .map(aggregation -> aggregateBuckets((InternalAggregation)aggregation)) - .collect(ImmutableList.toImmutableList()) - ) + InternalAggregations.from( + StreamSupport.stream(aggregations.spliterator(), false) + .map(aggregation -> aggregateBuckets((InternalAggregation) aggregation)) + .collect(ImmutableList.toImmutableList()) + ) ); } @@ -363,7 +408,10 @@ private static InternalAggregation aggregateBuckets(InternalAggregation aggregat return aggregation; } - private static List mergeBuckets(List buckets, Comparator comparator) { + private static List mergeBuckets( + List buckets, + Comparator comparator + ) { if (log.isDebugEnabled()) { log.debug("Merging buckets: {}", buckets.stream().map(b -> b.getKeyAsString()).collect(ImmutableList.toImmutableList())); } @@ -383,18 +431,28 @@ private void setDlsHeaders(EvaluatedDlsFlsConfig dlsFls, ActionRequest request) Map> dlsQueries = dlsFls.getDlsQueriesByIndex(); if (request instanceof ClusterSearchShardsRequest && HeaderHelper.isTrustedClusterRequest(threadContext)) { - threadContext.addResponseHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER, Base64Helper.serializeObject((Serializable) dlsQueries)); + threadContext.addResponseHeader( + ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER, + Base64Helper.serializeObject((Serializable) dlsQueries) + ); if (log.isDebugEnabled()) { log.debug("added response header for DLS info: {}", dlsQueries); } } else { if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER) != null) { - Object deserializedDlsQueries = Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER)); + Object deserializedDlsQueries = Base64Helper.deserializeObject( + threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER) + ); if (!dlsQueries.equals(deserializedDlsQueries)) { - throw new OpenSearchSecurityException(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER + " does not match (SG 900D)"); + throw new OpenSearchSecurityException( + ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER + " does not match (SG 900D)" + ); } } else { - threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER, Base64Helper.serializeObject((Serializable) dlsQueries)); + threadContext.putHeader( + ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER, + Base64Helper.serializeObject((Serializable) dlsQueries) + ); if (log.isDebugEnabled()) { log.debug("attach DLS info: {}", dlsQueries); } @@ -408,7 +466,12 @@ private void setDlsModeHeader(Mode mode) { if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_MODE_HEADER) != null) { if (!modeString.equals(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_MODE_HEADER))) { - log.warn("Cannot update DLS mode to " + mode + "; current: " + threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_MODE_HEADER)); + log.warn( + "Cannot update DLS mode to " + + mode + + "; current: " + + threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_MODE_HEADER) + ); } } else { threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_MODE_HEADER, modeString); @@ -430,22 +493,32 @@ private void setFlsHeaders(EvaluatedDlsFlsConfig dlsFls, ActionRequest request) Map> maskedFieldsMap = dlsFls.getFieldMaskingByIndex(); if (request instanceof ClusterSearchShardsRequest && HeaderHelper.isTrustedClusterRequest(threadContext)) { - threadContext.addResponseHeader(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER, Base64Helper.serializeObject((Serializable) maskedFieldsMap)); + threadContext.addResponseHeader( + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER, + Base64Helper.serializeObject((Serializable) maskedFieldsMap) + ); if (log.isDebugEnabled()) { log.debug("added response header for masked fields info: {}", maskedFieldsMap); } } else { if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER) != null) { - if (!maskedFieldsMap.equals(Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER)))) { - throw new OpenSearchSecurityException(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + " does not match (SG 901D)"); + if (!maskedFieldsMap.equals( + Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER)) + )) { + throw new OpenSearchSecurityException( + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + " does not match (SG 901D)" + ); } else { if (log.isDebugEnabled()) { log.debug(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + " already set"); } } } else { - threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER, Base64Helper.serializeObject((Serializable) maskedFieldsMap)); + threadContext.putHeader( + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER, + Base64Helper.serializeObject((Serializable) maskedFieldsMap) + ); if (log.isDebugEnabled()) { log.debug("attach masked fields info: {}", maskedFieldsMap); } @@ -457,22 +530,37 @@ private void setFlsHeaders(EvaluatedDlsFlsConfig dlsFls, ActionRequest request) Map> flsFields = dlsFls.getFlsByIndex(); if (request instanceof ClusterSearchShardsRequest && HeaderHelper.isTrustedClusterRequest(threadContext)) { - threadContext.addResponseHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER, Base64Helper.serializeObject((Serializable) flsFields)); + threadContext.addResponseHeader( + ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER, + Base64Helper.serializeObject((Serializable) flsFields) + ); if (log.isDebugEnabled()) { log.debug("added response header for FLS info: {}", flsFields); } } else { if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER) != null) { - if (!flsFields.equals(Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER)))) { - throw new OpenSearchSecurityException(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER + " does not match (SG 901D) " + flsFields - + "---" + Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER))); + if (!flsFields.equals( + Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER)) + )) { + throw new OpenSearchSecurityException( + ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER + + " does not match (SG 901D) " + + flsFields + + "---" + + Base64Helper.deserializeObject( + threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER) + ) + ); } else { if (log.isDebugEnabled()) { log.debug(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER + " already set"); } } } else { - threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER, Base64Helper.serializeObject((Serializable) flsFields)); + threadContext.putHeader( + ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER, + Base64Helper.serializeObject((Serializable) flsFields) + ); if (log.isDebugEnabled()) { log.debug("attach FLS info: {}", flsFields); } @@ -500,9 +588,16 @@ private void finalizeBucket() { if (mergeCount == 1) { builder.add(this.bucket); } else { - builder.add(new StringTerms.Bucket(StringTermsGetter.getTerm(bucket), mergedDocCount, - (InternalAggregations) bucket.getAggregations(), showDocCountError, mergedDocCountError, - StringTermsGetter.getDocValueFormat(bucket))); + builder.add( + new StringTerms.Bucket( + StringTermsGetter.getTerm(bucket), + mergedDocCount, + (InternalAggregations) bucket.getAggregations(), + showDocCountError, + mergedDocCountError, + StringTermsGetter.getDocValueFormat(bucket) + ) + ); } } @@ -543,8 +638,7 @@ private static class StringTermsGetter { private static final Field TERM_BYTES = getField(StringTerms.Bucket.class, "termBytes"); private static final Field FORMAT = getField(InternalTerms.Bucket.class, "format"); - private StringTermsGetter() { - } + private StringTermsGetter() {} private static Field getFieldPrivileged(Class cls, String name) { try { @@ -569,7 +663,7 @@ private static Field getField(Class cls, String name) { private static T getFieldValue(Field field, C c) { try { - return (T)field.get(c); + return (T) field.get(c); } catch (IllegalArgumentException | IllegalAccessException e) { log.error("Exception while getting value {} of class {}", field.getName(), c.getClass().getSimpleName(), e); if (e instanceof RuntimeException) { @@ -594,7 +688,9 @@ public static DocValueFormat getDocValueFormat(StringTerms.Bucket bucket) { } public static enum Mode { - ADAPTIVE, LUCENE_LEVEL, FILTER_LEVEL; + ADAPTIVE, + LUCENE_LEVEL, + FILTER_LEVEL; static Mode get(Settings settings) { String modeString = settings.get(ConfigConstants.SECURITY_DLS_MODE); diff --git a/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java b/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java index a5f07541c8..9640abcd8e 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java +++ b/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java @@ -41,24 +41,28 @@ import org.opensearch.index.query.TermsQueryBuilder; import org.opensearch.security.queries.QueryBuilderTraverser; - public final class DlsQueryParser { private static final Logger log = LogManager.getLogger(DlsQueryParser.class); private static final Query NON_NESTED_QUERY; static { - //Match all documents but not the nested ones - //Nested document types start with __ - //https://discuss.elastic.co/t/whats-nested-documents-layout-inside-the-lucene/59944/9 + // Match all documents but not the nested ones + // Nested document types start with __ + // https://discuss.elastic.co/t/whats-nested-documents-layout-inside-the-lucene/59944/9 NON_NESTED_QUERY = new BooleanQuery.Builder().add(new MatchAllDocsQuery(), Occur.FILTER) - .add(new PrefixQuery(new Term("_type", "__")), Occur.MUST_NOT).build(); + .add(new PrefixQuery(new Term("_type", "__")), Occur.MUST_NOT) + .build(); } - private static Cache parsedQueryCache = CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(4, TimeUnit.HOURS) - .build(); - private static Cache queryContainsTlqCache = CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(4, TimeUnit.HOURS) - .build(); + private static Cache parsedQueryCache = CacheBuilder.newBuilder() + .maximumSize(10000) + .expireAfterWrite(4, TimeUnit.HOURS) + .build(); + private static Cache queryContainsTlqCache = CacheBuilder.newBuilder() + .maximumSize(10000) + .expireAfterWrite(4, TimeUnit.HOURS) + .build(); private final NamedXContentRegistry namedXContentRegistry; @@ -70,8 +74,11 @@ public BooleanQuery.Builder parse(Set unparsedDlsQueries, QueryShardCont return parse(unparsedDlsQueries, queryShardContext, null); } - public BooleanQuery.Builder parse(Set unparsedDlsQueries, QueryShardContext queryShardContext, - Function queryMapFunction) { + public BooleanQuery.Builder parse( + Set unparsedDlsQueries, + QueryShardContext queryShardContext, + Function queryMapFunction + ) { if (unparsedDlsQueries == null || unparsedDlsQueries.isEmpty()) { return null; @@ -100,8 +107,11 @@ public BooleanQuery.Builder parse(Set unparsedDlsQueries, QueryShardCont return dlsQueryBuilder; } - private static void handleNested(final QueryShardContext queryShardContext, final BooleanQuery.Builder dlsQueryBuilder, - final Query parentQuery) { + private static void handleNested( + final QueryShardContext queryShardContext, + final BooleanQuery.Builder dlsQueryBuilder, + final Query parentQuery + ) { final BitSetProducer parentDocumentsFilter = queryShardContext.bitsetFilter(NON_NESTED_QUERY); dlsQueryBuilder.add(new ToChildBlockJoinQuery(parentQuery, parentDocumentsFilter), Occur.SHOULD); } @@ -112,8 +122,11 @@ public QueryBuilder parse(String unparsedDlsQuery) { @Override public QueryBuilder call() throws Exception { - final XContentParser parser = JsonXContent.jsonXContent.createParser(namedXContentRegistry, - DeprecationHandler.THROW_UNSUPPORTED_OPERATION, unparsedDlsQuery); + final XContentParser parser = JsonXContent.jsonXContent.createParser( + namedXContentRegistry, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + unparsedDlsQuery + ); return AbstractQueryBuilder.parseInnerQueryBuilder(parser); } @@ -143,18 +156,19 @@ boolean containsTermLookupQuery(Set unparsedQueries) { return false; } - boolean containsTermLookupQuery(String query) { + boolean containsTermLookupQuery(String query) { try { return queryContainsTlqCache.get(query, () -> { QueryBuilder queryBuilder = parse(query); - return QueryBuilderTraverser.exists(queryBuilder, - (q) -> (q instanceof TermsQueryBuilder) && ((TermsQueryBuilder) q).termsLookup() != null); + return QueryBuilderTraverser.exists( + queryBuilder, + (q) -> (q instanceof TermsQueryBuilder) && ((TermsQueryBuilder) q).termsLookup() != null + ); }); } catch (ExecutionException e) { throw new RuntimeException("Error handling parsing " + query, e.getCause()); } } - } diff --git a/src/main/java/org/opensearch/security/configuration/EmptyFilterLeafReader.java b/src/main/java/org/opensearch/security/configuration/EmptyFilterLeafReader.java index 79069ef53e..1a0460b91c 100644 --- a/src/main/java/org/opensearch/security/configuration/EmptyFilterLeafReader.java +++ b/src/main/java/org/opensearch/security/configuration/EmptyFilterLeafReader.java @@ -83,6 +83,7 @@ public CacheHelper getCoreCacheHelper() { public CacheHelper getReaderCacheHelper() { return null; } + private static class EmptySubReaderWrapper extends FilterDirectoryReader.SubReaderWrapper { @Override diff --git a/src/main/java/org/opensearch/security/configuration/MaskedField.java b/src/main/java/org/opensearch/security/configuration/MaskedField.java index c04567eeef..8cb20ccdfe 100644 --- a/src/main/java/org/opensearch/security/configuration/MaskedField.java +++ b/src/main/java/org/opensearch/security/configuration/MaskedField.java @@ -40,11 +40,11 @@ public MaskedField(final String value, final Salt salt) { } else if (tokenCount == 2) { name = tokens.get(0); algo = tokens.get(1); - } else if (tokenCount >= 3 && tokenCount%2==1) { + } else if (tokenCount >= 3 && tokenCount % 2 == 1) { name = tokens.get(0); - regexReplacements = new ArrayList<>((tokenCount-1)/2); - for(int i=1; i((tokenCount - 1) / 2); + for (int i = 1; i < tokenCount - 1; i = i + 2) { + regexReplacements.add(new RegexReplacement(tokens.get(i), tokens.get(i + 1))); } } else { throw new IllegalArgumentException("Expected 1 or 2 or >=3 (but then odd count) tokens, got " + tokenCount); @@ -52,7 +52,7 @@ public MaskedField(final String value, final Salt salt) { } public final void isValid() throws Exception { - mask(new byte[] {1,2,3,4,5}); + mask(new byte[] { 1, 2, 3, 4, 5 }); } public byte[] mask(byte[] value) { @@ -72,7 +72,7 @@ public String mask(String value) { } public BytesRef mask(BytesRef value) { - if(value == null) { + if (value == null) { return null; } @@ -87,8 +87,6 @@ public String getName() { return name; } - - @Override public int hashCode() { final int prime = 31; @@ -101,37 +99,35 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; MaskedField other = (MaskedField) obj; if (algo == null) { - if (other.algo != null) - return false; - } else if (!algo.equals(other.algo)) - return false; + if (other.algo != null) return false; + } else if (!algo.equals(other.algo)) return false; if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; + if (other.name != null) return false; + } else if (!name.equals(other.name)) return false; if (regexReplacements == null) { - if (other.regexReplacements != null) - return false; - } else if (!regexReplacements.equals(other.regexReplacements)) - return false; + if (other.regexReplacements != null) return false; + } else if (!regexReplacements.equals(other.regexReplacements)) return false; return true; } - - @Override public String toString() { - return "MaskedField [name=" + name + ", algo=" + algo + ", regexReplacements=" + regexReplacements - + ", defaultSalt=" + Arrays.toString(defaultSalt) + ", isDefault()=" + isDefault() + "]"; + return "MaskedField [name=" + + name + + ", algo=" + + algo + + ", regexReplacements=" + + regexReplacements + + ", defaultSalt=" + + Arrays.toString(defaultSalt) + + ", isDefault()=" + + isDefault() + + "]"; } private boolean isDefault() { @@ -148,7 +144,7 @@ private byte[] customHash(byte[] in) { } } else if (regexReplacements != null) { String cur = new String(in, StandardCharsets.UTF_8); - for(RegexReplacement rr: regexReplacements) { + for (RegexReplacement rr : regexReplacements) { cur = cur.replaceAll(rr.getRegex(), rr.getReplacement()); } return cur.getBytes(StandardCharsets.UTF_8); @@ -190,7 +186,7 @@ private static class RegexReplacement { public RegexReplacement(String regex, String replacement) { super(); - this.regex = regex.substring(1).substring(0, regex.length()-2); + this.regex = regex.substring(1).substring(0, regex.length() - 2); this.replacement = replacement; } @@ -213,23 +209,16 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; RegexReplacement other = (RegexReplacement) obj; if (regex == null) { - if (other.regex != null) - return false; - } else if (!regex.equals(other.regex)) - return false; + if (other.regex != null) return false; + } else if (!regex.equals(other.regex)) return false; if (replacement == null) { - if (other.replacement != null) - return false; - } else if (!replacement.equals(other.replacement)) - return false; + if (other.replacement != null) return false; + } else if (!replacement.equals(other.replacement)) return false; return true; } diff --git a/src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java b/src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java index e2f10dfcae..3f7b4737bf 100644 --- a/src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java +++ b/src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java @@ -56,18 +56,30 @@ public class PrivilegesInterceptorImpl extends PrivilegesInterceptor { private static final String USER_TENANT = "__user__"; private static final String EMPTY_STRING = ""; private static final Map KIBANA_INDEX_SETTINGS = ImmutableMap.of( - IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1, - IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS, "0-1" + IndexMetadata.SETTING_NUMBER_OF_SHARDS, + 1, + IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS, + "0-1" ); protected final Logger log = LogManager.getLogger(this.getClass()); - public PrivilegesInterceptorImpl(IndexNameExpressionResolver resolver, ClusterService clusterService, Client client, ThreadPool threadPool) { + public PrivilegesInterceptorImpl( + IndexNameExpressionResolver resolver, + ClusterService clusterService, + Client client, + ThreadPool threadPool + ) { super(resolver, clusterService, client, threadPool); } - private boolean isTenantAllowed(final ActionRequest request, final String action, final User user, final Map tenants, - final String requestedTenant) { + private boolean isTenantAllowed( + final ActionRequest request, + final String action, + final User user, + final Map tenants, + final String requestedTenant + ) { if (!tenants.keySet().contains(requestedTenant)) { log.warn("Tenant {} is not allowed for user {}", requestedTenant, user.getName()); @@ -94,23 +106,29 @@ private boolean isTenantAllowed(final ActionRequest request, final String action * */ @Override - public ReplaceResult replaceDashboardsIndex(final ActionRequest request, final String action, final User user, final DynamicConfigModel config, - final Resolved requestedResolved, final Map tenants) { + public ReplaceResult replaceDashboardsIndex( + final ActionRequest request, + final String action, + final User user, + final DynamicConfigModel config, + final Resolved requestedResolved, + final Map tenants + ) { - final boolean enabled = config.isDashboardsMultitenancyEnabled();//config.dynamic.kibana.multitenancy_enabled; + final boolean enabled = config.isDashboardsMultitenancyEnabled();// config.dynamic.kibana.multitenancy_enabled; if (!enabled) { return CONTINUE_EVALUATION_REPLACE_RESULT; } - //next two lines needs to be retrieved from configuration - final String dashboardsServerUsername = config.getDashboardsServerUsername();//config.dynamic.kibana.server_username; - final String dashboardsIndexName = config.getDashboardsIndexname();//config.dynamic.kibana.index; + // next two lines needs to be retrieved from configuration + final String dashboardsServerUsername = config.getDashboardsServerUsername();// config.dynamic.kibana.server_username; + final String dashboardsIndexName = config.getDashboardsIndexname();// config.dynamic.kibana.index; String requestedTenant = user.getRequestedTenant(); - if(USER_TENANT.equals(requestedTenant)) { + if (USER_TENANT.equals(requestedTenant)) { final boolean private_tenant_enabled = config.isDashboardsPrivateTenantEnabled(); - if(!private_tenant_enabled) { + if (!private_tenant_enabled) { return ACCESS_DENIED_REPLACE_RESULT; } } @@ -120,8 +138,10 @@ public ReplaceResult replaceDashboardsIndex(final ActionRequest request, final S log.debug("raw requestedTenant: '" + requestedTenant + "'"); } - //intercept when requests are not made by the kibana server and if the kibana index/alias (.kibana) is the only index/alias involved - final boolean dashboardsIndexOnly = !user.getName().equals(dashboardsServerUsername) && resolveToDashboardsIndexOrAlias(requestedResolved, dashboardsIndexName); + // intercept when requests are not made by the kibana server and if the kibana index/alias (.kibana) is the only index/alias + // involved + final boolean dashboardsIndexOnly = !user.getName().equals(dashboardsServerUsername) + && resolveToDashboardsIndexOrAlias(requestedResolved, dashboardsIndexName); final boolean isTraceEnabled = log.isTraceEnabled(); if (requestedTenant == null || requestedTenant.length() == 0) { if (isTraceEnabled) { @@ -140,21 +160,23 @@ public ReplaceResult replaceDashboardsIndex(final ActionRequest request, final S } if (isDebugEnabled && !user.getName().equals(dashboardsServerUsername)) { - //log statements only here + // log statements only here log.debug("requestedResolved: " + requestedResolved); } - //request not made by the kibana server and user index is the only index/alias involved + // request not made by the kibana server and user index is the only index/alias involved if (!user.getName().equals(dashboardsServerUsername) && !requestedResolved.isLocalAll()) { final Set indices = requestedResolved.getAllIndices(); final String tenantIndexName = toUserIndexName(dashboardsIndexName, requestedTenant); - if (indices.size() == 1 && indices.iterator().next().startsWith(tenantIndexName) && - isTenantAllowed(request, action, user, tenants, requestedTenant)) { - return ACCESS_GRANTED_REPLACE_RESULT; + if (indices.size() == 1 + && indices.iterator().next().startsWith(tenantIndexName) + && isTenantAllowed(request, action, user, tenants, requestedTenant)) { + return ACCESS_GRANTED_REPLACE_RESULT; } } - //intercept when requests are not made by the kibana server and if the kibana index/alias (.kibana) is the only index/alias involved + // intercept when requests are not made by the kibana server and if the kibana index/alias (.kibana) is the only index/alias + // involved if (dashboardsIndexOnly) { if (isDebugEnabled) { @@ -211,7 +233,8 @@ private CreateIndexRequestBuilder newCreateIndexRequestBuilderIfAbsent(final Str } String concreteName = getConcreteIndexName(name, indicesLookup); if (concreteName != null) { - return client.admin().indices() + return client.admin() + .indices() .prepareCreate(concreteName) .addAlias(new Alias(name)) .setSettings(KIBANA_INDEX_SETTINGS) @@ -220,7 +243,12 @@ private CreateIndexRequestBuilder newCreateIndexRequestBuilderIfAbsent(final Str return null; } - private CreateIndexRequestBuilder replaceIndex(final ActionRequest request, final String oldIndexName, final String newIndexName, final String action) { + private CreateIndexRequestBuilder replaceIndex( + final ActionRequest request, + final String oldIndexName, + final String newIndexName, + final String action + ) { boolean kibOk = false; CreateIndexRequestBuilder createIndexRequestBuilder = null; @@ -228,10 +256,10 @@ private CreateIndexRequestBuilder replaceIndex(final ActionRequest request, fina log.debug("{} index will be replaced with {} in this {} request", oldIndexName, newIndexName, request.getClass().getName()); } - //handle msearch and mget - //in case of GET change the .kibana index to the userskibanaindex - //in case of Search add the usersDashboardsindex - //if (request instanceof CompositeIndicesRequest) { + // handle msearch and mget + // in case of GET change the .kibana index to the userskibanaindex + // in case of Search add the usersDashboardsindex + // if (request instanceof CompositeIndicesRequest) { String[] newIndexNames = new String[] { newIndexName }; // CreateIndexRequest @@ -312,7 +340,7 @@ private CreateIndexRequestBuilder replaceIndex(final ActionRequest request, fina ((SingleShardRequest) request).index(newIndexName); kibOk = true; } else if (request instanceof RefreshRequest) { - ((RefreshRequest) request).indices(newIndexNames); //??? + ((RefreshRequest) request).indices(newIndexNames); // ??? kibOk = true; } else if (request instanceof ReplicationRequest) { ((ReplicationRequest) request).index(newIndexName); diff --git a/src/main/java/org/opensearch/security/configuration/Salt.java b/src/main/java/org/opensearch/security/configuration/Salt.java index cde4104fa2..3799fa846f 100644 --- a/src/main/java/org/opensearch/security/configuration/Salt.java +++ b/src/main/java/org/opensearch/security/configuration/Salt.java @@ -45,13 +45,19 @@ public Salt(final byte[] salt) { private Salt(final String saltAsString) { this.salt16 = new byte[SALT_SIZE]; if (saltAsString.equals(ConfigConstants.SECURITY_COMPLIANCE_SALT_DEFAULT)) { - log.warn("If you plan to use field masking pls configure compliance salt {} to be a random string of 16 chars length identical on all nodes", saltAsString); + log.warn( + "If you plan to use field masking pls configure compliance salt {} to be a random string of 16 chars length identical on all nodes", + saltAsString + ); } try { ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(saltAsString); byteBuffer.get(salt16); if (byteBuffer.remaining() > 0) { - log.warn("Provided compliance salt {} is greater than 16 bytes. Only the first 16 bytes are used for salting", saltAsString); + log.warn( + "Provided compliance salt {} is greater than 16 bytes. Only the first 16 bytes are used for salting", + saltAsString + ); } } catch (BufferUnderflowException e) { throw new OpenSearchException("Provided compliance salt " + saltAsString + " must at least contain 16 bytes", e); @@ -73,7 +79,10 @@ byte[] getSalt16() { * @return configuration */ public static Salt from(final Settings settings) { - final String saltAsString = settings.get(ConfigConstants.SECURITY_COMPLIANCE_SALT, ConfigConstants.SECURITY_COMPLIANCE_SALT_DEFAULT); + final String saltAsString = settings.get( + ConfigConstants.SECURITY_COMPLIANCE_SALT, + ConfigConstants.SECURITY_COMPLIANCE_SALT_DEFAULT + ); return new Salt(saltAsString); } } diff --git a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java index cfb2a1de61..2e58424c63 100644 --- a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java +++ b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java @@ -39,11 +39,24 @@ public class SecurityFlsDlsIndexSearcherWrapper extends SecurityIndexSearcherWrapper { // TODO: the list is outdated. It is necessary to change how meta fields are handled in the near future. - // We may consider using MapperService.isMetadataField() instead of relying on the static set or - // (if it is too costly or does not meet requirements) use IndicesModule.getBuiltInMetadataFields() - // for OpenSearch version specific Set of meta fields - private static final Set metaFields = Sets.newHashSet("_source", "_version", "_field_names", - "_seq_no", "_primary_term", "_id", IgnoredFieldMapper.NAME, "_index", "_routing", "_size", "_timestamp", "_ttl", "_type"); + // We may consider using MapperService.isMetadataField() instead of relying on the static set or + // (if it is too costly or does not meet requirements) use IndicesModule.getBuiltInMetadataFields() + // for OpenSearch version specific Set of meta fields + private static final Set metaFields = Sets.newHashSet( + "_source", + "_version", + "_field_names", + "_seq_no", + "_primary_term", + "_id", + IgnoredFieldMapper.NAME, + "_index", + "_routing", + "_size", + "_timestamp", + "_ttl", + "_type" + ); private final ClusterService clusterService; private final IndexService indexService; private final AuditLog auditlog; @@ -51,9 +64,16 @@ public class SecurityFlsDlsIndexSearcherWrapper extends SecurityIndexSearcherWra private final DlsQueryParser dlsQueryParser; private final Salt salt; - public SecurityFlsDlsIndexSearcherWrapper(final IndexService indexService, final Settings settings, - final AdminDNs adminDNs, final ClusterService clusterService, final AuditLog auditlog, - final ComplianceIndexingOperationListener ciol, final PrivilegesEvaluator evaluator, final Salt salt) { + public SecurityFlsDlsIndexSearcherWrapper( + final IndexService indexService, + final Settings settings, + final AdminDNs adminDNs, + final ClusterService clusterService, + final AuditLog auditlog, + final ComplianceIndexingOperationListener ciol, + final PrivilegesEvaluator evaluator, + final Salt salt + ) { super(indexService, settings, adminDNs, evaluator); ciol.setIs(indexService); this.clusterService = clusterService; @@ -64,7 +84,7 @@ public SecurityFlsDlsIndexSearcherWrapper(final IndexService indexService, final if (allowNowinDlsQueries) { nowInMillis = () -> System.currentTimeMillis(); } else { - nowInMillis = () -> {throw new IllegalArgumentException("'now' is not allowed in DLS queries");}; + nowInMillis = () -> { throw new IllegalArgumentException("'now' is not allowed in DLS queries"); }; } log.debug("FLS/DLS {} enabled for index {}", this, indexService.index().getName()); this.salt = salt; @@ -80,14 +100,20 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm Set maskedFields = null; Query dlsQuery = null; - if(!isAdmin) { - - final Map> allowedFlsFields = (Map>) HeaderHelper.deserializeSafeFromHeader(threadContext, - ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER); - final Map> queries = (Map>) HeaderHelper.deserializeSafeFromHeader(threadContext, - ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER); - final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader(threadContext, - ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER); + if (!isAdmin) { + + final Map> allowedFlsFields = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadContext, + ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER + ); + final Map> queries = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadContext, + ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER + ); + final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadContext, + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + ); final String flsEval = SecurityUtils.evalMap(allowedFlsFields, index.getName()); final String dlsEval = SecurityUtils.evalMap(queries, index.getName()); @@ -114,7 +140,17 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm } } - return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader(reader, flsFields, dlsQuery, - indexService, threadContext, clusterService, auditlog, maskedFields, shardId, salt); + return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader( + reader, + flsFields, + dlsQuery, + indexService, + threadContext, + clusterService, + auditlog, + maskedFields, + shardId, + salt + ); } } diff --git a/src/main/java/org/opensearch/security/configuration/SecurityIndexSearcherWrapper.java b/src/main/java/org/opensearch/security/configuration/SecurityIndexSearcherWrapper.java index a998b5f278..9c7d451fa3 100644 --- a/src/main/java/org/opensearch/security/configuration/SecurityIndexSearcherWrapper.java +++ b/src/main/java/org/opensearch/security/configuration/SecurityIndexSearcherWrapper.java @@ -47,7 +47,7 @@ import org.opensearch.security.support.WildcardMatcher; import org.opensearch.security.user.User; -public class SecurityIndexSearcherWrapper implements CheckedFunction { +public class SecurityIndexSearcherWrapper implements CheckedFunction { protected final Logger log = LogManager.getLogger(this.getClass()); protected final ThreadContext threadContext; @@ -63,18 +63,32 @@ public class SecurityIndexSearcherWrapper implements CheckedFunction existingConfiguration = load(getConfigName(), false); - - if (!isWriteable(channel, existingConfiguration, name)) { - return; - } - - boolean existed = existingConfiguration.exists(name); - existingConfiguration.remove(name); - - if (existed) { - AbstractApiAction.saveAndUpdateConfigs(this.securityIndexName, client, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { - - @Override - public void onResponse(IndexResponse response) { - successResponse(channel, "'" + name + "' deleted."); - } - }); - - } else { - notFound(channel, getResourceName() + " " + name + " not found."); - } - } - - protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { - - final String name = request.param("name"); - - if (name == null || name.length() == 0) { - badRequestResponse(channel, "No " + getResourceName() + " specified."); - return; - } - - final SecurityDynamicConfiguration existingConfiguration = load(getConfigName(), false); - - if (existingConfiguration.getSeqNo() < 0) { - forbidden(channel, "Security index need to be updated to support '" + getConfigName().toLCString() + "'. Use SecurityAdmin to populate."); - return; - } - - if (!isWriteable(channel, existingConfiguration, name)) { - return; - } - - if (isReadonlyFieldUpdated(existingConfiguration, content)) { - conflict(channel, "Attempted to update read-only property."); - return; - } - - if (log.isTraceEnabled() && content != null) { - log.trace(content.toString()); - } - - boolean existed = existingConfiguration.exists(name); - final Object newContent = DefaultObjectMapper.readTree(content, existingConfiguration.getImplementingClass()); - if (!hasPermissionsToCreate(existingConfiguration, newContent, getResourceName())) { - forbidden(channel, "No permissions"); - return; - } - existingConfiguration.putCObject(name, newContent); - - AbstractApiAction.saveAndUpdateConfigs(this.securityIndexName, client, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { - - @Override - public void onResponse(IndexResponse response) { - if (existed) { - successResponse(channel, "'" + name + "' updated."); - } else { - createdResponse(channel, "'" + name + "' created."); - } - - } - }); - - } - - protected void handlePost(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { - notImplemented(channel, Method.POST); - } - - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) throws IOException { - return false; - } + protected abstract String getResourceName(); + + protected abstract CType getConfigName(); + + protected void handleApiRequest(final RestChannel channel, final RestRequest request, final Client client) throws IOException { + + try { + // validate additional settings, if any + AbstractConfigurationValidator validator = getValidator(request, request.content()); + if (!validator.validate()) { + request.params().clear(); + badRequestResponse(channel, validator); + return; + } + switch (request.method()) { + case DELETE: + handleDelete(channel, request, client, validator.getContentAsNode()); + break; + case POST: + handlePost(channel, request, client, validator.getContentAsNode()); + break; + case PUT: + handlePut(channel, request, client, validator.getContentAsNode()); + break; + case GET: + handleGet(channel, request, client, validator.getContentAsNode()); + break; + default: + throw new IllegalArgumentException(request.method() + " not supported"); + } + } catch (JsonMappingException jme) { + throw jme; + // TODO strip source + // if(jme.getLocation() == null || jme.getLocation().getSourceRef() == null) { + // throw jme; + // } else throw new JsonMappingException(null, jme.getMessage()); + } + } + + protected void handleDelete(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + final String name = request.param("name"); + + if (name == null || name.length() == 0) { + badRequestResponse(channel, "No " + getResourceName() + " specified."); + return; + } + + final SecurityDynamicConfiguration existingConfiguration = load(getConfigName(), false); + + if (!isWriteable(channel, existingConfiguration, name)) { + return; + } + + boolean existed = existingConfiguration.exists(name); + existingConfiguration.remove(name); + + if (existed) { + AbstractApiAction.saveAndUpdateConfigs( + this.securityIndexName, + client, + getConfigName(), + existingConfiguration, + new OnSucessActionListener(channel) { + + @Override + public void onResponse(IndexResponse response) { + successResponse(channel, "'" + name + "' deleted."); + } + } + ); + + } else { + notFound(channel, getResourceName() + " " + name + " not found."); + } + } + + protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + + final String name = request.param("name"); + + if (name == null || name.length() == 0) { + badRequestResponse(channel, "No " + getResourceName() + " specified."); + return; + } + + final SecurityDynamicConfiguration existingConfiguration = load(getConfigName(), false); + + if (existingConfiguration.getSeqNo() < 0) { + forbidden( + channel, + "Security index need to be updated to support '" + getConfigName().toLCString() + "'. Use SecurityAdmin to populate." + ); + return; + } + + if (!isWriteable(channel, existingConfiguration, name)) { + return; + } + + if (isReadonlyFieldUpdated(existingConfiguration, content)) { + conflict(channel, "Attempted to update read-only property."); + return; + } + + if (log.isTraceEnabled() && content != null) { + log.trace(content.toString()); + } + + boolean existed = existingConfiguration.exists(name); + final Object newContent = DefaultObjectMapper.readTree(content, existingConfiguration.getImplementingClass()); + if (!hasPermissionsToCreate(existingConfiguration, newContent, getResourceName())) { + forbidden(channel, "No permissions"); + return; + } + existingConfiguration.putCObject(name, newContent); + + AbstractApiAction.saveAndUpdateConfigs( + this.securityIndexName, + client, + getConfigName(), + existingConfiguration, + new OnSucessActionListener(channel) { + + @Override + public void onResponse(IndexResponse response) { + if (existed) { + successResponse(channel, "'" + name + "' updated."); + } else { + createdResponse(channel, "'" + name + "' created."); + } + + } + } + ); + + } - protected void handleGet(final RestChannel channel, RestRequest request, Client client, final JsonNode content) - throws IOException{ + protected void handlePost(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + notImplemented(channel, Method.POST); + } - final String resourcename = request.param("name"); + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) throws IOException { + return false; + } - final SecurityDynamicConfiguration configuration = load(getConfigName(), true); - filter(configuration); + protected void handleGet(final RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { + final String resourcename = request.param("name"); - // no specific resource requested, return complete config - if (resourcename == null || resourcename.length() == 0) { + final SecurityDynamicConfiguration configuration = load(getConfigName(), true); + filter(configuration); - successResponse(channel, configuration); - return; - } + // no specific resource requested, return complete config + if (resourcename == null || resourcename.length() == 0) { - if (!configuration.exists(resourcename)) { - notFound(channel, "Resource '" + resourcename + "' not found."); - return; - } + successResponse(channel, configuration); + return; + } - configuration.removeOthers(resourcename); - successResponse(channel, configuration); + if (!configuration.exists(resourcename)) { + notFound(channel, "Resource '" + resourcename + "' not found."); + return; + } - return; - } + configuration.removeOthers(resourcename); + successResponse(channel, configuration); - protected final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent) { - SecurityDynamicConfiguration loaded = cl.getConfigurationsFromIndex(Collections.singleton(config), logComplianceEvent).get(config).deepClone(); - return DynamicConfigFactory.addStatics(loaded); - } + return; + } - protected final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent, boolean acceptInvalid) { - SecurityDynamicConfiguration loaded = cl.getConfigurationsFromIndex(Collections.singleton(config), logComplianceEvent, acceptInvalid).get(config).deepClone(); + protected final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent) { + SecurityDynamicConfiguration loaded = cl.getConfigurationsFromIndex(Collections.singleton(config), logComplianceEvent) + .get(config) + .deepClone(); return DynamicConfigFactory.addStatics(loaded); } - protected boolean ensureIndexExists() { - if (!cs.state().metadata().hasConcreteIndex(this.securityIndexName)) { - return false; - } - return true; - } - - protected void filter(SecurityDynamicConfiguration builder) { - if (!isSuperAdmin()){ - builder.removeHidden(); - } - builder.set_meta(null); - } - - protected boolean isReadonlyFieldUpdated(final JsonNode existingResource, final JsonNode targetResource) { - // Default is false. Override function for additional logic - return false; - } - - protected boolean isReadonlyFieldUpdated(final SecurityDynamicConfiguration configuration, final JsonNode targetResource) { - // Default is false. Override function for additional logic - return false; - } - - abstract class OnSucessActionListener implements ActionListener { - - private final RestChannel channel; - - public OnSucessActionListener(RestChannel channel) { - super(); - this.channel = channel; - } - - @Override - public final void onFailure(Exception e) { - if (ExceptionsHelper.unwrapCause(e) instanceof VersionConflictEngineException) { - conflict(channel, e.getMessage()); - } else { - internalErrorResponse(channel, "Error "+e.getMessage()); - } - } - - } - - public static void saveAndUpdateConfigs(final String indexName, final Client client, final CType cType, final SecurityDynamicConfiguration configuration, final ActionListener actionListener) { - final IndexRequest ir = new IndexRequest(indexName); - final String id = cType.toLCString(); - - configuration.removeStatic(); - - try { - client.index(ir.id(id) - .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .setIfSeqNo(configuration.getSeqNo()) - .setIfPrimaryTerm(configuration.getPrimaryTerm()) - .source(id, XContentHelper.toXContent(configuration, XContentType.JSON, false)), - new ConfigUpdatingActionListener<>(new String[]{id}, client, actionListener)); - } catch (IOException e) { - throw ExceptionsHelper.convertToOpenSearchException(e); - } - } - - protected static class ConfigUpdatingActionListener implements ActionListener { - private final String[] cTypes; - private final Client client; - private final ActionListener delegate; - - public ConfigUpdatingActionListener(String[] cTypes, Client client, ActionListener delegate) { - this.cTypes = Objects.requireNonNull(cTypes, "cTypes must not be null"); - this.client = Objects.requireNonNull(client, "client must not be null"); - this.delegate = Objects.requireNonNull(delegate, "delegate must not be null"); - } - - @Override - public void onResponse(Response response) { - - final ConfigUpdateRequest cur = new ConfigUpdateRequest(cTypes); - - client.execute(ConfigUpdateAction.INSTANCE, cur, new ActionListener() { - @Override - public void onResponse(final ConfigUpdateResponse ur) { - if(ur.hasFailures()) { - delegate.onFailure(ur.failures().get(0)); - return; - } - delegate.onResponse(response); - } - - @Override - public void onFailure(final Exception e) { - delegate.onFailure(e); - } - }); - - } - - @Override - public void onFailure(Exception e) { - delegate.onFailure(e); - } - - } - - @Override - protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - - // consume all parameters first so we can return a correct HTTP status, - // not 400 - consumeParameters(request); - - // check if .opendistro_security index has been initialized - if (!ensureIndexExists()) { - return channel -> internalErrorResponse(channel, ErrorType.SECURITY_NOT_INITIALIZED.getMessage()); - } - - // check if request is authorized - String authError = restApiPrivilegesEvaluator.checkAccessPermissions(request, getEndpoint()); - - final User user = (User) threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - final String userName = user == null ? null : user.getName(); - if (authError != null) { - log.error("No permission to access REST API: " + authError); - auditLog.logMissingPrivileges(authError, userName, request); - // for rest request - request.params().clear(); - return channel -> forbidden(channel, "No permission to access REST API: " + authError); - } else { - auditLog.logGrantedPrivileges(userName, request); - } - - final Object originalUser = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - final Object originalRemoteAddress = threadPool.getThreadContext() - .getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); - final Object originalOrigin = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN); - - return channel -> threadPool.generic().submit(() -> { - try (StoredContext ignore = threadPool.getThreadContext().stashContext()) { - threadPool.getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); - threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, originalUser); - threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, originalRemoteAddress); - threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, originalOrigin); - - handleApiRequest(channel, request, client); - } catch (Exception e) { - log.error("Error processing request {}", request, e); - try { - channel.sendResponse(new BytesRestResponse(channel, e)); - } catch (IOException ioe) { - throw ExceptionsHelper.convertToOpenSearchException(e); - } - } - }); - } - - protected boolean checkConfigUpdateResponse(final ConfigUpdateResponse response) { - - final int nodeCount = cs.state().getNodes().getNodes().size(); - final int expectedConfigCount = 1; - - boolean success = response.getNodes().size() == nodeCount; - if (!success) { - log.error( - "Expected " + nodeCount + " nodes to return response, but got only " + response.getNodes().size()); - } - - for (final String nodeId : response.getNodesMap().keySet()) { - final ConfigUpdateNodeResponse node = response.getNodesMap().get(nodeId); - final boolean successNode = node.getUpdatedConfigTypes() != null - && node.getUpdatedConfigTypes().length == expectedConfigCount; - - if (!successNode) { - log.error("Expected " + expectedConfigCount + " config types for node " + nodeId + " but got only " - + Arrays.toString(node.getUpdatedConfigTypes())); - } - - success = success && successNode; - } - - return success; - } - - protected static XContentBuilder convertToJson(RestChannel channel, ToXContent toxContent) { - try { - XContentBuilder builder = channel.newBuilder(); - toxContent.toXContent(builder, ToXContent.EMPTY_PARAMS); - return builder; - } catch (IOException e) { - throw ExceptionsHelper.convertToOpenSearchException(e); - } - } - - protected void response(RestChannel channel, RestStatus status, String message) { - try { - final XContentBuilder builder = channel.newBuilder(); - builder.startObject(); - builder.field("status", status.name()); - builder.field("message", message); - builder.endObject(); - channel.sendResponse(new BytesRestResponse(status, builder)); - } catch (IOException e) { - throw ExceptionsHelper.convertToOpenSearchException(e); - } - } - - protected void successResponse(RestChannel channel, SecurityDynamicConfiguration response) { - channel.sendResponse( - new BytesRestResponse(RestStatus.OK, convertToJson(channel, response))); - } - - protected void successResponse(RestChannel channel) { - try { - final XContentBuilder builder = channel.newBuilder(); - builder.startObject(); - builder.endObject(); - channel.sendResponse( - new BytesRestResponse(RestStatus.OK, builder)); - } catch (IOException e) { - internalErrorResponse(channel, "Unable to fetch license: " + e.getMessage()); - log.error("Cannot fetch convert license to XContent due to", e); - } - } - - protected void badRequestResponse(RestChannel channel, AbstractConfigurationValidator validator) { - channel.sendResponse(new BytesRestResponse(RestStatus.BAD_REQUEST, validator.errorsAsXContent(channel))); - } - - protected void successResponse(RestChannel channel, String message) { - response(channel, RestStatus.OK, message); - } - - protected void createdResponse(RestChannel channel, String message) { - response(channel, RestStatus.CREATED, message); - } - - protected void badRequestResponse(RestChannel channel, String message) { - response(channel, RestStatus.BAD_REQUEST, message); - } - - protected void notFound(RestChannel channel, String message) { - response(channel, RestStatus.NOT_FOUND, message); - } - - protected void forbidden(RestChannel channel, String message) { - response(channel, RestStatus.FORBIDDEN, message); - } - - protected void internalErrorResponse(RestChannel channel, String message) { - response(channel, RestStatus.INTERNAL_SERVER_ERROR, message); - } - - protected void unprocessable(RestChannel channel, String message) { - response(channel, RestStatus.UNPROCESSABLE_ENTITY, message); - } - - protected void conflict(RestChannel channel, String message) { - response(channel, RestStatus.CONFLICT, message); - } - - protected void notImplemented(RestChannel channel, Method method) { - response(channel, RestStatus.NOT_IMPLEMENTED, - "Method " + method.name() + " not supported for this action."); - } - - protected final boolean isReserved(SecurityDynamicConfiguration configuration, String resourceName) { - if(isStatic(configuration, resourceName)) { //static is also always reserved - return true; - } - - final Object o = configuration.getCEntry(resourceName); - return o != null && o instanceof Hideable && ((Hideable) o).isReserved(); - } - - protected final boolean isHidden(SecurityDynamicConfiguration configuration, String resourceName) { - return configuration.isHidden(resourceName) && !isSuperAdmin(); - } - - protected final boolean isStatic(SecurityDynamicConfiguration configuration, String resourceName) { - final Object o = configuration.getCEntry(resourceName); - return o != null && o instanceof StaticDefinable && ((StaticDefinable) o).isStatic(); - } - - /** - * Consume all defined parameters for the request. Before we handle the - * request in subclasses where we actually need the parameter, some global - * checks are performed, e.g. check whether the .security_index index exists. Thus, the - * parameter(s) have not been consumed, and OpenSearch will always return a 400 with - * an internal error message. - * - * @param request - */ - protected void consumeParameters(final RestRequest request) { - request.param("name"); - } - - @Override - public String getName() { - return getClass().getSimpleName(); - } - - protected abstract Endpoint getEndpoint(); - - protected boolean isSuperAdmin() { - return restApiAdminPrivilegesEvaluator.isCurrentUserRestApiAdminFor(getEndpoint()); - } - - /** - * Resource is readonly if it is reserved and user is not super admin. - * @param existingConfiguration Configuration - * @param name - * @return True if resource readonly - */ - protected boolean isReadOnly(final SecurityDynamicConfiguration existingConfiguration, - String name) { - return isSuperAdmin() ? false: isReserved(existingConfiguration, name); - } - - /** - * Checks if it is valid to add role to opendistro_security_roles or rolesmapping. - * Role can be mapped to user if it exists. Only superadmin can add hidden or reserved roles. - * - * @param channel Rest Channel for response - * @param role Name of the role - * @return True if role can be mapped - */ - protected boolean isValidRolesMapping(final RestChannel channel, final String role) { - final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); - final SecurityDynamicConfiguration rolesMappingConfiguration = load(CType.ROLESMAPPING, false); - - if (!rolesConfiguration.exists(role)) { - notFound(channel, "Role '"+role+"' is not available for role-mapping."); - return false; - } - - if (isHidden(rolesConfiguration, role)) { - notFound(channel, "Role '" + role + "' is not available for role-mapping."); - return false; - } - - return isWriteable(channel, rolesMappingConfiguration, role); - } - - boolean isWriteable(final RestChannel channel, final SecurityDynamicConfiguration configuration, final String resourceName) { - if (isHidden(configuration, resourceName)) { - notFound(channel, "Resource '" + resourceName + "' is not available."); - return false; - } - - if (isReadOnly(configuration, resourceName)) { - forbidden(channel, "Resource '" + resourceName + "' is read-only."); - return false; - } - return true; - } + protected final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent, boolean acceptInvalid) { + SecurityDynamicConfiguration loaded = cl.getConfigurationsFromIndex( + Collections.singleton(config), + logComplianceEvent, + acceptInvalid + ).get(config).deepClone(); + return DynamicConfigFactory.addStatics(loaded); + } + + protected boolean ensureIndexExists() { + if (!cs.state().metadata().hasConcreteIndex(this.securityIndexName)) { + return false; + } + return true; + } + + protected void filter(SecurityDynamicConfiguration builder) { + if (!isSuperAdmin()) { + builder.removeHidden(); + } + builder.set_meta(null); + } + + protected boolean isReadonlyFieldUpdated(final JsonNode existingResource, final JsonNode targetResource) { + // Default is false. Override function for additional logic + return false; + } + + protected boolean isReadonlyFieldUpdated(final SecurityDynamicConfiguration configuration, final JsonNode targetResource) { + // Default is false. Override function for additional logic + return false; + } + + abstract class OnSucessActionListener implements ActionListener { + + private final RestChannel channel; + + public OnSucessActionListener(RestChannel channel) { + super(); + this.channel = channel; + } + + @Override + public final void onFailure(Exception e) { + if (ExceptionsHelper.unwrapCause(e) instanceof VersionConflictEngineException) { + conflict(channel, e.getMessage()); + } else { + internalErrorResponse(channel, "Error " + e.getMessage()); + } + } + + } + + public static void saveAndUpdateConfigs( + final String indexName, + final Client client, + final CType cType, + final SecurityDynamicConfiguration configuration, + final ActionListener actionListener + ) { + final IndexRequest ir = new IndexRequest(indexName); + final String id = cType.toLCString(); + + configuration.removeStatic(); + + try { + client.index( + ir.id(id) + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .setIfSeqNo(configuration.getSeqNo()) + .setIfPrimaryTerm(configuration.getPrimaryTerm()) + .source(id, XContentHelper.toXContent(configuration, XContentType.JSON, false)), + new ConfigUpdatingActionListener<>(new String[] { id }, client, actionListener) + ); + } catch (IOException e) { + throw ExceptionsHelper.convertToOpenSearchException(e); + } + } + + protected static class ConfigUpdatingActionListener implements ActionListener { + private final String[] cTypes; + private final Client client; + private final ActionListener delegate; + + public ConfigUpdatingActionListener(String[] cTypes, Client client, ActionListener delegate) { + this.cTypes = Objects.requireNonNull(cTypes, "cTypes must not be null"); + this.client = Objects.requireNonNull(client, "client must not be null"); + this.delegate = Objects.requireNonNull(delegate, "delegate must not be null"); + } + + @Override + public void onResponse(Response response) { + + final ConfigUpdateRequest cur = new ConfigUpdateRequest(cTypes); + + client.execute(ConfigUpdateAction.INSTANCE, cur, new ActionListener() { + @Override + public void onResponse(final ConfigUpdateResponse ur) { + if (ur.hasFailures()) { + delegate.onFailure(ur.failures().get(0)); + return; + } + delegate.onResponse(response); + } + + @Override + public void onFailure(final Exception e) { + delegate.onFailure(e); + } + }); + + } + + @Override + public void onFailure(Exception e) { + delegate.onFailure(e); + } + + } + + @Override + protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + + // consume all parameters first so we can return a correct HTTP status, + // not 400 + consumeParameters(request); + + // check if .opendistro_security index has been initialized + if (!ensureIndexExists()) { + return channel -> internalErrorResponse(channel, ErrorType.SECURITY_NOT_INITIALIZED.getMessage()); + } + + // check if request is authorized + String authError = restApiPrivilegesEvaluator.checkAccessPermissions(request, getEndpoint()); + + final User user = (User) threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final String userName = user == null ? null : user.getName(); + if (authError != null) { + log.error("No permission to access REST API: " + authError); + auditLog.logMissingPrivileges(authError, userName, request); + // for rest request + request.params().clear(); + return channel -> forbidden(channel, "No permission to access REST API: " + authError); + } else { + auditLog.logGrantedPrivileges(userName, request); + } + + final Object originalUser = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final Object originalRemoteAddress = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); + final Object originalOrigin = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN); + + return channel -> threadPool.generic().submit(() -> { + try (StoredContext ignore = threadPool.getThreadContext().stashContext()) { + threadPool.getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, originalUser); + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, originalRemoteAddress); + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, originalOrigin); + + handleApiRequest(channel, request, client); + } catch (Exception e) { + log.error("Error processing request {}", request, e); + try { + channel.sendResponse(new BytesRestResponse(channel, e)); + } catch (IOException ioe) { + throw ExceptionsHelper.convertToOpenSearchException(e); + } + } + }); + } + + protected boolean checkConfigUpdateResponse(final ConfigUpdateResponse response) { + + final int nodeCount = cs.state().getNodes().getNodes().size(); + final int expectedConfigCount = 1; + + boolean success = response.getNodes().size() == nodeCount; + if (!success) { + log.error("Expected " + nodeCount + " nodes to return response, but got only " + response.getNodes().size()); + } + + for (final String nodeId : response.getNodesMap().keySet()) { + final ConfigUpdateNodeResponse node = response.getNodesMap().get(nodeId); + final boolean successNode = node.getUpdatedConfigTypes() != null && node.getUpdatedConfigTypes().length == expectedConfigCount; + + if (!successNode) { + log.error( + "Expected " + + expectedConfigCount + + " config types for node " + + nodeId + + " but got only " + + Arrays.toString(node.getUpdatedConfigTypes()) + ); + } + + success = success && successNode; + } + + return success; + } + + protected static XContentBuilder convertToJson(RestChannel channel, ToXContent toxContent) { + try { + XContentBuilder builder = channel.newBuilder(); + toxContent.toXContent(builder, ToXContent.EMPTY_PARAMS); + return builder; + } catch (IOException e) { + throw ExceptionsHelper.convertToOpenSearchException(e); + } + } + + protected void response(RestChannel channel, RestStatus status, String message) { + try { + final XContentBuilder builder = channel.newBuilder(); + builder.startObject(); + builder.field("status", status.name()); + builder.field("message", message); + builder.endObject(); + channel.sendResponse(new BytesRestResponse(status, builder)); + } catch (IOException e) { + throw ExceptionsHelper.convertToOpenSearchException(e); + } + } + + protected void successResponse(RestChannel channel, SecurityDynamicConfiguration response) { + channel.sendResponse(new BytesRestResponse(RestStatus.OK, convertToJson(channel, response))); + } + + protected void successResponse(RestChannel channel) { + try { + final XContentBuilder builder = channel.newBuilder(); + builder.startObject(); + builder.endObject(); + channel.sendResponse(new BytesRestResponse(RestStatus.OK, builder)); + } catch (IOException e) { + internalErrorResponse(channel, "Unable to fetch license: " + e.getMessage()); + log.error("Cannot fetch convert license to XContent due to", e); + } + } + + protected void badRequestResponse(RestChannel channel, AbstractConfigurationValidator validator) { + channel.sendResponse(new BytesRestResponse(RestStatus.BAD_REQUEST, validator.errorsAsXContent(channel))); + } + + protected void successResponse(RestChannel channel, String message) { + response(channel, RestStatus.OK, message); + } + + protected void createdResponse(RestChannel channel, String message) { + response(channel, RestStatus.CREATED, message); + } + + protected void badRequestResponse(RestChannel channel, String message) { + response(channel, RestStatus.BAD_REQUEST, message); + } + + protected void notFound(RestChannel channel, String message) { + response(channel, RestStatus.NOT_FOUND, message); + } + + protected void forbidden(RestChannel channel, String message) { + response(channel, RestStatus.FORBIDDEN, message); + } + + protected void internalErrorResponse(RestChannel channel, String message) { + response(channel, RestStatus.INTERNAL_SERVER_ERROR, message); + } + + protected void unprocessable(RestChannel channel, String message) { + response(channel, RestStatus.UNPROCESSABLE_ENTITY, message); + } + + protected void conflict(RestChannel channel, String message) { + response(channel, RestStatus.CONFLICT, message); + } + + protected void notImplemented(RestChannel channel, Method method) { + response(channel, RestStatus.NOT_IMPLEMENTED, "Method " + method.name() + " not supported for this action."); + } + + protected final boolean isReserved(SecurityDynamicConfiguration configuration, String resourceName) { + if (isStatic(configuration, resourceName)) { // static is also always reserved + return true; + } + + final Object o = configuration.getCEntry(resourceName); + return o != null && o instanceof Hideable && ((Hideable) o).isReserved(); + } + + protected final boolean isHidden(SecurityDynamicConfiguration configuration, String resourceName) { + return configuration.isHidden(resourceName) && !isSuperAdmin(); + } + + protected final boolean isStatic(SecurityDynamicConfiguration configuration, String resourceName) { + final Object o = configuration.getCEntry(resourceName); + return o != null && o instanceof StaticDefinable && ((StaticDefinable) o).isStatic(); + } + + /** + * Consume all defined parameters for the request. Before we handle the + * request in subclasses where we actually need the parameter, some global + * checks are performed, e.g. check whether the .security_index index exists. Thus, the + * parameter(s) have not been consumed, and OpenSearch will always return a 400 with + * an internal error message. + * + * @param request + */ + protected void consumeParameters(final RestRequest request) { + request.param("name"); + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + protected abstract Endpoint getEndpoint(); + + protected boolean isSuperAdmin() { + return restApiAdminPrivilegesEvaluator.isCurrentUserRestApiAdminFor(getEndpoint()); + } + + /** + * Resource is readonly if it is reserved and user is not super admin. + * @param existingConfiguration Configuration + * @param name + * @return True if resource readonly + */ + protected boolean isReadOnly(final SecurityDynamicConfiguration existingConfiguration, String name) { + return isSuperAdmin() ? false : isReserved(existingConfiguration, name); + } + + /** + * Checks if it is valid to add role to opendistro_security_roles or rolesmapping. + * Role can be mapped to user if it exists. Only superadmin can add hidden or reserved roles. + * + * @param channel Rest Channel for response + * @param role Name of the role + * @return True if role can be mapped + */ + protected boolean isValidRolesMapping(final RestChannel channel, final String role) { + final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); + final SecurityDynamicConfiguration rolesMappingConfiguration = load(CType.ROLESMAPPING, false); + + if (!rolesConfiguration.exists(role)) { + notFound(channel, "Role '" + role + "' is not available for role-mapping."); + return false; + } + + if (isHidden(rolesConfiguration, role)) { + notFound(channel, "Role '" + role + "' is not available for role-mapping."); + return false; + } + + return isWriteable(channel, rolesMappingConfiguration, role); + } + + boolean isWriteable(final RestChannel channel, final SecurityDynamicConfiguration configuration, final String resourceName) { + if (isHidden(configuration, resourceName)) { + notFound(channel, "Resource '" + resourceName + "' is not available."); + return false; + } + + if (isReadOnly(configuration, resourceName)) { + forbidden(channel, "Resource '" + resourceName + "' is read-only."); + return false; + } + return true; + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java index e5121f939d..68446366bc 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java @@ -59,34 +59,37 @@ */ public class AccountApiAction extends AbstractApiAction { private static final String RESOURCE_NAME = "account"; - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(Method.GET, "/account"), - new Route(Method.PUT, "/account") - )); + private static final List routes = addRoutesPrefix( + ImmutableList.of(new Route(Method.GET, "/account"), new Route(Method.PUT, "/account")) + ); private final PrivilegesEvaluator privilegesEvaluator; private final ThreadContext threadContext; - public AccountApiAction(Settings settings, - Path configPath, - RestController controller, - Client client, - AdminDNs adminDNs, - ConfigurationRepository cl, - ClusterService cs, - PrincipalExtractor principalExtractor, - PrivilegesEvaluator privilegesEvaluator, - ThreadPool threadPool, - AuditLog auditLog) { + public AccountApiAction( + Settings settings, + Path configPath, + RestController controller, + Client client, + AdminDNs adminDNs, + ConfigurationRepository cl, + ClusterService cs, + PrincipalExtractor principalExtractor, + PrivilegesEvaluator privilegesEvaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, privilegesEvaluator, threadPool, auditLog); this.privilegesEvaluator = privilegesEvaluator; this.threadContext = threadPool.getThreadContext(); } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -138,14 +141,14 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client final SecurityDynamicConfiguration configuration = load(getConfigName(), false); builder.field("user_name", user.getName()) - .field("is_reserved", isReserved(configuration, user.getName())) - .field("is_hidden", configuration.isHidden(user.getName())) - .field("is_internal_user", configuration.exists(user.getName())) - .field("user_requested_tenant", user.getRequestedTenant()) - .field("backend_roles", user.getRoles()) - .field("custom_attribute_names", user.getCustomAttributesMap().keySet()) - .field("tenants", privilegesEvaluator.mapTenants(user, securityRoles)) - .field("roles", securityRoles); + .field("is_reserved", isReserved(configuration, user.getName())) + .field("is_hidden", configuration.isHidden(user.getName())) + .field("is_internal_user", configuration.exists(user.getName())) + .field("user_requested_tenant", user.getRequestedTenant()) + .field("backend_roles", user.getRoles()) + .field("custom_attribute_names", user.getCustomAttributesMap().keySet()) + .field("tenants", privilegesEvaluator.mapTenants(user, securityRoles)) + .field("roles", securityRoles); } builder.endObject(); @@ -153,9 +156,7 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client } catch (final Exception exception) { log.error(exception.toString()); - builder.startObject() - .field("error", exception.toString()) - .endObject(); + builder.startObject().field("error", exception.toString()).endObject(); response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); } @@ -185,7 +186,8 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client * @throws IOException */ @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); final String username = user.getName(); final SecurityDynamicConfiguration internalUser = load(CType.INTERNALUSERS, false); @@ -224,12 +226,18 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C internalUserEntry.setHash(hash); - AccountApiAction.saveAndUpdateConfigs(this.securityIndexName, client, CType.INTERNALUSERS, internalUser, new OnSucessActionListener(channel) { - @Override - public void onResponse(IndexResponse response) { - successResponse(channel, "'" + username + "' updated."); + AccountApiAction.saveAndUpdateConfigs( + this.securityIndexName, + client, + CType.INTERNALUSERS, + internalUser, + new OnSucessActionListener(channel) { + @Override + public void onResponse(IndexResponse response) { + successResponse(channel, "'" + username + "' updated."); + } } - }); + ); } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java index 23a3a451b9..94af3ad3af 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java @@ -44,109 +44,123 @@ public class ActionGroupsApiAction extends PatchableResourceApiAction { - private static final List routes = addRoutesPrefix(ImmutableList.of( - // legacy mapping for backwards compatibility - // TODO: remove in next version - new Route(Method.GET, "/actiongroup/{name}"), - new Route(Method.GET, "/actiongroup/"), - new Route(Method.DELETE, "/actiongroup/{name}"), - new Route(Method.PUT, "/actiongroup/{name}"), - - // corrected mapping, introduced in OpenSearch Security - new Route(Method.GET, "/actiongroups/{name}"), - new Route(Method.GET, "/actiongroups/"), - new Route(Method.DELETE, "/actiongroups/{name}"), - new Route(Method.PUT, "/actiongroups/{name}"), - new Route(Method.PATCH, "/actiongroups/"), - new Route(Method.PATCH, "/actiongroups/{name}") - - )); - - @Override - protected Endpoint getEndpoint() { - return Endpoint.ACTIONGROUPS; - } - - @Inject - public ActionGroupsApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); - } - - @Override - public List routes() { - return routes; - } - - @Override - protected AbstractConfigurationValidator getValidator(final RestRequest request, BytesReference ref, Object... param) { - return new ActionGroupValidator(request, isSuperAdmin(), ref, this.settings, param); - } - - @Override - protected CType getConfigName() { - return CType.ACTIONGROUPS; - } - - @Override + private static final List routes = addRoutesPrefix( + ImmutableList.of( + // legacy mapping for backwards compatibility + // TODO: remove in next version + new Route(Method.GET, "/actiongroup/{name}"), + new Route(Method.GET, "/actiongroup/"), + new Route(Method.DELETE, "/actiongroup/{name}"), + new Route(Method.PUT, "/actiongroup/{name}"), + + // corrected mapping, introduced in OpenSearch Security + new Route(Method.GET, "/actiongroups/{name}"), + new Route(Method.GET, "/actiongroups/"), + new Route(Method.DELETE, "/actiongroups/{name}"), + new Route(Method.PUT, "/actiongroups/{name}"), + new Route(Method.PATCH, "/actiongroups/"), + new Route(Method.PATCH, "/actiongroups/{name}") + + ) + ); + + @Override + protected Endpoint getEndpoint() { + return Endpoint.ACTIONGROUPS; + } + + @Inject + public ActionGroupsApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + } + + @Override + public List routes() { + return routes; + } + + @Override + protected AbstractConfigurationValidator getValidator(final RestRequest request, BytesReference ref, Object... param) { + return new ActionGroupValidator(request, isSuperAdmin(), ref, this.settings, param); + } + + @Override + protected CType getConfigName() { + return CType.ACTIONGROUPS; + } + + @Override protected String getResourceName() { return "actiongroup"; - } - - @Override - protected void consumeParameters(final RestRequest request) { - request.param("name"); - } - - @Override - protected void handlePut(RestChannel channel, RestRequest request, Client client, JsonNode content) throws IOException { - final String name = request.param("name"); - - if (name == null || name.length() == 0) { - badRequestResponse(channel, "No " + getResourceName() + " specified."); - return; - } - - // Prevent the case where action group and role share a same name. - SecurityDynamicConfiguration existingRolesConfig = load(CType.ROLES, false); - Set existingRoles = existingRolesConfig.getCEntries().keySet(); - if (existingRoles.contains(name)) { - badRequestResponse(channel, name + " is an existing role. A action group cannot be named with an existing role name."); - return; - } - - // Prevent the case where action group references to itself in the allowed_actions. - final SecurityDynamicConfiguration existingActionGroupsConfig = load(getConfigName(), false); - final Object actionGroup = DefaultObjectMapper.readTree(content, existingActionGroupsConfig.getImplementingClass()); - existingActionGroupsConfig.putCObject(name, actionGroup); - if (hasActionGroupSelfReference(existingActionGroupsConfig, name)) { - badRequestResponse(channel, name + " cannot be an allowed_action of itself"); - return; - } - // prevent creation of groups for REST admin api - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(actionGroup)) { - forbidden(channel, "Not allowed"); - return; - } - super.handlePut(channel, request, client, content); - } - - @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfiguration, - final Object content, - final String resourceName) throws IOException { - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(content)) { - return false; - } - return true; - } - - @Override - protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(existingConfiguration.getCEntry(name))) { - return true; - } - return super.isReadOnly(existingConfiguration, name); - } + } + + @Override + protected void consumeParameters(final RestRequest request) { + request.param("name"); + } + + @Override + protected void handlePut(RestChannel channel, RestRequest request, Client client, JsonNode content) throws IOException { + final String name = request.param("name"); + + if (name == null || name.length() == 0) { + badRequestResponse(channel, "No " + getResourceName() + " specified."); + return; + } + + // Prevent the case where action group and role share a same name. + SecurityDynamicConfiguration existingRolesConfig = load(CType.ROLES, false); + Set existingRoles = existingRolesConfig.getCEntries().keySet(); + if (existingRoles.contains(name)) { + badRequestResponse(channel, name + " is an existing role. A action group cannot be named with an existing role name."); + return; + } + + // Prevent the case where action group references to itself in the allowed_actions. + final SecurityDynamicConfiguration existingActionGroupsConfig = load(getConfigName(), false); + final Object actionGroup = DefaultObjectMapper.readTree(content, existingActionGroupsConfig.getImplementingClass()); + existingActionGroupsConfig.putCObject(name, actionGroup); + if (hasActionGroupSelfReference(existingActionGroupsConfig, name)) { + badRequestResponse(channel, name + " cannot be an allowed_action of itself"); + return; + } + // prevent creation of groups for REST admin api + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(actionGroup)) { + forbidden(channel, "Not allowed"); + return; + } + super.handlePut(channel, request, client, content); + } + + @Override + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfiguration, + final Object content, + final String resourceName + ) throws IOException { + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(content)) { + return false; + } + return true; + } + + @Override + protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(existingConfiguration.getCEntry(name))) { + return true; + } + return super.isReadOnly(existingConfiguration, name); + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java index cefaeb5c6c..0c5b2775aa 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java @@ -9,7 +9,6 @@ * GitHub history for details. */ - package org.opensearch.security.dlic.rest.api; import java.io.IOException; @@ -84,24 +83,36 @@ */ public class AllowlistApiAction extends PatchableResourceApiAction { private static final List routes = ImmutableList.of( - new Route(RestRequest.Method.GET, "/_plugins/_security/api/allowlist"), - new Route(RestRequest.Method.PUT, "/_plugins/_security/api/allowlist"), - new Route(RestRequest.Method.PATCH, "/_plugins/_security/api/allowlist") + new Route(RestRequest.Method.GET, "/_plugins/_security/api/allowlist"), + new Route(RestRequest.Method.PUT, "/_plugins/_security/api/allowlist"), + new Route(RestRequest.Method.PATCH, "/_plugins/_security/api/allowlist") ); private static final String name = "config"; @Inject - public AllowlistApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public AllowlistApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -115,9 +126,7 @@ protected void handleApiRequest(final RestChannel channel, final RestRequest req } @Override - protected void handleGet(final RestChannel channel, RestRequest request, Client client, final JsonNode content) - throws IOException { - + protected void handleGet(final RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { final SecurityDynamicConfiguration configuration = load(getConfigName(), true); filter(configuration); @@ -125,36 +134,46 @@ protected void handleGet(final RestChannel channel, RestRequest request, Client } @Override - protected void handleDelete(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handleDelete(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, RestRequest.Method.DELETE); } @Override - protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { final SecurityDynamicConfiguration existingConfiguration = load(getConfigName(), false); if (existingConfiguration.getSeqNo() < 0) { - forbidden(channel, "Security index need to be updated to support '" + getConfigName().toLCString() + "'. Use SecurityAdmin to populate."); + forbidden( + channel, + "Security index need to be updated to support '" + getConfigName().toLCString() + "'. Use SecurityAdmin to populate." + ); return; } boolean existed = existingConfiguration.exists(name); existingConfiguration.putCObject(name, DefaultObjectMapper.readTree(content, existingConfiguration.getImplementingClass())); - saveAndUpdateConfigs(this.securityIndexName,client, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { - - @Override - public void onResponse(IndexResponse response) { - if (existed) { - successResponse(channel, "'" + name + "' updated."); - } else { - createdResponse(channel, "'" + name + "' created."); + saveAndUpdateConfigs( + this.securityIndexName, + client, + getConfigName(), + existingConfiguration, + new OnSucessActionListener(channel) { + + @Override + public void onResponse(IndexResponse response) { + if (existed) { + successResponse(channel, "'" + name + "' updated."); + } else { + createdResponse(channel, "'" + name + "' created."); + } } } - }); + ); } - @Override public List routes() { return routes; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java index e19f04d437..a61f66c6e3 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java @@ -124,11 +124,13 @@ * [{"op": "replace", "path": "/config/compliance/internal_config", "value": "true"}] */ public class AuditApiAction extends PatchableResourceApiAction { - private static final List routes = addRoutesPrefix(ImmutableList.of( + private static final List routes = addRoutesPrefix( + ImmutableList.of( new Route(RestRequest.Method.GET, "/audit/"), new Route(RestRequest.Method.PUT, "/audit/{name}"), new Route(RestRequest.Method.PATCH, "/audit/") - )); + ) + ); private static final String RESOURCE_NAME = "config"; @VisibleForTesting @@ -139,24 +141,28 @@ public class AuditApiAction extends PatchableResourceApiAction { private final PrivilegesEvaluator privilegesEvaluator; private final ThreadContext threadContext; - public AuditApiAction(final Settings settings, - final Path configPath, - final RestController controller, - final Client client, - final AdminDNs adminDNs, - final ConfigurationRepository cl, - final ClusterService cs, - final PrincipalExtractor principalExtractor, - final PrivilegesEvaluator privilegesEvaluator, - final ThreadPool threadPool, - final AuditLog auditLog) { + public AuditApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator privilegesEvaluator, + final ThreadPool threadPool, + final AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, privilegesEvaluator, threadPool, auditLog); this.privilegesEvaluator = privilegesEvaluator; this.threadContext = threadPool.getThreadContext(); try { - this.readonlyFields = DefaultObjectMapper.YAML_MAPPER - .readValue(this.getClass().getResourceAsStream(STATIC_RESOURCE), new TypeReference>>() {}) - .get(READONLY_FIELD); + this.readonlyFields = DefaultObjectMapper.YAML_MAPPER.readValue( + this.getClass().getResourceAsStream(STATIC_RESOURCE), + new TypeReference>>() { + } + ).get(READONLY_FIELD); if (!AuditConfig.FIELD_PATHS.containsAll(this.readonlyFields)) { throw new StaticResourceException("Invalid read-only field paths provided in static resource file " + STATIC_RESOURCE); } @@ -166,9 +172,11 @@ public AuditApiAction(final Settings settings, } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -189,7 +197,8 @@ protected void handleApiRequest(final RestChannel channel, final RestRequest req } @Override - protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { if (!RESOURCE_NAME.equals(request.param("name"))) { badRequestResponse(channel, "name must be config"); return; @@ -245,9 +254,7 @@ protected CType getConfigName() { @Override protected boolean isReadonlyFieldUpdated(final JsonNode existingResource, final JsonNode targetResource) { if (!isSuperAdmin()) { - return readonlyFields - .stream() - .anyMatch(path -> !existingResource.at(path).equals(targetResource.at(path))); + return readonlyFields.stream().anyMatch(path -> !existingResource.at(path).equals(targetResource.at(path))); } return false; } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java index 497efcdf76..fa6d967624 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java @@ -41,69 +41,77 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class AuthTokenProcessorAction extends AbstractApiAction { - private static final List routes = addRoutesPrefix(Collections.singletonList( - new Route(Method.POST, "/authtoken") - )); - - @Inject - public AuthTokenProcessorAction(final Settings settings, final Path configPath, final RestController controller, - final Client client, final AdminDNs adminDNs, final ConfigurationRepository cl, - final ClusterService cs, final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, - ThreadPool threadPool, AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, - auditLog); - } - - @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { - return true; - } - - @Override - public List routes() { - return routes; - } - - @Override - protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { - - // Just do nothing here. Eligible authenticators will intercept calls and - // provide own responses. - successResponse(channel,""); - } - - @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new NoOpValidator(request, ref, this.settings, param); - } - - @Override - protected String getResourceName() { - return "authtoken"; - } - - @Override + private static final List routes = addRoutesPrefix(Collections.singletonList(new Route(Method.POST, "/authtoken"))); + + @Inject + public AuthTokenProcessorAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + } + + @Override + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { + return true; + } + + @Override + public List routes() { + return routes; + } + + @Override + protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + + // Just do nothing here. Eligible authenticators will intercept calls and + // provide own responses. + successResponse(channel, ""); + } + + @Override + protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { + return new NoOpValidator(request, ref, this.settings, param); + } + + @Override + protected String getResourceName() { + return "authtoken"; + } + + @Override protected CType getConfigName() { - return null; - } + return null; + } - @Override - protected Endpoint getEndpoint() { - return Endpoint.AUTHTOKEN; - } + @Override + protected Endpoint getEndpoint() { + return Endpoint.AUTHTOKEN; + } + public static class Response { + private String authorization; - public static class Response { - private String authorization; + public String getAuthorization() { + return authorization; + } - public String getAuthorization() { - return authorization; - } - - public void setAuthorization(String authorization) { - this.authorization = authorization; - } - } + public void setAuthorization(String authorization) { + this.authorization = authorization; + } + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java index 406b81679c..2f8a60aa65 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java @@ -45,103 +45,118 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class FlushCacheApiAction extends AbstractApiAction { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(Method.DELETE, "/cache"), - new Route(Method.GET, "/cache"), - new Route(Method.PUT, "/cache"), - new Route(Method.POST, "/cache") - )); - - @Inject - public FlushCacheApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); - } - - @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { - return true; - } - - @Override - public List routes() { - return routes; - } - - @Override - protected Endpoint getEndpoint() { - return Endpoint.CACHE; - } - - @Override - protected void handleDelete(RestChannel channel, - RestRequest request, Client client, final JsonNode content) throws IOException - { - - client.execute( - ConfigUpdateAction.INSTANCE, - new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0])), - new ActionListener() { - - @Override - public void onResponse(ConfigUpdateResponse ur) { - if(ur.hasFailures()) { - log.error("Cannot flush cache due to", ur.failures().get(0)); - internalErrorResponse(channel, "Cannot flush cache due to "+ ur.failures().get(0).getMessage()+"."); - return; - } - successResponse(channel, "Cache flushed successfully."); - if (log.isDebugEnabled()) { - log.debug("cache flushed successfully"); - } - } - - @Override - public void onFailure(Exception e) { - log.error("Cannot flush cache due to", e); - internalErrorResponse(channel, "Cannot flush cache due to "+ e.getMessage()+"."); - } - - } - ); - } - - @Override - protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content)throws IOException { - notImplemented(channel, Method.POST); - } - - @Override - protected void handleGet(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException{ - notImplemented(channel, Method.GET); - } - - @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException{ - notImplemented(channel, Method.PUT); - } - - @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new NoOpValidator(request, ref, this.settings, param); - } - - @Override - protected String getResourceName() { - // not needed - return null; - } - - @Override + private static final List routes = addRoutesPrefix( + ImmutableList.of( + new Route(Method.DELETE, "/cache"), + new Route(Method.GET, "/cache"), + new Route(Method.PUT, "/cache"), + new Route(Method.POST, "/cache") + ) + ); + + @Inject + public FlushCacheApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + } + + @Override + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { + return true; + } + + @Override + public List routes() { + return routes; + } + + @Override + protected Endpoint getEndpoint() { + return Endpoint.CACHE; + } + + @Override + protected void handleDelete(RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { + + client.execute( + ConfigUpdateAction.INSTANCE, + new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0])), + new ActionListener() { + + @Override + public void onResponse(ConfigUpdateResponse ur) { + if (ur.hasFailures()) { + log.error("Cannot flush cache due to", ur.failures().get(0)); + internalErrorResponse(channel, "Cannot flush cache due to " + ur.failures().get(0).getMessage() + "."); + return; + } + successResponse(channel, "Cache flushed successfully."); + if (log.isDebugEnabled()) { + log.debug("cache flushed successfully"); + } + } + + @Override + public void onFailure(Exception e) { + log.error("Cannot flush cache due to", e); + internalErrorResponse(channel, "Cannot flush cache due to " + e.getMessage() + "."); + } + + } + ); + } + + @Override + protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + notImplemented(channel, Method.POST); + } + + @Override + protected void handleGet(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + notImplemented(channel, Method.GET); + } + + @Override + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + notImplemented(channel, Method.PUT); + } + + @Override + protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { + return new NoOpValidator(request, ref, this.settings, param); + } + + @Override + protected String getResourceName() { + // not needed + return null; + } + + @Override protected CType getConfigName() { - return null; - } + return null; + } - @Override - protected void consumeParameters(final RestRequest request) { - // not needed - } + @Override + protected void consumeParameters(final RestRequest request) { + // not needed + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java index 6c1169f35a..3e041961b0 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java @@ -52,10 +52,11 @@ public class InternalUsersApiAction extends PatchableResourceApiAction { static final List RESTRICTED_FROM_USERNAME = ImmutableList.of( - ":" // Not allowed in basic auth, see https://stackoverflow.com/a/33391003/533057 + ":" // Not allowed in basic auth, see https://stackoverflow.com/a/33391003/533057 ); - private static final List routes = addRoutesPrefix(ImmutableList.of( + private static final List routes = addRoutesPrefix( + ImmutableList.of( new Route(Method.GET, "/user/{name}"), new Route(Method.GET, "/user/"), new Route(Method.POST, "/user/{name}/authtoken"), @@ -70,24 +71,36 @@ public class InternalUsersApiAction extends PatchableResourceApiAction { new Route(Method.PUT, "/internalusers/{name}"), new Route(Method.PATCH, "/internalusers/"), new Route(Method.PATCH, "/internalusers/{name}") - )); + ) + ); UserService userService; @Inject - public InternalUsersApiAction(final Settings settings, final Path configPath, final RestController controller, - final Client client, final AdminDNs adminDNs, final ConfigurationRepository cl, - final ClusterService cs, final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, - ThreadPool threadPool, UserService userService, AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, - auditLog); + public InternalUsersApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + UserService userService, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); this.userService = userService; } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -102,7 +115,8 @@ protected Endpoint getEndpoint() { } @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { final String username = request.param("name"); @@ -118,7 +132,7 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C // Don't allow user to add non-existent role or a role for which role-mapping is hidden or reserved final List securityRoles = securityJsonNode.get("opendistro_security_roles").asList(); if (securityRoles != null) { - for (final String role: securityRoles) { + for (final String role : securityRoles) { if (!isValidRolesMapping(channel, role)) { return; } @@ -139,12 +153,10 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C } ((ObjectNode) content).put("name", username); internalUsersConfiguration = userService.createOrUpdateAccount((ObjectNode) content); - } - catch (UserServiceException ex) { + } catch (UserServiceException ex) { badRequestResponse(channel, ex.getMessage()); return; - } - catch (IOException ex) { + } catch (IOException ex) { throw new IOException(ex); } @@ -153,8 +165,10 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C // sanity check, this should usually not happen final String hash = ((Hashed) internalUsersConfiguration.getCEntry(username)).getHash(); if (hash == null || hash.length() == 0) { - internalErrorResponse(channel, - "Existing user " + username + " has no password, and no new password or hash was specified."); + internalErrorResponse( + channel, + "Existing user " + username + " has no password, and no new password or hash was specified." + ); return; } contentAsNode.put("hash", hash); @@ -163,23 +177,27 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C internalUsersConfiguration.remove(username); // checks complete, create or update the user - Object userData = DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass()); + Object userData = DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass()); internalUsersConfiguration.putCObject(username, userData); + saveAndUpdateConfigs( + this.securityIndexName, + client, + CType.INTERNALUSERS, + internalUsersConfiguration, + new OnSucessActionListener(channel) { + + @Override + public void onResponse(IndexResponse response) { + if (userExisted) { + successResponse(channel, "'" + username + "' updated."); + } else { + createdResponse(channel, "'" + username + "' created."); + } - saveAndUpdateConfigs(this.securityIndexName,client, CType.INTERNALUSERS, internalUsersConfiguration, new OnSucessActionListener(channel) { - - - @Override - public void onResponse(IndexResponse response) { - if (userExisted) { - successResponse(channel, "'" + username + "' updated."); - } else { - createdResponse(channel, "'" + username + "' created."); } - } - }); + ); } /** @@ -192,7 +210,7 @@ public void onResponse(IndexResponse response) { * @throws IOException when parsing of configuration files fails (should not happen) */ @Override - protected void handlePost(final RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException{ + protected void handlePost(final RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { final String username = request.param("name"); @@ -215,7 +233,10 @@ protected void handlePost(final RestChannel channel, RestRequest request, Client String authToken = ""; try { - if (request.uri().contains("/internalusers/" + username + "/authtoken") && request.uri().endsWith("/authtoken")) { // Handle auth token fetching + if (request.uri().contains("/internalusers/" + username + "/authtoken") && request.uri().endsWith("/authtoken")) { // Handle + // auth + // token + // fetching authToken = userService.generateAuthToken(username); } else { // Not an auth token request @@ -223,22 +244,20 @@ protected void handlePost(final RestChannel channel, RestRequest request, Client notImplemented(channel, Method.POST); return; } - } catch (UserServiceException ex) { + } catch (UserServiceException ex) { badRequestResponse(channel, ex.getMessage()); return; - } - catch (IOException ex) { + } catch (IOException ex) { throw new IOException(ex); } if (!authToken.isEmpty()) { - createdResponse(channel, "'" + username + "' authtoken generated " + authToken); + createdResponse(channel, "'" + username + "' authtoken generated " + authToken); } else { badRequestResponse(channel, "'" + username + "' authtoken failed to be created."); } } - @Override protected void filter(SecurityDynamicConfiguration builder) { super.filter(builder); @@ -249,8 +268,13 @@ protected void filter(SecurityDynamicConfiguration builder) { } @Override - protected AbstractConfigurationValidator postProcessApplyPatchResult(RestChannel channel, RestRequest request, JsonNode existingResourceAsJsonNode, - JsonNode updatedResourceAsJsonNode, String resourceName) { + protected AbstractConfigurationValidator postProcessApplyPatchResult( + RestChannel channel, + RestRequest request, + JsonNode existingResourceAsJsonNode, + JsonNode updatedResourceAsJsonNode, + String resourceName + ) { AbstractConfigurationValidator retVal = null; JsonNode passwordNode = updatedResourceAsJsonNode.get("password"); diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java index d252515f72..108e29b980 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java @@ -71,14 +71,22 @@ // CS-ENFORCE-SINGLE public class MigrateApiAction extends AbstractApiAction { - private static final List routes = addRoutesPrefix(Collections.singletonList( - new Route(Method.POST, "/migrate") - )); + private static final List routes = addRoutesPrefix(Collections.singletonList(new Route(Method.POST, "/migrate"))); @Inject - public MigrateApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, final PrincipalExtractor principalExtractor, - final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public MigrateApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } @@ -93,9 +101,11 @@ protected Endpoint getEndpoint() { } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -111,12 +121,22 @@ protected void handlePost(RestChannel channel, RestRequest request, Client clien } final SecurityDynamicConfiguration configV6 = (SecurityDynamicConfiguration) loadedConfig; - final SecurityDynamicConfiguration actionGroupsV6 = (SecurityDynamicConfiguration) load(CType.ACTIONGROUPS, true); - final SecurityDynamicConfiguration internalUsersV6 = (SecurityDynamicConfiguration) load(CType.INTERNALUSERS, true); + final SecurityDynamicConfiguration actionGroupsV6 = (SecurityDynamicConfiguration) load( + CType.ACTIONGROUPS, + true + ); + final SecurityDynamicConfiguration internalUsersV6 = (SecurityDynamicConfiguration) load( + CType.INTERNALUSERS, + true + ); final SecurityDynamicConfiguration rolesV6 = (SecurityDynamicConfiguration) load(CType.ROLES, true); - final SecurityDynamicConfiguration rolesmappingV6 = (SecurityDynamicConfiguration) load(CType.ROLESMAPPING, true); + final SecurityDynamicConfiguration rolesmappingV6 = (SecurityDynamicConfiguration) load( + CType.ROLESMAPPING, + true + ); final SecurityDynamicConfiguration nodesDnV6 = (SecurityDynamicConfiguration) load(CType.NODESDN, true); - final SecurityDynamicConfiguration whitelistingSettingV6 = (SecurityDynamicConfiguration) load(CType.WHITELIST, true); + final SecurityDynamicConfiguration whitelistingSettingV6 = (SecurityDynamicConfiguration< + WhitelistingSettings>) load(CType.WHITELIST, true); final SecurityDynamicConfiguration auditConfigV6 = (SecurityDynamicConfiguration) load(CType.AUDIT, true); final ImmutableList.Builder> builder = ImmutableList.builder(); @@ -127,21 +147,29 @@ protected void handlePost(RestChannel channel, RestRequest request, Client clien builder.add(configV7); final SecurityDynamicConfiguration internalUsersV7 = Migration.migrateInternalUsers(internalUsersV6); builder.add(internalUsersV7); - final Tuple, SecurityDynamicConfiguration> rolesTenantsV7 = Migration.migrateRoles(rolesV6, - rolesmappingV6); + final Tuple, SecurityDynamicConfiguration> rolesTenantsV7 = Migration.migrateRoles( + rolesV6, + rolesmappingV6 + ); builder.add(rolesTenantsV7.v1()); builder.add(rolesTenantsV7.v2()); final SecurityDynamicConfiguration rolesmappingV7 = Migration.migrateRoleMappings(rolesmappingV6); builder.add(rolesmappingV7); final SecurityDynamicConfiguration nodesDnV7 = Migration.migrateNodesDn(nodesDnV6); builder.add(nodesDnV7); - final SecurityDynamicConfiguration whitelistingSettingV7 = Migration.migrateWhitelistingSetting(whitelistingSettingV6); + final SecurityDynamicConfiguration whitelistingSettingV7 = Migration.migrateWhitelistingSetting( + whitelistingSettingV6 + ); builder.add(whitelistingSettingV7); final SecurityDynamicConfiguration auditConfigV7 = Migration.migrateAudit(auditConfigV6); builder.add(auditConfigV7); final int replicas = cs.state().metadata().index(securityIndexName).getNumberOfReplicas(); - final String autoExpandReplicas = cs.state().metadata().index(securityIndexName).getSettings().get(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS); + final String autoExpandReplicas = cs.state() + .metadata() + .index(securityIndexName) + .getSettings() + .get(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS); final Builder securityIndexSettings = Settings.builder(); @@ -161,57 +189,78 @@ public void onResponse(AcknowledgedResponse response) { if (response.isAcknowledged()) { log.debug("opendistro_security index deleted successfully"); - client.admin().indices().prepareCreate(securityIndexName).setSettings(securityIndexSettings) - .execute(new ActionListener() { - - @Override - public void onResponse(CreateIndexResponse response) { - final List> dynamicConfigurations = builder.build(); - final ImmutableList.Builder cTypes = ImmutableList.builderWithExpectedSize(dynamicConfigurations.size()); - final BulkRequestBuilder br = client.prepareBulk(securityIndexName); - br.setRefreshPolicy(RefreshPolicy.IMMEDIATE); - try { - for (SecurityDynamicConfiguration dynamicConfiguration : dynamicConfigurations) { - final String id = dynamicConfiguration.getCType().toLCString(); - final BytesReference xContent = XContentHelper.toXContent(dynamicConfiguration, XContentType.JSON, false); - br.add(new IndexRequest().id(id).source(id, xContent)); - cTypes.add(id); - } - } catch (final IOException e1) { - log.error("Unable to create bulk request " + e1, e1); - internalErrorResponse(channel, "Unable to create bulk request."); - return; + client.admin() + .indices() + .prepareCreate(securityIndexName) + .setSettings(securityIndexSettings) + .execute(new ActionListener() { + + @Override + public void onResponse(CreateIndexResponse response) { + final List> dynamicConfigurations = builder.build(); + final ImmutableList.Builder cTypes = ImmutableList.builderWithExpectedSize( + dynamicConfigurations.size() + ); + final BulkRequestBuilder br = client.prepareBulk(securityIndexName); + br.setRefreshPolicy(RefreshPolicy.IMMEDIATE); + try { + for (SecurityDynamicConfiguration dynamicConfiguration : dynamicConfigurations) { + final String id = dynamicConfiguration.getCType().toLCString(); + final BytesReference xContent = XContentHelper.toXContent( + dynamicConfiguration, + XContentType.JSON, + false + ); + br.add(new IndexRequest().id(id).source(id, xContent)); + cTypes.add(id); } + } catch (final IOException e1) { + log.error("Unable to create bulk request " + e1, e1); + internalErrorResponse(channel, "Unable to create bulk request."); + return; + } - br.execute(new ConfigUpdatingActionListener(cTypes.build().toArray(new String[0]), client, new ActionListener() { + br.execute( + new ConfigUpdatingActionListener( + cTypes.build().toArray(new String[0]), + client, + new ActionListener() { + + @Override + public void onResponse(BulkResponse response) { + if (response.hasFailures()) { + log.error( + "Unable to upload migrated configuration because of " + + response.buildFailureMessage() + ); + internalErrorResponse( + channel, + "Unable to upload migrated configuration (bulk index failed)." + ); + } else { + log.debug("Migration completed"); + successResponse(channel, "Migration completed."); + } - @Override - public void onResponse(BulkResponse response) { - if (response.hasFailures()) { - log.error("Unable to upload migrated configuration because of " + response.buildFailureMessage()); - internalErrorResponse(channel, "Unable to upload migrated configuration (bulk index failed)."); - } else { - log.debug("Migration completed"); - successResponse(channel, "Migration completed."); } + @Override + public void onFailure(Exception e) { + log.error("Unable to upload migrated configuration because of " + e, e); + internalErrorResponse(channel, "Unable to upload migrated configuration."); + } } + ) + ); - @Override - public void onFailure(Exception e) { - log.error("Unable to upload migrated configuration because of " + e, e); - internalErrorResponse(channel, "Unable to upload migrated configuration."); - } - })); - - } + } - @Override - public void onFailure(Exception e) { - log.error("Unable to create opendistro_security index because of " + e, e); - internalErrorResponse(channel, "Unable to create opendistro_security index."); - } - }); + @Override + public void onFailure(Exception e) { + log.error("Unable to create opendistro_security index because of " + e, e); + internalErrorResponse(channel, "Unable to create opendistro_security index."); + } + }); } else { log.error("Unable to create opendistro_security index."); @@ -228,17 +277,20 @@ public void onFailure(Exception e) { } @Override - protected void handleDelete(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handleDelete(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.POST); } @Override - protected void handleGet(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handleGet(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.GET); } @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.PUT); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java index e5ec82c245..ef5be4634f 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java @@ -51,20 +51,16 @@ import static org.opensearch.rest.RestRequest.Method.PUT; import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; - public class MultiTenancyConfigApiAction extends AbstractApiAction { private static final List ROUTES = addRoutesPrefix( - ImmutableList.of( - new Route(GET, "/tenancy/config"), - new Route(PUT, "/tenancy/config") - ) + ImmutableList.of(new Route(GET, "/tenancy/config"), new Route(PUT, "/tenancy/config")) ); private final static Set ACCEPTABLE_DEFAULT_TENANTS = ImmutableSet.of( - ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME, - ConfigConstants.TENANCY_GLOBAL_TENANT_NAME, - ConfigConstants.TENANCY_PRIVATE_TENANT_NAME + ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME, + ConfigConstants.TENANCY_GLOBAL_TENANT_NAME, + ConfigConstants.TENANCY_PRIVATE_TENANT_NAME ); @Override @@ -78,12 +74,18 @@ public List routes() { } public MultiTenancyConfigApiAction( - final Settings settings, final Path configPath, - final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, - final ClusterService cs, final PrincipalExtractor principalExtractor, - final PrivilegesEvaluator evaluator, final ThreadPool threadPool, - final AuditLog auditLog) { + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + final ThreadPool threadPool, + final AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } @@ -108,31 +110,25 @@ protected CType getConfigName() { } @Override - protected void handleDelete(final RestChannel channel, - final RestRequest request, - final Client client, - final JsonNode content) throws IOException { + protected void handleDelete(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, RestRequest.Method.DELETE); } private void multitenancyResponse(final ConfigV7 config, final RestChannel channel) { try (final XContentBuilder contentBuilder = channel.newBuilder()) { channel.sendResponse( - new BytesRestResponse( - RestStatus.OK, - contentBuilder - .startObject() - .field( - MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY, - config.dynamic.kibana.default_tenant - ).field( - MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY, - config.dynamic.kibana.private_tenant_enabled - ).field( - MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY, - config.dynamic.kibana.multitenancy_enabled - ).endObject() - ) + new BytesRestResponse( + RestStatus.OK, + contentBuilder.startObject() + .field(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY, config.dynamic.kibana.default_tenant) + .field( + MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY, + config.dynamic.kibana.private_tenant_enabled + ) + .field(MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY, config.dynamic.kibana.multitenancy_enabled) + .endObject() + ) ); } catch (final Exception e) { internalErrorResponse(channel, e.getMessage()); @@ -140,61 +136,49 @@ private void multitenancyResponse(final ConfigV7 config, final RestChannel chann } } - @Override - protected void handleGet(final RestChannel channel, - final RestRequest request, - final Client client, - final JsonNode content) throws IOException { + protected void handleGet(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { final SecurityDynamicConfiguration dynamicConfiguration = load(CType.CONFIG, false); final ConfigV7 config = (ConfigV7) dynamicConfiguration.getCEntry(CType.CONFIG.toLCString()); multitenancyResponse(config, channel); } @Override - protected void handlePut(final RestChannel channel, - final RestRequest request, - final Client client, - final JsonNode content) throws IOException { - final SecurityDynamicConfiguration dynamicConfiguration = (SecurityDynamicConfiguration) - load(CType.CONFIG, false); + protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + final SecurityDynamicConfiguration dynamicConfiguration = (SecurityDynamicConfiguration) load( + CType.CONFIG, + false + ); final ConfigV7 config = dynamicConfiguration.getCEntry(CType.CONFIG.toLCString()); updateAndValidatesValues(config, content); dynamicConfiguration.putCEntry(CType.CONFIG.toLCString(), config); - saveAndUpdateConfigs( - this.securityIndexName, - client, - getConfigName(), - dynamicConfiguration, - new OnSucessActionListener<>(channel) { - @Override - public void onResponse(IndexResponse response) { - multitenancyResponse(config, channel); - } - } - ); + saveAndUpdateConfigs(this.securityIndexName, client, getConfigName(), dynamicConfiguration, new OnSucessActionListener<>(channel) { + @Override + public void onResponse(IndexResponse response) { + multitenancyResponse(config, channel); + } + }); } private void updateAndValidatesValues(final ConfigV7 config, final JsonNode jsonContent) { if (Objects.nonNull(jsonContent.findValue(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY))) { - config.dynamic.kibana.default_tenant = - jsonContent.findValue(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY).asText(); + config.dynamic.kibana.default_tenant = jsonContent.findValue(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY).asText(); } if (Objects.nonNull(jsonContent.findValue(MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY))) { - config.dynamic.kibana.private_tenant_enabled = - jsonContent.findValue(MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY).booleanValue(); + config.dynamic.kibana.private_tenant_enabled = jsonContent.findValue( + MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY + ).booleanValue(); } if (Objects.nonNull(jsonContent.findValue(MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY))) { - config.dynamic.kibana.multitenancy_enabled = - jsonContent.findValue(MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY).asBoolean(); + config.dynamic.kibana.multitenancy_enabled = jsonContent.findValue( + MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY + ).asBoolean(); } - final String defaultTenant = - Optional.ofNullable(config.dynamic.kibana.default_tenant) - .map(String::toLowerCase) - .orElse(""); + final String defaultTenant = Optional.ofNullable(config.dynamic.kibana.default_tenant).map(String::toLowerCase).orElse(""); - if (!config.dynamic.kibana.private_tenant_enabled - && ConfigConstants.TENANCY_PRIVATE_TENANT_NAME.equals(defaultTenant)) { + if (!config.dynamic.kibana.private_tenant_enabled && ConfigConstants.TENANCY_PRIVATE_TENANT_NAME.equals(defaultTenant)) { throw new IllegalArgumentException("Private tenant can not be disabled if it is the default tenant."); } @@ -202,15 +186,17 @@ private void updateAndValidatesValues(final ConfigV7 config, final JsonNode json return; } - final Set availableTenants = - cl.getConfiguration(CType.TENANTS) - .getCEntries() - .keySet() - .stream() - .map(String::toLowerCase) - .collect(Collectors.toSet()); + final Set availableTenants = cl.getConfiguration(CType.TENANTS) + .getCEntries() + .keySet() + .stream() + .map(String::toLowerCase) + .collect(Collectors.toSet()); if (!availableTenants.contains(defaultTenant)) { - throw new IllegalArgumentException(config.dynamic.kibana.default_tenant + " can not be set to default tenant. Default tenant should be selected from one of the available tenants."); + throw new IllegalArgumentException( + config.dynamic.kibana.default_tenant + + " can not be set to default tenant. Default tenant should be selected from one of the available tenants." + ); } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java index 22897b8305..6d44e4073c 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java @@ -60,27 +60,41 @@ public class NodesDnApiAction extends PatchableResourceApiAction { public static final String STATIC_OPENSEARCH_YML_NODES_DN = "STATIC_OPENSEARCH_YML_NODES_DN"; private final List staticNodesDnFromEsYml; - private static final List routes = addRoutesPrefix(ImmutableList.of( + private static final List routes = addRoutesPrefix( + ImmutableList.of( new Route(Method.GET, "/nodesdn/{name}"), new Route(Method.GET, "/nodesdn/"), new Route(Method.DELETE, "/nodesdn/{name}"), new Route(Method.PUT, "/nodesdn/{name}"), new Route(Method.PATCH, "/nodesdn/"), new Route(Method.PATCH, "/nodesdn/{name}") - )); + ) + ); @Inject - public NodesDnApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public NodesDnApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); this.staticNodesDnFromEsYml = settings.getAsList(ConfigConstants.SECURITY_NODES_DN, Collections.emptyList()); } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -144,7 +158,7 @@ private void putStaticEntry(SecurityDynamicConfiguration configuration) { if (NodesDn.class.equals(configuration.getImplementingClass())) { NodesDn nodesDn = new NodesDn(); nodesDn.setNodesDn(staticNodesDnFromEsYml); - ((SecurityDynamicConfiguration)configuration).putCEntry(STATIC_OPENSEARCH_YML_NODES_DN, nodesDn); + ((SecurityDynamicConfiguration) configuration).putCEntry(STATIC_OPENSEARCH_YML_NODES_DN, nodesDn); } else { throw new RuntimeException("Unknown class type - " + configuration.getImplementingClass()); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java index 796a25fa9f..e6d1f1744a 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java @@ -54,16 +54,23 @@ public abstract class PatchableResourceApiAction extends AbstractApiAction { protected final Logger log = LogManager.getLogger(this.getClass()); - public PatchableResourceApiAction(Settings settings, Path configPath, RestController controller, Client client, - AdminDNs adminDNs, ConfigurationRepository cl, ClusterService cs, - PrincipalExtractor principalExtractor, PrivilegesEvaluator evaluator, ThreadPool threadPool, - AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, - auditLog); + public PatchableResourceApiAction( + Settings settings, + Path configPath, + RestController controller, + Client client, + AdminDNs adminDNs, + ConfigurationRepository cl, + ClusterService cs, + PrincipalExtractor principalExtractor, + PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } - private void handlePatch(RestChannel channel, final RestRequest request, final Client client) - throws IOException { + private void handlePatch(RestChannel channel, final RestRequest request, final Client client) throws IOException { if (request.getXContentType() != XContentType.JSON) { badRequestResponse(channel, "PATCH accepts only application/json"); return; @@ -103,8 +110,15 @@ private void handlePatch(RestChannel channel, final RestRequest request, final C } } - private void handleSinglePatch(RestChannel channel, RestRequest request, Client client, String name, - SecurityDynamicConfiguration existingConfiguration, ObjectNode existingAsObjectNode, JsonNode jsonPatch) throws IOException { + private void handleSinglePatch( + RestChannel channel, + RestRequest request, + Client client, + String name, + SecurityDynamicConfiguration existingConfiguration, + ObjectNode existingAsObjectNode, + JsonNode jsonPatch + ) throws IOException { if (!isWriteable(channel, existingConfiguration, name)) { return; } @@ -126,9 +140,15 @@ private void handleSinglePatch(RestChannel channel, RestRequest request, Client return; } - AbstractConfigurationValidator originalValidator = postProcessApplyPatchResult(channel, request, existingResourceAsJsonNode, patchedResourceAsJsonNode, name); + AbstractConfigurationValidator originalValidator = postProcessApplyPatchResult( + channel, + request, + existingResourceAsJsonNode, + patchedResourceAsJsonNode, + name + ); - if(originalValidator != null) { + if (originalValidator != null) { if (!originalValidator.validate()) { request.params().clear(); badRequestResponse(channel, originalValidator); @@ -152,8 +172,13 @@ private void handleSinglePatch(RestChannel channel, RestRequest request, Client JsonNode updatedAsJsonNode = existingAsObjectNode.deepCopy().set(name, patchedResourceAsJsonNode); - SecurityDynamicConfiguration mdc = SecurityDynamicConfiguration.fromNode(updatedAsJsonNode, existingConfiguration.getCType() - , existingConfiguration.getVersion(), existingConfiguration.getSeqNo(), existingConfiguration.getPrimaryTerm()); + SecurityDynamicConfiguration mdc = SecurityDynamicConfiguration.fromNode( + updatedAsJsonNode, + existingConfiguration.getCType(), + existingConfiguration.getVersion(), + existingConfiguration.getSeqNo(), + existingConfiguration.getPrimaryTerm() + ); if (existingConfiguration.getCType().equals(CType.ACTIONGROUPS)) { if (hasActionGroupSelfReference(mdc, name)) { @@ -162,18 +187,24 @@ private void handleSinglePatch(RestChannel channel, RestRequest request, Client } } - saveAndUpdateConfigs(this.securityIndexName,client, getConfigName(), mdc, new OnSucessActionListener(channel){ + saveAndUpdateConfigs(this.securityIndexName, client, getConfigName(), mdc, new OnSucessActionListener(channel) { @Override public void onResponse(IndexResponse response) { successResponse(channel, "'" + name + "' updated."); } - }); + }); } - private void handleBulkPatch(RestChannel channel, RestRequest request, Client client, - SecurityDynamicConfiguration existingConfiguration, ObjectNode existingAsObjectNode, JsonNode jsonPatch) throws IOException { + private void handleBulkPatch( + RestChannel channel, + RestRequest request, + Client client, + SecurityDynamicConfiguration existingConfiguration, + ObjectNode existingAsObjectNode, + JsonNode jsonPatch + ) throws IOException { JsonNode patchedAsJsonNode; @@ -193,16 +224,21 @@ private void handleBulkPatch(RestChannel channel, RestRequest request, Client cl } } - for (Iterator fieldNamesIter = patchedAsJsonNode.fieldNames(); fieldNamesIter.hasNext();) { String resourceName = fieldNamesIter.next(); JsonNode oldResource = existingAsObjectNode.get(resourceName); JsonNode patchedResource = patchedAsJsonNode.get(resourceName); - AbstractConfigurationValidator originalValidator = postProcessApplyPatchResult(channel, request, oldResource, patchedResource, resourceName); + AbstractConfigurationValidator originalValidator = postProcessApplyPatchResult( + channel, + request, + oldResource, + patchedResource, + resourceName + ); - if(originalValidator != null) { + if (originalValidator != null) { if (!originalValidator.validate()) { request.params().clear(); badRequestResponse(channel, originalValidator); @@ -232,25 +268,30 @@ private void handleBulkPatch(RestChannel channel, RestRequest request, Client cl } } } - SecurityDynamicConfiguration mdc = SecurityDynamicConfiguration.fromNode(patchedAsJsonNode, existingConfiguration.getCType() - , existingConfiguration.getVersion(), existingConfiguration.getSeqNo(), existingConfiguration.getPrimaryTerm()); + SecurityDynamicConfiguration mdc = SecurityDynamicConfiguration.fromNode( + patchedAsJsonNode, + existingConfiguration.getCType(), + existingConfiguration.getVersion(), + existingConfiguration.getSeqNo(), + existingConfiguration.getPrimaryTerm() + ); if (existingConfiguration.getCType().equals(CType.ACTIONGROUPS)) { for (String actiongroup : mdc.getCEntries().keySet()) { - if(hasActionGroupSelfReference(mdc, actiongroup)) { + if (hasActionGroupSelfReference(mdc, actiongroup)) { badRequestResponse(channel, actiongroup + " cannot be an allowed_action of itself"); return; } } } - saveAndUpdateConfigs(this.securityIndexName,client, getConfigName(), mdc, new OnSucessActionListener(channel) { + saveAndUpdateConfigs(this.securityIndexName, client, getConfigName(), mdc, new OnSucessActionListener(channel) { @Override public void onResponse(IndexResponse response) { successResponse(channel, "Resource updated."); } - }); + }); } @@ -258,14 +299,19 @@ private JsonNode applyPatch(JsonNode jsonPatch, JsonNode existingResourceAsJsonN return JsonPatch.apply(jsonPatch, existingResourceAsJsonNode); } - protected AbstractConfigurationValidator postProcessApplyPatchResult(RestChannel channel, RestRequest request, JsonNode existingResourceAsJsonNode, JsonNode updatedResourceAsJsonNode, String resourceName) { + protected AbstractConfigurationValidator postProcessApplyPatchResult( + RestChannel channel, + RestRequest request, + JsonNode existingResourceAsJsonNode, + JsonNode updatedResourceAsJsonNode, + String resourceName + ) { // do nothing by default return null; } @Override - protected void handleApiRequest(RestChannel channel, final RestRequest request, final Client client) - throws IOException { + protected void handleApiRequest(RestChannel channel, final RestRequest request, final Client client) throws IOException { if (request.method() == Method.PATCH) { handlePatch(channel, request, client); @@ -274,10 +320,10 @@ protected void handleApiRequest(RestChannel channel, final RestRequest request, } } - private AbstractConfigurationValidator getValidator(RestRequest request, JsonNode patchedResource) - throws JsonProcessingException { + private AbstractConfigurationValidator getValidator(RestRequest request, JsonNode patchedResource) throws JsonProcessingException { BytesReference patchedResourceAsByteReference = new BytesArray( - DefaultObjectMapper.objectMapper.writeValueAsString(patchedResource).getBytes(StandardCharsets.UTF_8)); + DefaultObjectMapper.objectMapper.writeValueAsString(patchedResource).getBytes(StandardCharsets.UTF_8) + ); return getValidator(request, patchedResourceAsByteReference); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/PermissionsInfoAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/PermissionsInfoAction.java index d07be301bd..f57c4eb59c 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/PermissionsInfoAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/PermissionsInfoAction.java @@ -49,85 +49,104 @@ * Provides the evaluated REST API permissions for the currently logged in user */ public class PermissionsInfoAction extends BaseRestHandler { - private static final List routes = addRoutesPrefix(Collections.singletonList( - new Route(Method.GET, "/permissionsinfo") - )); - - private final RestApiPrivilegesEvaluator restApiPrivilegesEvaluator; - private final ThreadPool threadPool; - private final PrivilegesEvaluator privilegesEvaluator; - private final ConfigurationRepository configurationRepository; - - protected PermissionsInfoAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository configurationRepository, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator privilegesEvaluator, ThreadPool threadPool, AuditLog auditLog) { - super(); - this.threadPool = threadPool; - this.privilegesEvaluator = privilegesEvaluator; - this.restApiPrivilegesEvaluator = new RestApiPrivilegesEvaluator(settings, adminDNs, privilegesEvaluator, principalExtractor, configPath, threadPool); - this.configurationRepository = configurationRepository; - } - - @Override - public String getName() { - return getClass().getSimpleName(); - } - - @Override - public List routes() { - return routes; - } - - @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - switch (request.method()) { - case GET: - return handleGet(request, client); - default: - throw new IllegalArgumentException(request.method() + " not supported"); - } - } - - private RestChannelConsumer handleGet(RestRequest request, NodeClient client) throws IOException { + private static final List routes = addRoutesPrefix(Collections.singletonList(new Route(Method.GET, "/permissionsinfo"))); + + private final RestApiPrivilegesEvaluator restApiPrivilegesEvaluator; + private final ThreadPool threadPool; + private final PrivilegesEvaluator privilegesEvaluator; + private final ConfigurationRepository configurationRepository; + + protected PermissionsInfoAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository configurationRepository, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator privilegesEvaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(); + this.threadPool = threadPool; + this.privilegesEvaluator = privilegesEvaluator; + this.restApiPrivilegesEvaluator = new RestApiPrivilegesEvaluator( + settings, + adminDNs, + privilegesEvaluator, + principalExtractor, + configPath, + threadPool + ); + this.configurationRepository = configurationRepository; + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Override + public List routes() { + return routes; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + switch (request.method()) { + case GET: + return handleGet(request, client); + default: + throw new IllegalArgumentException(request.method() + " not supported"); + } + } + + private RestChannelConsumer handleGet(RestRequest request, NodeClient client) throws IOException { return new RestChannelConsumer() { @Override public void accept(RestChannel channel) throws Exception { - XContentBuilder builder = channel.newBuilder(); //NOSONAR + XContentBuilder builder = channel.newBuilder(); // NOSONAR BytesRestResponse response = null; try { - final User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - final TransportAddress remoteAddress = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); - Set userRoles = privilegesEvaluator.mapRoles(user, remoteAddress); - Boolean hasApiAccess = restApiPrivilegesEvaluator.currentUserHasRestApiAccess(userRoles); - Map> disabledEndpoints = restApiPrivilegesEvaluator.getDisabledEndpointsForCurrentUser(user.getName(), userRoles); - if (!configurationRepository.isAuditHotReloadingEnabled()) { - disabledEndpoints.put(Endpoint.AUDIT, ImmutableList.copyOf(Method.values())); - } + final User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final TransportAddress remoteAddress = threadPool.getThreadContext() + .getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); + Set userRoles = privilegesEvaluator.mapRoles(user, remoteAddress); + Boolean hasApiAccess = restApiPrivilegesEvaluator.currentUserHasRestApiAccess(userRoles); + Map> disabledEndpoints = restApiPrivilegesEvaluator.getDisabledEndpointsForCurrentUser( + user.getName(), + userRoles + ); + if (!configurationRepository.isAuditHotReloadingEnabled()) { + disabledEndpoints.put(Endpoint.AUDIT, ImmutableList.copyOf(Method.values())); + } builder.startObject(); - builder.field("user", user==null?null:user.toString()); - builder.field("user_name", user==null?null:user.getName()); //NOSONAR + builder.field("user", user == null ? null : user.toString()); + builder.field("user_name", user == null ? null : user.getName()); // NOSONAR builder.field("has_api_access", hasApiAccess); builder.startObject("disabled_endpoints"); - for(Entry> entry : disabledEndpoints.entrySet()) { - builder.field(entry.getKey().name(), entry.getValue()); + for (Entry> entry : disabledEndpoints.entrySet()) { + builder.field(entry.getKey().name(), entry.getValue()); } builder.endObject(); builder.endObject(); response = new BytesRestResponse(RestStatus.OK, builder); } catch (final Exception e1) { e1.printStackTrace(); - builder = channel.newBuilder(); //NOSONAR + builder = channel.newBuilder(); // NOSONAR builder.startObject(); builder.field("error", e1.toString()); builder.endObject(); response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); } finally { - if(builder != null) { + if (builder != null) { builder.close(); } } @@ -136,6 +155,6 @@ public void accept(RestChannel channel) throws Exception { } }; - } + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java index c8e44ee4ca..b0a0d828cc 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java @@ -41,14 +41,11 @@ public class RestApiAdminPrivilegesEvaluator { private final static String REST_API_PERMISSION_PREFIX = "restapi:admin"; - private final static String REST_ENDPOINT_PERMISSION_PATTERN = - REST_API_PERMISSION_PREFIX + "/%s"; + private final static String REST_ENDPOINT_PERMISSION_PATTERN = REST_API_PERMISSION_PREFIX + "/%s"; - private final static String REST_ENDPOINT_ACTION_PERMISSION_PATTERN = - REST_API_PERMISSION_PREFIX + "/%s/%s"; + private final static String REST_ENDPOINT_ACTION_PERMISSION_PATTERN = REST_API_PERMISSION_PREFIX + "/%s/%s"; - private final static WildcardMatcher REST_API_PERMISSION_PREFIX_MATCHER = - WildcardMatcher.from(REST_API_PERMISSION_PREFIX + "/*"); + private final static WildcardMatcher REST_API_PERMISSION_PREFIX_MATCHER = WildcardMatcher.from(REST_API_PERMISSION_PREFIX + "/*"); @FunctionalInterface public interface PermissionBuilder { @@ -61,25 +58,25 @@ default String build() { } - public final static Map ENDPOINTS_WITH_PERMISSIONS = - ImmutableMap.builder() - .put(Endpoint.ACTIONGROUPS, action -> buildEndpointPermission(Endpoint.ACTIONGROUPS)) - .put(Endpoint.ALLOWLIST, action -> buildEndpointPermission(Endpoint.ALLOWLIST)) - .put(Endpoint.INTERNALUSERS, action -> buildEndpointPermission(Endpoint.INTERNALUSERS)) - .put(Endpoint.NODESDN, action -> buildEndpointPermission(Endpoint.NODESDN)) - .put(Endpoint.ROLES, action -> buildEndpointPermission(Endpoint.ROLES)) - .put(Endpoint.ROLESMAPPING, action -> buildEndpointPermission(Endpoint.ROLESMAPPING)) - .put(Endpoint.TENANTS, action -> buildEndpointPermission(Endpoint.TENANTS)) - .put(Endpoint.SSL, action -> { - switch (action) { - case CERTS_INFO_ACTION: - return buildEndpointActionPermission(Endpoint.SSL, "certs/info"); - case RELOAD_CERTS_ACTION: - return buildEndpointActionPermission(Endpoint.SSL, "certs/reload"); - default: - return null; - } - }).build(); + public final static Map ENDPOINTS_WITH_PERMISSIONS = ImmutableMap.builder() + .put(Endpoint.ACTIONGROUPS, action -> buildEndpointPermission(Endpoint.ACTIONGROUPS)) + .put(Endpoint.ALLOWLIST, action -> buildEndpointPermission(Endpoint.ALLOWLIST)) + .put(Endpoint.INTERNALUSERS, action -> buildEndpointPermission(Endpoint.INTERNALUSERS)) + .put(Endpoint.NODESDN, action -> buildEndpointPermission(Endpoint.NODESDN)) + .put(Endpoint.ROLES, action -> buildEndpointPermission(Endpoint.ROLES)) + .put(Endpoint.ROLESMAPPING, action -> buildEndpointPermission(Endpoint.ROLESMAPPING)) + .put(Endpoint.TENANTS, action -> buildEndpointPermission(Endpoint.TENANTS)) + .put(Endpoint.SSL, action -> { + switch (action) { + case CERTS_INFO_ACTION: + return buildEndpointActionPermission(Endpoint.SSL, "certs/info"); + case RELOAD_CERTS_ACTION: + return buildEndpointActionPermission(Endpoint.SSL, "certs/reload"); + default: + return null; + } + }) + .build(); private final ThreadContext threadContext; @@ -90,10 +87,11 @@ default String build() { private final boolean restapiAdminEnabled; public RestApiAdminPrivilegesEvaluator( - final ThreadContext threadContext, - final PrivilegesEvaluator privilegesEvaluator, - final AdminDNs adminDNs, - final boolean restapiAdminEnabled) { + final ThreadContext threadContext, + final PrivilegesEvaluator privilegesEvaluator, + final AdminDNs adminDNs, + final boolean restapiAdminEnabled + ) { this.threadContext = threadContext; this.privilegesEvaluator = privilegesEvaluator; this.adminDNs = adminDNs; @@ -108,8 +106,10 @@ public boolean isCurrentUserRestApiAdminFor(final Endpoint endpoint, final Strin if (adminDNs.isAdmin(userAndRemoteAddress.getLeft())) { if (logger.isDebugEnabled()) { logger.debug( - "Security admin permissions required for endpoint {} but {} is not an admin", - endpoint, userAndRemoteAddress.getLeft().getName()); + "Security admin permissions required for endpoint {} but {} is not an admin", + endpoint, + userAndRemoteAddress.getLeft().getName() + ); } return true; } @@ -119,23 +119,23 @@ public boolean isCurrentUserRestApiAdminFor(final Endpoint endpoint, final Strin } final String permission = ENDPOINTS_WITH_PERMISSIONS.get(endpoint).build(action); final boolean hasAccess = privilegesEvaluator.hasRestAdminPermissions( - userAndRemoteAddress.getLeft(), - userAndRemoteAddress.getRight(), - permission + userAndRemoteAddress.getLeft(), + userAndRemoteAddress.getRight(), + permission ); if (logger.isDebugEnabled()) { logger.debug( - "User {} with permission {} {} access to endpoint {}", - userAndRemoteAddress.getLeft().getName(), - permission, - hasAccess ? "has" : "has no", - endpoint + "User {} with permission {} {} access to endpoint {}", + userAndRemoteAddress.getLeft().getName(), + permission, + hasAccess ? "has" : "has no", + endpoint ); logger.debug( - "{} set to {}. {} use access decision", - SECURITY_RESTAPI_ADMIN_ENABLED, - restapiAdminEnabled, - restapiAdminEnabled ? "Will" : "Will not" + "{} set to {}. {} use access decision", + SECURITY_RESTAPI_ADMIN_ENABLED, + restapiAdminEnabled, + restapiAdminEnabled ? "Will" : "Will not" ); } return hasAccess && restapiAdminEnabled; @@ -146,15 +146,9 @@ public boolean containsRestApiAdminPermissions(final Object configObject) { return false; } if (configObject instanceof RoleV7) { - return ((RoleV7) configObject) - .getCluster_permissions() - .stream() - .anyMatch(REST_API_PERMISSION_PREFIX_MATCHER); + return ((RoleV7) configObject).getCluster_permissions().stream().anyMatch(REST_API_PERMISSION_PREFIX_MATCHER); } else if (configObject instanceof ActionGroupsV7) { - return ((ActionGroupsV7) configObject) - .getAllowed_actions() - .stream() - .anyMatch(REST_API_PERMISSION_PREFIX_MATCHER); + return ((ActionGroupsV7) configObject).getAllowed_actions().stream().anyMatch(REST_API_PERMISSION_PREFIX_MATCHER); } else { return false; } @@ -165,17 +159,11 @@ public boolean isCurrentUserRestApiAdminFor(final Endpoint endpoint) { } private static String buildEndpointActionPermission(final Endpoint endpoint, final String action) { - return String.format( - REST_ENDPOINT_ACTION_PERMISSION_PATTERN, - endpoint.name().toLowerCase(Locale.ROOT), - action); + return String.format(REST_ENDPOINT_ACTION_PERMISSION_PATTERN, endpoint.name().toLowerCase(Locale.ROOT), action); } private static String buildEndpointPermission(final Endpoint endpoint) { - return String.format( - REST_ENDPOINT_PERMISSION_PATTERN, - endpoint.name().toLowerCase(Locale.ROOT) - ); + return String.format(REST_ENDPOINT_PERMISSION_PATTERN, endpoint.name().toLowerCase(Locale.ROOT)); } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluator.java b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluator.java index 04b4b31c77..96787bb4f5 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluator.java @@ -45,390 +45,431 @@ // TODO: Make Singleton? public class RestApiPrivilegesEvaluator { - protected final Logger logger = LogManager.getLogger(this.getClass()); - - private final AdminDNs adminDNs; - private final PrivilegesEvaluator privilegesEvaluator; - private final PrincipalExtractor principalExtractor; - private final Path configPath; - private final ThreadPool threadPool; - private final Settings settings; - - private final Set allowedRoles = new HashSet<>(); - - // endpoints per role, read and cached from settings. Changes here require a - // node restart, so it's save to cache. - private final Map>> disabledEndpointsForRoles = new HashMap<>(); - - // endpoints per user, evaluated and cached dynamically. Changes here - // require a node restart, so it's save to cache. - private final Map>> disabledEndpointsForUsers = new HashMap<>(); - - // globally disabled endpoints and methods, will always be forbidden - Map> globallyDisabledEndpoints = new HashMap<>(); - - // all endpoints and methods, will be returned for users that do not have any access at all - Map> allEndpoints = new HashMap<>(); - - private final Boolean roleBasedAccessEnabled; - - public RestApiPrivilegesEvaluator(final Settings settings, - final AdminDNs adminDNs, - final PrivilegesEvaluator privilegesEvaluator, - final PrincipalExtractor principalExtractor, - final Path configPath, - ThreadPool threadPool) { - - this.adminDNs = adminDNs; - this.privilegesEvaluator = privilegesEvaluator; - this.principalExtractor = principalExtractor; - this.configPath = configPath; - this.threadPool = threadPool; - this.settings = settings; - // set up - // all endpoints and methods - Map> allEndpoints = new HashMap<>(); - for(Endpoint endpoint : Endpoint.values()) { - List allMethods = new LinkedList<>(); - allMethods.addAll(Arrays.asList(Method.values())); - allEndpoints.put(endpoint, allMethods); - } - this.allEndpoints = Collections.unmodifiableMap(allEndpoints); - - // setup role based permissions - allowedRoles.addAll(settings.getAsList(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED)); - - this.roleBasedAccessEnabled = !allowedRoles.isEmpty(); - - // globally disabled endpoints, disables access to Endpoint/Method combination for all roles - Settings globalSettings = settings.getAsSettings(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + ".global"); - if (!globalSettings.isEmpty()) { - globallyDisabledEndpoints = parseDisabledEndpoints(globalSettings); - } - - final boolean isDebugEnabled = logger.isDebugEnabled(); - if (isDebugEnabled) { - logger.debug("Globally disabled endpoints: {}", globallyDisabledEndpoints); - } - - for (String role : allowedRoles) { - Settings settingsForRole = settings.getAsSettings(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + "." + role); - if (settingsForRole.isEmpty()) { - if (isDebugEnabled) { - logger.debug("No disabled endpoints/methods for permitted role {} found, allowing all", role); - } - continue; - } - Map> disabledEndpointsForRole = parseDisabledEndpoints(settingsForRole); - if (!disabledEndpointsForRole.isEmpty()) { - disabledEndpointsForRoles.put(role, disabledEndpointsForRole); - } else { - logger.warn("Disabled endpoints/methods empty for role {}, please check configuration", role); - } - } - if (logger.isTraceEnabled()) { - logger.trace("Parsed permission set for endpoints: {}", disabledEndpointsForRoles); - } - } - - @SuppressWarnings({ "rawtypes" }) - private Map> parseDisabledEndpoints(Settings settings) { - - // Expects Setting like: 'ACTIONGROUPS=["GET", "POST"]' - if (settings == null || settings.isEmpty()) { - logger.error("Settings for disabled endpoint is null or empty: '{}', skipping.", settings); - return Collections.emptyMap(); - } - - final Map> disabledEndpoints = new HashMap>(); - - Map disabledEndpointsSettings = Utils.convertJsonToxToStructuredMap(settings); - - for (Entry value : disabledEndpointsSettings.entrySet()) { - // key is the endpoint, see if it is a valid one - String endpointString = value.getKey().toUpperCase(); - Endpoint endpoint = null; - try { - endpoint = Endpoint.valueOf(endpointString); - } catch (Exception e) { - logger.error("Unknown endpoint '{}' found in configuration, skipping.", endpointString); - continue; - } - // value must be non null - if (value.getValue() == null) { - logger.error("Disabled HTTP methods of endpoint '{}' is null, skipping.", endpointString); - continue; - } - - // value must be an array of methods - if (!(value.getValue() instanceof Collection)) { - logger.error("Disabled HTTP methods of endpoint '{}' must be an array, actually is '{}', skipping.", endpointString, (value.getValue().toString())); - } - List disabledMethods = new LinkedList<>(); - for (Object disabledMethodObj : (Collection) value.getValue()) { - if (disabledMethodObj == null) { - logger.error("Found null value in disabled HTTP methods of endpoint '{}', skipping.", endpointString); - continue; - } - - if (!(disabledMethodObj instanceof String)) { - logger.error("Found non-String value in disabled HTTP methods of endpoint '{}', skipping.", endpointString); - continue; - } - - String disabledMethodAsString = (String) disabledMethodObj; - - // Provide support for '*', means all methods - if (disabledMethodAsString.trim().equals("*")) { - disabledMethods.addAll(Arrays.asList(Method.values())); - break; - } - // no wild card, disabled method must be one of - // RestRequest.Method - Method disabledMethod = null; - try { - disabledMethod = Method.valueOf(disabledMethodAsString.toUpperCase()); - } catch (Exception e) { - logger.error("Invalid HTTP method '{}' found in disabled HTTP methods of endpoint '{}', skipping.", disabledMethodAsString.toUpperCase(), endpointString); - continue; - } - disabledMethods.add(disabledMethod); - } - - disabledEndpoints.put(endpoint, disabledMethods); - - } - return disabledEndpoints; - } - - /** - * Check if the current request is allowed to use the REST API and the - * requested end point. Using an admin certificate grants all permissions. A - * user/role can have restricted end points. - * - * @return an error message if user does not have access, null otherwise - * TODO: log failed attempt in audit log - */ - public String checkAccessPermissions(RestRequest request, Endpoint endpoint) throws IOException { - - if (logger.isDebugEnabled()) { - logger.debug("Checking admin access for endpoint {}, path {} and method {}", endpoint.name(), request.path(), request.method().name()); - } - - // Grant permission for Account endpoint. - // Return null to grant access. - if (endpoint == Endpoint.ACCOUNT) { - return null; - } - - String roleBasedAccessFailureReason = checkRoleBasedAccessPermissions(request, endpoint); - // Role based access granted - if (roleBasedAccessFailureReason == null) { - return null; - } - - String certBasedAccessFailureReason = checkAdminCertBasedAccessPermissions(request); - // TLS access granted, skip checking roles - if (certBasedAccessFailureReason == null) { - return null; - } - - - return constructAccessErrorMessage(roleBasedAccessFailureReason, certBasedAccessFailureReason); - } - - public Boolean currentUserHasRestApiAccess(Set userRoles) { - - // check if user has any role that grants access - return !Collections.disjoint(allowedRoles, userRoles); - - } - - public Map> getDisabledEndpointsForCurrentUser(String userPrincipal, Set userRoles) { - - final boolean isDebugEnabled = logger.isDebugEnabled(); - - // cache - if (disabledEndpointsForUsers.containsKey(userPrincipal)) { - return disabledEndpointsForUsers.get(userPrincipal); - } - - if (!currentUserHasRestApiAccess(userRoles)) { - return this.allEndpoints; - } - - // will contain the final list of disabled endpoints and methods - Map> finalEndpoints = new HashMap<>(); - - // List of all disabled endpoints for user. Disabled endpoints must be configured in all - // roles to take effect. If a role contains a disabled endpoint, but another role - // allows this endpoint (i.e. not contained in the disabled endpoints for this role), - // the access is allowed. - - // make list mutable - List remainingEndpoints = new LinkedList<>(Arrays.asList(Endpoint.values())); - - // only retain endpoints contained in all roles for user - boolean hasDisabledEndpoints = false; - for (String userRole : userRoles) { - Map> endpointsForRole = disabledEndpointsForRoles.get(userRole); - if (endpointsForRole == null || endpointsForRole.isEmpty()) { - continue; - } - Set disabledEndpoints = endpointsForRole.keySet(); - remainingEndpoints.retainAll(disabledEndpoints); - hasDisabledEndpoints = true; - } - - if (isDebugEnabled) { - logger.debug("Remaining endpoints for user {} after retaining all : {}", userPrincipal, remainingEndpoints); - } - - // if user does not have any disabled endpoints, only globally disabled endpoints apply - if (!hasDisabledEndpoints) { - - if (isDebugEnabled) { - logger.debug("No disabled endpoints for user {} at all, only globally disabledendpoints apply.", userPrincipal, remainingEndpoints); - } - disabledEndpointsForUsers.put(userPrincipal, addGloballyDisabledEndpoints(finalEndpoints)); - return finalEndpoints; - - } - - // one or more disabled remaining endpoints, keep only - // methods contained in all roles for each endpoint - for (Endpoint endpoint : remainingEndpoints) { - // make list mutable - List remainingMethodsForEndpoint = new LinkedList<>(Arrays.asList(Method.values())); - for (String userRole : userRoles) { - Map> endpoints = disabledEndpointsForRoles.get(userRole); - if (endpoints != null && !endpoints.isEmpty()) { - remainingMethodsForEndpoint.retainAll(endpoints.get(endpoint)); - } - } - - finalEndpoints.put(endpoint, remainingMethodsForEndpoint); - } - - if (isDebugEnabled) { - logger.debug("Disabled endpoints for user {} after retaining all : {}", userPrincipal, finalEndpoints); - } - - // add globally disabled endpoints and methods, will always be disabled - addGloballyDisabledEndpoints(finalEndpoints); - disabledEndpointsForUsers.put(userPrincipal, finalEndpoints); - - if (isDebugEnabled) { - logger.debug("Disabled endpoints for user {} after retaining all : {}", userPrincipal, disabledEndpointsForUsers.get(userPrincipal)); - } - - return disabledEndpointsForUsers.get(userPrincipal); - } - - private Map> addGloballyDisabledEndpoints(Map> endpoints) { - if(globallyDisabledEndpoints != null && !globallyDisabledEndpoints.isEmpty()) { - Set globalEndoints = globallyDisabledEndpoints.keySet(); - for(Endpoint endpoint : globalEndoints) { - endpoints.putIfAbsent(endpoint, new LinkedList<>()); - endpoints.get(endpoint).addAll(globallyDisabledEndpoints.get(endpoint)); - } - } - return endpoints; - } - - private String checkRoleBasedAccessPermissions(RestRequest request, Endpoint endpoint) { - final boolean isTraceEnabled = logger.isTraceEnabled(); - if (isTraceEnabled) { - logger.trace("Checking role based admin access for endpoint {} and method {}", endpoint.name(), request.method().name()); - } - final boolean isDebugEnabled = logger.isDebugEnabled(); - // Role based access. Check that user has role suitable for admin access - // and that the role has also access to this endpoint. - if (this.roleBasedAccessEnabled) { - - // get current user and roles - final Pair userAndRemoteAddress = - Utils.userAndRemoteAddressFrom(threadPool.getThreadContext()); - final User user = userAndRemoteAddress.getLeft(); - final TransportAddress remoteAddress = userAndRemoteAddress.getRight(); - - // map the users Security roles - Set userRoles = privilegesEvaluator.mapRoles(user, remoteAddress); - - // check if user has any role that grants access - if (currentUserHasRestApiAccess(userRoles)) { - // yes, calculate disabled end points. Since a user can have - // multiple roles, the endpoint - // needs to be disabled in all roles. - Map> disabledEndpointsForUser = getDisabledEndpointsForCurrentUser(user.getName(), userRoles); - - if (isDebugEnabled) { - logger.debug("Disabled endpoints for user {} : {} ", user, disabledEndpointsForUser); - } - - // check if we have any disabled methods for this endpoint - List disabledMethodsForEndpoint = disabledEndpointsForUser.get(endpoint); - - // no settings, all methods for this endpoint allowed - if (disabledMethodsForEndpoint == null || disabledMethodsForEndpoint.isEmpty()) { - if (isDebugEnabled) { - logger.debug("No disabled methods for user {} and endpoint {}, access allowed ", user, endpoint); - } - return null; - } - - // some methods disabled, check requested method - if (!disabledMethodsForEndpoint.contains(request.method())) { - if (isDebugEnabled) { - logger.debug("Request method {} for user {} and endpoint {} not restricted, access allowed ", request.method(), user, endpoint); - } - return null; - } - - logger.info("User {} with Security roles {} does not have access to endpoint {} and method {}, checking admin TLS certificate now.", user, userRoles, - endpoint.name(), request.method()); - return "User " + user.getName() + " with Security roles " + userRoles + " does not have any access to endpoint " + endpoint.name() + " and method " - + request.method().name(); - } else { - // no, but maybe the request contains a client certificate. - // Remember error reason for better response message later on. - logger.info("User {} with Security roles {} does not have any role privileged for admin access.", user, userRoles); - return "User " + user.getName() + " with Security roles " + userRoles + " does not have any role privileged for admin access"; - } - } - return "Role based access not enabled."; - } - - private String checkAdminCertBasedAccessPermissions(RestRequest request) throws IOException { - if (logger.isTraceEnabled()) { - logger.trace("Checking certificate based admin access for path {} and method {}", request.path(), request.method().name()); - } - - // Certificate based access, Check if we have an admin TLS certificate - SSLRequestHelper.SSLInfo sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor); - - if (sslInfo == null) { - // here we log on error level, since authentication finally failed - logger.warn("No ssl info found in request."); - return "No ssl info found in request."; - } - - X509Certificate[] certs = sslInfo.getX509Certs(); - - if (certs == null || certs.length == 0) { - logger.warn("No client TLS certificate found in request"); - return "No client TLS certificate found in request"; - } - - if (!adminDNs.isAdminDN(sslInfo.getPrincipal())) { - logger.warn("Security admin permissions required but {} is not an admin", sslInfo.getPrincipal()); - return "Security admin permissions required but " + sslInfo.getPrincipal() + " is not an admin"; - } - return null; - } - - private String constructAccessErrorMessage(String roleBasedAccessFailure, String certBasedAccessFailure) { - return roleBasedAccessFailure + ". " + certBasedAccessFailure; - } + protected final Logger logger = LogManager.getLogger(this.getClass()); + + private final AdminDNs adminDNs; + private final PrivilegesEvaluator privilegesEvaluator; + private final PrincipalExtractor principalExtractor; + private final Path configPath; + private final ThreadPool threadPool; + private final Settings settings; + + private final Set allowedRoles = new HashSet<>(); + + // endpoints per role, read and cached from settings. Changes here require a + // node restart, so it's save to cache. + private final Map>> disabledEndpointsForRoles = new HashMap<>(); + + // endpoints per user, evaluated and cached dynamically. Changes here + // require a node restart, so it's save to cache. + private final Map>> disabledEndpointsForUsers = new HashMap<>(); + + // globally disabled endpoints and methods, will always be forbidden + Map> globallyDisabledEndpoints = new HashMap<>(); + + // all endpoints and methods, will be returned for users that do not have any access at all + Map> allEndpoints = new HashMap<>(); + + private final Boolean roleBasedAccessEnabled; + + public RestApiPrivilegesEvaluator( + final Settings settings, + final AdminDNs adminDNs, + final PrivilegesEvaluator privilegesEvaluator, + final PrincipalExtractor principalExtractor, + final Path configPath, + ThreadPool threadPool + ) { + + this.adminDNs = adminDNs; + this.privilegesEvaluator = privilegesEvaluator; + this.principalExtractor = principalExtractor; + this.configPath = configPath; + this.threadPool = threadPool; + this.settings = settings; + // set up + // all endpoints and methods + Map> allEndpoints = new HashMap<>(); + for (Endpoint endpoint : Endpoint.values()) { + List allMethods = new LinkedList<>(); + allMethods.addAll(Arrays.asList(Method.values())); + allEndpoints.put(endpoint, allMethods); + } + this.allEndpoints = Collections.unmodifiableMap(allEndpoints); + + // setup role based permissions + allowedRoles.addAll(settings.getAsList(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED)); + + this.roleBasedAccessEnabled = !allowedRoles.isEmpty(); + + // globally disabled endpoints, disables access to Endpoint/Method combination for all roles + Settings globalSettings = settings.getAsSettings(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + ".global"); + if (!globalSettings.isEmpty()) { + globallyDisabledEndpoints = parseDisabledEndpoints(globalSettings); + } + + final boolean isDebugEnabled = logger.isDebugEnabled(); + if (isDebugEnabled) { + logger.debug("Globally disabled endpoints: {}", globallyDisabledEndpoints); + } + + for (String role : allowedRoles) { + Settings settingsForRole = settings.getAsSettings(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + "." + role); + if (settingsForRole.isEmpty()) { + if (isDebugEnabled) { + logger.debug("No disabled endpoints/methods for permitted role {} found, allowing all", role); + } + continue; + } + Map> disabledEndpointsForRole = parseDisabledEndpoints(settingsForRole); + if (!disabledEndpointsForRole.isEmpty()) { + disabledEndpointsForRoles.put(role, disabledEndpointsForRole); + } else { + logger.warn("Disabled endpoints/methods empty for role {}, please check configuration", role); + } + } + if (logger.isTraceEnabled()) { + logger.trace("Parsed permission set for endpoints: {}", disabledEndpointsForRoles); + } + } + + @SuppressWarnings({ "rawtypes" }) + private Map> parseDisabledEndpoints(Settings settings) { + + // Expects Setting like: 'ACTIONGROUPS=["GET", "POST"]' + if (settings == null || settings.isEmpty()) { + logger.error("Settings for disabled endpoint is null or empty: '{}', skipping.", settings); + return Collections.emptyMap(); + } + + final Map> disabledEndpoints = new HashMap>(); + + Map disabledEndpointsSettings = Utils.convertJsonToxToStructuredMap(settings); + + for (Entry value : disabledEndpointsSettings.entrySet()) { + // key is the endpoint, see if it is a valid one + String endpointString = value.getKey().toUpperCase(); + Endpoint endpoint = null; + try { + endpoint = Endpoint.valueOf(endpointString); + } catch (Exception e) { + logger.error("Unknown endpoint '{}' found in configuration, skipping.", endpointString); + continue; + } + // value must be non null + if (value.getValue() == null) { + logger.error("Disabled HTTP methods of endpoint '{}' is null, skipping.", endpointString); + continue; + } + + // value must be an array of methods + if (!(value.getValue() instanceof Collection)) { + logger.error( + "Disabled HTTP methods of endpoint '{}' must be an array, actually is '{}', skipping.", + endpointString, + (value.getValue().toString()) + ); + } + List disabledMethods = new LinkedList<>(); + for (Object disabledMethodObj : (Collection) value.getValue()) { + if (disabledMethodObj == null) { + logger.error("Found null value in disabled HTTP methods of endpoint '{}', skipping.", endpointString); + continue; + } + + if (!(disabledMethodObj instanceof String)) { + logger.error("Found non-String value in disabled HTTP methods of endpoint '{}', skipping.", endpointString); + continue; + } + + String disabledMethodAsString = (String) disabledMethodObj; + + // Provide support for '*', means all methods + if (disabledMethodAsString.trim().equals("*")) { + disabledMethods.addAll(Arrays.asList(Method.values())); + break; + } + // no wild card, disabled method must be one of + // RestRequest.Method + Method disabledMethod = null; + try { + disabledMethod = Method.valueOf(disabledMethodAsString.toUpperCase()); + } catch (Exception e) { + logger.error( + "Invalid HTTP method '{}' found in disabled HTTP methods of endpoint '{}', skipping.", + disabledMethodAsString.toUpperCase(), + endpointString + ); + continue; + } + disabledMethods.add(disabledMethod); + } + + disabledEndpoints.put(endpoint, disabledMethods); + + } + return disabledEndpoints; + } + + /** + * Check if the current request is allowed to use the REST API and the + * requested end point. Using an admin certificate grants all permissions. A + * user/role can have restricted end points. + * + * @return an error message if user does not have access, null otherwise + * TODO: log failed attempt in audit log + */ + public String checkAccessPermissions(RestRequest request, Endpoint endpoint) throws IOException { + + if (logger.isDebugEnabled()) { + logger.debug( + "Checking admin access for endpoint {}, path {} and method {}", + endpoint.name(), + request.path(), + request.method().name() + ); + } + + // Grant permission for Account endpoint. + // Return null to grant access. + if (endpoint == Endpoint.ACCOUNT) { + return null; + } + + String roleBasedAccessFailureReason = checkRoleBasedAccessPermissions(request, endpoint); + // Role based access granted + if (roleBasedAccessFailureReason == null) { + return null; + } + + String certBasedAccessFailureReason = checkAdminCertBasedAccessPermissions(request); + // TLS access granted, skip checking roles + if (certBasedAccessFailureReason == null) { + return null; + } + + return constructAccessErrorMessage(roleBasedAccessFailureReason, certBasedAccessFailureReason); + } + + public Boolean currentUserHasRestApiAccess(Set userRoles) { + + // check if user has any role that grants access + return !Collections.disjoint(allowedRoles, userRoles); + + } + + public Map> getDisabledEndpointsForCurrentUser(String userPrincipal, Set userRoles) { + + final boolean isDebugEnabled = logger.isDebugEnabled(); + + // cache + if (disabledEndpointsForUsers.containsKey(userPrincipal)) { + return disabledEndpointsForUsers.get(userPrincipal); + } + + if (!currentUserHasRestApiAccess(userRoles)) { + return this.allEndpoints; + } + + // will contain the final list of disabled endpoints and methods + Map> finalEndpoints = new HashMap<>(); + + // List of all disabled endpoints for user. Disabled endpoints must be configured in all + // roles to take effect. If a role contains a disabled endpoint, but another role + // allows this endpoint (i.e. not contained in the disabled endpoints for this role), + // the access is allowed. + + // make list mutable + List remainingEndpoints = new LinkedList<>(Arrays.asList(Endpoint.values())); + + // only retain endpoints contained in all roles for user + boolean hasDisabledEndpoints = false; + for (String userRole : userRoles) { + Map> endpointsForRole = disabledEndpointsForRoles.get(userRole); + if (endpointsForRole == null || endpointsForRole.isEmpty()) { + continue; + } + Set disabledEndpoints = endpointsForRole.keySet(); + remainingEndpoints.retainAll(disabledEndpoints); + hasDisabledEndpoints = true; + } + + if (isDebugEnabled) { + logger.debug("Remaining endpoints for user {} after retaining all : {}", userPrincipal, remainingEndpoints); + } + + // if user does not have any disabled endpoints, only globally disabled endpoints apply + if (!hasDisabledEndpoints) { + + if (isDebugEnabled) { + logger.debug( + "No disabled endpoints for user {} at all, only globally disabledendpoints apply.", + userPrincipal, + remainingEndpoints + ); + } + disabledEndpointsForUsers.put(userPrincipal, addGloballyDisabledEndpoints(finalEndpoints)); + return finalEndpoints; + + } + + // one or more disabled remaining endpoints, keep only + // methods contained in all roles for each endpoint + for (Endpoint endpoint : remainingEndpoints) { + // make list mutable + List remainingMethodsForEndpoint = new LinkedList<>(Arrays.asList(Method.values())); + for (String userRole : userRoles) { + Map> endpoints = disabledEndpointsForRoles.get(userRole); + if (endpoints != null && !endpoints.isEmpty()) { + remainingMethodsForEndpoint.retainAll(endpoints.get(endpoint)); + } + } + + finalEndpoints.put(endpoint, remainingMethodsForEndpoint); + } + + if (isDebugEnabled) { + logger.debug("Disabled endpoints for user {} after retaining all : {}", userPrincipal, finalEndpoints); + } + + // add globally disabled endpoints and methods, will always be disabled + addGloballyDisabledEndpoints(finalEndpoints); + disabledEndpointsForUsers.put(userPrincipal, finalEndpoints); + + if (isDebugEnabled) { + logger.debug( + "Disabled endpoints for user {} after retaining all : {}", + userPrincipal, + disabledEndpointsForUsers.get(userPrincipal) + ); + } + + return disabledEndpointsForUsers.get(userPrincipal); + } + + private Map> addGloballyDisabledEndpoints(Map> endpoints) { + if (globallyDisabledEndpoints != null && !globallyDisabledEndpoints.isEmpty()) { + Set globalEndoints = globallyDisabledEndpoints.keySet(); + for (Endpoint endpoint : globalEndoints) { + endpoints.putIfAbsent(endpoint, new LinkedList<>()); + endpoints.get(endpoint).addAll(globallyDisabledEndpoints.get(endpoint)); + } + } + return endpoints; + } + + private String checkRoleBasedAccessPermissions(RestRequest request, Endpoint endpoint) { + final boolean isTraceEnabled = logger.isTraceEnabled(); + if (isTraceEnabled) { + logger.trace("Checking role based admin access for endpoint {} and method {}", endpoint.name(), request.method().name()); + } + final boolean isDebugEnabled = logger.isDebugEnabled(); + // Role based access. Check that user has role suitable for admin access + // and that the role has also access to this endpoint. + if (this.roleBasedAccessEnabled) { + + // get current user and roles + final Pair userAndRemoteAddress = Utils.userAndRemoteAddressFrom(threadPool.getThreadContext()); + final User user = userAndRemoteAddress.getLeft(); + final TransportAddress remoteAddress = userAndRemoteAddress.getRight(); + + // map the users Security roles + Set userRoles = privilegesEvaluator.mapRoles(user, remoteAddress); + + // check if user has any role that grants access + if (currentUserHasRestApiAccess(userRoles)) { + // yes, calculate disabled end points. Since a user can have + // multiple roles, the endpoint + // needs to be disabled in all roles. + Map> disabledEndpointsForUser = getDisabledEndpointsForCurrentUser(user.getName(), userRoles); + + if (isDebugEnabled) { + logger.debug("Disabled endpoints for user {} : {} ", user, disabledEndpointsForUser); + } + + // check if we have any disabled methods for this endpoint + List disabledMethodsForEndpoint = disabledEndpointsForUser.get(endpoint); + + // no settings, all methods for this endpoint allowed + if (disabledMethodsForEndpoint == null || disabledMethodsForEndpoint.isEmpty()) { + if (isDebugEnabled) { + logger.debug("No disabled methods for user {} and endpoint {}, access allowed ", user, endpoint); + } + return null; + } + + // some methods disabled, check requested method + if (!disabledMethodsForEndpoint.contains(request.method())) { + if (isDebugEnabled) { + logger.debug( + "Request method {} for user {} and endpoint {} not restricted, access allowed ", + request.method(), + user, + endpoint + ); + } + return null; + } + + logger.info( + "User {} with Security roles {} does not have access to endpoint {} and method {}, checking admin TLS certificate now.", + user, + userRoles, + endpoint.name(), + request.method() + ); + return "User " + + user.getName() + + " with Security roles " + + userRoles + + " does not have any access to endpoint " + + endpoint.name() + + " and method " + + request.method().name(); + } else { + // no, but maybe the request contains a client certificate. + // Remember error reason for better response message later on. + logger.info("User {} with Security roles {} does not have any role privileged for admin access.", user, userRoles); + return "User " + + user.getName() + + " with Security roles " + + userRoles + + " does not have any role privileged for admin access"; + } + } + return "Role based access not enabled."; + } + + private String checkAdminCertBasedAccessPermissions(RestRequest request) throws IOException { + if (logger.isTraceEnabled()) { + logger.trace("Checking certificate based admin access for path {} and method {}", request.path(), request.method().name()); + } + + // Certificate based access, Check if we have an admin TLS certificate + SSLRequestHelper.SSLInfo sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor); + + if (sslInfo == null) { + // here we log on error level, since authentication finally failed + logger.warn("No ssl info found in request."); + return "No ssl info found in request."; + } + + X509Certificate[] certs = sslInfo.getX509Certs(); + + if (certs == null || certs.length == 0) { + logger.warn("No client TLS certificate found in request"); + return "No client TLS certificate found in request"; + } + + if (!adminDNs.isAdminDN(sslInfo.getPrincipal())) { + logger.warn("Security admin permissions required but {} is not an admin", sslInfo.getPrincipal()); + return "Security admin permissions required but " + sslInfo.getPrincipal() + " is not an admin"; + } + return null; + } + + private String constructAccessErrorMessage(String roleBasedAccessFailure, String certBasedAccessFailure) { + return roleBasedAccessFailure + ". " + certBasedAccessFailure; + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java index 7b2676c246..65dbaac05e 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java @@ -39,62 +39,79 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class RolesApiAction extends PatchableResourceApiAction { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(Method.GET, "/roles/"), - new Route(Method.GET, "/roles/{name}"), - new Route(Method.DELETE, "/roles/{name}"), - new Route(Method.PUT, "/roles/{name}"), - new Route(Method.PATCH, "/roles/"), - new Route(Method.PATCH, "/roles/{name}") - )); + private static final List routes = addRoutesPrefix( + ImmutableList.of( + new Route(Method.GET, "/roles/"), + new Route(Method.GET, "/roles/{name}"), + new Route(Method.DELETE, "/roles/{name}"), + new Route(Method.PUT, "/roles/{name}"), + new Route(Method.PATCH, "/roles/"), + new Route(Method.PATCH, "/roles/{name}") + ) + ); - @Inject - public RolesApiAction(Settings settings, final Path configPath, RestController controller, Client client, AdminDNs adminDNs, ConfigurationRepository cl, - ClusterService cs, final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); - } + @Inject + public RolesApiAction( + Settings settings, + final Path configPath, + RestController controller, + Client client, + AdminDNs adminDNs, + ConfigurationRepository cl, + ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + } - @Override - public List routes() { - return routes; - } + @Override + public List routes() { + return routes; + } - @Override - protected Endpoint getEndpoint() { - return Endpoint.ROLES; - } + @Override + protected Endpoint getEndpoint() { + return Endpoint.ROLES; + } - @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new RolesValidator(request, isSuperAdmin(), ref, this.settings, param); - } + @Override + protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { + return new RolesValidator(request, isSuperAdmin(), ref, this.settings, param); + } - @Override - protected String getResourceName() { - return "role"; - } + @Override + protected String getResourceName() { + return "role"; + } - @Override + @Override protected CType getConfigName() { return CType.ROLES; - } + } - @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfiguration, final Object content, final String resourceName) throws IOException { - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(content)) { - return isSuperAdmin(); - } else { - return true; - } - } + @Override + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfiguration, + final Object content, + final String resourceName + ) throws IOException { + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(content)) { + return isSuperAdmin(); + } else { + return true; + } + } - @Override - protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(existingConfiguration.getCEntry(name))) { - return !isSuperAdmin(); - } else { - return super.isReadOnly(existingConfiguration, name); - } - } + @Override + protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(existingConfiguration.getCEntry(name))) { + return !isSuperAdmin(); + } else { + return super.isReadOnly(existingConfiguration, name); + } + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java index 72928cd0ad..627dabfaa2 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java @@ -43,102 +43,125 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class RolesMappingApiAction extends PatchableResourceApiAction { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(Method.GET, "/rolesmapping/"), - new Route(Method.GET, "/rolesmapping/{name}"), - new Route(Method.DELETE, "/rolesmapping/{name}"), - new Route(Method.PUT, "/rolesmapping/{name}"), - new Route(Method.PATCH, "/rolesmapping/"), - new Route(Method.PATCH, "/rolesmapping/{name}") - )); - - @Inject - public RolesMappingApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); - } - - @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { - final String name = request.param("name"); - - if (name == null || name.length() == 0) { - badRequestResponse(channel, "No " + getResourceName() + " specified."); - return; - } - - final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); - final SecurityDynamicConfiguration rolesMappingConfiguration = load(getConfigName(), false); - final boolean rolesMappingExists = rolesMappingConfiguration.exists(name); - - if (!isValidRolesMapping(channel, name)) return; - - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(name))) { - if (!isSuperAdmin()) { - forbidden(channel, "No permissions"); - return; - } - } - rolesMappingConfiguration.putCObject(name, DefaultObjectMapper.readTree(content, rolesMappingConfiguration.getImplementingClass())); - - saveAndUpdateConfigs(this.securityIndexName,client, getConfigName(), rolesMappingConfiguration, new OnSucessActionListener(channel) { - - @Override - public void onResponse(IndexResponse response) { - if (rolesMappingExists) { - successResponse(channel, "'" + name + "' updated."); - } else { - createdResponse(channel, "'" + name + "' created."); - } - - } - }); - } - - @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, final Object content, final String resourceName) throws IOException { - final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(resourceName))) { - return isSuperAdmin(); - } else { - return true; - } - } - - @Override - protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { - final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(name))) { - return !isSuperAdmin(); - } else { - return super.isReadOnly(existingConfiguration, name); - } - } - - @Override - public List routes() { - return routes; - } - - @Override - protected Endpoint getEndpoint() { - return Endpoint.ROLESMAPPING; - } - - @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new RolesMappingValidator(request, isSuperAdmin(), ref, this.settings, param); - } - - @Override - protected String getResourceName() { - return "rolesmapping"; - } - - @Override + private static final List routes = addRoutesPrefix( + ImmutableList.of( + new Route(Method.GET, "/rolesmapping/"), + new Route(Method.GET, "/rolesmapping/{name}"), + new Route(Method.DELETE, "/rolesmapping/{name}"), + new Route(Method.PUT, "/rolesmapping/{name}"), + new Route(Method.PATCH, "/rolesmapping/"), + new Route(Method.PATCH, "/rolesmapping/{name}") + ) + ); + + @Inject + public RolesMappingApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + } + + @Override + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + final String name = request.param("name"); + + if (name == null || name.length() == 0) { + badRequestResponse(channel, "No " + getResourceName() + " specified."); + return; + } + + final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); + final SecurityDynamicConfiguration rolesMappingConfiguration = load(getConfigName(), false); + final boolean rolesMappingExists = rolesMappingConfiguration.exists(name); + + if (!isValidRolesMapping(channel, name)) return; + + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(name))) { + if (!isSuperAdmin()) { + forbidden(channel, "No permissions"); + return; + } + } + rolesMappingConfiguration.putCObject(name, DefaultObjectMapper.readTree(content, rolesMappingConfiguration.getImplementingClass())); + + saveAndUpdateConfigs( + this.securityIndexName, + client, + getConfigName(), + rolesMappingConfiguration, + new OnSucessActionListener(channel) { + + @Override + public void onResponse(IndexResponse response) { + if (rolesMappingExists) { + successResponse(channel, "'" + name + "' updated."); + } else { + createdResponse(channel, "'" + name + "' created."); + } + + } + } + ); + } + + @Override + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) throws IOException { + final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(resourceName))) { + return isSuperAdmin(); + } else { + return true; + } + } + + @Override + protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { + final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(name))) { + return !isSuperAdmin(); + } else { + return super.isReadOnly(existingConfiguration, name); + } + } + + @Override + public List routes() { + return routes; + } + + @Override + protected Endpoint getEndpoint() { + return Endpoint.ROLESMAPPING; + } + + @Override + protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { + return new RolesMappingValidator(request, isSuperAdmin(), ref, this.settings, param); + } + + @Override + protected String getResourceName() { + return "rolesmapping"; + } + + @Override protected CType getConfigName() { return CType.ROLESMAPPING; - } + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java index 66888bc126..9584a34c06 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java @@ -44,26 +44,30 @@ public class SecurityConfigAction extends PatchableResourceApiAction { - private static final List getRoutes = addRoutesPrefix(Collections.singletonList( - new Route(Method.GET, "/securityconfig/") - )); - - private static final List allRoutes = new ImmutableList.Builder() - .addAll(getRoutes) - .addAll(addRoutesPrefix( - ImmutableList.of( - new Route(Method.PUT, "/securityconfig/{name}"), - new Route(Method.PATCH, "/securityconfig/") - ) - )) - .build(); + private static final List getRoutes = addRoutesPrefix(Collections.singletonList(new Route(Method.GET, "/securityconfig/"))); + + private static final List allRoutes = new ImmutableList.Builder().addAll(getRoutes) + .addAll( + addRoutesPrefix(ImmutableList.of(new Route(Method.PUT, "/securityconfig/{name}"), new Route(Method.PATCH, "/securityconfig/"))) + ) + .build(); private final boolean allowPutOrPatch; @Inject - public SecurityConfigAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public SecurityConfigAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); allowPutOrPatch = settings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, false); @@ -75,14 +79,16 @@ public List routes() { } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @Override - protected void handleGet(RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException{ + protected void handleGet(RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { final SecurityDynamicConfiguration configuration = load(getConfigName(), true); filter(configuration); @@ -90,8 +96,6 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client successResponse(channel, configuration); } - - @Override protected void handleApiRequest(RestChannel channel, RestRequest request, Client client) throws IOException { if (request.method() == Method.PATCH && !allowPutOrPatch) { @@ -102,10 +106,11 @@ protected void handleApiRequest(RestChannel channel, RestRequest request, Client } @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException{ + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { if (allowPutOrPatch) { - if(!"config".equals(request.param("name"))) { + if (!"config".equals(request.param("name"))) { badRequestResponse(channel, "name must be config"); return; } @@ -117,12 +122,14 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C } @Override - protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException{ + protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.POST); } @Override - protected void handleDelete(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException{ + protected void handleDelete(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.DELETE); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java index cd489cab4d..e7d68a8677 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java @@ -33,39 +33,296 @@ public class SecurityRestApiActions { - public static Collection getHandler(final Settings settings, - final Path configPath, - final RestController controller, - final Client client, - final AdminDNs adminDns, - final ConfigurationRepository cr, - final ClusterService cs, - final PrincipalExtractor principalExtractor, - final PrivilegesEvaluator evaluator, - final ThreadPool threadPool, - final AuditLog auditLog, - final SecurityKeyStore securityKeyStore, - final UserService userService, - final boolean certificatesReloadEnabled) { + public static Collection getHandler( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDns, + final ConfigurationRepository cr, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + final ThreadPool threadPool, + final AuditLog auditLog, + final SecurityKeyStore securityKeyStore, + final UserService userService, + final boolean certificatesReloadEnabled + ) { final List handlers = new ArrayList(16); - handlers.add(new InternalUsersApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, userService, auditLog)); - handlers.add(new RolesMappingApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new RolesApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new ActionGroupsApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new FlushCacheApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new SecurityConfigAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new PermissionsInfoAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new AuthTokenProcessorAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new TenantsApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new MigrateApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new ValidateApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new AccountApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new NodesDnApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new WhitelistApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new AllowlistApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new AuditApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new MultiTenancyConfigApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new SecuritySSLCertsAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog, securityKeyStore, certificatesReloadEnabled)); + handlers.add( + new InternalUsersApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + userService, + auditLog + ) + ); + handlers.add( + new RolesMappingApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new RolesApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new ActionGroupsApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new FlushCacheApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new SecurityConfigAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new PermissionsInfoAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new AuthTokenProcessorAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new TenantsApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new MigrateApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new ValidateApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new AccountApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new NodesDnApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new WhitelistApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new AllowlistApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new AuditApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new MultiTenancyConfigApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new SecuritySSLCertsAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog, + securityKeyStore, + certificatesReloadEnabled + ) + ); return Collections.unmodifiableCollection(handlers); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java index 1c1fe9b815..4949dedad9 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java @@ -51,7 +51,6 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; - /** * Rest API action to get SSL certificate information related to http and transport encryption. * Only super admin users are allowed to access this API. @@ -60,10 +59,7 @@ */ public class SecuritySSLCertsAction extends AbstractApiAction { private static final List ROUTES = addRoutesPrefix( - ImmutableList.of( - new Route(Method.GET, "/ssl/certs"), - new Route(Method.PUT, "/ssl/{certType}/reloadcerts") - ) + ImmutableList.of(new Route(Method.GET, "/ssl/certs"), new Route(Method.PUT, "/ssl/{certType}/reloadcerts")) ); private final Logger log = LogManager.getLogger(this.getClass()); @@ -74,19 +70,21 @@ public class SecuritySSLCertsAction extends AbstractApiAction { private final boolean httpsEnabled; - public SecuritySSLCertsAction(final Settings settings, - final Path configPath, - final RestController controller, - final Client client, - final AdminDNs adminDNs, - final ConfigurationRepository cl, - final ClusterService cs, - final PrincipalExtractor principalExtractor, - final PrivilegesEvaluator privilegesEvaluator, - final ThreadPool threadPool, - final AuditLog auditLog, - final SecurityKeyStore securityKeyStore, - final boolean certificatesReloadEnabled) { + public SecuritySSLCertsAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator privilegesEvaluator, + final ThreadPool threadPool, + final AuditLog auditLog, + final SecurityKeyStore securityKeyStore, + final boolean certificatesReloadEnabled + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, privilegesEvaluator, threadPool, auditLog); this.securityKeyStore = securityKeyStore; this.certificatesReloadEnabled = certificatesReloadEnabled; @@ -94,9 +92,11 @@ public SecuritySSLCertsAction(final Settings settings, } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -122,13 +122,13 @@ protected void handleApiRequest(final RestChannel channel, final RestRequest req } if (!certificatesReloadEnabled) { badRequestResponse( - channel, - String.format( - "no handler found for uri [%s] and method [%s]. In order to use SSL reload functionality set %s to true", - request.path(), - request.method(), - ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED - ) + channel, + String.format( + "no handler found for uri [%s] and method [%s]. In order to use SSL reload functionality set %s to true", + request.path(), + request.method(), + ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED + ) ); return; } @@ -172,28 +172,21 @@ protected void handleApiRequest(final RestChannel channel, final RestRequest req * @throws IOException */ @Override - protected void handleGet(final RestChannel channel, - final RestRequest request, - final Client client, - final JsonNode content) throws IOException { + protected void handleGet(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { if (securityKeyStore == null) { noKeyStoreResponse(channel); return; } try (final XContentBuilder contentBuilder = channel.newBuilder()) { channel.sendResponse( - new BytesRestResponse( - RestStatus.OK, - contentBuilder - .startObject() - .field( - "http_certificates_list", - httpsEnabled ? generateCertDetailList(securityKeyStore.getHttpCerts()) : null - ).field( - "transport_certificates_list", - generateCertDetailList(securityKeyStore.getTransportCerts()) - ).endObject() - ) + new BytesRestResponse( + RestStatus.OK, + contentBuilder.startObject() + .field("http_certificates_list", httpsEnabled ? generateCertDetailList(securityKeyStore.getHttpCerts()) : null) + .field("transport_certificates_list", generateCertDetailList(securityKeyStore.getTransportCerts())) + .endObject() + ) ); } catch (final Exception e) { internalErrorResponse(channel, e.getMessage()); @@ -219,10 +212,8 @@ protected void handleGet(final RestChannel channel, * @throws IOException */ @Override - protected void handlePut(final RestChannel channel, - final RestRequest request, - final Client client, - final JsonNode content) throws IOException { + protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { if (securityKeyStore == null) { noKeyStoreResponse(channel); return; @@ -237,44 +228,37 @@ protected void handlePut(final RestChannel channel, } securityKeyStore.initHttpSSLConfig(); channel.sendResponse( - new BytesRestResponse( - RestStatus.OK, - contentBuilder - .startObject() - .field("message", "updated http certs") - .endObject() - ) + new BytesRestResponse( + RestStatus.OK, + contentBuilder.startObject().field("message", "updated http certs").endObject() + ) ); break; case "transport": securityKeyStore.initTransportSSLConfig(); channel.sendResponse( - new BytesRestResponse( - RestStatus.OK, - contentBuilder - .startObject() - .field("message", "updated transport certs") - .endObject() - ) + new BytesRestResponse( + RestStatus.OK, + contentBuilder.startObject().field("message", "updated transport certs").endObject() + ) ); break; default: - forbidden(channel, - "invalid uri path, please use /_plugins/_security/api/ssl/http/reload or " - + "/_plugins/_security/api/ssl/transport/reload" + forbidden( + channel, + "invalid uri path, please use /_plugins/_security/api/ssl/http/reload or " + + "/_plugins/_security/api/ssl/transport/reload" ); break; } } catch (final Exception e) { log.error("Reload of certificates for {} failed", certType, e); try (final XContentBuilder contentBuilder = channel.newBuilder()) { - channel.sendResponse(new BytesRestResponse( - RestStatus.INTERNAL_SERVER_ERROR, - contentBuilder - .startObject() - .field("error", e.toString()) - .endObject() - ) + channel.sendResponse( + new BytesRestResponse( + RestStatus.INTERNAL_SERVER_ERROR, + contentBuilder.startObject().field("error", e.toString()).endObject() + ) ); } } @@ -284,25 +268,27 @@ private List> generateCertDetailList(final X509Certificate[] if (certs == null) { return null; } - return Arrays - .stream(certs) - .map(cert -> { - final String issuerDn = cert != null && cert.getIssuerX500Principal() != null ? cert.getIssuerX500Principal().getName() : ""; - final String subjectDn = cert != null && cert.getSubjectX500Principal() != null ? cert.getSubjectX500Principal().getName() : ""; + return Arrays.stream(certs).map(cert -> { + final String issuerDn = cert != null && cert.getIssuerX500Principal() != null ? cert.getIssuerX500Principal().getName() : ""; + final String subjectDn = cert != null && cert.getSubjectX500Principal() != null ? cert.getSubjectX500Principal().getName() : ""; - final String san = securityKeyStore.getSubjectAlternativeNames(cert); + final String san = securityKeyStore.getSubjectAlternativeNames(cert); - final String notBefore = cert != null && cert.getNotBefore() != null ? cert.getNotBefore().toInstant().toString() : ""; - final String notAfter = cert != null && cert.getNotAfter() != null ? cert.getNotAfter().toInstant().toString() : ""; - return ImmutableMap.of( - "issuer_dn", issuerDn, - "subject_dn", subjectDn, - "san", san, - "not_before", notBefore, - "not_after", notAfter - ); - }) - .collect(Collectors.toList()); + final String notBefore = cert != null && cert.getNotBefore() != null ? cert.getNotBefore().toInstant().toString() : ""; + final String notAfter = cert != null && cert.getNotAfter() != null ? cert.getNotAfter().toInstant().toString() : ""; + return ImmutableMap.of( + "issuer_dn", + issuerDn, + "subject_dn", + subjectDn, + "san", + san, + "not_before", + notBefore, + "not_after", + notAfter + ); + }).collect(Collectors.toList()); } private void noKeyStoreResponse(final RestChannel channel) throws IOException { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java index ef8d1d3b9f..5fbb907ecf 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java @@ -54,26 +54,40 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class TenantsApiAction extends PatchableResourceApiAction { - private static final List routes = addRoutesPrefix(ImmutableList.of( + private static final List routes = addRoutesPrefix( + ImmutableList.of( new Route(Method.GET, "/tenants/{name}"), new Route(Method.GET, "/tenants/"), new Route(Method.DELETE, "/tenants/{name}"), new Route(Method.PUT, "/tenants/{name}"), new Route(Method.PATCH, "/tenants/"), new Route(Method.PATCH, "/tenants/{name}") - )); + ) + ); @Inject - public TenantsApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public TenantsApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java index 8e2222cab0..de79b131b3 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java @@ -54,23 +54,32 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; - public class ValidateApiAction extends AbstractApiAction { - private static final List routes = addRoutesPrefix(Collections.singletonList( - new Route(Method.GET, "/validate") - )); + private static final List routes = addRoutesPrefix(Collections.singletonList(new Route(Method.GET, "/validate"))); @Inject - public ValidateApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, final PrincipalExtractor principalExtractor, - final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public ValidateApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -99,17 +108,36 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client try { final SecurityDynamicConfiguration configV6 = (SecurityDynamicConfiguration) loadedConfig; - final SecurityDynamicConfiguration actionGroupsV6 = (SecurityDynamicConfiguration) load(CType.ACTIONGROUPS, true, acceptInvalid); - final SecurityDynamicConfiguration internalUsersV6 = (SecurityDynamicConfiguration) load(CType.INTERNALUSERS, true, acceptInvalid); - final SecurityDynamicConfiguration rolesV6 = (SecurityDynamicConfiguration) load(CType.ROLES, true, acceptInvalid); - final SecurityDynamicConfiguration rolesmappingV6 = (SecurityDynamicConfiguration) load(CType.ROLESMAPPING, true, acceptInvalid); - final SecurityDynamicConfiguration auditConfigV6 = (SecurityDynamicConfiguration) load(CType.AUDIT, true); + final SecurityDynamicConfiguration actionGroupsV6 = (SecurityDynamicConfiguration) load( + CType.ACTIONGROUPS, + true, + acceptInvalid + ); + final SecurityDynamicConfiguration internalUsersV6 = (SecurityDynamicConfiguration) load( + CType.INTERNALUSERS, + true, + acceptInvalid + ); + final SecurityDynamicConfiguration rolesV6 = (SecurityDynamicConfiguration) load( + CType.ROLES, + true, + acceptInvalid + ); + final SecurityDynamicConfiguration rolesmappingV6 = (SecurityDynamicConfiguration) load( + CType.ROLESMAPPING, + true, + acceptInvalid + ); + final SecurityDynamicConfiguration auditConfigV6 = (SecurityDynamicConfiguration) load( + CType.AUDIT, + true + ); final SecurityDynamicConfiguration actionGroupsV7 = Migration.migrateActionGroups(actionGroupsV6); final SecurityDynamicConfiguration configV7 = Migration.migrateConfig(configV6); final SecurityDynamicConfiguration internalUsersV7 = Migration.migrateInternalUsers(internalUsersV6); - final Tuple, SecurityDynamicConfiguration> rolesTenantsV7 = Migration.migrateRoles(rolesV6, - rolesmappingV6); + final Tuple, SecurityDynamicConfiguration> rolesTenantsV7 = Migration + .migrateRoles(rolesV6, rolesmappingV6); final SecurityDynamicConfiguration rolesmappingV7 = Migration.migrateRoleMappings(rolesmappingV6); final SecurityDynamicConfiguration auditConfigV7 = Migration.migrateAudit(auditConfigV6); @@ -120,17 +148,20 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client } @Override - protected void handleDelete(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handleDelete(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.POST); } @Override - protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.GET); } @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.PUT); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/WhitelistApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/WhitelistApiAction.java index 6f55a2d762..d3bc92959c 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/WhitelistApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/WhitelistApiAction.java @@ -74,16 +74,40 @@ *

*/ public class WhitelistApiAction extends AllowlistApiAction { - private static final List routes = addDeprecatedRoutesPrefix(ImmutableList.of( - new DeprecatedRoute(RestRequest.Method.GET, "/whitelist", "[/whitelist] is a deprecated endpoint. Please use [/allowlist] instead."), - new DeprecatedRoute(RestRequest.Method.PUT, "/whitelist", "[/whitelist] is a deprecated endpoint. Please use [/allowlist] instead."), - new DeprecatedRoute(RestRequest.Method.PATCH, "/whitelist", "[/whitelist] is a deprecated endpoint. Please use [/allowlist] instead.") - )); + private static final List routes = addDeprecatedRoutesPrefix( + ImmutableList.of( + new DeprecatedRoute( + RestRequest.Method.GET, + "/whitelist", + "[/whitelist] is a deprecated endpoint. Please use [/allowlist] instead." + ), + new DeprecatedRoute( + RestRequest.Method.PUT, + "/whitelist", + "[/whitelist] is a deprecated endpoint. Please use [/allowlist] instead." + ), + new DeprecatedRoute( + RestRequest.Method.PATCH, + "/whitelist", + "[/whitelist] is a deprecated endpoint. Please use [/allowlist] instead." + ) + ) + ); @Inject - public WhitelistApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public WhitelistApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java b/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java index aba2807846..74908dbf60 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java +++ b/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java @@ -69,7 +69,10 @@ public static Map convertJsonToxToStructuredMap(ToXContent jsonC } public static Map convertJsonToxToStructuredMap(String jsonContent) { - try (XContentParser parser = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, jsonContent)) { + try ( + XContentParser parser = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, jsonContent) + ) { return parser.map(); } catch (IOException e1) { throw ExceptionsHelper.convertToOpenSearchException(e1); @@ -179,7 +182,8 @@ public static Map byteArrayToMutableJsonMap(byte[] jsonBytes) th return AccessController.doPrivileged(new PrivilegedExceptionAction>() { @Override public Map run() throws Exception { - return internalMapper.readValue(jsonBytes, new TypeReference>() {}); + return internalMapper.readValue(jsonBytes, new TypeReference>() { + }); } }); } catch (final PrivilegedActionException e) { @@ -215,10 +219,7 @@ public static String hash(final char[] clearTextPassword) { * @return new set of fields resource paths */ public static Set generateFieldResourcePaths(final Set fields, final String prefix) { - return fields - .stream() - .map(field -> prefix + field) - .collect(ImmutableSet.toImmutableSet()); + return fields.stream().map(field -> prefix + field).collect(ImmutableSet.toImmutableSet()); } /** @@ -227,7 +228,7 @@ public static Set generateFieldResourcePaths(final Set fields, f * @return new list of API routes prefixed with _opendistro... and _plugins... *Total number of routes is expanded as twice as the number of routes passed in */ - public static List addRoutesPrefix(List routes){ + public static List addRoutesPrefix(List routes) { return addRoutesPrefix(routes, "/_opendistro/_security/api", "/_plugins/_security/api"); } @@ -238,12 +239,10 @@ public static List addRoutesPrefix(List routes){ * @return new list of API routes prefixed with the strings listed in prefixes * Total number of routes will be expanded len(prefixes) as much comparing to the list passed in */ - public static List addRoutesPrefix(List routes, final String... prefixes){ + public static List addRoutesPrefix(List routes, final String... prefixes) { return routes.stream() - .flatMap( - r -> Arrays.stream(prefixes) - .map(p -> new Route(r.getMethod(), p + r.getPath()))) - .collect(ImmutableList.toImmutableList()); + .flatMap(r -> Arrays.stream(prefixes).map(p -> new Route(r.getMethod(), p + r.getPath()))) + .collect(ImmutableList.toImmutableList()); } /** @@ -252,7 +251,7 @@ public static List addRoutesPrefix(List routes, final String... pr * @return new list of API routes prefixed with _opendistro... and _plugins... *Total number of routes is expanded as twice as the number of routes passed in */ - public static List addDeprecatedRoutesPrefix(List deprecatedRoutes){ + public static List addDeprecatedRoutesPrefix(List deprecatedRoutes) { return addDeprecatedRoutesPrefix(deprecatedRoutes, "/_opendistro/_security/api", "/_plugins/_security/api"); } @@ -263,12 +262,10 @@ public static List addDeprecatedRoutesPrefix(List addDeprecatedRoutesPrefix(List deprecatedRoutes, final String... prefixes){ + public static List addDeprecatedRoutesPrefix(List deprecatedRoutes, final String... prefixes) { return deprecatedRoutes.stream() - .flatMap( - r -> Arrays.stream(prefixes) - .map(p -> new DeprecatedRoute(r.getMethod(), p + r.getPath(), r.getDeprecationMessage()))) - .collect(ImmutableList.toImmutableList()); + .flatMap(r -> Arrays.stream(prefixes).map(p -> new DeprecatedRoute(r.getMethod(), p + r.getPath(), r.getDeprecationMessage()))) + .collect(ImmutableList.toImmutableList()); } public static Pair userAndRemoteAddressFrom(final ThreadContext threadContext) { diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java index e3221de7e6..51d58d75f6 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java @@ -39,7 +39,6 @@ import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.support.ConfigConstants; - public abstract class AbstractConfigurationValidator { JsonFactory factory = new JsonFactory(); @@ -91,7 +90,12 @@ public abstract class AbstractConfigurationValidator { private JsonNode contentAsNode; - public AbstractConfigurationValidator(final RestRequest request, final BytesReference ref, final Settings opensearchSettings, Object... param) { + public AbstractConfigurationValidator( + final RestRequest request, + final BytesReference ref, + final Settings opensearchSettings, + Object... param + ) { this.content = ref; this.method = request.method(); this.opensearchSettings = opensearchSettings; @@ -113,19 +117,19 @@ public boolean validate() { return true; } - if(this.payloadMandatory && content.length() == 0) { + if (this.payloadMandatory && content.length() == 0) { this.errorType = ErrorType.PAYLOAD_MANDATORY; return false; } - if(!this.payloadMandatory && content.length() == 0) { + if (!this.payloadMandatory && content.length() == 0) { return true; } - if(this.payloadMandatory && content.length() > 0) { + if (this.payloadMandatory && content.length() > 0) { try { - if(DefaultObjectMapper.readTree(content.utf8ToString()).size() == 0) { + if (DefaultObjectMapper.readTree(content.utf8ToString()).size() == 0) { this.errorType = ErrorType.PAYLOAD_MANDATORY; return false; } @@ -184,7 +188,7 @@ public boolean validate() { return false; } - //null element in the values of all the possible keys with DataType as ARRAY + // null element in the values of all the possible keys with DataType as ARRAY for (Entry allowedKey : allowedKeys.entrySet()) { JsonNode value = contentAsNode.get(allowedKey.getKey()); if (value != null) { @@ -249,18 +253,21 @@ public XContentBuilder errorsAsXContent(RestChannel channel) { break; case INVALID_PASSWORD: builder.field("status", "error"); - builder.field("reason", opensearchSettings.get( + builder.field( + "reason", + opensearchSettings.get( ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, - "Password does not match minimum criteria")); + "Password does not match minimum criteria" + ) + ); break; case WEAK_PASSWORD: case SIMILAR_PASSWORD: builder.field("status", "error"); builder.field( - "reason", - opensearchSettings.get( - ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, - errorType.message)); + "reason", + opensearchSettings.get(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, errorType.message) + ); break; case WRONG_DATATYPE: builder.field("status", "error"); @@ -295,7 +302,10 @@ private void addErrorMessage(final XContentBuilder builder, final String message } public static enum DataType { - STRING, ARRAY, OBJECT, BOOLEAN; + STRING, + ARRAY, + OBJECT, + BOOLEAN; } public static enum ErrorType { @@ -307,7 +317,8 @@ public static enum ErrorType { WRONG_DATATYPE("Wrong datatype"), BODY_NOT_PARSEABLE("Could not parse content of request."), PAYLOAD_NOT_ALLOWED("Request body not allowed for this action."), - PAYLOAD_MANDATORY("Request body required for this action."), SECURITY_NOT_INITIALIZED("Security index not initialized"), + PAYLOAD_MANDATORY("Request body required for this action."), + SECURITY_NOT_INITIALIZED("Security index not initialized"), NULL_ARRAY_ELEMENT("`null` is not allowed as json array element"); private String message; @@ -326,8 +337,8 @@ protected final boolean hasParams() { } private boolean hasNullArrayElement(JsonNode node) { - for (JsonNode element: node) { - if(element.isNull()) { + for (JsonNode element : node) { + if (element.isNull()) { if (node.isArray()) { return true; } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/ActionGroupValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/ActionGroupValidator.java index ee1ab61238..a9f298fb15 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/ActionGroupValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/ActionGroupValidator.java @@ -17,15 +17,21 @@ public class ActionGroupValidator extends AbstractConfigurationValidator { - public ActionGroupValidator(final RestRequest request, boolean isSuperAdmin, BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("allowed_actions", DataType.ARRAY); - allowedKeys.put("description", DataType.STRING); - allowedKeys.put("type", DataType.STRING); - if (isSuperAdmin) allowedKeys.put("reserved" , DataType.BOOLEAN); + public ActionGroupValidator( + final RestRequest request, + boolean isSuperAdmin, + BytesReference ref, + final Settings opensearchSettings, + Object... param + ) { + super(request, ref, opensearchSettings, param); + this.payloadMandatory = true; + allowedKeys.put("allowed_actions", DataType.ARRAY); + allowedKeys.put("description", DataType.STRING); + allowedKeys.put("type", DataType.STRING); + if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); - mandatoryKeys.add("allowed_actions"); - } + mandatoryKeys.add("allowed_actions"); + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/AuditValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/AuditValidator.java index 6cc2aaca1b..1bff373c0d 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/AuditValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/AuditValidator.java @@ -25,29 +25,26 @@ public class AuditValidator extends AbstractConfigurationValidator { private static final Set DISABLED_REST_CATEGORIES = ImmutableSet.of( - AuditCategory.BAD_HEADERS, - AuditCategory.SSL_EXCEPTION, - AuditCategory.AUTHENTICATED, - AuditCategory.FAILED_LOGIN, - AuditCategory.GRANTED_PRIVILEGES, - AuditCategory.MISSING_PRIVILEGES + AuditCategory.BAD_HEADERS, + AuditCategory.SSL_EXCEPTION, + AuditCategory.AUTHENTICATED, + AuditCategory.FAILED_LOGIN, + AuditCategory.GRANTED_PRIVILEGES, + AuditCategory.MISSING_PRIVILEGES ); private static final Set DISABLED_TRANSPORT_CATEGORIES = ImmutableSet.of( - AuditCategory.BAD_HEADERS, - AuditCategory.SSL_EXCEPTION, - AuditCategory.AUTHENTICATED, - AuditCategory.FAILED_LOGIN, - AuditCategory.GRANTED_PRIVILEGES, - AuditCategory.MISSING_PRIVILEGES, - AuditCategory.INDEX_EVENT, - AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT + AuditCategory.BAD_HEADERS, + AuditCategory.SSL_EXCEPTION, + AuditCategory.AUTHENTICATED, + AuditCategory.FAILED_LOGIN, + AuditCategory.GRANTED_PRIVILEGES, + AuditCategory.MISSING_PRIVILEGES, + AuditCategory.INDEX_EVENT, + AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT ); - public AuditValidator(final RestRequest request, - final BytesReference ref, - final Settings opensearchSettings, - final Object... param) { + public AuditValidator(final RestRequest request, final BytesReference ref, final Settings opensearchSettings, final Object... param) { super(request, ref, opensearchSettings, param); this.payloadMandatory = true; this.allowedKeys.put("enabled", DataType.BOOLEAN); @@ -62,8 +59,8 @@ public boolean validate() { } if ((request.method() == RestRequest.Method.PUT || request.method() == RestRequest.Method.PATCH) - && this.content != null - && this.content.length() > 0) { + && this.content != null + && this.content.length() > 0) { try { // try parsing to target type final AuditConfig auditConfig = DefaultObjectMapper.readTree(getContentAsNode(), AuditConfig.class); diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java index ff1addc11e..a0f67c97ce 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java @@ -29,10 +29,7 @@ public class CredentialsValidator extends AbstractConfigurationValidator { private final PasswordValidator passwordValidator; - public CredentialsValidator(final RestRequest request, - final BytesReference ref, - final Settings opensearchSettings, - Object... param) { + public CredentialsValidator(final RestRequest request, final BytesReference ref, final Settings opensearchSettings, Object... param) { super(request, ref, opensearchSettings, param); this.payloadMandatory = true; this.passwordValidator = PasswordValidator.of(opensearchSettings); @@ -50,8 +47,8 @@ public boolean validate() { return false; } if ((request.method() == RestRequest.Method.PUT || request.method() == RestRequest.Method.PATCH) - && this.content != null - && this.content.length() > 1) { + && this.content != null + && this.content.length() > 1) { try { final Map contentAsMap = XContentHelper.convertToMap(this.content, false, XContentType.JSON).v2(); final String password = (String) contentAsMap.get("password"); @@ -75,7 +72,7 @@ public boolean validate() { } } } catch (NotXContentException e) { - //this.content is not valid json/yaml + // this.content is not valid json/yaml log.error("Invalid xContent: " + e, e); return false; } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/InternalUsersValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/InternalUsersValidator.java index 5f9828eba4..9681c47232 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/InternalUsersValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/InternalUsersValidator.java @@ -20,8 +20,13 @@ */ public class InternalUsersValidator extends CredentialsValidator { - public InternalUsersValidator(final RestRequest request, boolean isSuperAdmin, BytesReference ref, final Settings opensearchSettings, - Object... param) { + public InternalUsersValidator( + final RestRequest request, + boolean isSuperAdmin, + BytesReference ref, + final Settings opensearchSettings, + Object... param + ) { super(request, ref, opensearchSettings, param); allowedKeys.put("backend_roles", DataType.ARRAY); allowedKeys.put("attributes", DataType.OBJECT); diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java index 42870f1c13..42f86dbee5 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java @@ -20,7 +20,6 @@ public class MultiTenancyConfigValidator extends AbstractConfigurationValidator public static final String PRIVATE_TENANT_ENABLED_JSON_PROPERTY = "private_tenant_enabled"; public static final String MULTITENANCY_ENABLED_JSON_PROPERTY = "multitenancy_enabled"; - public MultiTenancyConfigValidator(RestRequest request, BytesReference ref, Settings opensearchSettings, Object... param) { super(request, ref, opensearchSettings, param); this.payloadMandatory = true; diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/NoOpValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/NoOpValidator.java index ec85c1fae8..7c64102091 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/NoOpValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/NoOpValidator.java @@ -17,8 +17,8 @@ public class NoOpValidator extends AbstractConfigurationValidator { - public NoOpValidator(final RestRequest request, BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - } + public NoOpValidator(final RestRequest request, BytesReference ref, final Settings opensearchSettings, Object... param) { + super(request, ref, opensearchSettings, param); + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java index f5ae9bee49..ac521dee8a 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java @@ -46,8 +46,8 @@ public class PasswordValidator { * are similar * "user_inputs" - is a default dictionary zxcvbn creates for checking similarity */ - private final static Predicate USERNAME_SIMILARITY_CHECK = m -> - m.pattern == com.nulabinc.zxcvbn.Pattern.Dictionary && "user_inputs".equals(m.dictionaryName); + private final static Predicate USERNAME_SIMILARITY_CHECK = m -> m.pattern == com.nulabinc.zxcvbn.Pattern.Dictionary + && "user_inputs".equals(m.dictionaryName); private final Logger logger = LogManager.getLogger(this.getClass()); @@ -59,9 +59,7 @@ public class PasswordValidator { private final Zxcvbn zxcvbn; - private PasswordValidator(final int minPasswordLength, - final Pattern passwordRegexpPattern, - final ScoreStrength scoreStrength) { + private PasswordValidator(final int minPasswordLength, final Pattern passwordRegexpPattern, final ScoreStrength scoreStrength) { this.minPasswordLength = minPasswordLength; this.passwordRegexpPattern = passwordRegexpPattern; this.scoreStrength = scoreStrength; @@ -71,49 +69,47 @@ private PasswordValidator(final int minPasswordLength, public static PasswordValidator of(final Settings settings) { final String passwordRegex = settings.get(SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, null); final ScoreStrength scoreStrength = ScoreStrength.fromConfiguration( - settings.get(SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, ScoreStrength.STRONG.name()) - ); + settings.get(SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, ScoreStrength.STRONG.name()) + ); final int minPasswordLength = settings.getAsInt(SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, -1); return new PasswordValidator( - minPasswordLength, - !Strings.isNullOrEmpty(passwordRegex) ? Pattern.compile(String.format("^%s$", passwordRegex)) : null, - scoreStrength); + minPasswordLength, + !Strings.isNullOrEmpty(passwordRegex) ? Pattern.compile(String.format("^%s$", passwordRegex)) : null, + scoreStrength + ); } ErrorType validate(final String username, final String password) { if (minPasswordLength > 0 && password.length() < minPasswordLength) { logger.debug( - "Password is too short, the minimum required length is {}, but current length is {}", - minPasswordLength, - password.length() + "Password is too short, the minimum required length is {}, but current length is {}", + minPasswordLength, + password.length() ); return ErrorType.INVALID_PASSWORD; } if (password.length() > MAX_LENGTH) { logger.debug( - "Password is too long, the maximum required length is {}, but current length is {}", - MAX_LENGTH, - password.length() + "Password is too long, the maximum required length is {}, but current length is {}", + MAX_LENGTH, + password.length() ); return ErrorType.INVALID_PASSWORD; } - if (Objects.nonNull(passwordRegexpPattern) - && !passwordRegexpPattern.matcher(password).matches()) { + if (Objects.nonNull(passwordRegexpPattern) && !passwordRegexpPattern.matcher(password).matches()) { logger.debug("Regex does not match password"); return ErrorType.INVALID_PASSWORD; } final Strength strength = zxcvbn.measure(password, ImmutableList.of(username)); if (strength.getScore() < scoreStrength.score()) { logger.debug( - "Password is weak the required score is {}, but current is {}", - scoreStrength, - ScoreStrength.fromScore(strength.getScore()) + "Password is weak the required score is {}, but current is {}", + scoreStrength, + ScoreStrength.fromScore(strength.getScore()) ); return ErrorType.WEAK_PASSWORD; } - final boolean similar = strength.getSequence() - .stream() - .anyMatch(USERNAME_SIMILARITY_CHECK); + final boolean similar = strength.getSequence().stream().anyMatch(USERNAME_SIMILARITY_CHECK); if (similar) { logger.debug("Password is too similar to the user name {}", username); return ErrorType.SIMILAR_PASSWORD; @@ -137,12 +133,10 @@ public enum ScoreStrength { static final List CONFIGURATION_VALUES = ImmutableList.of(FAIR, STRONG, VERY_STRONG); - static final String EXPECTED_CONFIGURATION_VALUES = - new StringJoiner(",") - .add(FAIR.name().toLowerCase(Locale.ROOT)) - .add(STRONG.name().toLowerCase(Locale.ROOT)) - .add(VERY_STRONG.name().toLowerCase(Locale.ROOT)) - .toString(); + static final String EXPECTED_CONFIGURATION_VALUES = new StringJoiner(",").add(FAIR.name().toLowerCase(Locale.ROOT)) + .add(STRONG.name().toLowerCase(Locale.ROOT)) + .add(VERY_STRONG.name().toLowerCase(Locale.ROOT)) + .toString(); private ScoreStrength(final int score, final String description) { this.score = score; @@ -151,24 +145,22 @@ private ScoreStrength(final int score, final String description) { public static ScoreStrength fromScore(final int score) { for (final ScoreStrength strength : values()) { - if (strength.score == score) - return strength; + if (strength.score == score) return strength; } throw new IllegalArgumentException("Unknown score " + score); } public static ScoreStrength fromConfiguration(final String value) { for (final ScoreStrength strength : CONFIGURATION_VALUES) { - if (strength.name().equalsIgnoreCase(value)) - return strength; + if (strength.name().equalsIgnoreCase(value)) return strength; } throw new IllegalArgumentException( - String.format( - "Setting [%s] cannot be used with the configured: %s. Expected one of [%s]", - SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, - value, - EXPECTED_CONFIGURATION_VALUES - ) + String.format( + "Setting [%s] cannot be used with the configured: %s. Expected one of [%s]", + SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, + value, + EXPECTED_CONFIGURATION_VALUES + ) ); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/RolesMappingValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/RolesMappingValidator.java index 0f36371176..728c2e0ca0 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/RolesMappingValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/RolesMappingValidator.java @@ -17,19 +17,25 @@ public class RolesMappingValidator extends AbstractConfigurationValidator { - public RolesMappingValidator(final RestRequest request, boolean isSuperAdmin, final BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("backend_roles", DataType.ARRAY); - allowedKeys.put("and_backend_roles", DataType.ARRAY); - allowedKeys.put("hosts", DataType.ARRAY); - allowedKeys.put("users", DataType.ARRAY); - allowedKeys.put("description", DataType.STRING); - if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); + public RolesMappingValidator( + final RestRequest request, + boolean isSuperAdmin, + final BytesReference ref, + final Settings opensearchSettings, + Object... param + ) { + super(request, ref, opensearchSettings, param); + this.payloadMandatory = true; + allowedKeys.put("backend_roles", DataType.ARRAY); + allowedKeys.put("and_backend_roles", DataType.ARRAY); + allowedKeys.put("hosts", DataType.ARRAY); + allowedKeys.put("users", DataType.ARRAY); + allowedKeys.put("description", DataType.STRING); + if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); - mandatoryOrKeys.add("backend_roles"); - mandatoryOrKeys.add("and_backend_roles"); - mandatoryOrKeys.add("hosts"); - mandatoryOrKeys.add("users"); - } + mandatoryOrKeys.add("backend_roles"); + mandatoryOrKeys.add("and_backend_roles"); + mandatoryOrKeys.add("hosts"); + mandatoryOrKeys.add("users"); + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/RolesValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/RolesValidator.java index 07708c6615..2e57730e41 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/RolesValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/RolesValidator.java @@ -24,17 +24,23 @@ public class RolesValidator extends AbstractConfigurationValidator { - private static final Salt SALT = new Salt(new byte[] {1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,6}); - - public RolesValidator(final RestRequest request, boolean isSuperAdmin, final BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; + private static final Salt SALT = new Salt(new byte[] { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6 }); + + public RolesValidator( + final RestRequest request, + boolean isSuperAdmin, + final BytesReference ref, + final Settings opensearchSettings, + Object... param + ) { + super(request, ref, opensearchSettings, param); + this.payloadMandatory = true; allowedKeys.put("cluster_permissions", DataType.ARRAY); allowedKeys.put("tenant_permissions", DataType.ARRAY); allowedKeys.put("index_permissions", DataType.ARRAY); allowedKeys.put("description", DataType.STRING); if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); - } + } @Override public boolean validate() { @@ -43,7 +49,7 @@ public boolean validate() { return false; } - boolean valid=true; + boolean valid = true; if (this.content != null && this.content.length() > 0) { @@ -60,8 +66,8 @@ public boolean validate() { } } - if(!valid) { - this.errorType = ErrorType.WRONG_DATATYPE; + if (!valid) { + this.errorType = ErrorType.WRONG_DATATYPE; } return valid; @@ -71,7 +77,7 @@ private boolean validateMaskedFieldSyntax(String mf) { try { new MaskedField(mf, SALT).isValid(); } catch (Exception e) { - wrongDatatypes.put("Masked field not valid: "+mf, e.getMessage()); + wrongDatatypes.put("Masked field not valid: " + mf, e.getMessage()); return false; } return true; diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/SecurityConfigValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/SecurityConfigValidator.java index 1a8db220b9..cd2ee56b4a 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/SecurityConfigValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/SecurityConfigValidator.java @@ -17,10 +17,10 @@ public class SecurityConfigValidator extends AbstractConfigurationValidator { - public SecurityConfigValidator(final RestRequest request, BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("dynamic", DataType.OBJECT); - } + public SecurityConfigValidator(final RestRequest request, BytesReference ref, final Settings opensearchSettings, Object... param) { + super(request, ref, opensearchSettings, param); + this.payloadMandatory = true; + allowedKeys.put("dynamic", DataType.OBJECT); + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/TenantValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/TenantValidator.java index 15068500e8..51e0e97264 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/TenantValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/TenantValidator.java @@ -33,7 +33,13 @@ public class TenantValidator extends AbstractConfigurationValidator { - public TenantValidator(final RestRequest request, boolean isSuperAdmin, BytesReference ref, final Settings opensearchSettings, Object... param) { + public TenantValidator( + final RestRequest request, + boolean isSuperAdmin, + BytesReference ref, + final Settings opensearchSettings, + Object... param + ) { super(request, ref, opensearchSettings, param); this.payloadMandatory = true; allowedKeys.put("description", DataType.STRING); diff --git a/src/main/java/org/opensearch/security/filter/SecurityFilter.java b/src/main/java/org/opensearch/security/filter/SecurityFilter.java index 4dd629c010..38675d97c5 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityFilter.java @@ -113,9 +113,18 @@ public class SecurityFilter implements ActionFilter { private final RolesInjector rolesInjector; private final UserInjector userInjector; - public SecurityFilter(final Settings settings, final PrivilegesEvaluator evalp, final AdminDNs adminDns, - DlsFlsRequestValve dlsFlsValve, AuditLog auditLog, ThreadPool threadPool, ClusterService cs, - final CompatConfig compatConfig, final IndexResolverReplacer indexResolverReplacer, final XFFResolver xffResolver) { + public SecurityFilter( + final Settings settings, + final PrivilegesEvaluator evalp, + final AdminDNs adminDns, + DlsFlsRequestValve dlsFlsValve, + AuditLog auditLog, + ThreadPool threadPool, + ClusterService cs, + final CompatConfig compatConfig, + final IndexResolverReplacer indexResolverReplacer, + final XFFResolver xffResolver + ) { this.evalp = evalp; this.adminDns = adminDns; this.dlsFlsValve = dlsFlsValve; @@ -125,7 +134,9 @@ public SecurityFilter(final Settings settings, final PrivilegesEvaluator evalp, this.compatConfig = compatConfig; this.indexResolverReplacer = indexResolverReplacer; this.xffResolver = xffResolver; - this.immutableIndicesMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_COMPLIANCE_IMMUTABLE_INDICES, Collections.emptyList())); + this.immutableIndicesMatcher = WildcardMatcher.from( + settings.getAsList(ConfigConstants.SECURITY_COMPLIANCE_IMMUTABLE_INDICES, Collections.emptyList()) + ); this.rolesInjector = new RolesInjector(auditLog); this.userInjector = new UserInjector(settings, threadPool, auditLog, xffResolver); log.info("{} indices are made immutable.", immutableIndicesMatcher); @@ -142,9 +153,14 @@ public int order() { } @Override - public void apply(Task task, final String action, Request request, - ActionListener listener, ActionFilterChain chain) { - try (StoredContext ctx = threadContext.newStoredContext(true)){ + public void apply( + Task task, + final String action, + Request request, + ActionListener listener, + ActionFilterChain chain + ) { + try (StoredContext ctx = threadContext.newStoredContext(true)) { org.apache.logging.log4j.ThreadContext.clearAll(); apply0(task, action, request, listener, chain); } @@ -154,11 +170,16 @@ private static Set alias2Name(Set aliases) { return aliases.stream().map(a -> a.name()).collect(ImmutableSet.toImmutableSet()); } - private void apply0(Task task, final String action, Request request, - ActionListener listener, ActionFilterChain chain) { + private void apply0( + Task task, + final String action, + Request request, + ActionListener listener, + ActionFilterChain chain + ) { try { - if(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN) == null) { + if (threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN) == null) { threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, Origin.LOCAL.toString()); } @@ -169,7 +190,7 @@ private void ap final Set injectedRoles = rolesInjector.injectUserAndRoles(request, action, task, threadContext); boolean enforcePrivilegesEvaluation = false; User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - if(user == null && (user = userInjector.getInjectedUser()) != null) { + if (user == null && (user = userInjector.getInjectedUser()) != null) { threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); // since there is no support for TransportClient auth/auth in 2.0 anymore, usually we // can skip any checks on transport in case of trusted requests. @@ -180,14 +201,14 @@ private void ap final boolean userIsAdmin = isUserAdmin(user, adminDns); final boolean interClusterRequest = HeaderHelper.isInterClusterRequest(threadContext); final boolean trustedClusterRequest = HeaderHelper.isTrustedClusterRequest(threadContext); - final boolean confRequest = "true".equals(HeaderHelper.getSafeFromHeader(threadContext, ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER)); - final boolean passThroughRequest = action.startsWith("indices:admin/seq_no") - || action.equals(WhoAmIAction.NAME); + final boolean confRequest = "true".equals( + HeaderHelper.getSafeFromHeader(threadContext, ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER) + ); + final boolean passThroughRequest = action.startsWith("indices:admin/seq_no") || action.equals(WhoAmIAction.NAME); - final boolean internalRequest = - (interClusterRequest || HeaderHelper.isDirectRequest(threadContext)) - && action.startsWith("internal:") - && !action.startsWith("internal:transport/proxy"); + final boolean internalRequest = (interClusterRequest || HeaderHelper.isDirectRequest(threadContext)) + && action.startsWith("internal:") + && !action.startsWith("internal:transport/proxy"); if (user != null) { org.apache.logging.log4j.ThreadContext.put("user", user.getName()); @@ -196,35 +217,72 @@ private void ap if (isActionTraceEnabled()) { String count = ""; - if(request instanceof BulkRequest) { - count = ""+((BulkRequest) request).requests().size(); + if (request instanceof BulkRequest) { + count = "" + ((BulkRequest) request).requests().size(); } - if(request instanceof MultiGetRequest) { - count = ""+((MultiGetRequest) request).getItems().size(); + if (request instanceof MultiGetRequest) { + count = "" + ((MultiGetRequest) request).getItems().size(); } - if(request instanceof MultiSearchRequest) { - count = ""+((MultiSearchRequest) request).requests().size(); + if (request instanceof MultiSearchRequest) { + count = "" + ((MultiSearchRequest) request).requests().size(); } - traceAction("Node "+cs.localNode().getName()+" -> "+action+" ("+count+"): userIsAdmin="+userIsAdmin+"/conRequest="+confRequest+"/internalRequest="+internalRequest - +"origin="+threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)+"/directRequest="+HeaderHelper.isDirectRequest(threadContext)+"/remoteAddress="+request.remoteAddress()); - - - threadContext.putHeader("_opendistro_security_trace"+System.currentTimeMillis()+"#"+UUID.randomUUID().toString(), Thread.currentThread().getName()+" FILTER -> "+"Node "+cs.localNode().getName()+" -> "+action+" userIsAdmin="+userIsAdmin+"/conRequest="+confRequest+"/internalRequest="+internalRequest - +"origin="+threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)+"/directRequest="+HeaderHelper.isDirectRequest(threadContext)+"/remoteAddress="+request.remoteAddress()+" "+threadContext.getHeaders().entrySet().stream().filter(p->!p.getKey().startsWith("_opendistro_security_trace")).collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()))); - + traceAction( + "Node " + + cs.localNode().getName() + + " -> " + + action + + " (" + + count + + "): userIsAdmin=" + + userIsAdmin + + "/conRequest=" + + confRequest + + "/internalRequest=" + + internalRequest + + "origin=" + + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN) + + "/directRequest=" + + HeaderHelper.isDirectRequest(threadContext) + + "/remoteAddress=" + + request.remoteAddress() + ); + + threadContext.putHeader( + "_opendistro_security_trace" + System.currentTimeMillis() + "#" + UUID.randomUUID().toString(), + Thread.currentThread().getName() + + " FILTER -> " + + "Node " + + cs.localNode().getName() + + " -> " + + action + + " userIsAdmin=" + + userIsAdmin + + "/conRequest=" + + confRequest + + "/internalRequest=" + + internalRequest + + "origin=" + + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN) + + "/directRequest=" + + HeaderHelper.isDirectRequest(threadContext) + + "/remoteAddress=" + + request.remoteAddress() + + " " + + threadContext.getHeaders() + .entrySet() + .stream() + .filter(p -> !p.getKey().startsWith("_opendistro_security_trace")) + .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue())) + ); } + if (userIsAdmin || confRequest || internalRequest || passThroughRequest) { - if(userIsAdmin - || confRequest - || internalRequest - || passThroughRequest){ - - if(userIsAdmin && !confRequest && !internalRequest && !passThroughRequest) { + if (userIsAdmin && !confRequest && !internalRequest && !passThroughRequest) { auditLog.logGrantedPrivileges(action, request, task); auditLog.logIndexEvent(action, request, task); } @@ -233,15 +291,14 @@ private void ap return; } - - if(immutableIndicesMatcher != WildcardMatcher.NONE) { + if (immutableIndicesMatcher != WildcardMatcher.NONE) { boolean isImmutable = false; - if(request instanceof BulkShardRequest) { - for(BulkItemRequest bsr: ((BulkShardRequest) request).items()) { + if (request instanceof BulkShardRequest) { + for (BulkItemRequest bsr : ((BulkShardRequest) request).items()) { isImmutable = checkImmutableIndices(bsr.request(), listener); - if(isImmutable) { + if (isImmutable) { break; } } @@ -249,50 +306,67 @@ private void ap isImmutable = checkImmutableIndices(request, listener); } - if(isImmutable) { + if (isImmutable) { return; } } - if(Origin.LOCAL.toString().equals(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) - && (interClusterRequest || HeaderHelper.isDirectRequest(threadContext)) - && (injectedRoles == null) - && !enforcePrivilegesEvaluation - ) { + if (Origin.LOCAL.toString().equals(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) + && (interClusterRequest || HeaderHelper.isDirectRequest(threadContext)) + && (injectedRoles == null) + && !enforcePrivilegesEvaluation) { chain.proceed(task, action, request, listener); return; } - if(user == null) { + if (user == null) { - if(action.startsWith("cluster:monitor/state")) { + if (action.startsWith("cluster:monitor/state")) { chain.proceed(task, action, request, listener); return; } - boolean skipSecurityIfDualMode = threadContext.getTransient(ConfigConstants.SECURITY_SSL_DUAL_MODE_SKIP_SECURITY) == Boolean.TRUE; - if((interClusterRequest || trustedClusterRequest || request.remoteAddress() == null) && !compatConfig.transportInterClusterAuthEnabled()) { + boolean skipSecurityIfDualMode = threadContext.getTransient( + ConfigConstants.SECURITY_SSL_DUAL_MODE_SKIP_SECURITY + ) == Boolean.TRUE; + if ((interClusterRequest || trustedClusterRequest || request.remoteAddress() == null) + && !compatConfig.transportInterClusterAuthEnabled()) { chain.proceed(task, action, request, listener); return; - } else if((interClusterRequest || trustedClusterRequest || request.remoteAddress() == null || skipSecurityIfDualMode) && compatConfig.transportInterClusterPassiveAuthEnabled()) { - log.info("Transport auth in passive mode and no user found. Injecting default user"); - user = User.DEFAULT_TRANSPORT_USER; - threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); - } else { - log.error("No user found for "+ action+" from "+request.remoteAddress()+" "+threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)+" via "+threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_CHANNEL_TYPE)+" "+threadContext.getHeaders()); - listener.onFailure(new OpenSearchSecurityException("No user found for "+action, RestStatus.INTERNAL_SERVER_ERROR)); - return; - } + } else if ((interClusterRequest || trustedClusterRequest || request.remoteAddress() == null || skipSecurityIfDualMode) + && compatConfig.transportInterClusterPassiveAuthEnabled()) { + log.info("Transport auth in passive mode and no user found. Injecting default user"); + user = User.DEFAULT_TRANSPORT_USER; + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); + } else { + log.error( + "No user found for " + + action + + " from " + + request.remoteAddress() + + " " + + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN) + + " via " + + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_CHANNEL_TYPE) + + " " + + threadContext.getHeaders() + ); + listener.onFailure( + new OpenSearchSecurityException("No user found for " + action, RestStatus.INTERNAL_SERVER_ERROR) + ); + return; + } } final PrivilegesEvaluator eval = evalp; if (!eval.isInitialized()) { log.error("OpenSearch Security not initialized for {}", action); - listener.onFailure(new OpenSearchSecurityException("OpenSearch Security not initialized for " - + action, RestStatus.SERVICE_UNAVAILABLE)); + listener.onFailure( + new OpenSearchSecurityException("OpenSearch Security not initialized for " + action, RestStatus.SERVICE_UNAVAILABLE) + ); return; } @@ -317,18 +391,30 @@ private void ap chain.proceed(task, action, request, listener); } else { CreateIndexRequest createIndexRequest = createIndexRequestBuilder.request(); - log.info("Request {} requires new tenant index {} with aliases {}", - request.getClass().getSimpleName(), createIndexRequest.index(), alias2Name(createIndexRequest.aliases())); + log.info( + "Request {} requires new tenant index {} with aliases {}", + request.getClass().getSimpleName(), + createIndexRequest.index(), + alias2Name(createIndexRequest.aliases()) + ); createIndexRequestBuilder.execute(new ActionListener() { @Override public void onResponse(CreateIndexResponse createIndexResponse) { if (createIndexResponse.isAcknowledged()) { - log.debug("Request to create index {} with aliases {} acknowledged, proceeding with {}", - createIndexRequest.index(), alias2Name(createIndexRequest.aliases()), request.getClass().getSimpleName()); + log.debug( + "Request to create index {} with aliases {} acknowledged, proceeding with {}", + createIndexRequest.index(), + alias2Name(createIndexRequest.aliases()), + request.getClass().getSimpleName() + ); chain.proceed(task, action, request, listener); } else { - String message = LoggerMessageFormat.format("Request to create index {} with aliases {} was not acknowledged, failing {}", - createIndexRequest.index(), alias2Name(createIndexRequest.aliases()), request.getClass().getSimpleName()); + String message = LoggerMessageFormat.format( + "Request to create index {} with aliases {} was not acknowledged, failing {}", + createIndexRequest.index(), + alias2Name(createIndexRequest.aliases()), + request.getClass().getSimpleName() + ); log.error(message); listener.onFailure(new OpenSearchException(message)); } @@ -338,12 +424,22 @@ public void onResponse(CreateIndexResponse createIndexResponse) { public void onFailure(Exception e) { Throwable cause = ExceptionsHelper.unwrapCause(e); if (cause instanceof ResourceAlreadyExistsException) { - log.warn("Request to create index {} with aliases {} failed as the resource already exists, proceeding with {}", - createIndexRequest.index(), alias2Name(createIndexRequest.aliases()), request.getClass().getSimpleName(), e); + log.warn( + "Request to create index {} with aliases {} failed as the resource already exists, proceeding with {}", + createIndexRequest.index(), + alias2Name(createIndexRequest.aliases()), + request.getClass().getSimpleName(), + e + ); chain.proceed(task, action, request, listener); } else { - log.error("Request to create index {} with aliases {} failed, failing {}", - createIndexRequest.index(), alias2Name(createIndexRequest.aliases()), request.getClass().getSimpleName(), e); + log.error( + "Request to create index {} with aliases {} failed, failing {}", + createIndexRequest.index(), + alias2Name(createIndexRequest.aliases()), + request.getClass().getSimpleName(), + e + ); listener.onFailure(e); } } @@ -352,12 +448,16 @@ public void onFailure(Exception e) { } else { auditLog.logMissingPrivileges(action, request, task); String err; - if(!pres.getMissingSecurityRoles().isEmpty()) { + if (!pres.getMissingSecurityRoles().isEmpty()) { err = String.format("No mapping for %s on roles %s", user, pres.getMissingSecurityRoles()); } else { - err = (injectedRoles != null) ? - String.format("no permissions for %s and associated roles %s", pres.getMissingPrivileges(), pres.getResolvedSecurityRoles()) : - String.format("no permissions for %s and %s", pres.getMissingPrivileges(), user); + err = (injectedRoles != null) + ? String.format( + "no permissions for %s and associated roles %s", + pres.getMissingPrivileges(), + pres.getResolvedSecurityRoles() + ) + : String.format("no permissions for %s and %s", pres.getMissingPrivileges(), user); } log.debug(err); listener.onFailure(new OpenSearchSecurityException(err, RestStatus.FORBIDDEN)); @@ -370,7 +470,7 @@ public void onFailure(Exception e) { } listener.onFailure(e); } catch (Throwable e) { - log.error("Unexpected exception "+e, e); + log.error("Unexpected exception " + e, e); listener.onFailure(new OpenSearchSecurityException("Unexpected exception " + action, RestStatus.INTERNAL_SERVER_ERROR)); } } @@ -385,13 +485,13 @@ private static boolean isUserAdmin(User user, final AdminDNs adminDns) { private void attachSourceFieldContext(ActionRequest request) { - if(request instanceof SearchRequest && SourceFieldsContext.isNeeded((SearchRequest) request)) { - if(threadContext.getHeader("_opendistro_security_source_field_context") == null) { + if (request instanceof SearchRequest && SourceFieldsContext.isNeeded((SearchRequest) request)) { + if (threadContext.getHeader("_opendistro_security_source_field_context") == null) { final String serializedSourceFieldContext = Base64Helper.serializeObject(new SourceFieldsContext((SearchRequest) request)); threadContext.putHeader("_opendistro_security_source_field_context", serializedSourceFieldContext); } } else if (request instanceof GetRequest && SourceFieldsContext.isNeeded((GetRequest) request)) { - if(threadContext.getHeader("_opendistro_security_source_field_context") == null) { + if (threadContext.getHeader("_opendistro_security_source_field_context") == null) { final String serializedSourceFieldContext = Base64Helper.serializeObject(new SourceFieldsContext((GetRequest) request)); threadContext.putHeader("_opendistro_security_source_field_context", serializedSourceFieldContext); } @@ -401,13 +501,13 @@ private void attachSourceFieldContext(ActionRequest request) { @SuppressWarnings("rawtypes") private boolean checkImmutableIndices(Object request, ActionListener listener) { final boolean isModifyIndexRequest = request instanceof DeleteRequest - || request instanceof UpdateRequest - || request instanceof UpdateByQueryRequest - || request instanceof DeleteByQueryRequest - || request instanceof DeleteIndexRequest - || request instanceof RestoreSnapshotRequest - || request instanceof CloseIndexRequest - || request instanceof IndicesAliasesRequest; + || request instanceof UpdateRequest + || request instanceof UpdateByQueryRequest + || request instanceof DeleteByQueryRequest + || request instanceof DeleteIndexRequest + || request instanceof RestoreSnapshotRequest + || request instanceof CloseIndexRequest + || request instanceof IndicesAliasesRequest; if (isModifyIndexRequest && isRequestIndexImmutable(request)) { listener.onFailure(new OpenSearchSecurityException("Index is immutable", RestStatus.FORBIDDEN)); diff --git a/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java b/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java index a5a23957ed..80bba54ea2 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java @@ -83,13 +83,18 @@ public class SecurityRestFilter { private static final String HEALTH_SUFFIX = "health"; private static final String WHO_AM_I_SUFFIX = "whoami"; - private static final String REGEX_PATH_PREFIX = "/("+ LEGACY_OPENDISTRO_PREFIX + "|" + PLUGINS_PREFIX + ")/" +"(.*)"; + private static final String REGEX_PATH_PREFIX = "/(" + LEGACY_OPENDISTRO_PREFIX + "|" + PLUGINS_PREFIX + ")/" + "(.*)"; private static final Pattern PATTERN_PATH_PREFIX = Pattern.compile(REGEX_PATH_PREFIX); - - public SecurityRestFilter(final BackendRegistry registry, final AuditLog auditLog, - final ThreadPool threadPool, final PrincipalExtractor principalExtractor, - final Settings settings, final Path configPath, final CompatConfig compatConfig) { + public SecurityRestFilter( + final BackendRegistry registry, + final AuditLog auditLog, + final ThreadPool threadPool, + final PrincipalExtractor principalExtractor, + final Settings settings, + final Path configPath, + final CompatConfig compatConfig + ) { super(); this.registry = registry; this.auditLog = auditLog; @@ -123,7 +128,9 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c org.apache.logging.log4j.ThreadContext.clearAll(); if (!checkAndAuthenticateRequest(request, channel, client)) { User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - if (userIsSuperAdmin(user, adminDNs) || (whitelistingSettings.checkRequestIsAllowed(request, channel, client) && allowlistingSettings.checkRequestIsAllowed(request, channel, client))) { + if (userIsSuperAdmin(user, adminDNs) + || (whitelistingSettings.checkRequestIsAllowed(request, channel, client) + && allowlistingSettings.checkRequestIsAllowed(request, channel, client))) { original.handleRequest(request, channel, client); } } @@ -138,12 +145,11 @@ private boolean userIsSuperAdmin(User user, AdminDNs adminDNs) { return user != null && adminDNs.isAdmin(user); } - private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel channel, - NodeClient client) throws Exception { + private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception { threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, Origin.REST.toString()); - if(HTTPHelper.containsBadHeader(request)) { + if (HTTPHelper.containsBadHeader(request)) { final OpenSearchException exception = ExceptionUtils.createBadHeaderException(); log.error(exception.toString()); auditLog.logBadHeaders(request); @@ -151,7 +157,7 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha return true; } - if(SSLRequestHelper.containsBadHeader(threadContext, ConfigConstants.OPENDISTRO_SECURITY_CONFIG_PREFIX)) { + if (SSLRequestHelper.containsBadHeader(threadContext, ConfigConstants.OPENDISTRO_SECURITY_CONFIG_PREFIX)) { final OpenSearchException exception = ExceptionUtils.createBadHeaderException(); log.error(exception.toString()); auditLog.logBadHeaders(request); @@ -161,13 +167,13 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha final SSLInfo sslInfo; try { - if((sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor)) != null) { - if(sslInfo.getPrincipal() != null) { + if ((sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor)) != null) { + if (sslInfo.getPrincipal() != null) { threadContext.putTransient("_opendistro_security_ssl_principal", sslInfo.getPrincipal()); } - if(sslInfo.getX509Certs() != null) { - threadContext.putTransient("_opendistro_security_ssl_peer_certificates", sslInfo.getX509Certs()); + if (sslInfo.getX509Certs() != null) { + threadContext.putTransient("_opendistro_security_ssl_peer_certificates", sslInfo.getX509Certs()); } threadContext.putTransient("_opendistro_security_ssl_protocol", sslInfo.getProtocol()); threadContext.putTransient("_opendistro_security_ssl_cipher", sslInfo.getCipher()); @@ -179,22 +185,23 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha return true; } - if(!compatConfig.restAuthEnabled()) { + if (!compatConfig.restAuthEnabled()) { return false; } Matcher matcher = PATTERN_PATH_PREFIX.matcher(request.path()); final String suffix = matcher.matches() ? matcher.group(2) : null; - if(request.method() != Method.OPTIONS - && !(HEALTH_SUFFIX.equals(suffix)) - && !(WHO_AM_I_SUFFIX.equals(suffix))) { + if (request.method() != Method.OPTIONS && !(HEALTH_SUFFIX.equals(suffix)) && !(WHO_AM_I_SUFFIX.equals(suffix))) { if (!registry.authenticate(request, channel, threadContext)) { // another roundtrip org.apache.logging.log4j.ThreadContext.remove("user"); return true; } else { // make it possible to filter logs by username - org.apache.logging.log4j.ThreadContext.put("user", ((User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER)).getName()); + org.apache.logging.log4j.ThreadContext.put( + "user", + ((User) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER)).getName() + ); } } diff --git a/src/main/java/org/opensearch/security/http/HTTPBasicAuthenticator.java b/src/main/java/org/opensearch/security/http/HTTPBasicAuthenticator.java index 30e6134381..88ac128828 100644 --- a/src/main/java/org/opensearch/security/http/HTTPBasicAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/HTTPBasicAuthenticator.java @@ -55,7 +55,7 @@ public AuthCredentials extractCredentials(final RestRequest request, ThreadConte final boolean forceLogin = request.paramAsBoolean("force_login", false); - if(forceLogin) { + if (forceLogin) { return null; } diff --git a/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java b/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java index 373919669d..b1e5d4ef40 100644 --- a/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java @@ -71,16 +71,16 @@ public AuthCredentials extractCredentials(final RestRequest request, final Threa String username = principal.trim(); String[] backendRoles = null; - if(usernameAttribute != null && usernameAttribute.length() > 0) { + if (usernameAttribute != null && usernameAttribute.length() > 0) { final List usernames = getDnAttribute(rfc2253dn, usernameAttribute); - if(usernames.isEmpty() == false) { + if (usernames.isEmpty() == false) { username = usernames.get(0); } } - if(rolesAttribute != null && rolesAttribute.length() > 0) { + if (rolesAttribute != null && rolesAttribute.length() > 0) { final List roles = getDnAttribute(rfc2253dn, rolesAttribute); - if(roles.isEmpty() == false) { + if (roles.isEmpty() == false) { backendRoles = roles.toArray(new String[0]); } } diff --git a/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java b/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java index 348811b694..a58a842394 100644 --- a/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java @@ -52,13 +52,13 @@ public class HTTPProxyAuthenticator implements HTTPAuthenticator { public HTTPProxyAuthenticator(Settings settings, final Path configPath) { super(); this.settings = settings; - this.rolesSeparator = Pattern.compile(settings.get("roles_separator", ",")); + this.rolesSeparator = Pattern.compile(settings.get("roles_separator", ",")); } @Override public AuthCredentials extractCredentials(final RestRequest request, ThreadContext context) { - if(context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE) != Boolean.TRUE) { + if (context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE) != Boolean.TRUE) { throw new OpenSearchSecurityException("xff not done"); } @@ -76,11 +76,10 @@ public AuthCredentials extractCredentials(final RestRequest request, ThreadConte String[] backendRoles = null; if (!Strings.isNullOrEmpty(rolesHeader) && !Strings.isNullOrEmpty((String) request.header(rolesHeader))) { - backendRoles = rolesSeparator - .splitAsStream((String) request.header(rolesHeader)) - .map(String::trim) - .filter(Predicates.not(String::isEmpty)) - .toArray(String[]::new); + backendRoles = rolesSeparator.splitAsStream((String) request.header(rolesHeader)) + .map(String::trim) + .filter(Predicates.not(String::isEmpty)) + .toArray(String[]::new); } return new AuthCredentials((String) request.header(userHeader), backendRoles).markComplete(); } else { diff --git a/src/main/java/org/opensearch/security/http/RemoteIpDetector.java b/src/main/java/org/opensearch/security/http/RemoteIpDetector.java index 5d9e933c8f..f464c0653a 100644 --- a/src/main/java/org/opensearch/security/http/RemoteIpDetector.java +++ b/src/main/java/org/opensearch/security/http/RemoteIpDetector.java @@ -73,21 +73,23 @@ final class RemoteIpDetector { * @return array of String (non null) */ protected static String[] commaDelimitedListToStringArray(String commaDelimitedStrings) { - return (commaDelimitedStrings == null || commaDelimitedStrings.length() == 0) ? new String[0] : commaSeparatedValuesPattern - .split(commaDelimitedStrings); + return (commaDelimitedStrings == null || commaDelimitedStrings.length() == 0) + ? new String[0] + : commaSeparatedValuesPattern.split(commaDelimitedStrings); } /** * @see #setInternalProxies(String) */ private Pattern internalProxies = Pattern.compile( - "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + - "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + - "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + - "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}"); + "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}" + ); /** * @see #setRemoteIpHeader(String) @@ -113,31 +115,30 @@ public String getRemoteIpHeader() { return remoteIpHeader; } - String detect(RestRequest request, ThreadContext threadContext){ - final String originalRemoteAddr = ((InetSocketAddress)request.getHttpChannel().getRemoteAddress()).getAddress().getHostAddress(); + String detect(RestRequest request, ThreadContext threadContext) { + final String originalRemoteAddr = ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress().getHostAddress(); final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { log.trace("originalRemoteAddr {}", originalRemoteAddr); } - //X-Forwarded-For: client1, proxy1, proxy2 - // ^^^^^^ originalRemoteAddr + // X-Forwarded-For: client1, proxy1, proxy2 + // ^^^^^^ originalRemoteAddr - //originalRemoteAddr need to be in the list of internalProxies - if (internalProxies !=null && - internalProxies.matcher(originalRemoteAddr).matches()) { + // originalRemoteAddr need to be in the list of internalProxies + if (internalProxies != null && internalProxies.matcher(originalRemoteAddr).matches()) { String remoteIp = null; final StringBuilder concatRemoteIpHeaderValue = new StringBuilder(); - //client1, proxy1, proxy2 - final List remoteIpHeaders = request.getHeaders().get(remoteIpHeader); //X-Forwarded-For + // client1, proxy1, proxy2 + final List remoteIpHeaders = request.getHeaders().get(remoteIpHeader); // X-Forwarded-For - if(remoteIpHeaders == null || remoteIpHeaders.isEmpty()) { + if (remoteIpHeaders == null || remoteIpHeaders.isEmpty()) { return originalRemoteAddr; } - for (String rh:remoteIpHeaders) { + for (String rh : remoteIpHeaders) { if (concatRemoteIpHeaderValue.length() > 0) { concatRemoteIpHeaderValue.append(", "); } @@ -172,8 +173,15 @@ String detect(RestRequest request, ThreadContext threadContext){ if (remoteIp != null) { if (isTraceEnabled) { - final String originalRemoteHost = ((InetSocketAddress)request.getHttpChannel().getRemoteAddress()).getAddress().getHostName(); - log.trace("Incoming request {} with originalRemoteAddr '{}', originalRemoteHost='{}', will be seen as newRemoteAddr='{}'", request.uri(), originalRemoteAddr, originalRemoteHost, remoteIp); + final String originalRemoteHost = ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress() + .getHostName(); + log.trace( + "Incoming request {} with originalRemoteAddr '{}', originalRemoteHost='{}', will be seen as newRemoteAddr='{}'", + request.uri(), + originalRemoteAddr, + originalRemoteHost, + remoteIp + ); } threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE, Boolean.TRUE); @@ -185,7 +193,11 @@ String detect(RestRequest request, ThreadContext threadContext){ } else { if (isTraceEnabled) { - log.trace("Skip RemoteIpDetector for request {} with originalRemoteAddr '{}' cause no internal proxy matches", request.uri(), request.getHttpChannel().getRemoteAddress()); + log.trace( + "Skip RemoteIpDetector for request {} with originalRemoteAddr '{}' cause no internal proxy matches", + request.uri(), + request.getHttpChannel().getRemoteAddress() + ); } } diff --git a/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java b/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java index 6f2f57053f..e9487a49a9 100644 --- a/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java +++ b/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java @@ -40,9 +40,29 @@ public class SecurityHttpServerTransport extends SecuritySSLNettyHttpServerTransport { - public SecurityHttpServerTransport(final Settings settings, final NetworkService networkService, - final BigArrays bigArrays, final ThreadPool threadPool, final SecurityKeyStore odsks, - final SslExceptionHandler sslExceptionHandler, final NamedXContentRegistry namedXContentRegistry, final ValidatingDispatcher dispatcher, final ClusterSettings clusterSettings, SharedGroupFactory sharedGroupFactory) { - super(settings, networkService, bigArrays, threadPool, odsks, namedXContentRegistry, dispatcher, sslExceptionHandler, clusterSettings, sharedGroupFactory); + public SecurityHttpServerTransport( + final Settings settings, + final NetworkService networkService, + final BigArrays bigArrays, + final ThreadPool threadPool, + final SecurityKeyStore odsks, + final SslExceptionHandler sslExceptionHandler, + final NamedXContentRegistry namedXContentRegistry, + final ValidatingDispatcher dispatcher, + final ClusterSettings clusterSettings, + SharedGroupFactory sharedGroupFactory + ) { + super( + settings, + networkService, + bigArrays, + threadPool, + odsks, + namedXContentRegistry, + dispatcher, + sslExceptionHandler, + clusterSettings, + sharedGroupFactory + ); } } diff --git a/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java b/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java index 3c1dedc55e..1c21f0c4a2 100644 --- a/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java +++ b/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java @@ -41,10 +41,16 @@ public class SecurityNonSslHttpServerTransport extends Netty4HttpServerTransport { - - public SecurityNonSslHttpServerTransport(final Settings settings, final NetworkService networkService, final BigArrays bigArrays, - final ThreadPool threadPool, final NamedXContentRegistry namedXContentRegistry, final Dispatcher dispatcher, - ClusterSettings clusterSettings, SharedGroupFactory sharedGroupFactory) { + public SecurityNonSslHttpServerTransport( + final Settings settings, + final NetworkService networkService, + final BigArrays bigArrays, + final ThreadPool threadPool, + final NamedXContentRegistry namedXContentRegistry, + final Dispatcher dispatcher, + ClusterSettings clusterSettings, + SharedGroupFactory sharedGroupFactory + ) { super(settings, networkService, bigArrays, threadPool, namedXContentRegistry, dispatcher, clusterSettings, sharedGroupFactory); } diff --git a/src/main/java/org/opensearch/security/http/XFFResolver.java b/src/main/java/org/opensearch/security/http/XFFResolver.java index c44e98537d..aff5043f61 100644 --- a/src/main/java/org/opensearch/security/http/XFFResolver.java +++ b/src/main/java/org/opensearch/security/http/XFFResolver.java @@ -59,39 +59,48 @@ public TransportAddress resolve(final RestRequest request) throws OpenSearchSecu log.trace("resolve {}", request.getHttpChannel().getRemoteAddress()); } - if(enabled && request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress && request.getHttpChannel() instanceof Netty4HttpChannel) { + if (enabled + && request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress + && request.getHttpChannel() instanceof Netty4HttpChannel) { - final InetSocketAddress isa = new InetSocketAddress(detector.detect(request, threadContext), ((InetSocketAddress)request.getHttpChannel().getRemoteAddress()).getPort()); + final InetSocketAddress isa = new InetSocketAddress( + detector.detect(request, threadContext), + ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getPort() + ); - if(isa.isUnresolved()) { - throw new OpenSearchSecurityException("Cannot resolve address "+isa.getHostString()); + if (isa.isUnresolved()) { + throw new OpenSearchSecurityException("Cannot resolve address " + isa.getHostString()); } - if (isTraceEnabled) { - if(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE) == Boolean.TRUE) { + if (threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE) == Boolean.TRUE) { log.trace("xff resolved {} to {}", request.getHttpChannel().getRemoteAddress(), isa); } else { - log.trace("no xff done for {}",request.getClass()); + log.trace("no xff done for {}", request.getClass()); } } return new TransportAddress(isa); - } else if(request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress){ + } else if (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress) { if (isTraceEnabled) { - log.trace("no xff done (enabled or no netty request) {},{},{},{}",enabled, request.getClass()); + log.trace("no xff done (enabled or no netty request) {},{},{},{}", enabled, request.getClass()); } - return new TransportAddress((InetSocketAddress)request.getHttpChannel().getRemoteAddress()); + return new TransportAddress((InetSocketAddress) request.getHttpChannel().getRemoteAddress()); } else { - throw new OpenSearchSecurityException("Cannot handle this request. Remote address is "+request.getHttpChannel().getRemoteAddress()+" with request class "+request.getClass()); + throw new OpenSearchSecurityException( + "Cannot handle this request. Remote address is " + + request.getHttpChannel().getRemoteAddress() + + " with request class " + + request.getClass() + ); } } @Subscribe public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { enabled = dcm.isXffEnabled(); - if(enabled) { + if (enabled) { detector = new RemoteIpDetector(); detector.setInternalProxies(dcm.getInternalProxies()); detector.setRemoteIpHeader(dcm.getRemoteIpHeader()); diff --git a/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java b/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java index e98f26d85a..ef20374d69 100644 --- a/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java @@ -42,7 +42,7 @@ import org.opensearch.security.http.HTTPProxyAuthenticator; import org.opensearch.security.user.AuthCredentials; -public class HTTPExtendedProxyAuthenticator extends HTTPProxyAuthenticator{ +public class HTTPExtendedProxyAuthenticator extends HTTPProxyAuthenticator { private static final String ATTR_PROXY = "attr.proxy."; private static final String ATTR_PROXY_USERNAME = "attr.proxy.username"; @@ -56,16 +56,16 @@ public HTTPExtendedProxyAuthenticator(Settings settings, final Path configPath) @Override public AuthCredentials extractCredentials(final RestRequest request, ThreadContext context) { - AuthCredentials credentials = super.extractCredentials(request, context); - if(credentials == null) { - return null; - } + AuthCredentials credentials = super.extractCredentials(request, context); + if (credentials == null) { + return null; + } String attrHeaderPrefix = settings.get("attr_header_prefix"); - if(Strings.isNullOrEmpty(attrHeaderPrefix)) { + if (Strings.isNullOrEmpty(attrHeaderPrefix)) { log.debug("attr_header_prefix is null. Skipping additional attribute extraction"); return credentials; - } else if(log.isDebugEnabled()) { + } else if (log.isDebugEnabled()) { log.debug("attrHeaderPrefix {}", attrHeaderPrefix); } @@ -73,10 +73,10 @@ public AuthCredentials extractCredentials(final RestRequest request, ThreadConte attrHeaderPrefix = attrHeaderPrefix.toLowerCase(); for (Entry> entry : request.getHeaders().entrySet()) { String key = entry.getKey().toLowerCase(); - if(key.startsWith(attrHeaderPrefix)) { + if (key.startsWith(attrHeaderPrefix)) { key = ATTR_PROXY + key.substring(attrHeaderPrefix.length()); credentials.addAttribute(key, Joiner.on(",").join(entry.getValue().iterator())); - if(log.isTraceEnabled()) { + if (log.isTraceEnabled()) { log.trace("Found user custom attribute '{}'", key); } } diff --git a/src/main/java/org/opensearch/security/httpclient/HttpClient.java b/src/main/java/org/opensearch/security/httpclient/HttpClient.java index ad507ea47c..ba788a2c13 100644 --- a/src/main/java/org/opensearch/security/httpclient/HttpClient.java +++ b/src/main/java/org/opensearch/security/httpclient/HttpClient.java @@ -117,8 +117,18 @@ public HttpClientBuilder setSupportedCipherSuites(String[] cipherSuites) { } public HttpClient build() throws Exception { - return new HttpClient(trustStore, basicCredentials, keystore, keyPassword, keystoreAlias, verifyHostnames, ssl, - supportedProtocols, supportedCipherSuites, servers); + return new HttpClient( + trustStore, + basicCredentials, + keystore, + keyPassword, + keystoreAlias, + verifyHostnames, + ssl, + supportedProtocols, + supportedCipherSuites, + servers + ); } private static String encodeBasicHeader(final String username, final String password) { @@ -143,10 +153,19 @@ public static HttpClientBuilder builder(final String... servers) { private String[] supportedProtocols; private String[] supportedCipherSuites; - private HttpClient(final KeyStore trustStore, final String basicCredentials, final KeyStore keystore, - final char[] keyPassword, final String keystoreAlias, final boolean verifyHostnames, final boolean ssl, String[] supportedProtocols, String[] supportedCipherSuites, final String... servers) - throws UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException, KeyStoreException, CertificateException, - IOException { + private HttpClient( + final KeyStore trustStore, + final String basicCredentials, + final KeyStore keystore, + final char[] keyPassword, + final String keystoreAlias, + final boolean verifyHostnames, + final boolean ssl, + String[] supportedProtocols, + String[] supportedCipherSuites, + final String... servers + ) throws UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException, KeyStoreException, CertificateException, + IOException { super(); this.trustStore = trustStore; this.basicCredentials = basicCredentials; @@ -159,13 +178,13 @@ private HttpClient(final KeyStore trustStore, final String basicCredentials, fin this.keystoreAlias = keystoreAlias; HttpHost[] hosts = Arrays.stream(servers) - .map(s->s.split(":")) - .map(s->new HttpHost(ssl?"https":"http", s[0], Integer.parseInt(s[1]))) - .collect(Collectors.toList()).toArray(new HttpHost[0]); - + .map(s -> s.split(":")) + .map(s -> new HttpHost(ssl ? "https" : "http", s[0], Integer.parseInt(s[1]))) + .collect(Collectors.toList()) + .toArray(new HttpHost[0]); RestClientBuilder builder = RestClient.builder(hosts); - //builder.setMaxRetryTimeoutMillis(10000); + // builder.setMaxRetryTimeoutMillis(10000); builder.setFailureListener(new RestClient.FailureListener() { @Override @@ -181,7 +200,7 @@ public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpCli try { return asyncClientBuilder(httpClientBuilder); } catch (Exception e) { - log.error("Unable to build http client",e); + log.error("Unable to build http client", e); throw new RuntimeException(e); } } @@ -192,24 +211,25 @@ public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpCli public boolean index(final String content, final String index, final String type, final boolean refresh) { - try { + try { - final IndexRequest ir = new IndexRequest(index); + final IndexRequest ir = new IndexRequest(index); - final IndexResponse response = rclient.index(ir - .setRefreshPolicy(refresh?RefreshPolicy.IMMEDIATE:RefreshPolicy.NONE) - .source(content, XContentType.JSON), RequestOptions.DEFAULT); + final IndexResponse response = rclient.index( + ir.setRefreshPolicy(refresh ? RefreshPolicy.IMMEDIATE : RefreshPolicy.NONE).source(content, XContentType.JSON), + RequestOptions.DEFAULT + ); - return response.getShardInfo().getSuccessful() > 0 && response.getShardInfo().getFailed() == 0; + return response.getShardInfo().getSuccessful() > 0 && response.getShardInfo().getFailed() == 0; - } catch (Exception e) { - log.error(e.toString(),e); - return false; - } + } catch (Exception e) { + log.error(e.toString(), e); + return false; + } } - private final HttpAsyncClientBuilder asyncClientBuilder(HttpAsyncClientBuilder httpClientBuilder) - throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { + private final HttpAsyncClientBuilder asyncClientBuilder(HttpAsyncClientBuilder httpClientBuilder) throws NoSuchAlgorithmException, + KeyStoreException, UnrecoverableKeyException, KeyManagementException { // basic auth // pki auth @@ -231,11 +251,11 @@ private final HttpAsyncClientBuilder asyncClientBuilder(HttpAsyncClientBuilder h @Override public String chooseAlias(Map aliases, SSLParameters sslParameters) { - if(aliases == null || aliases.isEmpty()) { + if (aliases == null || aliases.isEmpty()) { return keystoreAlias; } - if(keystoreAlias == null || keystoreAlias.isEmpty()) { + if (keystoreAlias == null || keystoreAlias.isEmpty()) { return aliases.keySet().iterator().next(); } @@ -248,35 +268,36 @@ public String chooseAlias(Map aliases, SSLParameters final SSLContext sslContext = sslContextBuilder.build(); TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() - .setSslContext(sslContext) - .setTlsVersions(supportedProtocols) - .setCiphers(supportedCipherSuites) - .setHostnameVerifier(hnv) - // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 - .setTlsDetailsFactory(new Factory() { - @Override - public TlsDetails create(final SSLEngine sslEngine) { - return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); - } - }) - .build(); + .setSslContext(sslContext) + .setTlsVersions(supportedProtocols) + .setCiphers(supportedCipherSuites) + .setHostnameVerifier(hnv) + // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 + .setTlsDetailsFactory(new Factory() { + @Override + public TlsDetails create(final SSLEngine sslEngine) { + return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); + } + }) + .build(); - final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() - .setTlsStrategy(tlsStrategy) - .build(); + final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create().setTlsStrategy(tlsStrategy).build(); httpClientBuilder.setConnectionManager(cm); } if (basicCredentials != null) { - httpClientBuilder.setDefaultHeaders(Lists.newArrayList(new BasicHeader(HttpHeaders.AUTHORIZATION, "Basic " + basicCredentials))); + httpClientBuilder.setDefaultHeaders( + Lists.newArrayList(new BasicHeader(HttpHeaders.AUTHORIZATION, "Basic " + basicCredentials)) + ); } // TODO: set a timeout until we have a proper way to deal with back pressure int timeout = 5; RequestConfig config = RequestConfig.custom() - .setConnectTimeout(timeout, TimeUnit.SECONDS) - .setConnectionRequestTimeout(timeout, TimeUnit.SECONDS).build(); + .setConnectTimeout(timeout, TimeUnit.SECONDS) + .setConnectionRequestTimeout(timeout, TimeUnit.SECONDS) + .build(); httpClientBuilder.setDefaultRequestConfig(config); diff --git a/src/main/java/org/opensearch/security/privileges/PitPrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PitPrivilegesEvaluator.java index b146365d57..57c1c18414 100644 --- a/src/main/java/org/opensearch/security/privileges/PitPrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PitPrivilegesEvaluator.java @@ -30,7 +30,6 @@ import org.opensearch.security.securityconf.SecurityRoles; import org.opensearch.security.user.User; - /** * This class evaluates privileges for point in time (Delete and List all) operations. * For aliases - users must have either alias permission or backing index permissions @@ -38,13 +37,18 @@ */ public class PitPrivilegesEvaluator { - public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final ClusterService clusterService, - final User user, final SecurityRoles securityRoles, final String action, - final IndexNameExpressionResolver resolver, - final PrivilegesEvaluatorResponse presponse, - final IndexResolverReplacer irr) { + public PrivilegesEvaluatorResponse evaluate( + final ActionRequest request, + final ClusterService clusterService, + final User user, + final SecurityRoles securityRoles, + final String action, + final IndexNameExpressionResolver resolver, + final PrivilegesEvaluatorResponse presponse, + final IndexResolverReplacer irr + ) { - if(!(request instanceof DeletePitRequest || request instanceof PitSegmentsRequest)) { + if (!(request instanceof DeletePitRequest || request instanceof PitSegmentsRequest)) { return presponse; } List pitIds = new ArrayList<>(); @@ -52,7 +56,7 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final C if (request instanceof DeletePitRequest) { DeletePitRequest deletePitRequest = (DeletePitRequest) request; pitIds = deletePitRequest.getPitIds(); - } else if(request instanceof PitSegmentsRequest) { + } else if (request instanceof PitSegmentsRequest) { PitSegmentsRequest pitSegmentsRequest = (PitSegmentsRequest) request; pitIds = pitSegmentsRequest.getPitIds(); } @@ -60,29 +64,32 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final C if (pitIds.size() == 1 && "_all".equals(pitIds.get(0))) { return presponse; } else { - return handlePitsAccess(pitIds, clusterService, user, securityRoles, - action, resolver, presponse, irr); + return handlePitsAccess(pitIds, clusterService, user, securityRoles, action, resolver, presponse, irr); } } /** * Handle access for delete operation / pit segments operation where PIT IDs are explicitly passed */ - private PrivilegesEvaluatorResponse handlePitsAccess(List pitIds, ClusterService clusterService, - User user, SecurityRoles securityRoles, final String action, - IndexNameExpressionResolver resolver, PrivilegesEvaluatorResponse presponse, - final IndexResolverReplacer irr) { - Map pitToIndicesMap = OpenSearchSecurityPlugin. - GuiceHolder.getPitService().getIndicesForPits(pitIds); + private PrivilegesEvaluatorResponse handlePitsAccess( + List pitIds, + ClusterService clusterService, + User user, + SecurityRoles securityRoles, + final String action, + IndexNameExpressionResolver resolver, + PrivilegesEvaluatorResponse presponse, + final IndexResolverReplacer irr + ) { + Map pitToIndicesMap = OpenSearchSecurityPlugin.GuiceHolder.getPitService().getIndicesForPits(pitIds); Set pitIndices = new HashSet<>(); // add indices across all PITs to a set and evaluate if user has access to all indices - for(String[] indices: pitToIndicesMap.values()) { + for (String[] indices : pitToIndicesMap.values()) { pitIndices.addAll(Arrays.asList(indices)); } - Set allPermittedIndices = getPermittedIndices(pitIndices, clusterService, user, - securityRoles, action, resolver, irr); + Set allPermittedIndices = getPermittedIndices(pitIndices, clusterService, user, securityRoles, action, resolver, irr); // Only if user has access to all PIT's indices, allow operation, otherwise continue evaluation in PrivilegesEvaluator. - if(allPermittedIndices.containsAll(pitIndices)) { + if (allPermittedIndices.containsAll(pitIndices)) { presponse.allowed = true; presponse.markComplete(); } @@ -92,14 +99,18 @@ private PrivilegesEvaluatorResponse handlePitsAccess(List pitIds, Cluste /** * This method returns list of permitted indices for the PIT indices passed */ - private Set getPermittedIndices(Set pitIndices, ClusterService clusterService, - User user, SecurityRoles securityRoles, final String action, - IndexNameExpressionResolver resolver, final IndexResolverReplacer irr) { + private Set getPermittedIndices( + Set pitIndices, + ClusterService clusterService, + User user, + SecurityRoles securityRoles, + final String action, + IndexNameExpressionResolver resolver, + final IndexResolverReplacer irr + ) { String[] indicesArr = new String[pitIndices.size()]; - CreatePitRequest req = new CreatePitRequest(new TimeValue(1, TimeUnit.DAYS), true, - pitIndices.toArray(indicesArr)); + CreatePitRequest req = new CreatePitRequest(new TimeValue(1, TimeUnit.DAYS), true, pitIndices.toArray(indicesArr)); final IndexResolverReplacer.Resolved pitResolved = irr.resolveRequest(req); - return securityRoles.reduce(pitResolved, - user, new String[]{action}, resolver, clusterService); + return securityRoles.reduce(pitResolved, user, new String[] { action }, resolver, clusterService); } } diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 278dc86b7c..b118a62e5d 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -105,7 +105,7 @@ public class PrivilegesEvaluator { private static final WildcardMatcher ACTION_MATCHER = WildcardMatcher.from("indices:data/read/*search*"); private static final Pattern DNFOF_PATTERNS = Pattern.compile( - "indices:(data/read/.*|(admin/(mappings/fields/get.*|shards/search_shards|resolve/index)))" + "indices:(data/read/.*|(admin/(mappings/fields/get.*|shards/search_shards|resolve/index)))" ); private static final IndicesOptions ALLOW_EMPTY = IndicesOptions.fromOptions(true, true, false, false); @@ -135,10 +135,19 @@ public class PrivilegesEvaluator { private DynamicConfigModel dcm; private final NamedXContentRegistry namedXContentRegistry; - public PrivilegesEvaluator(final ClusterService clusterService, final ThreadPool threadPool, - final ConfigurationRepository configurationRepository, final IndexNameExpressionResolver resolver, - AuditLog auditLog, final Settings settings, final PrivilegesInterceptor privilegesInterceptor, final ClusterInfoHolder clusterInfoHolder, - final IndexResolverReplacer irr, boolean dlsFlsEnabled, NamedXContentRegistry namedXContentRegistry) { + public PrivilegesEvaluator( + final ClusterService clusterService, + final ThreadPool threadPool, + final ConfigurationRepository configurationRepository, + final IndexNameExpressionResolver resolver, + AuditLog auditLog, + final Settings settings, + final PrivilegesInterceptor privilegesInterceptor, + final ClusterInfoHolder clusterInfoHolder, + final IndexResolverReplacer irr, + boolean dlsFlsEnabled, + NamedXContentRegistry namedXContentRegistry + ) { super(); this.clusterService = clusterService; @@ -148,9 +157,10 @@ public PrivilegesEvaluator(final ClusterService clusterService, final ThreadPool this.threadContext = threadPool.getThreadContext(); this.privilegesInterceptor = privilegesInterceptor; - - this.checkSnapshotRestoreWritePrivileges = settings.getAsBoolean(ConfigConstants.SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, - ConfigConstants.SECURITY_DEFAULT_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES); + this.checkSnapshotRestoreWritePrivileges = settings.getAsBoolean( + ConfigConstants.SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, + ConfigConstants.SECURITY_DEFAULT_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES + ); this.clusterInfoHolder = clusterInfoHolder; this.irr = irr; @@ -178,9 +188,7 @@ private SecurityRoles getSecurityRoles(Set roles) { return configModel.getSecurityRoles().filter(roles); } - public boolean hasRestAdminPermissions(final User user, - final TransportAddress remoteAddress, - final String permissions) { + public boolean hasRestAdminPermissions(final User user, final TransportAddress remoteAddress, final String permissions) { final Set userRoles = mapRoles(user, remoteAddress); return hasRestAdminPermissions(userRoles, permissions); } @@ -191,7 +199,7 @@ private boolean hasRestAdminPermissions(final Set roles, String permissi } public boolean isInitialized() { - return configModel !=null && configModel.getSecurityRoles() != null && dcm != null; + return configModel != null && configModel.getSecurityRoles() != null && dcm != null; } private void setUserInfoInThreadContext(User user, Set mappedRoles) { @@ -208,14 +216,19 @@ private void setUserInfoInThreadContext(User user, Set mappedRoles) { } } - public PrivilegesEvaluatorResponse evaluate(final User user, String action0, final ActionRequest request, - Task task, final Set injectedRoles) { + public PrivilegesEvaluatorResponse evaluate( + final User user, + String action0, + final ActionRequest request, + Task task, + final Set injectedRoles + ) { if (!isInitialized()) { throw new OpenSearchSecurityException("OpenSearch Security is not initialized."); } - if(action0.startsWith("internal:indices/admin/upgrade")) { + if (action0.startsWith("internal:indices/admin/upgrade")) { action0 = "indices:admin/upgrade"; } @@ -231,10 +244,12 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin final TransportAddress caller = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); Set mappedRoles = (injectedRoles == null) ? mapRoles(user, caller) : injectedRoles; - final String injectedRolesValidationString = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION); - if(injectedRolesValidationString != null) { + final String injectedRolesValidationString = threadContext.getTransient( + ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION + ); + if (injectedRolesValidationString != null) { HashSet injectedRolesValidationSet = new HashSet<>(Arrays.asList(injectedRolesValidationString.split(","))); - if(!mappedRoles.containsAll(injectedRolesValidationSet)) { + if (!mappedRoles.containsAll(injectedRolesValidationSet)) { presponse.allowed = false; presponse.missingSecurityRoles.addAll(injectedRolesValidationSet); log.info("Roles {} are not mapped to the user {}", injectedRolesValidationSet, user); @@ -257,15 +272,23 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin } if (request instanceof BulkRequest && (Strings.isNullOrEmpty(user.getRequestedTenant()))) { - // Shortcut for bulk actions. The details are checked on the lower level of the BulkShardRequests (Action indices:data/write/bulk[s]). - // This shortcut is only possible if the default tenant is selected, as we might need to rewrite the request for non-default tenants. - // No further access check for the default tenant is necessary, as access will be also checked on the TransportShardBulkAction level. + // Shortcut for bulk actions. The details are checked on the lower level of the BulkShardRequests (Action + // indices:data/write/bulk[s]). + // This shortcut is only possible if the default tenant is selected, as we might need to rewrite the request for non-default + // tenants. + // No further access check for the default tenant is necessary, as access will be also checked on the TransportShardBulkAction + // level. if (!securityRoles.impliesClusterPermissionPermission(action0)) { presponse.missingPrivileges.add(action0); presponse.allowed = false; - log.info("No cluster-level perm match for {} [Action [{}]] [RolesChecked {}]. No permissions for {}", user, action0, - securityRoles.getRoleNames(), presponse.missingPrivileges); + log.info( + "No cluster-level perm match for {} [Action [{}]] [RolesChecked {}]. No permissions for {}", + user, + action0, + securityRoles.getRoleNames(), + presponse.missingPrivileges + ); } else { presponse.allowed = true; } @@ -275,7 +298,6 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin final Resolved requestedResolved = irr.resolveRequest(request); presponse.resolved = requestedResolved; - if (isDebugEnabled) { log.debug("RequestedResolved : {}", requestedResolved); } @@ -296,8 +318,7 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin } // check access for point in time requests - if(pitPrivilegesEvaluator.evaluate(request, clusterService, user, securityRoles, - action0, resolver, presponse, irr).isComplete()) { + if (pitPrivilegesEvaluator.evaluate(request, clusterService, user, securityRoles, action0, resolver, presponse, irr).isComplete()) { return presponse; } @@ -308,27 +329,44 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin log.trace("dnfof enabled? {}", dnfofEnabled); } - presponse.evaluatedDlsFlsConfig = getSecurityRoles(mappedRoles).getDlsFls(user, dfmEmptyOverwritesAll, resolver, clusterService, namedXContentRegistry); - + presponse.evaluatedDlsFlsConfig = getSecurityRoles(mappedRoles).getDlsFls( + user, + dfmEmptyOverwritesAll, + resolver, + clusterService, + namedXContentRegistry + ); if (isClusterPerm(action0)) { - if(!securityRoles.impliesClusterPermissionPermission(action0)) { + if (!securityRoles.impliesClusterPermissionPermission(action0)) { presponse.missingPrivileges.add(action0); presponse.allowed = false; - log.info("No cluster-level perm match for {} {} [Action [{}]] [RolesChecked {}]. No permissions for {}", user, requestedResolved, action0, - securityRoles.getRoleNames(), presponse.missingPrivileges); + log.info( + "No cluster-level perm match for {} {} [Action [{}]] [RolesChecked {}]. No permissions for {}", + user, + requestedResolved, + action0, + securityRoles.getRoleNames(), + presponse.missingPrivileges + ); return presponse; } else { - if(request instanceof RestoreSnapshotRequest && checkSnapshotRestoreWritePrivileges) { + if (request instanceof RestoreSnapshotRequest && checkSnapshotRestoreWritePrivileges) { if (isDebugEnabled) { log.debug("Normally allowed but we need to apply some extra checks for a restore request."); } } else { - if(privilegesInterceptor.getClass() != PrivilegesInterceptor.class) { + if (privilegesInterceptor.getClass() != PrivilegesInterceptor.class) { - final PrivilegesInterceptor.ReplaceResult replaceResult = privilegesInterceptor.replaceDashboardsIndex(request, action0, user, dcm, requestedResolved, - mapTenants(user, mappedRoles)); + final PrivilegesInterceptor.ReplaceResult replaceResult = privilegesInterceptor.replaceDashboardsIndex( + request, + action0, + user, + dcm, + requestedResolved, + mapTenants(user, mappedRoles) + ); if (isDebugEnabled) { log.debug("Result from privileges interceptor for cluster perm: {}", replaceResult); @@ -345,26 +383,28 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin } } - if (dnfofEnabled - && (action0.startsWith("indices:data/read/")) - && !requestedResolved.getAllIndices().isEmpty() - ) { + if (dnfofEnabled && (action0.startsWith("indices:data/read/")) && !requestedResolved.getAllIndices().isEmpty()) { - if(requestedResolved.getAllIndices().isEmpty()) { + if (requestedResolved.getAllIndices().isEmpty()) { presponse.missingPrivileges.clear(); presponse.allowed = true; return presponse; } + Set reduced = securityRoles.reduce( + requestedResolved, + user, + new String[] { action0 }, + resolver, + clusterService + ); - Set reduced = securityRoles.reduce(requestedResolved, user, new String[]{action0}, resolver, clusterService); - - if(reduced.isEmpty()) { + if (reduced.isEmpty()) { presponse.allowed = false; return presponse; } - if(irr.replace(request, true, reduced.toArray(new String[0]))) { + if (irr.replace(request, true, reduced.toArray(new String[0]))) { presponse.missingPrivileges.clear(); presponse.allowed = true; return presponse; @@ -386,7 +426,8 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin } // term aggregations - if (termsAggregationEvaluator.evaluate(requestedResolved, request, clusterService, user, securityRoles, resolver, presponse) .isComplete()) { + if (termsAggregationEvaluator.evaluate(requestedResolved, request, clusterService, user, securityRoles, resolver, presponse) + .isComplete()) { return presponse; } @@ -405,11 +446,18 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin log.debug("Security roles: {}", securityRoles.getRoleNames()); } - //TODO exclude Security index + // TODO exclude Security index - if(privilegesInterceptor.getClass() != PrivilegesInterceptor.class) { + if (privilegesInterceptor.getClass() != PrivilegesInterceptor.class) { - final PrivilegesInterceptor.ReplaceResult replaceResult = privilegesInterceptor.replaceDashboardsIndex(request, action0, user, dcm, requestedResolved, mapTenants(user, mappedRoles)); + final PrivilegesInterceptor.ReplaceResult replaceResult = privilegesInterceptor.replaceDashboardsIndex( + request, + action0, + user, + dcm, + requestedResolved, + mapTenants(user, mappedRoles) + ); if (isDebugEnabled) { log.debug("Result from privileges interceptor: {}", replaceResult); @@ -428,7 +476,7 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin if (dnfofEnabled && DNFOF_PATTERNS.matcher(action0).matches()) { - if(requestedResolved.getAllIndices().isEmpty()) { + if (requestedResolved.getAllIndices().isEmpty()) { presponse.missingPrivileges.clear(); presponse.allowed = true; return presponse; @@ -436,18 +484,18 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin Set reduced = securityRoles.reduce(requestedResolved, user, allIndexPermsRequiredA, resolver, clusterService); - if(reduced.isEmpty()) { - if(dcm.isDnfofForEmptyResultsEnabled() && request instanceof IndicesRequest.Replaceable) { + if (reduced.isEmpty()) { + if (dcm.isDnfofForEmptyResultsEnabled() && request instanceof IndicesRequest.Replaceable) { ((IndicesRequest.Replaceable) request).indices(new String[0]); presponse.missingPrivileges.clear(); presponse.allowed = true; - if(request instanceof SearchRequest) { + if (request instanceof SearchRequest) { ((SearchRequest) request).indicesOptions(ALLOW_EMPTY); - } else if(request instanceof ClusterSearchShardsRequest) { + } else if (request instanceof ClusterSearchShardsRequest) { ((ClusterSearchShardsRequest) request).indicesOptions(ALLOW_EMPTY); - } else if(request instanceof GetFieldMappingsRequest) { + } else if (request instanceof GetFieldMappingsRequest) { ((GetFieldMappingsRequest) request).indicesOptions(ALLOW_EMPTY); } @@ -457,16 +505,14 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin return presponse; } - - if(irr.replace(request, true, reduced.toArray(new String[0]))) { + if (irr.replace(request, true, reduced.toArray(new String[0]))) { presponse.missingPrivileges.clear(); presponse.allowed = true; return presponse; } } - - //not bulk, mget, etc request here + // not bulk, mget, etc request here boolean permGiven = false; if (isDebugEnabled) { @@ -475,19 +521,25 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin if (dcm.isMultiRolespanEnabled()) { permGiven = securityRoles.impliesTypePermGlobal(requestedResolved, user, allIndexPermsRequiredA, resolver, clusterService); - } else { + } else { permGiven = securityRoles.get(requestedResolved, user, allIndexPermsRequiredA, resolver, clusterService); } - if (!permGiven) { - log.info("No {}-level perm match for {} {} [Action [{}]] [RolesChecked {}]", "index" , user, requestedResolved, action0, - securityRoles.getRoleNames()); + if (!permGiven) { + log.info( + "No {}-level perm match for {} {} [Action [{}]] [RolesChecked {}]", + "index", + user, + requestedResolved, + action0, + securityRoles.getRoleNames() + ); log.info("No permissions for {}", presponse.missingPrivileges); } else { - if(checkFilteredAliases(requestedResolved, action0, isDebugEnabled)) { - presponse.allowed=false; + if (checkFilteredAliases(requestedResolved, action0, isDebugEnabled)) { + presponse.allowed = false; return presponse; } @@ -505,26 +557,21 @@ public Set mapRoles(final User user, final TransportAddress caller) { return this.configModel.mapSecurityRoles(user, caller); } - public Map mapTenants(final User user, Set roles) { return this.configModel.mapTenants(user, roles); } - - public Set getAllConfiguredTenantNames() { return configModel.getAllConfiguredTenantNames(); } public boolean multitenancyEnabled() { - return privilegesInterceptor.getClass() != PrivilegesInterceptor.class - && dcm.isDashboardsMultitenancyEnabled(); + return privilegesInterceptor.getClass() != PrivilegesInterceptor.class && dcm.isDashboardsMultitenancyEnabled(); } public boolean privateTenantEnabled() { - return privilegesInterceptor.getClass() != PrivilegesInterceptor.class - && dcm.isDashboardsPrivateTenantEnabled(); + return privilegesInterceptor.getClass() != PrivilegesInterceptor.class && dcm.isDashboardsPrivateTenantEnabled(); } public String dashboardsDefaultTenant() { @@ -532,8 +579,7 @@ public String dashboardsDefaultTenant() { } public boolean notFailOnForbiddenEnabled() { - return privilegesInterceptor.getClass() != PrivilegesInterceptor.class - && dcm.isDnfofEnabled(); + return privilegesInterceptor.getClass() != PrivilegesInterceptor.class && dcm.isDnfofEnabled(); } public String dashboardsIndex() { @@ -549,10 +595,10 @@ public String dashboardsOpenSearchRole() { } private Set evaluateAdditionalIndexPermissions(final ActionRequest request, final String originalAction) { - //--- check inner bulk requests + // --- check inner bulk requests final Set additionalPermissionsRequired = new HashSet<>(); - if(!isClusterPerm(originalAction)) { + if (!isClusterPerm(originalAction)) { additionalPermissionsRequired.add(originalAction); } @@ -564,18 +610,18 @@ private Set evaluateAdditionalIndexPermissions(final ActionRequest reque BulkShardRequest bsr = (BulkShardRequest) request; for (BulkItemRequest bir : bsr.items()) { switch (bir.request().opType()) { - case CREATE: - additionalPermissionsRequired.add(IndexAction.NAME); - break; - case INDEX: - additionalPermissionsRequired.add(IndexAction.NAME); - break; - case DELETE: - additionalPermissionsRequired.add(DeleteAction.NAME); - break; - case UPDATE: - additionalPermissionsRequired.add(UpdateAction.NAME); - break; + case CREATE: + additionalPermissionsRequired.add(IndexAction.NAME); + break; + case INDEX: + additionalPermissionsRequired.add(IndexAction.NAME); + break; + case DELETE: + additionalPermissionsRequired.add(DeleteAction.NAME); + break; + case UPDATE: + additionalPermissionsRequired.add(UpdateAction.NAME); + break; } } } @@ -584,11 +630,11 @@ private Set evaluateAdditionalIndexPermissions(final ActionRequest reque IndicesAliasesRequest bsr = (IndicesAliasesRequest) request; for (AliasActions bir : bsr.getAliasActions()) { switch (bir.actionType()) { - case REMOVE_INDEX: - additionalPermissionsRequired.add(DeleteIndexAction.NAME); - break; - default: - break; + case REMOVE_INDEX: + additionalPermissionsRequired.add(DeleteIndexAction.NAME); + break; + default: + break; } } } @@ -616,9 +662,9 @@ private Set evaluateAdditionalIndexPermissions(final ActionRequest reque } public static boolean isClusterPerm(String action0) { - return ( action0.startsWith("cluster:") - || action0.startsWith("indices:admin/template/") - || action0.startsWith("indices:admin/index_template/") + return (action0.startsWith("cluster:") + || action0.startsWith("indices:admin/template/") + || action0.startsWith("indices:admin/index_template/") || action0.startsWith(SearchScrollAction.NAME) || (action0.equals(BulkAction.NAME)) || (action0.equals(MultiGetAction.NAME)) @@ -626,7 +672,7 @@ public static boolean isClusterPerm(String action0) { || (action0.equals(MultiTermVectorsAction.NAME)) || (action0.equals(ReindexAction.NAME)) - ) ; + ); } @SuppressWarnings("unchecked") @@ -667,20 +713,20 @@ public Iterator iterator() { indexMetaDataCollection = indexMetaDataSet; } - //check filtered aliases + // check filtered aliases for (IndexMetadata indexMetaData : indexMetaDataCollection) { final List filteredAliases = new ArrayList(); final Map aliases = indexMetaData.getAliases(); - if(aliases != null && aliases.size() > 0) { + if (aliases != null && aliases.size() > 0) { if (isDebugEnabled) { log.debug("Aliases for {}: {}", indexMetaData.getIndex().getName(), aliases); } final Iterator it = aliases.keySet().iterator(); - while(it.hasNext()) { + while (it.hasNext()) { final String alias = it.next(); final AliasMetadata aliasMetadata = aliases.get(alias); @@ -697,18 +743,21 @@ public Iterator iterator() { } } - if(filteredAliases.size() > 1 && ACTION_MATCHER.test(action)) { - //TODO add queries as dls queries (works only if dls module is installed) - log.error("More than one ({}) filtered alias found for same index ({}). This is currently not supported. Aliases: {}", - filteredAliases.size(), indexMetaData.getIndex().getName(), toString(filteredAliases)); + if (filteredAliases.size() > 1 && ACTION_MATCHER.test(action)) { + // TODO add queries as dls queries (works only if dls module is installed) + log.error( + "More than one ({}) filtered alias found for same index ({}). This is currently not supported. Aliases: {}", + filteredAliases.size(), + indexMetaData.getIndex().getName(), + toString(filteredAliases) + ); return true; } - } //end-for + } // end-for return false; } - private boolean checkDocAllowListHeader(User user, String action, ActionRequest request) { String docAllowListHeader = threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DOC_ALLOWLIST_HEADER); @@ -741,14 +790,14 @@ private boolean checkDocAllowListHeader(User user, String action, ActionRequest } private List toString(List aliases) { - if(aliases == null || aliases.size() == 0) { + if (aliases == null || aliases.size() == 0) { return Collections.emptyList(); } final List ret = new ArrayList<>(aliases.size()); - for(final AliasMetadata amd: aliases) { - if(amd != null) { + for (final AliasMetadata amd : aliases) { + if (amd != null) { ret.add(amd.alias()); } } diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java index 31ce7095d2..eb082a4a9f 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java @@ -44,19 +44,24 @@ public class PrivilegesEvaluatorResponse { CreateIndexRequestBuilder createIndexRequestBuilder; public Resolved getResolved() { - return resolved; - } + return resolved; + } public boolean isAllowed() { return allowed; } + public Set getMissingPrivileges() { return new HashSet(missingPrivileges); } - public Set getMissingSecurityRoles() {return new HashSet<>(missingSecurityRoles); } + public Set getMissingSecurityRoles() { + return new HashSet<>(missingSecurityRoles); + } - public Set getResolvedSecurityRoles() {return new HashSet<>(resolvedSecurityRoles); } + public Set getResolvedSecurityRoles() { + return new HashSet<>(resolvedSecurityRoles); + } public EvaluatedDlsFlsConfig getEvaluatedDlsFlsConfig() { return evaluatedDlsFlsConfig; @@ -86,8 +91,13 @@ public boolean isPending() { @Override public String toString() { - return "PrivEvalResponse [allowed=" + allowed + ", missingPrivileges=" + missingPrivileges + ", evaluatedDlsFlsConfig=" - + evaluatedDlsFlsConfig + "]"; + return "PrivEvalResponse [allowed=" + + allowed + + ", missingPrivileges=" + + missingPrivileges + + ", evaluatedDlsFlsConfig=" + + evaluatedDlsFlsConfig + + "]"; } public static enum PrivilegesEvaluatorResponseState { diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesInterceptor.java b/src/main/java/org/opensearch/security/privileges/PrivilegesInterceptor.java index dd569b05fb..f177596573 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesInterceptor.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesInterceptor.java @@ -56,6 +56,7 @@ private ReplaceResult(boolean continueEvaluation, boolean accessDenied, CreateIn public static final ReplaceResult CONTINUE_EVALUATION_REPLACE_RESULT = new ReplaceResult(true, false, null); public static final ReplaceResult ACCESS_DENIED_REPLACE_RESULT = new ReplaceResult(false, true, null); public static final ReplaceResult ACCESS_GRANTED_REPLACE_RESULT = new ReplaceResult(false, false, null); + protected static ReplaceResult newAccessGrantedReplaceResult(CreateIndexRequestBuilder createIndexRequestBuilder) { return new ReplaceResult(false, false, createIndexRequestBuilder); } @@ -65,16 +66,26 @@ protected static ReplaceResult newAccessGrantedReplaceResult(CreateIndexRequestB protected final Client client; protected final ThreadPool threadPool; - public PrivilegesInterceptor(final IndexNameExpressionResolver resolver, final ClusterService clusterService, - final Client client, ThreadPool threadPool) { + public PrivilegesInterceptor( + final IndexNameExpressionResolver resolver, + final ClusterService clusterService, + final Client client, + ThreadPool threadPool + ) { this.resolver = resolver; this.clusterService = clusterService; this.client = client; this.threadPool = threadPool; } - public ReplaceResult replaceDashboardsIndex(final ActionRequest request, final String action, final User user, final DynamicConfigModel config, - final Resolved requestedResolved, final Map tenants) { + public ReplaceResult replaceDashboardsIndex( + final ActionRequest request, + final String action, + final User user, + final DynamicConfigModel config, + final Resolved requestedResolved, + final Map tenants + ) { throw new RuntimeException("not implemented"); } diff --git a/src/main/java/org/opensearch/security/privileges/ProtectedIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/ProtectedIndexAccessEvaluator.java index f00f8be73d..e4fd404daa 100644 --- a/src/main/java/org/opensearch/security/privileges/ProtectedIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/ProtectedIndexAccessEvaluator.java @@ -38,11 +38,20 @@ public class ProtectedIndexAccessEvaluator { private final Boolean protectedIndexEnabled; private final WildcardMatcher deniedActionMatcher; - public ProtectedIndexAccessEvaluator(final Settings settings, AuditLog auditLog) { - this.indexMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_DEFAULT)); - this.allowedRolesMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_DEFAULT)); - this.protectedIndexEnabled = settings.getAsBoolean(ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_DEFAULT); + this.indexMatcher = WildcardMatcher.from( + settings.getAsList(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_DEFAULT) + ); + this.allowedRolesMatcher = WildcardMatcher.from( + settings.getAsList( + ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, + ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_DEFAULT + ) + ); + this.protectedIndexEnabled = settings.getAsBoolean( + ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, + ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_DEFAULT + ); this.auditLog = auditLog; final List indexDeniedActionPatterns = new ArrayList(); @@ -58,15 +67,21 @@ public ProtectedIndexAccessEvaluator(final Settings settings, AuditLog auditLog) this.deniedActionMatcher = WildcardMatcher.from(indexDeniedActionPatterns); } - public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final Task task, final String action, final IndexResolverReplacer.Resolved requestedResolved, - final PrivilegesEvaluatorResponse presponse, final SecurityRoles securityRoles) { + public PrivilegesEvaluatorResponse evaluate( + final ActionRequest request, + final Task task, + final String action, + final IndexResolverReplacer.Resolved requestedResolved, + final PrivilegesEvaluatorResponse presponse, + final SecurityRoles securityRoles + ) { if (!protectedIndexEnabled) { return presponse; } if (!requestedResolved.isLocalAll() - && indexMatcher.matchAny(requestedResolved.getAllIndices()) - && deniedActionMatcher.test(action) - && !allowedRolesMatcher.matchAny(securityRoles.getRoleNames())) { + && indexMatcher.matchAny(requestedResolved.getAllIndices()) + && deniedActionMatcher.test(action) + && !allowedRolesMatcher.matchAny(securityRoles.getRoleNames())) { auditLog.logMissingPrivileges(action, request, task); log.warn("{} for '{}' index/indices is not allowed for a regular user", action, indexMatcher); presponse.allowed = false; @@ -74,26 +89,25 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T } if (requestedResolved.isLocalAll() - && deniedActionMatcher.test(action) - && !allowedRolesMatcher.matchAny(securityRoles.getRoleNames())) { + && deniedActionMatcher.test(action) + && !allowedRolesMatcher.matchAny(securityRoles.getRoleNames())) { auditLog.logMissingPrivileges(action, request, task); log.warn("{} for '_all' indices is not allowed for a regular user", action); presponse.allowed = false; return presponse.markComplete(); } - if((requestedResolved.isLocalAll() - || indexMatcher.matchAny(requestedResolved.getAllIndices())) - && !allowedRolesMatcher.matchAny(securityRoles.getRoleNames())) { + if ((requestedResolved.isLocalAll() || indexMatcher.matchAny(requestedResolved.getAllIndices())) + && !allowedRolesMatcher.matchAny(securityRoles.getRoleNames())) { final boolean isDebugEnabled = log.isDebugEnabled(); - if(request instanceof SearchRequest) { - ((SearchRequest)request).requestCache(Boolean.FALSE); + if (request instanceof SearchRequest) { + ((SearchRequest) request).requestCache(Boolean.FALSE); if (isDebugEnabled) { log.debug("Disable search request cache for this request"); } } - if(request instanceof RealtimeRequest) { + if (request instanceof RealtimeRequest) { ((RealtimeRequest) request).realtime(Boolean.FALSE); if (isDebugEnabled) { log.debug("Disable realtime for this request"); diff --git a/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java index a74ea17ccd..94b0478759 100644 --- a/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java @@ -61,14 +61,25 @@ public class SecurityIndexAccessEvaluator { private final boolean systemIndexEnabled; public SecurityIndexAccessEvaluator(final Settings settings, AuditLog auditLog, IndexResolverReplacer irr) { - this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + this.securityIndex = settings.get( + ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + ); this.auditLog = auditLog; this.irr = irr; this.filterSecurityIndex = settings.getAsBoolean(ConfigConstants.SECURITY_FILTER_SECURITYINDEX_FROM_ALL_REQUESTS, false); - this.systemIndexMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, ConfigConstants.SECURITY_SYSTEM_INDICES_DEFAULT)); - this.systemIndexEnabled = settings.getAsBoolean(ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_DEFAULT); - - final boolean restoreSecurityIndexEnabled = settings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED, false); + this.systemIndexMatcher = WildcardMatcher.from( + settings.getAsList(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, ConfigConstants.SECURITY_SYSTEM_INDICES_DEFAULT) + ); + this.systemIndexEnabled = settings.getAsBoolean( + ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, + ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_DEFAULT + ); + + final boolean restoreSecurityIndexEnabled = settings.getAsBoolean( + ConfigConstants.SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED, + false + ); final List securityIndexDeniedActionPatternsList = new ArrayList(); securityIndexDeniedActionPatternsList.add("indices:data/write*"); @@ -84,31 +95,44 @@ public SecurityIndexAccessEvaluator(final Settings settings, AuditLog auditLog, securityIndexDeniedActionPatternsListNoSnapshot.add("indices:admin/close*"); securityIndexDeniedActionPatternsListNoSnapshot.add("cluster:admin/snapshot/restore*"); - securityDeniedActionMatcher = WildcardMatcher.from(restoreSecurityIndexEnabled ? securityIndexDeniedActionPatternsList : securityIndexDeniedActionPatternsListNoSnapshot); + securityDeniedActionMatcher = WildcardMatcher.from( + restoreSecurityIndexEnabled ? securityIndexDeniedActionPatternsList : securityIndexDeniedActionPatternsListNoSnapshot + ); } - public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final Task task, final String action, final Resolved requestedResolved, - final PrivilegesEvaluatorResponse presponse) { + public PrivilegesEvaluatorResponse evaluate( + final ActionRequest request, + final Task task, + final String action, + final Resolved requestedResolved, + final PrivilegesEvaluatorResponse presponse + ) { final boolean isDebugEnabled = log.isDebugEnabled(); if (securityDeniedActionMatcher.test(action)) { - if(requestedResolved.isLocalAll()) { - if(filterSecurityIndex) { - irr.replace(request, false, "*","-"+ securityIndex); + if (requestedResolved.isLocalAll()) { + if (filterSecurityIndex) { + irr.replace(request, false, "*", "-" + securityIndex); if (isDebugEnabled) { - log.debug("Filtered '{}'from {}, resulting list with *,-{} is {}", securityIndex, requestedResolved, securityIndex, irr.resolveRequest(request)); + log.debug( + "Filtered '{}'from {}, resulting list with *,-{} is {}", + securityIndex, + requestedResolved, + securityIndex, + irr.resolveRequest(request) + ); } return presponse; } else { auditLog.logSecurityIndexAttempt(request, action, task); - log.warn( "{} for '_all' indices is not allowed for a regular user", action); + log.warn("{} for '_all' indices is not allowed for a regular user", action); presponse.allowed = false; return presponse.markComplete(); } } else if (matchAnySystemIndices(requestedResolved)) { - if(filterSecurityIndex) { + if (filterSecurityIndex) { Set allWithoutSecurity = new HashSet<>(requestedResolved.getAllIndices()); allWithoutSecurity.remove(securityIndex); - if(allWithoutSecurity.isEmpty()) { + if (allWithoutSecurity.isEmpty()) { if (isDebugEnabled) { log.debug("Filtered '{}' but resulting list is empty", securityIndex); } @@ -130,17 +154,18 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T } } - if(requestedResolved.isLocalAll() || requestedResolved.getAllIndices().contains(securityIndex) - || matchAnySystemIndices(requestedResolved)) { + if (requestedResolved.isLocalAll() + || requestedResolved.getAllIndices().contains(securityIndex) + || matchAnySystemIndices(requestedResolved)) { - if(request instanceof SearchRequest) { - ((SearchRequest)request).requestCache(Boolean.FALSE); + if (request instanceof SearchRequest) { + ((SearchRequest) request).requestCache(Boolean.FALSE); if (isDebugEnabled) { log.debug("Disable search request cache for this request"); } } - if(request instanceof RealtimeRequest) { + if (request instanceof RealtimeRequest) { ((RealtimeRequest) request).realtime(Boolean.FALSE); if (isDebugEnabled) { log.debug("Disable realtime for this request"); @@ -150,12 +175,15 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T return presponse; } - private boolean matchAnySystemIndices(final Resolved requestedResolved){ + private boolean matchAnySystemIndices(final Resolved requestedResolved) { return !getProtectedIndexes(requestedResolved).isEmpty(); } private List getProtectedIndexes(final Resolved requestedResolved) { - final List protectedIndexes = requestedResolved.getAllIndices().stream().filter(securityIndex::equals).collect(Collectors.toList()); + final List protectedIndexes = requestedResolved.getAllIndices() + .stream() + .filter(securityIndex::equals) + .collect(Collectors.toList()); if (systemIndexEnabled) { protectedIndexes.addAll(systemIndexMatcher.getMatchAny(requestedResolved.getAllIndices(), Collectors.toList())); } diff --git a/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java b/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java index c536ae2d2e..23612e1a52 100644 --- a/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java @@ -49,16 +49,26 @@ public class SnapshotRestoreEvaluator { private final boolean restoreSecurityIndexEnabled; public SnapshotRestoreEvaluator(final Settings settings, AuditLog auditLog) { - this.enableSnapshotRestorePrivilege = settings.getAsBoolean(ConfigConstants.SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, - ConfigConstants.SECURITY_DEFAULT_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE); + this.enableSnapshotRestorePrivilege = settings.getAsBoolean( + ConfigConstants.SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, + ConfigConstants.SECURITY_DEFAULT_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE + ); this.restoreSecurityIndexEnabled = settings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED, false); - this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + this.securityIndex = settings.get( + ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + ); this.auditLog = auditLog; } - public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final Task task, final String action, final ClusterInfoHolder clusterInfoHolder, - final PrivilegesEvaluatorResponse presponse) { + public PrivilegesEvaluatorResponse evaluate( + final ActionRequest request, + final Task task, + final String action, + final ClusterInfoHolder clusterInfoHolder, + final PrivilegesEvaluatorResponse presponse + ) { if (!(request instanceof RestoreSnapshotRequest)) { return presponse; @@ -78,7 +88,6 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T return presponse; } - if (clusterInfoHolder.isLocalNodeElectedClusterManager() == Boolean.FALSE) { presponse.allowed = true; return presponse.markComplete(); diff --git a/src/main/java/org/opensearch/security/privileges/TermsAggregationEvaluator.java b/src/main/java/org/opensearch/security/privileges/TermsAggregationEvaluator.java index 53709458fd..d06a45726a 100644 --- a/src/main/java/org/opensearch/security/privileges/TermsAggregationEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/TermsAggregationEvaluator.java @@ -48,55 +48,65 @@ public class TermsAggregationEvaluator { protected final Logger log = LogManager.getLogger(this.getClass()); - private static final String[] READ_ACTIONS = new String[]{ - "indices:data/read/msearch", - "indices:data/read/mget", - "indices:data/read/get", - "indices:data/read/search", - "indices:data/read/field_caps*" - //"indices:admin/mappings/fields/get*" - }; + private static final String[] READ_ACTIONS = new String[] { + "indices:data/read/msearch", + "indices:data/read/mget", + "indices:data/read/get", + "indices:data/read/search", + "indices:data/read/field_caps*" + // "indices:admin/mappings/fields/get*" + }; private static final QueryBuilder NONE_QUERY = new MatchNoneQueryBuilder(); - public TermsAggregationEvaluator() { - } - - public PrivilegesEvaluatorResponse evaluate(final Resolved resolved, final ActionRequest request, ClusterService clusterService, User user, SecurityRoles securityRoles, IndexNameExpressionResolver resolver, PrivilegesEvaluatorResponse presponse) { + public TermsAggregationEvaluator() {} + + public PrivilegesEvaluatorResponse evaluate( + final Resolved resolved, + final ActionRequest request, + ClusterService clusterService, + User user, + SecurityRoles securityRoles, + IndexNameExpressionResolver resolver, + PrivilegesEvaluatorResponse presponse + ) { try { - if(request instanceof SearchRequest) { + if (request instanceof SearchRequest) { SearchRequest sr = (SearchRequest) request; - if( sr.source() != null - && sr.source().query() == null - && sr.source().aggregations() != null - && sr.source().aggregations().getAggregatorFactories() != null - && sr.source().aggregations().getAggregatorFactories().size() == 1 - && sr.source().size() == 0) { - AggregationBuilder ab = sr.source().aggregations().getAggregatorFactories().iterator().next(); - if( ab instanceof TermsAggregationBuilder - && "terms".equals(ab.getType()) - && "indices".equals(ab.getName())) { - if("_index".equals(((TermsAggregationBuilder) ab).field()) - && ab.getPipelineAggregations().isEmpty() - && ab.getSubAggregations().isEmpty()) { - - - final Set allPermittedIndices = securityRoles.getAllPermittedIndicesForDashboards(resolved, user, READ_ACTIONS, resolver, clusterService); - if(allPermittedIndices == null || allPermittedIndices.isEmpty()) { - sr.source().query(NONE_QUERY); - } else { - sr.source().query(new TermsQueryBuilder("_index", allPermittedIndices)); - } - - presponse.allowed = true; - return presponse.markComplete(); - } - } + if (sr.source() != null + && sr.source().query() == null + && sr.source().aggregations() != null + && sr.source().aggregations().getAggregatorFactories() != null + && sr.source().aggregations().getAggregatorFactories().size() == 1 + && sr.source().size() == 0) { + AggregationBuilder ab = sr.source().aggregations().getAggregatorFactories().iterator().next(); + if (ab instanceof TermsAggregationBuilder && "terms".equals(ab.getType()) && "indices".equals(ab.getName())) { + if ("_index".equals(((TermsAggregationBuilder) ab).field()) + && ab.getPipelineAggregations().isEmpty() + && ab.getSubAggregations().isEmpty()) { + + final Set allPermittedIndices = securityRoles.getAllPermittedIndicesForDashboards( + resolved, + user, + READ_ACTIONS, + resolver, + clusterService + ); + if (allPermittedIndices == null || allPermittedIndices.isEmpty()) { + sr.source().query(NONE_QUERY); + } else { + sr.source().query(new TermsQueryBuilder("_index", allPermittedIndices)); + } + + presponse.allowed = true; + return presponse.markComplete(); + } + } } } } catch (Exception e) { - log.warn("Unable to evaluate terms aggregation",e); + log.warn("Unable to evaluate terms aggregation", e); return presponse; } diff --git a/src/main/java/org/opensearch/security/queries/QueryBuilderTraverser.java b/src/main/java/org/opensearch/security/queries/QueryBuilderTraverser.java index 7bde8523a5..61cc969fdb 100644 --- a/src/main/java/org/opensearch/security/queries/QueryBuilderTraverser.java +++ b/src/main/java/org/opensearch/security/queries/QueryBuilderTraverser.java @@ -65,7 +65,11 @@ public static Set findAll(QueryBuilder queryBuilder, Predicate patterns = requestedPatterns==null?null:Arrays.asList(requestedPatterns); + final List patterns = requestedPatterns == null ? null : Arrays.asList(requestedPatterns); - if(IndexNameExpressionResolver.isAllIndices(patterns)) { + if (IndexNameExpressionResolver.isAllIndices(patterns)) { return true; } - if(patterns.size() == 1 && patterns.contains("*")) { + if (patterns.size() == 1 && patterns.contains("*")) { return true; } - if(new HashSet(patterns).equals(NULL_SET)) { + if (new HashSet(patterns).equals(NULL_SET)) { return true; } @@ -136,15 +136,15 @@ private static final boolean isLocalAll(String... requestedPatterns) { } private static final boolean isLocalAll(Collection patterns) { - if(IndexNameExpressionResolver.isAllIndices(patterns)) { + if (IndexNameExpressionResolver.isAllIndices(patterns)) { return true; } - if(patterns.contains("_all")) { + if (patterns.contains("_all")) { return true; } - if(new HashSet(patterns).equals(NULL_SET)) { + if (new HashSet(patterns).equals(NULL_SET)) { return true; } @@ -170,10 +170,15 @@ private class ResolvedIndicesProvider implements IndicesProvider { name = request.getClass().getSimpleName(); } - private void resolveIndexPatterns(final String name, final IndicesOptions indicesOptions, final boolean enableCrossClusterResolution, final String[] original) { + private void resolveIndexPatterns( + final String name, + final IndicesOptions indicesOptions, + final boolean enableCrossClusterResolution, + final String[] original + ) { final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { - log.trace("resolve requestedPatterns: "+ Arrays.toString(original)); + log.trace("resolve requestedPatterns: " + Arrays.toString(original)); } if (isAllWithNoRemote(original)) { @@ -189,14 +194,16 @@ private void resolveIndexPatterns(final String name, final IndicesOptions indice final RemoteClusterService remoteClusterService = OpenSearchSecurityPlugin.GuiceHolder.getRemoteClusterService(); - if(remoteClusterService.isCrossClusterSearchEnabled() && enableCrossClusterResolution) { + if (remoteClusterService.isCrossClusterSearchEnabled() && enableCrossClusterResolution) { remoteIndices = new HashSet<>(); final Map remoteClusterIndices = OpenSearchSecurityPlugin.GuiceHolder.getRemoteClusterService() - .groupIndices(indicesOptions, original, idx -> resolver.hasIndexAbstraction(idx, clusterService.state())); - final Set remoteClusters = remoteClusterIndices.keySet().stream() - .filter(k->!RemoteClusterService.LOCAL_CLUSTER_GROUP_KEY.equals(k)).collect(Collectors.toSet()); - for(String remoteCluster : remoteClusters) { - for(String remoteIndex : remoteClusterIndices.get(remoteCluster).indices()) { + .groupIndices(indicesOptions, original, idx -> resolver.hasIndexAbstraction(idx, clusterService.state())); + final Set remoteClusters = remoteClusterIndices.keySet() + .stream() + .filter(k -> !RemoteClusterService.LOCAL_CLUSTER_GROUP_KEY.equals(k)) + .collect(Collectors.toSet()); + for (String remoteCluster : remoteClusters) { + for (String remoteIndex : remoteClusterIndices.get(remoteCluster).indices()) { remoteIndices.add(RemoteClusterService.buildRemoteIndexName(remoteCluster, remoteIndex)); } } @@ -211,7 +218,12 @@ private void resolveIndexPatterns(final String name, final IndicesOptions indice } if (isTraceEnabled) { - log.trace("CCS is enabled, we found this local patterns " + localRequestedPatterns + " and this remote patterns: " + remoteIndices); + log.trace( + "CCS is enabled, we found this local patterns " + + localRequestedPatterns + + " and this remote patterns: " + + remoteIndices + ); } } else { @@ -239,28 +251,33 @@ private void resolveIndexPatterns(final String name, final IndicesOptions indice else { final ClusterState state = clusterService.state(); - final Set dateResolvedLocalRequestedPatterns = localRequestedPatterns - .stream() - .map(resolver::resolveDateMathExpression) - .collect(Collectors.toSet()); + final Set dateResolvedLocalRequestedPatterns = localRequestedPatterns.stream() + .map(resolver::resolveDateMathExpression) + .collect(Collectors.toSet()); final WildcardMatcher dateResolvedMatcher = WildcardMatcher.from(dateResolvedLocalRequestedPatterns); - //fill matchingAliases + // fill matchingAliases final Map lookup = state.metadata().getIndicesLookup(); matchingAliases = lookup.entrySet() - .stream() - .filter(e -> e.getValue().getType() == ALIAS) - .map(Map.Entry::getKey) - .filter(dateResolvedMatcher) - .collect(Collectors.toSet()); + .stream() + .filter(e -> e.getValue().getType() == ALIAS) + .map(Map.Entry::getKey) + .filter(dateResolvedMatcher) + .collect(Collectors.toSet()); final boolean isDebugEnabled = log.isDebugEnabled(); try { - matchingAllIndices = Arrays.asList(resolver.concreteIndexNames(state, indicesOptions, localRequestedPatterns.toArray(new String[0]))); + matchingAllIndices = Arrays.asList( + resolver.concreteIndexNames(state, indicesOptions, localRequestedPatterns.toArray(new String[0])) + ); matchingDataStreams = resolver.dataStreamNames(state, indicesOptions, localRequestedPatterns.toArray(new String[0])); if (isDebugEnabled) { - log.debug("Resolved pattern {} to indices: {} and data-streams: {}", - localRequestedPatterns, matchingAllIndices, matchingDataStreams); + log.debug( + "Resolved pattern {} to indices: {} and data-streams: {}", + localRequestedPatterns, + matchingAllIndices, + matchingDataStreams + ); } } catch (IndexNotFoundException e1) { if (isDebugEnabled) { @@ -276,8 +293,17 @@ private void resolveIndexPatterns(final String name, final IndicesOptions indice } if (isTraceEnabled) { - log.trace("Resolved patterns {} for {} ({}) to [aliases {}, allIndices {}, dataStreams {}, originalRequested{}, remote indices {}]", - original, name, this.name, matchingAliases, matchingAllIndices, matchingDataStreams, Arrays.toString(original), remoteIndices); + log.trace( + "Resolved patterns {} for {} ({}) to [aliases {}, allIndices {}, dataStreams {}, originalRequested{}, remote indices {}]", + original, + name, + this.name, + matchingAliases, + matchingAllIndices, + matchingDataStreams, + Arrays.toString(original), + remoteIndices + ); } resolveTo(matchingAliases, matchingAllIndices, matchingDataStreams, original, remoteIndices); @@ -289,8 +315,13 @@ private void resolveToLocalAll() { originalRequested.add(Resolved.ANY); } - private void resolveTo(Iterable matchingAliases, Iterable matchingAllIndices, - Iterable matchingDataStreams, String[] original, Iterable remoteIndices) { + private void resolveTo( + Iterable matchingAliases, + Iterable matchingAllIndices, + Iterable matchingDataStreams, + String[] original, + Iterable remoteIndices + ) { aliases.addAll(matchingAliases); allIndices.addAll(matchingAllIndices); allIndices.addAll(matchingDataStreams); @@ -302,21 +333,23 @@ private void resolveTo(Iterable matchingAliases, Iterable matchi public String[] provide(String[] original, Object localRequest, boolean supportsReplace) { final IndicesOptions indicesOptions = indicesOptionsFrom(localRequest); final boolean enableCrossClusterResolution = localRequest instanceof FieldCapabilitiesRequest - || localRequest instanceof SearchRequest - || localRequest instanceof ResolveIndexAction.Request; + || localRequest instanceof SearchRequest + || localRequest instanceof ResolveIndexAction.Request; // skip the whole thing if we have seen this exact resolveIndexPatterns request - if (alreadyResolved.add(new MultiKey(indicesOptions, enableCrossClusterResolution, - (original != null) ? new MultiKey(original, false) : null))) { + if (alreadyResolved.add( + new MultiKey(indicesOptions, enableCrossClusterResolution, (original != null) ? new MultiKey(original, false) : null) + )) { resolveIndexPatterns(localRequest.getClass().getSimpleName(), indicesOptions, enableCrossClusterResolution, original); } return IndicesProvider.NOOP; } Resolved resolved(IndicesOptions indicesOptions) { - final Resolved resolved = alreadyResolved.isEmpty() ? Resolved._LOCAL_ALL : - new Resolved(aliases.build(), allIndices.build(), originalRequested.build(), remoteIndices.build(), indicesOptions); + final Resolved resolved = alreadyResolved.isEmpty() + ? Resolved._LOCAL_ALL + : new Resolved(aliases.build(), allIndices.build(), originalRequested.build(), remoteIndices.build(), indicesOptions); - if(log.isTraceEnabled()) { + if (log.isTraceEnabled()) { log.trace("Finally resolved for {}: {}", name, resolved); } @@ -324,16 +357,17 @@ Resolved resolved(IndicesOptions indicesOptions) { } } - //dnfof + // dnfof public boolean replace(final TransportRequest request, boolean retainMode, String... replacements) { return getOrReplaceAllIndices(request, new IndicesProvider() { @Override public String[] provide(String[] original, Object request, boolean supportsReplace) { - if(supportsReplace) { - if(retainMode && !isAllWithNoRemote(original)) { + if (supportsReplace) { + if (retainMode && !isAllWithNoRemote(original)) { final Resolved resolved = resolveRequest(request); - final List retained = WildcardMatcher.from(resolved.getAllIndices()).getMatchAny(replacements, Collectors.toList()); + final List retained = WildcardMatcher.from(resolved.getAllIndices()) + .getMatchAny(replacements, Collectors.toList()); retained.addAll(resolved.getRemoteIndices()); return retained.toArray(new String[0]); } @@ -361,7 +395,13 @@ public final static class Resolved { private static final String ANY = "*"; private static final ImmutableSet All_SET = ImmutableSet.of(ANY); private static final Set types = All_SET; - public static final Resolved _LOCAL_ALL = new Resolved(All_SET, All_SET, All_SET, ImmutableSet.of(), SearchRequest.DEFAULT_INDICES_OPTIONS); + public static final Resolved _LOCAL_ALL = new Resolved( + All_SET, + All_SET, + All_SET, + ImmutableSet.of(), + SearchRequest.DEFAULT_INDICES_OPTIONS + ); private final Set aliases; private final Set allIndices; @@ -370,16 +410,19 @@ public final static class Resolved { private final boolean isLocalAll; private final IndicesOptions indicesOptions; - public Resolved(final ImmutableSet aliases, - final ImmutableSet allIndices, - final ImmutableSet originalRequested, - final ImmutableSet remoteIndices, - IndicesOptions indicesOptions) { + public Resolved( + final ImmutableSet aliases, + final ImmutableSet allIndices, + final ImmutableSet originalRequested, + final ImmutableSet remoteIndices, + IndicesOptions indicesOptions + ) { this.aliases = aliases; this.allIndices = allIndices; this.originalRequested = originalRequested; this.remoteIndices = remoteIndices; - this.isLocalAll = IndexResolverReplacer.isLocalAll(originalRequested.toArray(new String[0])) || (aliases.contains("*") && allIndices.contains("*")); + this.isLocalAll = IndexResolverReplacer.isLocalAll(originalRequested.toArray(new String[0])) + || (aliases.contains("*") && allIndices.contains("*")); this.indicesOptions = indicesOptions; } @@ -417,8 +460,17 @@ public Set getRemoteIndices() { @Override public String toString() { - return "Resolved [aliases=" + aliases + ", allIndices=" + allIndices + ", types=" + types - + ", originalRequested=" + originalRequested + ", remoteIndices=" + remoteIndices + "]"; + return "Resolved [aliases=" + + aliases + + ", allIndices=" + + allIndices + + ", types=" + + types + + ", originalRequested=" + + originalRequested + + ", remoteIndices=" + + remoteIndices + + "]"; } @Override @@ -434,33 +486,22 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; Resolved other = (Resolved) obj; if (aliases == null) { - if (other.aliases != null) - return false; - } else if (!aliases.equals(other.aliases)) - return false; + if (other.aliases != null) return false; + } else if (!aliases.equals(other.aliases)) return false; if (allIndices == null) { - if (other.allIndices != null) - return false; - } else if (!allIndices.equals(other.allIndices)) - return false; + if (other.allIndices != null) return false; + } else if (!allIndices.equals(other.allIndices)) return false; if (originalRequested == null) { - if (other.originalRequested != null) - return false; - } else if (!originalRequested.equals(other.originalRequested)) - return false; + if (other.originalRequested != null) return false; + } else if (!originalRequested.equals(other.originalRequested)) return false; if (remoteIndices == null) { - if (other.remoteIndices != null) - return false; - } else if (!remoteIndices.equals(other.remoteIndices)) - return false; + if (other.remoteIndices != null) return false; + } else if (!remoteIndices.equals(other.remoteIndices)) return false; return true; } } @@ -482,42 +523,42 @@ private List renamedIndices(final RestoreSnapshotRequest request, final } } - - //-- + // -- @FunctionalInterface public interface IndicesProvider { public static final String[] NOOP = new String[0]; + String[] provide(String[] original, Object request, boolean supportsReplace); } private boolean checkIndices(Object request, String[] indices, boolean needsToBeSizeOne, boolean allowEmpty) { - if(indices == IndicesProvider.NOOP) { + if (indices == IndicesProvider.NOOP) { return false; } final boolean isTraceEnabled = log.isTraceEnabled(); - if(!allowEmpty && (indices == null || indices.length == 0)) { - if(isTraceEnabled && request != null) { - log.trace("Null or empty indices for "+request.getClass().getName()); + if (!allowEmpty && (indices == null || indices.length == 0)) { + if (isTraceEnabled && request != null) { + log.trace("Null or empty indices for " + request.getClass().getName()); } return false; } - if(!allowEmpty && needsToBeSizeOne && indices.length != 1) { - if(isTraceEnabled && request != null) { - log.trace("To much indices for "+request.getClass().getName()); + if (!allowEmpty && needsToBeSizeOne && indices.length != 1) { + if (isTraceEnabled && request != null) { + log.trace("To much indices for " + request.getClass().getName()); } return false; } for (int i = 0; i < indices.length; i++) { final String index = indices[i]; - if(index == null || index.isEmpty()) { - //not allowed - if(isTraceEnabled && request != null) { - log.trace("At least one null or empty index for "+request.getClass().getName()); + if (index == null || index.isEmpty()) { + // not allowed + if (isTraceEnabled && request != null) { + log.trace("At least one null or empty index for " + request.getClass().getName()); } return false; } @@ -537,7 +578,7 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid final boolean isDebugEnabled = log.isDebugEnabled(); final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { - log.trace("getOrReplaceAllIndices() for "+request.getClass()); + log.trace("getOrReplaceAllIndices() for " + request.getClass()); } boolean result = true; @@ -550,7 +591,7 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid } else if (request instanceof MultiGetRequest) { - for (ListIterator it = ((MultiGetRequest) request).getItems().listIterator(); it.hasNext();){ + for (ListIterator it = ((MultiGetRequest) request).getItems().listIterator(); it.hasNext();) { Item item = it.next(); result = getOrReplaceAllIndices(item, provider, false) && result; /*if(item.index() == null || item.indices() == null || item.indices().length == 0) { @@ -574,12 +615,12 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid result = getOrReplaceAllIndices(ar, provider, false) && result; } - } else if(request instanceof PutMappingRequest) { + } else if (request instanceof PutMappingRequest) { PutMappingRequest pmr = (PutMappingRequest) request; Index concreteIndex = pmr.getConcreteIndex(); - if(concreteIndex != null && (pmr.indices() == null || pmr.indices().length == 0)) { - String[] newIndices = provider.provide(new String[]{concreteIndex.getName()}, request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (concreteIndex != null && (pmr.indices() == null || pmr.indices().length == 0)) { + String[] newIndices = provider.provide(new String[] { concreteIndex.getName() }, request, true); + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } @@ -587,58 +628,64 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid ((PutMappingRequest) request).setConcreteIndex(null); } else { String[] newIndices = provider.provide(((PutMappingRequest) request).indices(), request, true); - if(checkIndices(request, newIndices, false, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, false, allowEmptyIndices) == false) { return false; } ((PutMappingRequest) request).indices(newIndices); } - } else if(request instanceof RestoreSnapshotRequest) { + } else if (request instanceof RestoreSnapshotRequest) { - if(clusterInfoHolder.isLocalNodeElectedClusterManager() == Boolean.FALSE) { - return true; - } + if (clusterInfoHolder.isLocalNodeElectedClusterManager() == Boolean.FALSE) { + return true; + } - final RestoreSnapshotRequest restoreRequest = (RestoreSnapshotRequest) request; - final SnapshotInfo snapshotInfo = SnapshotRestoreHelper.getSnapshotInfo(restoreRequest); + final RestoreSnapshotRequest restoreRequest = (RestoreSnapshotRequest) request; + final SnapshotInfo snapshotInfo = SnapshotRestoreHelper.getSnapshotInfo(restoreRequest); - if (snapshotInfo == null) { - log.warn("snapshot repository '" + restoreRequest.repository() + "', snapshot '" + restoreRequest.snapshot() + "' not found"); - provider.provide(new String[]{"*"}, request, false); - } else { - final List requestedResolvedIndices = SnapshotUtils.filterIndices(snapshotInfo.indices(), restoreRequest.indices(), restoreRequest.indicesOptions()); - final List renamedTargetIndices = renamedIndices(restoreRequest, requestedResolvedIndices); - //final Set indices = new HashSet<>(requestedResolvedIndices); - //indices.addAll(renamedTargetIndices); - if (isDebugEnabled) { - log.debug("snapshot: {} contains this indices: {}", snapshotInfo.snapshotId().getName(), renamedTargetIndices); - } - provider.provide(renamedTargetIndices.toArray(new String[0]), request, false); + if (snapshotInfo == null) { + log.warn( + "snapshot repository '" + restoreRequest.repository() + "', snapshot '" + restoreRequest.snapshot() + "' not found" + ); + provider.provide(new String[] { "*" }, request, false); + } else { + final List requestedResolvedIndices = SnapshotUtils.filterIndices( + snapshotInfo.indices(), + restoreRequest.indices(), + restoreRequest.indicesOptions() + ); + final List renamedTargetIndices = renamedIndices(restoreRequest, requestedResolvedIndices); + // final Set indices = new HashSet<>(requestedResolvedIndices); + // indices.addAll(renamedTargetIndices); + if (isDebugEnabled) { + log.debug("snapshot: {} contains this indices: {}", snapshotInfo.snapshotId().getName(), renamedTargetIndices); + } + provider.provide(renamedTargetIndices.toArray(new String[0]), request, false); } } else if (request instanceof IndicesAliasesRequest) { - for(AliasActions ar: ((IndicesAliasesRequest) request).getAliasActions()) { + for (AliasActions ar : ((IndicesAliasesRequest) request).getAliasActions()) { result = getOrReplaceAllIndices(ar, provider, false) && result; } } else if (request instanceof DeleteRequest) { String[] newIndices = provider.provide(((DeleteRequest) request).indices(), request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } - ((DeleteRequest) request).index(newIndices.length!=1?null:newIndices[0]); + ((DeleteRequest) request).index(newIndices.length != 1 ? null : newIndices[0]); } else if (request instanceof UpdateRequest) { String[] newIndices = provider.provide(((UpdateRequest) request).indices(), request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } - ((UpdateRequest) request).index(newIndices.length!=1?null:newIndices[0]); + ((UpdateRequest) request).index(newIndices.length != 1 ? null : newIndices[0]); } else if (request instanceof SingleShardRequest) { final SingleShardRequest singleShardRequest = (SingleShardRequest) request; final String index = singleShardRequest.index(); - String[] indices = provider.provide(index == null ? null : new String[]{index}, request, true); + String[] indices = provider.provide(index == null ? null : new String[] { index }, request, true); if (!checkIndices(request, indices, true, allowEmptyIndices)) { return false; } - singleShardRequest.index(indices.length != 1? null : indices[0]); + singleShardRequest.index(indices.length != 1 ? null : indices[0]); } else if (request instanceof FieldCapabilitiesIndexRequest) { // FieldCapabilitiesIndexRequest does not support replacing the indexes. // However, the indexes are always determined by FieldCapabilitiesRequest which will be reduced below @@ -648,56 +695,56 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid String index = fieldCapabilitiesRequest.index(); - String[] newIndices = provider.provide(new String[]{index}, request, true); + String[] newIndices = provider.provide(new String[] { index }, request, true); if (!checkIndices(request, newIndices, true, allowEmptyIndices)) { return false; } } else if (request instanceof IndexRequest) { String[] newIndices = provider.provide(((IndexRequest) request).indices(), request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } - ((IndexRequest) request).index(newIndices.length!=1?null:newIndices[0]); + ((IndexRequest) request).index(newIndices.length != 1 ? null : newIndices[0]); } else if (request instanceof Replaceable) { String[] newIndices = provider.provide(((Replaceable) request).indices(), request, true); - if(checkIndices(request, newIndices, false, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, false, allowEmptyIndices) == false) { return false; } ((Replaceable) request).indices(newIndices); } else if (request instanceof BulkShardRequest) { provider.provide(((ReplicationRequest) request).indices(), request, false); - //replace not supported? + // replace not supported? } else if (request instanceof ReplicationRequest) { String[] newIndices = provider.provide(((ReplicationRequest) request).indices(), request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } - ((ReplicationRequest) request).index(newIndices.length!=1?null:newIndices[0]); + ((ReplicationRequest) request).index(newIndices.length != 1 ? null : newIndices[0]); } else if (request instanceof MultiGetRequest.Item) { String[] newIndices = provider.provide(((MultiGetRequest.Item) request).indices(), request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } - ((MultiGetRequest.Item) request).index(newIndices.length!=1?null:newIndices[0]); + ((MultiGetRequest.Item) request).index(newIndices.length != 1 ? null : newIndices[0]); } else if (request instanceof CreateIndexRequest) { String[] newIndices = provider.provide(((CreateIndexRequest) request).indices(), request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } - ((CreateIndexRequest) request).index(newIndices.length!=1?null:newIndices[0]); + ((CreateIndexRequest) request).index(newIndices.length != 1 ? null : newIndices[0]); } else if (request instanceof CreateDataStreamAction.Request) { provider.provide(((CreateDataStreamAction.Request) request).indices(), request, false); } else if (request instanceof ReindexRequest) { result = getOrReplaceAllIndices(((ReindexRequest) request).getDestination(), provider, false) && result; result = getOrReplaceAllIndices(((ReindexRequest) request).getSearchRequest(), provider, false) && result; } else if (request instanceof BaseNodesRequest) { - //do nothing + // do nothing } else if (request instanceof MainRequest) { - //do nothing + // do nothing } else if (request instanceof ClearScrollRequest) { - //do nothing + // do nothing } else if (request instanceof SearchScrollRequest) { - //do nothing + // do nothing } else if (request instanceof PutComponentTemplateAction.Request) { // do nothing } else { @@ -712,17 +759,15 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid private IndicesOptions indicesOptionsFrom(Object localRequest) { - if(!respectRequestIndicesOptions) { + if (!respectRequestIndicesOptions) { return IndicesOptions.fromOptions(false, true, true, false, true); } if (IndicesRequest.class.isInstance(localRequest)) { return ((IndicesRequest) localRequest).indicesOptions(); - } - else if (RestoreSnapshotRequest.class.isInstance(localRequest)) { + } else if (RestoreSnapshotRequest.class.isInstance(localRequest)) { return ((RestoreSnapshotRequest) localRequest).indicesOptions(); - } - else { + } else { return IndicesOptions.fromOptions(false, true, true, false, true); } } diff --git a/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java b/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java index a7620f6bdc..352d99b57e 100644 --- a/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java @@ -55,25 +55,22 @@ public class DashboardsInfoAction extends BaseRestHandler { private static final List routes = ImmutableList.builder() - .addAll(addRoutesPrefix( - ImmutableList.of( - new Route(GET, "/dashboardsinfo"), - new Route(POST, "/dashboardsinfo") - ), - "/_plugins/_security")) - .addAll(addRoutesPrefix( - ImmutableList.of( - new Route(GET, "/kibanainfo"), - new Route(POST, "/kibanainfo") - ), - "/_opendistro/_security")) + .addAll( + addRoutesPrefix(ImmutableList.of(new Route(GET, "/dashboardsinfo"), new Route(POST, "/dashboardsinfo")), "/_plugins/_security") + ) + .addAll(addRoutesPrefix(ImmutableList.of(new Route(GET, "/kibanainfo"), new Route(POST, "/kibanainfo")), "/_opendistro/_security")) .build(); private final Logger log = LogManager.getLogger(this.getClass()); private final PrivilegesEvaluator evaluator; private final ThreadContext threadContext; - public DashboardsInfoAction(final Settings settings, final RestController controller, final PrivilegesEvaluator evaluator, final ThreadPool threadPool) { + public DashboardsInfoAction( + final Settings settings, + final RestController controller, + final PrivilegesEvaluator evaluator, + final ThreadPool threadPool + ) { super(); this.threadContext = threadPool.getThreadContext(); this.evaluator = evaluator; @@ -90,15 +87,15 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli @Override public void accept(RestChannel channel) throws Exception { - XContentBuilder builder = channel.newBuilder(); //NOSONAR + XContentBuilder builder = channel.newBuilder(); // NOSONAR BytesRestResponse response = null; try { - final User user = (User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user = (User) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); builder.startObject(); - builder.field("user_name", user==null?null:user.getName()); + builder.field("user_name", user == null ? null : user.getName()); builder.field("not_fail_on_forbidden_enabled", evaluator.notFailOnForbiddenEnabled()); builder.field("opensearch_dashboards_mt_enabled", evaluator.multitenancyEnabled()); builder.field("opensearch_dashboards_index", evaluator.dashboardsIndex()); @@ -111,13 +108,13 @@ public void accept(RestChannel channel) throws Exception { response = new BytesRestResponse(RestStatus.OK, builder); } catch (final Exception e1) { log.error(e1.toString()); - builder = channel.newBuilder(); //NOSONAR + builder = channel.newBuilder(); // NOSONAR builder.startObject(); builder.field("error", e1.toString()); builder.endObject(); response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); } finally { - if(builder != null) { + if (builder != null) { builder.close(); } } @@ -132,5 +129,4 @@ public String getName() { return "Kibana Info Action"; } - } diff --git a/src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java b/src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java index b328a049c1..379a3b6b13 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java @@ -40,9 +40,7 @@ public class SecurityConfigUpdateAction extends BaseRestHandler { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(PUT, "/configupdate")), - "/_plugins/_security"); + private static final List routes = addRoutesPrefix(ImmutableList.of(new Route(PUT, "/configupdate")), "/_plugins/_security"); private final ThreadContext threadContext; private final AdminDNs adminDns; @@ -50,8 +48,14 @@ public class SecurityConfigUpdateAction extends BaseRestHandler { private final Path configPath; private final PrincipalExtractor principalExtractor; - public SecurityConfigUpdateAction(final Settings settings, final RestController controller, final ThreadPool threadPool, final AdminDNs adminDns, - Path configPath, PrincipalExtractor principalExtractor) { + public SecurityConfigUpdateAction( + final Settings settings, + final RestController controller, + final ThreadPool threadPool, + final AdminDNs adminDns, + Path configPath, + PrincipalExtractor principalExtractor + ) { super(); this.threadContext = threadPool.getThreadContext(); this.adminDns = adminDns; @@ -60,11 +64,13 @@ public SecurityConfigUpdateAction(final Settings settings, final RestController this.principalExtractor = principalExtractor; } - @Override public List routes() { + @Override + public List routes() { return routes; } - @Override protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { String[] configTypes = request.paramAsStringArrayOrEmptyIfAll("config_types"); SSLRequestHelper.SSLInfo sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor); @@ -75,7 +81,7 @@ public SecurityConfigUpdateAction(final Settings settings, final RestController final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - //only allowed for admins + // only allowed for admins if (user == null || !adminDns.isAdmin(user)) { return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, "")); } else { @@ -86,7 +92,8 @@ public SecurityConfigUpdateAction(final Settings settings, final RestController } } - @Override public String getName() { + @Override + public String getName() { return "Security config update"; } diff --git a/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java b/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java index 17d5ee122f..0631e3044a 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java @@ -47,10 +47,11 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class SecurityHealthAction extends BaseRestHandler { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(GET, "/health"), - new Route(POST, "/health") - ), "/_opendistro/_security", "/_plugins/_security"); + private static final List routes = addRoutesPrefix( + ImmutableList.of(new Route(GET, "/health"), new Route(POST, "/health")), + "/_opendistro/_security", + "/_plugins/_security" + ); private final BackendRegistry registry; @@ -68,7 +69,7 @@ public List routes() { protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { return new RestChannelConsumer() { - final String mode = request.param("mode","strict"); + final String mode = request.param("mode", "strict"); @Override public void accept(RestChannel channel) throws Exception { @@ -77,7 +78,6 @@ public void accept(RestChannel channel) throws Exception { BytesRestResponse response = null; try { - String status = "UP"; String message = null; @@ -99,11 +99,9 @@ public void accept(RestChannel channel) throws Exception { builder.close(); } - channel.sendResponse(response); } - }; } diff --git a/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java b/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java index 7867e8790d..6159f555c7 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java @@ -60,16 +60,22 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class SecurityInfoAction extends BaseRestHandler { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(GET, "/authinfo"), - new Route(POST, "/authinfo") - ),"/_opendistro/_security", "/_plugins/_security"); + private static final List routes = addRoutesPrefix( + ImmutableList.of(new Route(GET, "/authinfo"), new Route(POST, "/authinfo")), + "/_opendistro/_security", + "/_plugins/_security" + ); private final Logger log = LogManager.getLogger(this.getClass()); private final PrivilegesEvaluator evaluator; private final ThreadContext threadContext; - public SecurityInfoAction(final Settings settings, final RestController controller, final PrivilegesEvaluator evaluator, final ThreadPool threadPool) { + public SecurityInfoAction( + final Settings settings, + final RestController controller, + final PrivilegesEvaluator evaluator, + final ThreadPool threadPool + ) { super(); this.threadContext = threadPool.getThreadContext(); this.evaluator = evaluator; @@ -86,12 +92,11 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli @Override public void accept(RestChannel channel) throws Exception { - XContentBuilder builder = channel.newBuilder(); //NOSONAR + XContentBuilder builder = channel.newBuilder(); // NOSONAR BytesRestResponse response = null; try { - final boolean verbose = request.paramAsBoolean("verbose", false); final X509Certificate[] certs = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PEER_CERTIFICATES); @@ -101,41 +106,54 @@ public void accept(RestChannel channel) throws Exception { final Set securityRoles = evaluator.mapRoles(user, remoteAddress); builder.startObject(); - builder.field("user", user==null?null:user.toString()); - builder.field("user_name", user==null?null:user.getName()); - builder.field("user_requested_tenant", user==null?null:user.getRequestedTenant()); + builder.field("user", user == null ? null : user.toString()); + builder.field("user_name", user == null ? null : user.getName()); + builder.field("user_requested_tenant", user == null ? null : user.getRequestedTenant()); builder.field("remote_address", remoteAddress); - builder.field("backend_roles", user==null?null:user.getRoles()); - builder.field("custom_attribute_names", user==null?null:user.getCustomAttributesMap().keySet()); + builder.field("backend_roles", user == null ? null : user.getRoles()); + builder.field("custom_attribute_names", user == null ? null : user.getCustomAttributesMap().keySet()); builder.field("roles", securityRoles); builder.field("tenants", evaluator.mapTenants(user, securityRoles)); - builder.field("principal", (String)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PRINCIPAL)); + builder.field("principal", (String) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PRINCIPAL)); builder.field("peer_certificates", certs != null && certs.length > 0 ? certs.length + "" : "0"); - builder.field("sso_logout_url", (String)threadContext.getTransient(ConfigConstants.SSO_LOGOUT_URL)); + builder.field("sso_logout_url", (String) threadContext.getTransient(ConfigConstants.SSO_LOGOUT_URL)); - if(user != null && verbose) { + if (user != null && verbose) { try { - builder.field("size_of_user", RamUsageEstimator.humanReadableUnits(Base64Helper.serializeObject(user).length())); - builder.field("size_of_custom_attributes", RamUsageEstimator.humanReadableUnits(Base64Helper.serializeObject((Serializable) user.getCustomAttributesMap()).getBytes(StandardCharsets.UTF_8).length)); - builder.field("size_of_backendroles", RamUsageEstimator.humanReadableUnits(Base64Helper.serializeObject((Serializable)user.getRoles()).getBytes(StandardCharsets.UTF_8).length)); + builder.field( + "size_of_user", + RamUsageEstimator.humanReadableUnits(Base64Helper.serializeObject(user).length()) + ); + builder.field( + "size_of_custom_attributes", + RamUsageEstimator.humanReadableUnits( + Base64Helper.serializeObject((Serializable) user.getCustomAttributesMap()) + .getBytes(StandardCharsets.UTF_8).length + ) + ); + builder.field( + "size_of_backendroles", + RamUsageEstimator.humanReadableUnits( + Base64Helper.serializeObject((Serializable) user.getRoles()).getBytes(StandardCharsets.UTF_8).length + ) + ); } catch (Throwable e) { - //ignore + // ignore } } - builder.endObject(); response = new BytesRestResponse(RestStatus.OK, builder); } catch (final Exception e1) { - log.error(e1.toString(),e1); - builder = channel.newBuilder(); //NOSONAR + log.error(e1.toString(), e1); + builder = channel.newBuilder(); // NOSONAR builder.startObject(); builder.field("error", e1.toString()); builder.endObject(); response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); } finally { - if(builder != null) { + if (builder != null) { builder.close(); } } diff --git a/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java b/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java index 982448a53f..8f20a0b9a2 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java @@ -41,88 +41,93 @@ import static org.opensearch.rest.RestRequest.Method.POST; import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; - public class SecurityWhoAmIAction extends BaseRestHandler { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(GET, "/whoami"), - new Route(POST, "/whoami")), - "/_plugins/_security"); - - private final Logger log = LogManager.getLogger(this.getClass()); - private final AdminDNs adminDns; - private final Settings settings; - private final Path configPath; - private final PrincipalExtractor principalExtractor; - private final List nodesDn ; - - public SecurityWhoAmIAction(final Settings settings, final RestController controller, - final ThreadPool threadPool, final AdminDNs adminDns, Path configPath, PrincipalExtractor principalExtractor) { - super(); - this.adminDns = adminDns; - this.settings = settings; - this.configPath = configPath; - this.principalExtractor = principalExtractor; - - nodesDn = settings.getAsList(ConfigConstants.SECURITY_NODES_DN, Collections.emptyList()); - } - - @Override - public List routes() { - return routes; - } - - @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - return new RestChannelConsumer() { - - @Override - public void accept(RestChannel channel) throws Exception { - XContentBuilder builder = channel.newBuilder(); - BytesRestResponse response = null; - - try { - - SSLInfo sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor); - - if(sslInfo == null) { - response = new BytesRestResponse(RestStatus.FORBIDDEN, "No security data"); - } else { - - final String dn = sslInfo.getPrincipal(); - final boolean isAdmin = adminDns.isAdminDN(dn); - final boolean isNodeCertificateRequest = dn != null && WildcardMatcher.from(nodesDn, true).matchAny(dn); - - builder.startObject(); - builder.field("dn", dn); - builder.field("is_admin", isAdmin); - builder.field("is_node_certificate_request", isNodeCertificateRequest); - builder.endObject(); - - response = new BytesRestResponse(RestStatus.OK, builder); - - } - } catch (final Exception e1) { - log.error(e1.toString(), e1); - builder = channel.newBuilder(); - builder.startObject(); - builder.field("error", e1.toString()); - builder.endObject(); - response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); - } finally { - if (builder != null) { - builder.close(); - } - } - - channel.sendResponse(response); - } - }; - } - - @Override - public String getName() { - return "Security Plugin Who am i"; - } + private static final List routes = addRoutesPrefix( + ImmutableList.of(new Route(GET, "/whoami"), new Route(POST, "/whoami")), + "/_plugins/_security" + ); + + private final Logger log = LogManager.getLogger(this.getClass()); + private final AdminDNs adminDns; + private final Settings settings; + private final Path configPath; + private final PrincipalExtractor principalExtractor; + private final List nodesDn; + + public SecurityWhoAmIAction( + final Settings settings, + final RestController controller, + final ThreadPool threadPool, + final AdminDNs adminDns, + Path configPath, + PrincipalExtractor principalExtractor + ) { + super(); + this.adminDns = adminDns; + this.settings = settings; + this.configPath = configPath; + this.principalExtractor = principalExtractor; + + nodesDn = settings.getAsList(ConfigConstants.SECURITY_NODES_DN, Collections.emptyList()); + } + + @Override + public List routes() { + return routes; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + return new RestChannelConsumer() { + + @Override + public void accept(RestChannel channel) throws Exception { + XContentBuilder builder = channel.newBuilder(); + BytesRestResponse response = null; + + try { + + SSLInfo sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor); + + if (sslInfo == null) { + response = new BytesRestResponse(RestStatus.FORBIDDEN, "No security data"); + } else { + + final String dn = sslInfo.getPrincipal(); + final boolean isAdmin = adminDns.isAdminDN(dn); + final boolean isNodeCertificateRequest = dn != null && WildcardMatcher.from(nodesDn, true).matchAny(dn); + + builder.startObject(); + builder.field("dn", dn); + builder.field("is_admin", isAdmin); + builder.field("is_node_certificate_request", isNodeCertificateRequest); + builder.endObject(); + + response = new BytesRestResponse(RestStatus.OK, builder); + + } + } catch (final Exception e1) { + log.error(e1.toString(), e1); + builder = channel.newBuilder(); + builder.startObject(); + builder.field("error", e1.toString()); + builder.endObject(); + response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); + } finally { + if (builder != null) { + builder.close(); + } + } + + channel.sendResponse(response); + } + }; + } + + @Override + public String getName() { + return "Security Plugin Who am i"; + } } diff --git a/src/main/java/org/opensearch/security/rest/TenantInfoAction.java b/src/main/java/org/opensearch/security/rest/TenantInfoAction.java index f7b2a606c6..c6f09efd98 100644 --- a/src/main/java/org/opensearch/security/rest/TenantInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/TenantInfoAction.java @@ -65,11 +65,10 @@ public class TenantInfoAction extends BaseRestHandler { private static final List routes = addRoutesPrefix( - ImmutableList.of( - new Route(GET, "/tenantinfo"), - new Route(POST, "/tenantinfo") - ), - "/_opendistro/_security", "/_plugins/_security"); + ImmutableList.of(new Route(GET, "/tenantinfo"), new Route(POST, "/tenantinfo")), + "/_opendistro/_security", + "/_plugins/_security" + ); private final Logger log = LogManager.getLogger(this.getClass()); private final PrivilegesEvaluator evaluator; @@ -78,9 +77,15 @@ public class TenantInfoAction extends BaseRestHandler { private final AdminDNs adminDns; private final ConfigurationRepository configurationRepository; - public TenantInfoAction(final Settings settings, final RestController controller, - final PrivilegesEvaluator evaluator, final ThreadPool threadPool, final ClusterService clusterService, final AdminDNs adminDns, - final ConfigurationRepository configurationRepository) { + public TenantInfoAction( + final Settings settings, + final RestController controller, + final PrivilegesEvaluator evaluator, + final ThreadPool threadPool, + final ClusterService clusterService, + final AdminDNs adminDns, + final ConfigurationRepository configurationRepository + ) { super(); this.threadContext = threadPool.getThreadContext(); this.evaluator = evaluator; @@ -100,41 +105,41 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli @Override public void accept(RestChannel channel) throws Exception { - XContentBuilder builder = channel.newBuilder(); //NOSONAR + XContentBuilder builder = channel.newBuilder(); // NOSONAR BytesRestResponse response = null; try { - final User user = (User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user = (User) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - //only allowed for admins or the kibanaserveruser - if(!isAuthorized()) { - response = new BytesRestResponse(RestStatus.FORBIDDEN,""); + // only allowed for admins or the kibanaserveruser + if (!isAuthorized()) { + response = new BytesRestResponse(RestStatus.FORBIDDEN, ""); } else { - builder.startObject(); + builder.startObject(); - final SortedMap lookup = clusterService.state().metadata().getIndicesLookup(); - for(final String indexOrAlias: lookup.keySet()) { - final String tenant = tenantNameForIndex(indexOrAlias); - if(tenant != null) { - builder.field(indexOrAlias, tenant); - } - } + final SortedMap lookup = clusterService.state().metadata().getIndicesLookup(); + for (final String indexOrAlias : lookup.keySet()) { + final String tenant = tenantNameForIndex(indexOrAlias); + if (tenant != null) { + builder.field(indexOrAlias, tenant); + } + } - builder.endObject(); + builder.endObject(); - response = new BytesRestResponse(RestStatus.OK, builder); + response = new BytesRestResponse(RestStatus.OK, builder); } } catch (final Exception e1) { log.error(e1.toString()); - builder = channel.newBuilder(); //NOSONAR + builder = channel.newBuilder(); // NOSONAR builder.startObject(); builder.field("error", e1.toString()); builder.endObject(); response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); } finally { - if(builder != null) { + if (builder != null) { builder.close(); } } @@ -145,7 +150,7 @@ public void accept(RestChannel channel) throws Exception { } private boolean isAuthorized() { - final User user = (User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user = (User) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); if (user == null) { return false; @@ -173,38 +178,38 @@ private boolean isAuthorized() { } private final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent) { - SecurityDynamicConfiguration loaded = configurationRepository.getConfigurationsFromIndex(Collections.singleton(config), logComplianceEvent).get(config).deepClone(); + SecurityDynamicConfiguration loaded = configurationRepository.getConfigurationsFromIndex( + Collections.singleton(config), + logComplianceEvent + ).get(config).deepClone(); return DynamicConfigFactory.addStatics(loaded); } private String tenantNameForIndex(String index) { - String[] indexParts; - if(index == null - || (indexParts = index.split("_")).length != 3 - ) { - return null; - } - - - if(!indexParts[0].equals(evaluator.dashboardsIndex())) { - return null; - } - - try { - final int expectedHash = Integer.parseInt(indexParts[1]); - final String sanitizedName = indexParts[2]; - - for(String tenant: evaluator.getAllConfiguredTenantNames()) { - if(tenant.hashCode() == expectedHash && sanitizedName.equals(tenant.toLowerCase().replaceAll("[^a-z0-9]+",""))) { - return tenant; - } - } - - return "__private__"; - } catch (NumberFormatException e) { - log.warn("Index {} looks like a Security tenant index but we cannot parse the hashcode so we ignore it.", index); - return null; - } + String[] indexParts; + if (index == null || (indexParts = index.split("_")).length != 3) { + return null; + } + + if (!indexParts[0].equals(evaluator.dashboardsIndex())) { + return null; + } + + try { + final int expectedHash = Integer.parseInt(indexParts[1]); + final String sanitizedName = indexParts[2]; + + for (String tenant : evaluator.getAllConfiguredTenantNames()) { + if (tenant.hashCode() == expectedHash && sanitizedName.equals(tenant.toLowerCase().replaceAll("[^a-z0-9]+", ""))) { + return tenant; + } + } + + return "__private__"; + } catch (NumberFormatException e) { + log.warn("Index {} looks like a Security tenant index but we cannot parse the hashcode so we ignore it.", index); + return null; + } } @Override @@ -212,5 +217,4 @@ public String getName() { return "Tenant Info Action"; } - } diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModel.java b/src/main/java/org/opensearch/security/securityconf/ConfigModel.java index b4b2a9dd20..653ff23896 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModel.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModel.java @@ -32,11 +32,12 @@ import org.opensearch.common.transport.TransportAddress; import org.opensearch.security.user.User; - public abstract class ConfigModel { public abstract Map mapTenants(User user, Set roles); + public abstract Set mapSecurityRoles(User user, TransportAddress caller); + public abstract SecurityRoles getSecurityRoles(); public abstract Set getAllConfiguredTenantNames(); diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java index 7a978034f1..837dc0cff0 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java @@ -66,7 +66,6 @@ import static org.opensearch.cluster.metadata.IndexAbstraction.Type.ALIAS; - public class ConfigModelV6 extends ConfigModel { protected final Logger log = LogManager.getLogger(this.getClass()); @@ -78,18 +77,22 @@ public class ConfigModelV6 extends ConfigModel { private SecurityDynamicConfiguration roles; public ConfigModelV6( - SecurityDynamicConfiguration roles, - SecurityDynamicConfiguration actiongroups, - SecurityDynamicConfiguration rolesmapping, - DynamicConfigModel dcm, - Settings opensearchSettings) { + SecurityDynamicConfiguration roles, + SecurityDynamicConfiguration actiongroups, + SecurityDynamicConfiguration rolesmapping, + DynamicConfigModel dcm, + Settings opensearchSettings + ) { this.roles = roles; try { rolesMappingResolution = ConfigConstants.RolesMappingResolution.valueOf( - opensearchSettings.get(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, ConfigConstants.RolesMappingResolution.MAPPING_ONLY.toString()) - .toUpperCase()); + opensearchSettings.get( + ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, + ConfigConstants.RolesMappingResolution.MAPPING_ONLY.toString() + ).toUpperCase() + ); } catch (Exception e) { log.error("Cannot apply roles mapping resolution", e); rolesMappingResolution = ConfigConstants.RolesMappingResolution.MAPPING_ONLY; @@ -137,15 +140,14 @@ private Set getGroupMembers(final String groupname) { private Set resolve(final SecurityDynamicConfiguration actionGroups, final String entry) { - // SG5 format, plain array - //List en = actionGroups.getAsList(DotPath.of(entry)); - //if (en.isEmpty()) { - // try SG6 format including readonly and permissions key - // en = actionGroups.getAsList(DotPath.of(entry + "." + ConfigConstants.CONFIGKEY_ACTION_GROUPS_PERMISSIONS)); - //} + // List en = actionGroups.getAsList(DotPath.of(entry)); + // if (en.isEmpty()) { + // try SG6 format including readonly and permissions key + // en = actionGroups.getAsList(DotPath.of(entry + "." + ConfigConstants.CONFIGKEY_ACTION_GROUPS_PERMISSIONS)); + // } - if(!actionGroups.getCEntries().containsKey(entry)) { + if (!actionGroups.getCEntries().containsKey(entry)) { return Collections.emptySet(); } @@ -153,27 +155,26 @@ private Set resolve(final SecurityDynamicConfiguration actionGroups, final Object actionGroupAsObject = actionGroups.getCEntries().get(entry); - if(actionGroupAsObject != null && actionGroupAsObject instanceof List) { + if (actionGroupAsObject != null && actionGroupAsObject instanceof List) { - for (final String perm: ((List) actionGroupAsObject)) { + for (final String perm : ((List) actionGroupAsObject)) { if (actionGroups.getCEntries().keySet().contains(perm)) { - ret.addAll(resolve(actionGroups,perm)); + ret.addAll(resolve(actionGroups, perm)); } else { ret.add(perm); } } - - } else if(actionGroupAsObject != null && actionGroupAsObject instanceof ActionGroupsV6) { - for (final String perm: ((ActionGroupsV6) actionGroupAsObject).getPermissions()) { + } else if (actionGroupAsObject != null && actionGroupAsObject instanceof ActionGroupsV6) { + for (final String perm : ((ActionGroupsV6) actionGroupAsObject).getPermissions()) { if (actionGroups.getCEntries().keySet().contains(perm)) { - ret.addAll(resolve(actionGroups,perm)); + ret.addAll(resolve(actionGroups, perm)); } else { ret.add(perm); } } } else { - throw new RuntimeException("Unable to handle "+actionGroupAsObject); + throw new RuntimeException("Unable to handle " + actionGroupAsObject); } return Collections.unmodifiableSet(ret); @@ -182,7 +183,7 @@ private Set resolve(final SecurityDynamicConfiguration actionGroups, @Override public Set resolvedActions(final List actions) { final Set resolvedActions = new HashSet(); - for (String string: actions) { + for (String string : actions) { final Set groups = getGroupMembers(string); if (groups.isEmpty()) { resolvedActions.add(string); @@ -201,7 +202,7 @@ private SecurityRoles reload(SecurityDynamicConfiguration settings) { final Set> futures = new HashSet<>(5000); final ExecutorService execs = Executors.newFixedThreadPool(10); - for(Entry securityRole: settings.getCEntries().entrySet()) { + for (Entry securityRole : settings.getCEntries().entrySet()) { Future future = execs.submit(new Callable() { @@ -209,61 +210,60 @@ private SecurityRoles reload(SecurityDynamicConfiguration settings) { public SecurityRole call() throws Exception { SecurityRole _securityRole = new SecurityRole(securityRole.getKey()); - if(securityRole.getValue() == null) { + if (securityRole.getValue() == null) { return null; } final Set permittedClusterActions = agr.resolvedActions(securityRole.getValue().getCluster()); _securityRole.addClusterPerms(permittedClusterActions); - //if(tenants != null) { - for(Entry tenant: securityRole.getValue().getTenants().entrySet()) { + // if(tenants != null) { + for (Entry tenant : securityRole.getValue().getTenants().entrySet()) { - //if(tenant.equals(user.getName())) { - // continue; - //} + // if(tenant.equals(user.getName())) { + // continue; + // } - if("RW".equalsIgnoreCase(tenant.getValue())) { - _securityRole.addTenant(new Tenant(tenant.getKey(), true)); - } else { - _securityRole.addTenant(new Tenant(tenant.getKey(), false)); - //if(_securityRole.tenants.stream().filter(t->t.tenant.equals(tenant)).count() > 0) { //RW outperforms RO - // _securityRole.addTenant(new Tenant(tenant, false)); - //} - } + if ("RW".equalsIgnoreCase(tenant.getValue())) { + _securityRole.addTenant(new Tenant(tenant.getKey(), true)); + } else { + _securityRole.addTenant(new Tenant(tenant.getKey(), false)); + // if(_securityRole.tenants.stream().filter(t->t.tenant.equals(tenant)).count() > 0) { //RW outperforms RO + // _securityRole.addTenant(new Tenant(tenant, false)); + // } } - //} - - - //final Map permittedAliasesIndices = securityRoleSettings.getGroups(DotPath.of("indices")); - - for (final Entry permittedAliasesIndex : securityRole.getValue().getIndices().entrySet()) { + } + // } - //final String resolvedRole = securityRole; - //final String indexPattern = permittedAliasesIndex; + // final Map permittedAliasesIndices = + // securityRoleSettings.getGroups(DotPath.of("indices")); - final String dls = permittedAliasesIndex.getValue().get_dls_(); - final List fls = permittedAliasesIndex.getValue().get_fls_(); - final List maskedFields = permittedAliasesIndex.getValue().get_masked_fields_(); + for (final Entry permittedAliasesIndex : securityRole.getValue().getIndices().entrySet()) { - IndexPattern _indexPattern = new IndexPattern(permittedAliasesIndex.getKey()); - _indexPattern.setDlsQuery(dls); - _indexPattern.addFlsFields(fls); - _indexPattern.addMaskedFields(maskedFields); + // final String resolvedRole = securityRole; + // final String indexPattern = permittedAliasesIndex; - for(Entry> type: permittedAliasesIndex.getValue().getTypes().entrySet()) { - TypePerm typePerm = new TypePerm(type.getKey()); - final List perms = type.getValue(); - typePerm.addPerms(agr.resolvedActions(perms)); - _indexPattern.addTypePerms(typePerm); - } + final String dls = permittedAliasesIndex.getValue().get_dls_(); + final List fls = permittedAliasesIndex.getValue().get_fls_(); + final List maskedFields = permittedAliasesIndex.getValue().get_masked_fields_(); - _securityRole.addIndexPattern(_indexPattern); + IndexPattern _indexPattern = new IndexPattern(permittedAliasesIndex.getKey()); + _indexPattern.setDlsQuery(dls); + _indexPattern.addFlsFields(fls); + _indexPattern.addMaskedFields(maskedFields); + for (Entry> type : permittedAliasesIndex.getValue().getTypes().entrySet()) { + TypePerm typePerm = new TypePerm(type.getKey()); + final List perms = type.getValue(); + typePerm.addPerms(agr.resolvedActions(perms)); + _indexPattern.addTypePerms(typePerm); } + _securityRole.addIndexPattern(_indexPattern); - return _securityRole; + } + + return _securityRole; } }); @@ -296,8 +296,7 @@ public SecurityRole call() throws Exception { } } - - //beans + // beans public static class SecurityRoles implements org.opensearch.security.securityconf.SecurityRoles { @@ -326,18 +325,13 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; SecurityRoles other = (SecurityRoles) obj; if (roles == null) { - if (other.roles != null) - return false; - } else if (!roles.equals(other.roles)) - return false; + if (other.roles != null) return false; + } else if (!roles.equals(other.roles)) return false; return true; } @@ -349,6 +343,7 @@ public String toString() { public Set getRoles() { return Collections.unmodifiableSet(roles); } + public Set getRoleNames() { return getRoles().stream().map(r -> r.getName()).collect(Collectors.toSet()); } @@ -363,10 +358,14 @@ public SecurityRoles filter(Set keep) { return retVal; } - @Override - public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, IndexNameExpressionResolver resolver, ClusterService cs, - NamedXContentRegistry namedXContentRegistry) { + public EvaluatedDlsFlsConfig getDlsFls( + User user, + boolean dfmEmptyOverwritesAll, + IndexNameExpressionResolver resolver, + ClusterService cs, + NamedXContentRegistry namedXContentRegistry + ) { final Map> dlsQueries = new HashMap>(); final Map> flsFields = new HashMap>(); @@ -380,8 +379,9 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, final Set maskedFields = ip.getMaskedFields(); Set concreteIndices = new HashSet<>(); - - if ((dls != null && dls.length() > 0) || (fls != null && fls.size() > 0) || (maskedFields != null && maskedFields.size() > 0)) { + if ((dls != null && dls.length() > 0) + || (fls != null && fls.size() > 0) + || (maskedFields != null && maskedFields.size() > 0)) { concreteIndices = ip.getResolvedIndexPattern(user, resolver, cs); } @@ -448,10 +448,14 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, return new EvaluatedDlsFlsConfig(dlsQueries, flsFields, maskedFieldsMap); } - - - //opensearchDashboards special only, terms eval - public Set getAllPermittedIndicesForDashboards(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { + // opensearchDashboards special only, terms eval + public Set getAllPermittedIndicesForDashboards( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { Set retVal = new HashSet<>(); for (SecurityRole sr : roles) { retVal.addAll(sr.getAllResolvedPermittedIndices(Resolved._LOCAL_ALL, user, actions, resolver, cs)); @@ -460,7 +464,7 @@ public Set getAllPermittedIndicesForDashboards(Resolved resolved, User u return Collections.unmodifiableSet(retVal); } - //dnfof only + // dnfof only public Set reduce(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { Set retVal = new HashSet<>(); for (SecurityRole sr : roles) { @@ -472,7 +476,7 @@ public Set reduce(Resolved resolved, User user, String[] actions, IndexN return Collections.unmodifiableSet(retVal); } - //return true on success + // return true on success public boolean get(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { for (SecurityRole sr : roles) { if (ConfigModelV6.impliesTypePerm(sr.getIpatterns(), resolved, user, actions, resolver, cs)) { @@ -489,16 +493,20 @@ public boolean impliesClusterPermissionPermission(String action) { @Override public boolean hasExplicitClusterPermissionPermission(String action) { - return roles.stream() - .map(r -> { - final WildcardMatcher m = WildcardMatcher.from(r.clusterPerms); - return m == WildcardMatcher.ANY ? WildcardMatcher.NONE : m; - }).filter(m -> m.test(action)).count() > 0; - } - - //rolespan - public boolean impliesTypePermGlobal(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, - ClusterService cs) { + return roles.stream().map(r -> { + final WildcardMatcher m = WildcardMatcher.from(r.clusterPerms); + return m == WildcardMatcher.ANY ? WildcardMatcher.NONE : m; + }).filter(m -> m.test(action)).count() > 0; + } + + // rolespan + public boolean impliesTypePermGlobal( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { Set ipatterns = new HashSet(); roles.stream().forEach(p -> ipatterns.addAll(p.getIpatterns())); return ConfigModelV6.impliesTypePerm(ipatterns, resolved, user, actions, resolver, cs); @@ -521,14 +529,19 @@ private boolean impliesClusterPermission(String action) { return WildcardMatcher.from(clusterPerms).test(action); } - //get indices which are permitted for the given types and actions - //dnfof + opensearchDashboards special only - private Set getAllResolvedPermittedIndices(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, - ClusterService cs) { + // get indices which are permitted for the given types and actions + // dnfof + opensearchDashboards special only + private Set getAllResolvedPermittedIndices( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { final Set retVal = new HashSet<>(); for (IndexPattern p : ipatterns) { - //what if we cannot resolve one (for create purposes) + // what if we cannot resolve one (for create purposes) boolean patternMatch = false; final Set tperms = p.getTypePerms(); for (TypePerm tp : tperms) { @@ -537,24 +550,25 @@ private Set getAllResolvedPermittedIndices(Resolved resolved, User user, } } if (patternMatch) { - //resolved but can contain patterns for nonexistent indices - final WildcardMatcher permitted = WildcardMatcher.from(p.getResolvedIndexPattern(user, resolver, cs)); //maybe they do not exist + // resolved but can contain patterns for nonexistent indices + final WildcardMatcher permitted = WildcardMatcher.from(p.getResolvedIndexPattern(user, resolver, cs)); // maybe they do + // not exist final Set res = new HashSet<>(); if (!resolved.isLocalAll() && !resolved.getAllIndices().contains("*") && !resolved.getAllIndices().contains("_all")) { - //resolved but can contain patterns for nonexistent indices + // resolved but can contain patterns for nonexistent indices resolved.getAllIndices().stream().filter(permitted).forEach(res::add); } else { - //we want all indices so just return what's permitted + // we want all indices so just return what's permitted - //#557 - //final String[] allIndices = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), "*"); + // #557 + // final String[] allIndices = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), "*"); Arrays.stream(cs.state().metadata().getConcreteAllOpenIndices()).filter(permitted).forEach(res::add); } retVal.addAll(res); } } - //all that we want and all thats permitted of them + // all that we want and all thats permitted of them return Collections.unmodifiableSet(retVal); } @@ -592,44 +606,43 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; SecurityRole other = (SecurityRole) obj; if (clusterPerms == null) { - if (other.clusterPerms != null) - return false; - } else if (!clusterPerms.equals(other.clusterPerms)) - return false; + if (other.clusterPerms != null) return false; + } else if (!clusterPerms.equals(other.clusterPerms)) return false; if (ipatterns == null) { - if (other.ipatterns != null) - return false; - } else if (!ipatterns.equals(other.ipatterns)) - return false; + if (other.ipatterns != null) return false; + } else if (!ipatterns.equals(other.ipatterns)) return false; if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; + if (other.name != null) return false; + } else if (!name.equals(other.name)) return false; if (tenants == null) { - if (other.tenants != null) - return false; - } else if (!tenants.equals(other.tenants)) - return false; + if (other.tenants != null) return false; + } else if (!tenants.equals(other.tenants)) return false; return true; } @Override public String toString() { - return System.lineSeparator() + " " + name + System.lineSeparator() + " tenants=" + tenants + System.lineSeparator() - + " ipatterns=" + ipatterns + System.lineSeparator() + " clusterPerms=" + clusterPerms; + return System.lineSeparator() + + " " + + name + + System.lineSeparator() + + " tenants=" + + tenants + + System.lineSeparator() + + " ipatterns=" + + ipatterns + + System.lineSeparator() + + " clusterPerms=" + + clusterPerms; } public Set getTenants(User user) { - //TODO filter out user tenants + // TODO filter out user tenants return Collections.unmodifiableSet(tenants); } @@ -647,7 +660,7 @@ public String getName() { } - //sg roles + // sg roles public static class IndexPattern { private final String indexPattern; private String dlsQuery; @@ -702,45 +715,42 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; IndexPattern other = (IndexPattern) obj; if (dlsQuery == null) { - if (other.dlsQuery != null) - return false; - } else if (!dlsQuery.equals(other.dlsQuery)) - return false; + if (other.dlsQuery != null) return false; + } else if (!dlsQuery.equals(other.dlsQuery)) return false; if (fls == null) { - if (other.fls != null) - return false; - } else if (!fls.equals(other.fls)) - return false; + if (other.fls != null) return false; + } else if (!fls.equals(other.fls)) return false; if (maskedFields == null) { - if (other.maskedFields != null) - return false; - } else if (!maskedFields.equals(other.maskedFields)) - return false; + if (other.maskedFields != null) return false; + } else if (!maskedFields.equals(other.maskedFields)) return false; if (indexPattern == null) { - if (other.indexPattern != null) - return false; - } else if (!indexPattern.equals(other.indexPattern)) - return false; + if (other.indexPattern != null) return false; + } else if (!indexPattern.equals(other.indexPattern)) return false; if (typePerms == null) { - if (other.typePerms != null) - return false; - } else if (!typePerms.equals(other.typePerms)) - return false; + if (other.typePerms != null) return false; + } else if (!typePerms.equals(other.typePerms)) return false; return true; } @Override public String toString() { - return System.lineSeparator() + " indexPattern=" + indexPattern + System.lineSeparator() + " dlsQuery=" + dlsQuery - + System.lineSeparator() + " fls=" + fls + System.lineSeparator() + " typePerms=" + typePerms; + return System.lineSeparator() + + " indexPattern=" + + indexPattern + + System.lineSeparator() + + " dlsQuery=" + + dlsQuery + + System.lineSeparator() + + " fls=" + + fls + + System.lineSeparator() + + " typePerms=" + + typePerms; } public String getUnresolvedIndexPattern(User user) { @@ -752,11 +762,15 @@ private Set getResolvedIndexPattern(User user, IndexNameExpressionResolv WildcardMatcher matcher = WildcardMatcher.from(unresolved); String[] resolved = null; if (!(matcher instanceof WildcardMatcher.Exact)) { - final String[] aliasesForPermittedPattern = cs.state().getMetadata().getIndicesLookup().entrySet().stream() - .filter(e -> e.getValue().getType() == ALIAS) - .filter(e -> matcher.test(e.getKey())) - .map(e -> e.getKey()) - .toArray(String[]::new); + final String[] aliasesForPermittedPattern = cs.state() + .getMetadata() + .getIndicesLookup() + .entrySet() + .stream() + .filter(e -> e.getValue().getType() == ALIAS) + .filter(e -> matcher.test(e.getKey())) + .map(e -> e.getKey()) + .toArray(String[]::new); if (aliasesForPermittedPattern.length > 0) { resolved = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), aliasesForPermittedPattern); @@ -769,10 +783,7 @@ private Set getResolvedIndexPattern(User user, IndexNameExpressionResolv if (resolved == null || resolved.length == 0) { return ImmutableSet.of(unresolved); } else { - return ImmutableSet.builder() - .addAll(Arrays.asList(resolved)) - .add(unresolved) - .build(); + return ImmutableSet.builder().addAll(Arrays.asList(resolved)).add(unresolved).build(); } } @@ -820,29 +831,27 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; TypePerm other = (TypePerm) obj; if (perms == null) { - if (other.perms != null) - return false; - } else if (!perms.equals(other.perms)) - return false; + if (other.perms != null) return false; + } else if (!perms.equals(other.perms)) return false; if (typeMatcher == null) { - if (other.typeMatcher != null) - return false; - } else if (!typeMatcher.equals(other.typeMatcher)) - return false; + if (other.typeMatcher != null) return false; + } else if (!typeMatcher.equals(other.typeMatcher)) return false; return true; } @Override public String toString() { - return System.lineSeparator() + " typePattern=" + typeMatcher + System.lineSeparator() + " perms=" + perms; + return System.lineSeparator() + + " typePattern=" + + typeMatcher + + System.lineSeparator() + + " perms=" + + perms; } public WildcardMatcher getTypeMatcher() { @@ -884,26 +893,25 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; Tenant other = (Tenant) obj; - if (readWrite != other.readWrite) - return false; + if (readWrite != other.readWrite) return false; if (tenant == null) { - if (other.tenant != null) - return false; - } else if (!tenant.equals(other.tenant)) - return false; + if (other.tenant != null) return false; + } else if (!tenant.equals(other.tenant)) return false; return true; } @Override public String toString() { - return System.lineSeparator() + " tenant=" + tenant + System.lineSeparator() + " readWrite=" + readWrite; + return System.lineSeparator() + + " tenant=" + + tenant + + System.lineSeparator() + + " readWrite=" + + readWrite; } } @@ -981,38 +989,43 @@ public boolean matches(String index, String type, String action) { } } - private static boolean impliesTypePerm(Set ipatterns, Resolved resolved, User user, String[] requestedActions, - IndexNameExpressionResolver resolver, ClusterService cs) { + private static boolean impliesTypePerm( + Set ipatterns, + Resolved resolved, + User user, + String[] requestedActions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { IndexMatcherAndTypePermissions[] indexMatcherAndTypePermissions; if (resolved.isLocalAll()) { // Only let localAll pass if there is an explicit privilege for a * index pattern - indexMatcherAndTypePermissions = ipatterns - .stream() - .filter(indexPattern -> "*".equals(indexPattern.getUnresolvedIndexPattern(user))) - .map(p -> new IndexMatcherAndTypePermissions(p.getResolvedIndexPattern(user, resolver, cs), p.getTypePerms())) - .toArray(IndexMatcherAndTypePermissions[]::new); + indexMatcherAndTypePermissions = ipatterns.stream() + .filter(indexPattern -> "*".equals(indexPattern.getUnresolvedIndexPattern(user))) + .map(p -> new IndexMatcherAndTypePermissions(p.getResolvedIndexPattern(user, resolver, cs), p.getTypePerms())) + .toArray(IndexMatcherAndTypePermissions[]::new); } else { - indexMatcherAndTypePermissions = ipatterns - .stream() - .map(p -> new IndexMatcherAndTypePermissions(p.getResolvedIndexPattern(user, resolver, cs), p.getTypePerms())) - .toArray(IndexMatcherAndTypePermissions[]::new); + indexMatcherAndTypePermissions = ipatterns.stream() + .map(p -> new IndexMatcherAndTypePermissions(p.getResolvedIndexPattern(user, resolver, cs), p.getTypePerms())) + .toArray(IndexMatcherAndTypePermissions[]::new); } return resolved.getAllIndices() - .stream().allMatch(index -> - resolved.getTypes().stream().allMatch(type -> - Arrays.stream(requestedActions).allMatch(action -> - Arrays.stream(indexMatcherAndTypePermissions).anyMatch(ipatp -> - ipatp.matches(index, type, action) - ) - ) - ) - ); + .stream() + .allMatch( + index -> resolved.getTypes() + .stream() + .allMatch( + type -> Arrays.stream(requestedActions) + .allMatch( + action -> Arrays.stream(indexMatcherAndTypePermissions) + .anyMatch(ipatp -> ipatp.matches(index, type, action)) + ) + ) + ); } - - - //####### + // ####### private class TenantHolder { @@ -1023,37 +1036,39 @@ public TenantHolder(SecurityDynamicConfiguration roles) { final ExecutorService execs = Executors.newFixedThreadPool(10); - for(Entry securityRole: roles.getCEntries().entrySet()) { + for (Entry securityRole : roles.getCEntries().entrySet()) { - if(securityRole.getValue() == null) { + if (securityRole.getValue() == null) { continue; } - Future>>> future = execs.submit(new Callable>>>() { - @Override - public Tuple>> call() throws Exception { - final Set> tuples = new HashSet<>(); - final Map tenants = securityRole.getValue().getTenants(); - - if (tenants != null) { - - for (String tenant : tenants.keySet()) { - - if ("RW".equalsIgnoreCase(tenants.get(tenant))) { - //RW - tuples.add(new Tuple(tenant, true)); - } else { - //RO - //if(!tenantsMM.containsValue(value)) { //RW outperforms RO - tuples.add(new Tuple(tenant, false)); - //} + Future>>> future = execs.submit( + new Callable>>>() { + @Override + public Tuple>> call() throws Exception { + final Set> tuples = new HashSet<>(); + final Map tenants = securityRole.getValue().getTenants(); + + if (tenants != null) { + + for (String tenant : tenants.keySet()) { + + if ("RW".equalsIgnoreCase(tenants.get(tenant))) { + // RW + tuples.add(new Tuple(tenant, true)); + } else { + // RO + // if(!tenantsMM.containsValue(value)) { //RW outperforms RO + tuples.add(new Tuple(tenant, false)); + // } + } } } - } - return new Tuple>>(securityRole.getKey(), tuples); + return new Tuple>>(securityRole.getKey(), tuples); + } } - }); + ); futures.add(future); @@ -1069,7 +1084,9 @@ public Tuple>> call() throws Exception { } try { - final SetMultimap> tenantsMM_ = SetMultimapBuilder.hashKeys(futures.size()).hashSetValues(16).build(); + final SetMultimap> tenantsMM_ = SetMultimapBuilder.hashKeys(futures.size()) + .hashSetValues(16) + .build(); for (Future>>> future : futures) { Tuple>> result = future.get(); @@ -1097,14 +1114,18 @@ public Map mapTenants(final User user, Set roles) { final Map result = new HashMap<>(roles.size()); result.put(user.getName(), true); - tenantsMM.entries().stream().filter(e -> roles.contains(e.getKey())).filter(e -> !user.getName().equals(e.getValue().v1())).forEach(e -> { - final String tenant = e.getValue().v1(); - final boolean rw = e.getValue().v2(); + tenantsMM.entries() + .stream() + .filter(e -> roles.contains(e.getKey())) + .filter(e -> !user.getName().equals(e.getValue().v1())) + .forEach(e -> { + final String tenant = e.getValue().v1(); + final boolean rw = e.getValue().v2(); - if (rw || !result.containsKey(tenant)) { //RW outperforms RO - result.put(tenant, rw); - } - }); + if (rw || !result.containsKey(tenant)) { // RW outperforms RO + result.put(tenant, rw); + } + }); return Collections.unmodifiableMap(result); } @@ -1171,7 +1192,7 @@ private Set map(final User user, final TransportAddress caller) { final Set securityRoles = new HashSet<>(); if (rolesMappingResolution == ConfigConstants.RolesMappingResolution.BOTH - || rolesMappingResolution == ConfigConstants.RolesMappingResolution.BACKENDROLES_ONLY) { + || rolesMappingResolution == ConfigConstants.RolesMappingResolution.BACKENDROLES_ONLY) { if (log.isDebugEnabled()) { log.debug("Pass backendroles from {}", user); } @@ -1179,7 +1200,7 @@ private Set map(final User user, final TransportAddress caller) { } if (((rolesMappingResolution == ConfigConstants.RolesMappingResolution.BOTH - || rolesMappingResolution == ConfigConstants.RolesMappingResolution.MAPPING_ONLY))) { + || rolesMappingResolution == ConfigConstants.RolesMappingResolution.MAPPING_ONLY))) { for (String p : WildcardMatcher.getAllMatchingPatterns(userMatchers, user.getName())) { securityRoles.addAll(users.get(p)); @@ -1196,7 +1217,7 @@ private Set map(final User user, final TransportAddress caller) { } if (caller != null) { - //IPV4 or IPv6 (compressed and without scope identifiers) + // IPV4 or IPv6 (compressed and without scope identifiers) final String ipAddress = caller.getAddress(); final List hostMatchers = WildcardMatcher.matchers(hosts.keySet()); @@ -1205,7 +1226,7 @@ private Set map(final User user, final TransportAddress caller) { } if (caller.address() != null - && (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) { + && (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) { final String hostName = caller.address().getHostString(); for (String p : WildcardMatcher.getAllMatchingPatterns(hostMatchers, hostName)) { @@ -1229,10 +1250,6 @@ private Set map(final User user, final TransportAddress caller) { } } - - - - public Map mapTenants(User user, Set roles) { return tenantHolder.mapTenants(user, roles); } diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index 560cfb8a6d..1fb6e4da0e 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -83,20 +83,24 @@ public class ConfigModelV7 extends ConfigModel { private SecurityDynamicConfiguration tenants; public ConfigModelV7( - SecurityDynamicConfiguration roles, - SecurityDynamicConfiguration rolemappings, - SecurityDynamicConfiguration actiongroups, - SecurityDynamicConfiguration tenants, - DynamicConfigModel dcm, - Settings opensearchSettings) { + SecurityDynamicConfiguration roles, + SecurityDynamicConfiguration rolemappings, + SecurityDynamicConfiguration actiongroups, + SecurityDynamicConfiguration tenants, + DynamicConfigModel dcm, + Settings opensearchSettings + ) { this.roles = roles; this.tenants = tenants; try { rolesMappingResolution = ConfigConstants.RolesMappingResolution.valueOf( - opensearchSettings.get(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, ConfigConstants.RolesMappingResolution.MAPPING_ONLY.toString()) - .toUpperCase()); + opensearchSettings.get( + ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, + ConfigConstants.RolesMappingResolution.MAPPING_ONLY.toString() + ).toUpperCase() + ); } catch (Exception e) { log.error("Cannot apply roles mapping resolution", e); rolesMappingResolution = ConfigConstants.RolesMappingResolution.MAPPING_ONLY; @@ -134,15 +138,14 @@ private Set getGroupMembers(final String groupname) { private Set resolve(final SecurityDynamicConfiguration actionGroups, final String entry) { - // SG5 format, plain array - //List en = actionGroups.getAsList(DotPath.of(entry)); - //if (en.isEmpty()) { - // try SG6 format including readonly and permissions key - // en = actionGroups.getAsList(DotPath.of(entry + "." + ConfigConstants.CONFIGKEY_ACTION_GROUPS_PERMISSIONS)); - //} + // List en = actionGroups.getAsList(DotPath.of(entry)); + // if (en.isEmpty()) { + // try SG6 format including readonly and permissions key + // en = actionGroups.getAsList(DotPath.of(entry + "." + ConfigConstants.CONFIGKEY_ACTION_GROUPS_PERMISSIONS)); + // } - if(!actionGroups.getCEntries().containsKey(entry)) { + if (!actionGroups.getCEntries().containsKey(entry)) { return Collections.emptySet(); } @@ -150,27 +153,26 @@ private Set resolve(final SecurityDynamicConfiguration actionGroups, final Object actionGroupAsObject = actionGroups.getCEntries().get(entry); - if(actionGroupAsObject != null && actionGroupAsObject instanceof List) { + if (actionGroupAsObject != null && actionGroupAsObject instanceof List) { - for (final String perm: ((List) actionGroupAsObject)) { + for (final String perm : ((List) actionGroupAsObject)) { if (actionGroups.getCEntries().keySet().contains(perm)) { - ret.addAll(resolve(actionGroups,perm)); + ret.addAll(resolve(actionGroups, perm)); } else { ret.add(perm); } } - - } else if(actionGroupAsObject != null && actionGroupAsObject instanceof ActionGroupsV7) { - for (final String perm: ((ActionGroupsV7) actionGroupAsObject).getAllowed_actions()) { + } else if (actionGroupAsObject != null && actionGroupAsObject instanceof ActionGroupsV7) { + for (final String perm : ((ActionGroupsV7) actionGroupAsObject).getAllowed_actions()) { if (actionGroups.getCEntries().keySet().contains(perm)) { - ret.addAll(resolve(actionGroups,perm)); + ret.addAll(resolve(actionGroups, perm)); } else { ret.add(perm); } } } else { - throw new RuntimeException("Unable to handle "+actionGroupAsObject); + throw new RuntimeException("Unable to handle " + actionGroupAsObject); } return Collections.unmodifiableSet(ret); @@ -179,7 +181,7 @@ private Set resolve(final SecurityDynamicConfiguration actionGroups, @Override public Set resolvedActions(final List actions) { final Set resolvedActions = new HashSet(); - for (String string: actions) { + for (String string : actions) { final Set groups = getGroupMembers(string); if (groups.isEmpty()) { resolvedActions.add(string); @@ -198,7 +200,7 @@ private SecurityRoles reload(SecurityDynamicConfiguration settings) { final Set> futures = new HashSet<>(5000); final ExecutorService execs = Executors.newFixedThreadPool(10); - for(Entry securityRole: settings.getCEntries().entrySet()) { + for (Entry securityRole : settings.getCEntries().entrySet()) { Future future = execs.submit(new Callable() { @@ -206,54 +208,53 @@ private SecurityRoles reload(SecurityDynamicConfiguration settings) { public SecurityRole call() throws Exception { SecurityRole.Builder _securityRole = new SecurityRole.Builder(securityRole.getKey()); - if(securityRole.getValue() == null) { + if (securityRole.getValue() == null) { return null; } final Set permittedClusterActions = agr.resolvedActions(securityRole.getValue().getCluster_permissions()); _securityRole.addClusterPerms(permittedClusterActions); - /*for(RoleV7.Tenant tenant: securityRole.getValue().getTenant_permissions()) { + /*for(RoleV7.Tenant tenant: securityRole.getValue().getTenant_permissions()) { - //if(tenant.equals(user.getName())) { - // continue; - //} + //if(tenant.equals(user.getName())) { + // continue; + //} - if(isTenantsRw(tenant)) { - _securityRole.addTenant(new Tenant(tenant.getKey(), true)); - } else { - _securityRole.addTenant(new Tenant(tenant.getKey(), false)); - } - }*/ + if(isTenantsRw(tenant)) { + _securityRole.addTenant(new Tenant(tenant.getKey(), true)); + } else { + _securityRole.addTenant(new Tenant(tenant.getKey(), false)); + } + }*/ - for (final Index permittedAliasesIndex : securityRole.getValue().getIndex_permissions()) { + for (final Index permittedAliasesIndex : securityRole.getValue().getIndex_permissions()) { - final String dls = permittedAliasesIndex.getDls(); - final List fls = permittedAliasesIndex.getFls(); - final List maskedFields = permittedAliasesIndex.getMasked_fields(); + final String dls = permittedAliasesIndex.getDls(); + final List fls = permittedAliasesIndex.getFls(); + final List maskedFields = permittedAliasesIndex.getMasked_fields(); - for(String pat: permittedAliasesIndex.getIndex_patterns()) { - IndexPattern _indexPattern = new IndexPattern(pat); - _indexPattern.setDlsQuery(dls); - _indexPattern.addFlsFields(fls); - _indexPattern.addMaskedFields(maskedFields); - _indexPattern.addPerm(agr.resolvedActions(permittedAliasesIndex.getAllowed_actions())); + for (String pat : permittedAliasesIndex.getIndex_patterns()) { + IndexPattern _indexPattern = new IndexPattern(pat); + _indexPattern.setDlsQuery(dls); + _indexPattern.addFlsFields(fls); + _indexPattern.addMaskedFields(maskedFields); + _indexPattern.addPerm(agr.resolvedActions(permittedAliasesIndex.getAllowed_actions())); - /*for(Entry> type: permittedAliasesIndex.getValue().getTypes(-).entrySet()) { - TypePerm typePerm = new TypePerm(type.getKey()); - final List perms = type.getValue(); - typePerm.addPerms(agr.resolvedActions(perms)); - _indexPattern.addTypePerms(typePerm); - }*/ + /*for(Entry> type: permittedAliasesIndex.getValue().getTypes(-).entrySet()) { + TypePerm typePerm = new TypePerm(type.getKey()); + final List perms = type.getValue(); + typePerm.addPerms(agr.resolvedActions(perms)); + _indexPattern.addTypePerms(typePerm); + }*/ - _securityRole.addIndexPattern(_indexPattern); - - } + _securityRole.addIndexPattern(_indexPattern); } + } - return _securityRole.build(); + return _securityRole.build(); } }); @@ -286,8 +287,7 @@ public SecurityRole call() throws Exception { } } - - //beans + // beans public static class SecurityRoles implements org.opensearch.security.securityconf.SecurityRoles { @@ -316,18 +316,13 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; SecurityRoles other = (SecurityRoles) obj; if (roles == null) { - if (other.roles != null) - return false; - } else if (!roles.equals(other.roles)) - return false; + if (other.roles != null) return false; + } else if (!roles.equals(other.roles)) return false; return true; } @@ -354,14 +349,17 @@ public SecurityRoles filter(Set keep) { return retVal; } - @Override - public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, IndexNameExpressionResolver resolver, ClusterService cs, - NamedXContentRegistry namedXContentRegistry) { - + public EvaluatedDlsFlsConfig getDlsFls( + User user, + boolean dfmEmptyOverwritesAll, + IndexNameExpressionResolver resolver, + ClusterService cs, + NamedXContentRegistry namedXContentRegistry + ) { if (!containsDlsFlsConfig()) { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("No fls or dls found for {} in {} security roles", user, roles.size()); } @@ -382,17 +380,17 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, for (SecurityRole role : roles) { for (IndexPattern ip : role.getIpatterns()) { - final Set concreteIndices = ip.concreteIndexNames(user, resolver, cs); - String dls = ip.getDlsQuery(user); + final Set concreteIndices = ip.concreteIndexNames(user, resolver, cs); + String dls = ip.getDlsQuery(user); - if (dls != null && dls.length() > 0) { + if (dls != null && dls.length() > 0) { - for (String concreteIndex : concreteIndices) { - dlsQueriesByIndex.computeIfAbsent(concreteIndex, (key) -> new HashSet()).add(dls); - } - } else if (dfmEmptyOverwritesAll) { - noDlsConcreteIndices.addAll(concreteIndices); - } + for (String concreteIndex : concreteIndices) { + dlsQueriesByIndex.computeIfAbsent(concreteIndex, (key) -> new HashSet()).add(dls); + } + } else if (dfmEmptyOverwritesAll) { + noDlsConcreteIndices.addAll(concreteIndices); + } Set fls = ip.getFls(); @@ -429,12 +427,21 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, } if (dfmEmptyOverwritesAll) { if (log.isDebugEnabled()) { - log.debug("Index patterns with no dls queries attached: {} - They will be removed from {}", noDlsConcreteIndices, - dlsQueriesByIndex.keySet()); - log.debug("Index patterns with no fls fields attached: {} - They will be removed from {}", noFlsConcreteIndices, - flsFields.keySet()); - log.debug("Index patterns with no masked fields attached: {} - They will be removed from {}", noMaskedFieldConcreteIndices, - maskedFieldsMap.keySet()); + log.debug( + "Index patterns with no dls queries attached: {} - They will be removed from {}", + noDlsConcreteIndices, + dlsQueriesByIndex.keySet() + ); + log.debug( + "Index patterns with no fls fields attached: {} - They will be removed from {}", + noFlsConcreteIndices, + flsFields.keySet() + ); + log.debug( + "Index patterns with no masked fields attached: {} - They will be removed from {}", + noMaskedFieldConcreteIndices, + maskedFieldsMap.keySet() + ); } // removing the indices that do not have D/M/F restrictions // from the keySet will also modify the underlying map @@ -446,9 +453,14 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, return new EvaluatedDlsFlsConfig(dlsQueriesByIndex, flsFields, maskedFieldsMap); } - - //opensearchDashboards special only, terms eval - public Set getAllPermittedIndicesForDashboards(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { + // opensearchDashboards special only, terms eval + public Set getAllPermittedIndicesForDashboards( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { Set retVal = new HashSet<>(); for (SecurityRole sr : roles) { retVal.addAll(sr.getAllResolvedPermittedIndices(Resolved._LOCAL_ALL, user, actions, resolver, cs)); @@ -457,7 +469,7 @@ public Set getAllPermittedIndicesForDashboards(Resolved resolved, User u return Collections.unmodifiableSet(retVal); } - //dnfof only + // dnfof only public Set reduce(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { Set retVal = new HashSet<>(); for (SecurityRole sr : roles) { @@ -469,7 +481,7 @@ public Set reduce(Resolved resolved, User user, String[] actions, IndexN return Collections.unmodifiableSet(retVal); } - //return true on success + // return true on success public boolean get(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { for (SecurityRole sr : roles) { if (ConfigModelV7.impliesTypePerm(sr.getIpatterns(), resolved, user, actions, resolver, cs)) { @@ -487,13 +499,19 @@ public boolean impliesClusterPermissionPermission(String action) { @Override public boolean hasExplicitClusterPermissionPermission(String action) { return roles.stream() - .map(r -> r.clusterPerms == WildcardMatcher.ANY ? WildcardMatcher.NONE : r.clusterPerms) - .filter(m -> m.test(action)).count() > 0; - } - - //rolespan - public boolean impliesTypePermGlobal(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, - ClusterService cs) { + .map(r -> r.clusterPerms == WildcardMatcher.ANY ? WildcardMatcher.NONE : r.clusterPerms) + .filter(m -> m.test(action)) + .count() > 0; + } + + // rolespan + public boolean impliesTypePermGlobal( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { Set ipatterns = new HashSet(); roles.stream().forEach(p -> ipatterns.addAll(p.getIpatterns())); return ConfigModelV7.impliesTypePerm(ipatterns, resolved, user, actions, resolver, cs); @@ -521,6 +539,7 @@ public static final class Builder { private final String name; private final Set clusterPerms = new HashSet<>(); private final Set ipatterns = new HashSet<>(); + public Builder(String name) { this.name = Objects.requireNonNull(name); } @@ -537,7 +556,6 @@ public Builder addClusterPerms(Collection clusterPerms) { return this; } - public SecurityRole build() { return new SecurityRole(name, ipatterns, WildcardMatcher.from(clusterPerms)); } @@ -553,34 +571,40 @@ private boolean impliesClusterPermission(String action) { return clusterPerms.test(action); } - //get indices which are permitted for the given types and actions - //dnfof + opensearchDashboards special only - private Set getAllResolvedPermittedIndices(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, - ClusterService cs) { + // get indices which are permitted for the given types and actions + // dnfof + opensearchDashboards special only + private Set getAllResolvedPermittedIndices( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { final Set retVal = new HashSet<>(); for (IndexPattern p : ipatterns) { - //what if we cannot resolve one (for create purposes) + // what if we cannot resolve one (for create purposes) final boolean patternMatch = p.getPerms().matchAll(actions); -// final Set tperms = p.getTypePerms(); -// for (TypePerm tp : tperms) { -// if (WildcardMatcher.matchAny(tp.typePattern, resolved.getTypes(-).toArray(new String[0]))) { -// patternMatch = WildcardMatcher.matchAll(tp.perms.toArray(new String[0]), actions); -// } -// } + // final Set tperms = p.getTypePerms(); + // for (TypePerm tp : tperms) { + // if (WildcardMatcher.matchAny(tp.typePattern, resolved.getTypes(-).toArray(new String[0]))) { + // patternMatch = WildcardMatcher.matchAll(tp.perms.toArray(new String[0]), actions); + // } + // } if (patternMatch) { - //resolved but can contain patterns for nonexistent indices - final WildcardMatcher permitted = WildcardMatcher.from(p.attemptResolveIndexNames(user, resolver, cs)); //maybe they do not exist + // resolved but can contain patterns for nonexistent indices + final WildcardMatcher permitted = WildcardMatcher.from(p.attemptResolveIndexNames(user, resolver, cs)); // maybe they do + // not exist final Set res = new HashSet<>(); if (!resolved.isLocalAll() && !resolved.getAllIndices().contains("*") && !resolved.getAllIndices().contains("_all")) { - //resolved but can contain patterns for nonexistent indices + // resolved but can contain patterns for nonexistent indices resolved.getAllIndices().stream().filter(permitted).forEach(res::add); } else { - //we want all indices so just return what's permitted + // we want all indices so just return what's permitted - //#557 - //final String[] allIndices = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), "*"); + // #557 + // final String[] allIndices = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), "*"); final String[] allIndices = cs.state().metadata().getConcreteAllOpenIndices(); Arrays.stream(allIndices).filter(permitted).forEach(res::add); } @@ -588,7 +612,7 @@ private Set getAllResolvedPermittedIndices(Resolved resolved, User user, } } - //all that we want and all thats permitted of them + // all that we want and all thats permitted of them return Collections.unmodifiableSet(retVal); } @@ -606,52 +630,50 @@ public int hashCode() { result = prime * result + ((clusterPerms == null) ? 0 : clusterPerms.hashCode()); result = prime * result + ((ipatterns == null) ? 0 : ipatterns.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); - //result = prime * result + ((tenants == null) ? 0 : tenants.hashCode()); + // result = prime * result + ((tenants == null) ? 0 : tenants.hashCode()); return result; } @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; SecurityRole other = (SecurityRole) obj; if (clusterPerms == null) { - if (other.clusterPerms != null) - return false; - } else if (!clusterPerms.equals(other.clusterPerms)) - return false; + if (other.clusterPerms != null) return false; + } else if (!clusterPerms.equals(other.clusterPerms)) return false; if (ipatterns == null) { - if (other.ipatterns != null) - return false; - } else if (!ipatterns.equals(other.ipatterns)) - return false; + if (other.ipatterns != null) return false; + } else if (!ipatterns.equals(other.ipatterns)) return false; if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; -// if (tenants == null) { -// if (other.tenants != null) -// return false; -// } else if (!tenants.equals(other.tenants)) -// return false; + if (other.name != null) return false; + } else if (!name.equals(other.name)) return false; + // if (tenants == null) { + // if (other.tenants != null) + // return false; + // } else if (!tenants.equals(other.tenants)) + // return false; return true; } @Override public String toString() { - return System.lineSeparator() + " " + name + System.lineSeparator() - + " ipatterns=" + ipatterns + System.lineSeparator() + " clusterPerms=" + clusterPerms; - } - - //public Set getTenants(User user) { - // //TODO filter out user tenants - // return Collections.unmodifiableSet(tenants); - //} + return System.lineSeparator() + + " " + + name + + System.lineSeparator() + + " ipatterns=" + + ipatterns + + System.lineSeparator() + + " clusterPerms=" + + clusterPerms; + } + + // public Set getTenants(User user) { + // //TODO filter out user tenants + // return Collections.unmodifiableSet(tenants); + // } public Set getIpatterns() { return Collections.unmodifiableSet(ipatterns); @@ -663,7 +685,7 @@ public String getName() { } - //sg roles + // sg roles public static class IndexPattern { private final String indexPattern; private String dlsQuery; @@ -718,45 +740,42 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; IndexPattern other = (IndexPattern) obj; if (dlsQuery == null) { - if (other.dlsQuery != null) - return false; - } else if (!dlsQuery.equals(other.dlsQuery)) - return false; + if (other.dlsQuery != null) return false; + } else if (!dlsQuery.equals(other.dlsQuery)) return false; if (fls == null) { - if (other.fls != null) - return false; - } else if (!fls.equals(other.fls)) - return false; + if (other.fls != null) return false; + } else if (!fls.equals(other.fls)) return false; if (maskedFields == null) { - if (other.maskedFields != null) - return false; - } else if (!maskedFields.equals(other.maskedFields)) - return false; + if (other.maskedFields != null) return false; + } else if (!maskedFields.equals(other.maskedFields)) return false; if (indexPattern == null) { - if (other.indexPattern != null) - return false; - } else if (!indexPattern.equals(other.indexPattern)) - return false; + if (other.indexPattern != null) return false; + } else if (!indexPattern.equals(other.indexPattern)) return false; if (perms == null) { - if (other.perms != null) - return false; - } else if (!perms.equals(other.perms)) - return false; + if (other.perms != null) return false; + } else if (!perms.equals(other.perms)) return false; return true; } @Override public String toString() { - return System.lineSeparator() + " indexPattern=" + indexPattern + System.lineSeparator() + " dlsQuery=" + dlsQuery - + System.lineSeparator() + " fls=" + fls + System.lineSeparator() + " perms=" + perms; + return System.lineSeparator() + + " indexPattern=" + + indexPattern + + System.lineSeparator() + + " dlsQuery=" + + dlsQuery + + System.lineSeparator() + + " fls=" + + fls + + System.lineSeparator() + + " perms=" + + perms; } public String getUnresolvedIndexPattern(User user) { @@ -773,27 +792,45 @@ public Set attemptResolveIndexNames(final User user, final IndexNameExpr return getResolvedIndexPattern(user, resolver, cs, true); } - public Set getResolvedIndexPattern(final User user, final IndexNameExpressionResolver resolver, final ClusterService cs, final boolean appendUnresolved) { + public Set getResolvedIndexPattern( + final User user, + final IndexNameExpressionResolver resolver, + final ClusterService cs, + final boolean appendUnresolved + ) { final String unresolved = getUnresolvedIndexPattern(user); final ImmutableSet.Builder resolvedIndices = new ImmutableSet.Builder<>(); final WildcardMatcher matcher = WildcardMatcher.from(unresolved); boolean includeDataStreams = true; if (!(matcher instanceof WildcardMatcher.Exact)) { - final String[] aliasesAndDataStreamsForPermittedPattern = cs.state().getMetadata().getIndicesLookup().entrySet().stream() - .filter(e -> (e.getValue().getType() == ALIAS) || (e.getValue().getType() == DATA_STREAM)) - .filter(e -> matcher.test(e.getKey())) - .map(e -> e.getKey()) - .toArray(String[]::new); + final String[] aliasesAndDataStreamsForPermittedPattern = cs.state() + .getMetadata() + .getIndicesLookup() + .entrySet() + .stream() + .filter(e -> (e.getValue().getType() == ALIAS) || (e.getValue().getType() == DATA_STREAM)) + .filter(e -> matcher.test(e.getKey())) + .map(e -> e.getKey()) + .toArray(String[]::new); if (aliasesAndDataStreamsForPermittedPattern.length > 0) { - final String[] resolvedAliasesAndDataStreamIndices = resolver.concreteIndexNames(cs.state(), - IndicesOptions.lenientExpandOpen(), includeDataStreams, aliasesAndDataStreamsForPermittedPattern); + final String[] resolvedAliasesAndDataStreamIndices = resolver.concreteIndexNames( + cs.state(), + IndicesOptions.lenientExpandOpen(), + includeDataStreams, + aliasesAndDataStreamsForPermittedPattern + ); resolvedIndices.addAll(Arrays.asList(resolvedAliasesAndDataStreamIndices)); } } if (Strings.isNotBlank(unresolved)) { - final String[] resolvedIndicesFromPattern = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), includeDataStreams, unresolved); + final String[] resolvedIndicesFromPattern = resolver.concreteIndexNames( + cs.state(), + IndicesOptions.lenientExpandOpen(), + includeDataStreams, + unresolved + ); resolvedIndices.addAll(Arrays.asList(resolvedIndicesFromPattern)); } @@ -831,7 +868,6 @@ public WildcardMatcher getPerms() { return WildcardMatcher.from(perms); } - } /*public static class TypePerm { @@ -928,26 +964,25 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; Tenant other = (Tenant) obj; - if (readWrite != other.readWrite) - return false; + if (readWrite != other.readWrite) return false; if (tenant == null) { - if (other.tenant != null) - return false; - } else if (!tenant.equals(other.tenant)) - return false; + if (other.tenant != null) return false; + } else if (!tenant.equals(other.tenant)) return false; return true; } @Override public String toString() { - return System.lineSeparator() + " tenant=" + tenant + System.lineSeparator() + " readWrite=" + readWrite; + return System.lineSeparator() + + " tenant=" + + tenant + + System.lineSeparator() + + " readWrite=" + + readWrite; } } @@ -997,6 +1032,7 @@ private static String toQuotedCommaSeparatedString(final Set roles) { private static final class IndexMatcherAndPermissions { private WildcardMatcher matcher; private WildcardMatcher perms; + public IndexMatcherAndPermissions(Set patterns, Set perms) { this.matcher = WildcardMatcher.from(patterns); this.perms = WildcardMatcher.from(perms); @@ -1007,31 +1043,31 @@ public boolean matches(String index, String action) { } } - private static boolean impliesTypePerm(Set ipatterns, Resolved resolved, User user, String[] requestedActions, - IndexNameExpressionResolver resolver, ClusterService cs) { + private static boolean impliesTypePerm( + Set ipatterns, + Resolved resolved, + User user, + String[] requestedActions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { Set resolvedRequestedIndices = resolved.getAllIndices(); IndexMatcherAndPermissions[] indexMatcherAndPermissions; if (resolved.isLocalAll()) { - indexMatcherAndPermissions = ipatterns - .stream() - .filter(indexPattern -> "*".equals(indexPattern.getUnresolvedIndexPattern(user))) - .map(p -> new IndexMatcherAndPermissions(p.attemptResolveIndexNames(user, resolver, cs), p.perms)) - .toArray(IndexMatcherAndPermissions[]::new); + indexMatcherAndPermissions = ipatterns.stream() + .filter(indexPattern -> "*".equals(indexPattern.getUnresolvedIndexPattern(user))) + .map(p -> new IndexMatcherAndPermissions(p.attemptResolveIndexNames(user, resolver, cs), p.perms)) + .toArray(IndexMatcherAndPermissions[]::new); } else { - indexMatcherAndPermissions = ipatterns - .stream() - .map(p -> new IndexMatcherAndPermissions(p.attemptResolveIndexNames(user, resolver, cs), p.perms)) - .toArray(IndexMatcherAndPermissions[]::new); - } - return resolvedRequestedIndices - .stream() - .allMatch(index -> - Arrays.stream(requestedActions).allMatch(action -> - Arrays.stream(indexMatcherAndPermissions).anyMatch(ipap -> - ipap.matches(index, action) - ) - ) - ); + indexMatcherAndPermissions = ipatterns.stream() + .map(p -> new IndexMatcherAndPermissions(p.attemptResolveIndexNames(user, resolver, cs), p.perms)) + .toArray(IndexMatcherAndPermissions[]::new); + } + return resolvedRequestedIndices.stream() + .allMatch( + index -> Arrays.stream(requestedActions) + .allMatch(action -> Arrays.stream(indexMatcherAndPermissions).anyMatch(ipap -> ipap.matches(index, action))) + ); } private class TenantHolder { @@ -1043,38 +1079,54 @@ public TenantHolder(SecurityDynamicConfiguration roles, SecurityDynamicC final ExecutorService execs = Executors.newFixedThreadPool(10); - for(Entry securityRole: roles.getCEntries().entrySet()) { + for (Entry securityRole : roles.getCEntries().entrySet()) { - if(securityRole.getValue() == null) { + if (securityRole.getValue() == null) { continue; } - Future>>> future = execs.submit(new Callable>>>() { - @Override - public Tuple>> call() throws Exception { - final Set> tuples = new HashSet<>(); - final List tenants = securityRole.getValue().getTenant_permissions(); - if (tenants != null) { - - for (RoleV7.Tenant tenant : tenants) { - - // find Wildcarded tenant patterns - List matchingTenants = WildcardMatcher.from(tenant.getTenant_patterns()).getMatchAny(definedTenants.getCEntries().keySet(), Collectors.toList()) ; - for(String matchingTenant: matchingTenants ) { - tuples.add(new Tuple(matchingTenant, agr.resolvedActions(tenant.getAllowed_actions()).contains("kibana:saved_objects/*/write"))); - } - // find parameter substitution specified tenant - Pattern parameterPattern = Pattern.compile("^\\$\\{attr"); - List matchingParameterTenantList = tenant.getTenant_patterns().stream().filter(parameterPattern.asPredicate()).collect(Collectors.toList()); - for(String matchingParameterTenant : matchingParameterTenantList ) { - tuples.add(new Tuple(matchingParameterTenant,agr.resolvedActions(tenant.getAllowed_actions()).contains("kibana:saved_objects/*/write"))) ; + Future>>> future = execs.submit( + new Callable>>>() { + @Override + public Tuple>> call() throws Exception { + final Set> tuples = new HashSet<>(); + final List tenants = securityRole.getValue().getTenant_permissions(); + if (tenants != null) { + + for (RoleV7.Tenant tenant : tenants) { + + // find Wildcarded tenant patterns + List matchingTenants = WildcardMatcher.from(tenant.getTenant_patterns()) + .getMatchAny(definedTenants.getCEntries().keySet(), Collectors.toList()); + for (String matchingTenant : matchingTenants) { + tuples.add( + new Tuple( + matchingTenant, + agr.resolvedActions(tenant.getAllowed_actions()).contains("kibana:saved_objects/*/write") + ) + ); + } + // find parameter substitution specified tenant + Pattern parameterPattern = Pattern.compile("^\\$\\{attr"); + List matchingParameterTenantList = tenant.getTenant_patterns() + .stream() + .filter(parameterPattern.asPredicate()) + .collect(Collectors.toList()); + for (String matchingParameterTenant : matchingParameterTenantList) { + tuples.add( + new Tuple( + matchingParameterTenant, + agr.resolvedActions(tenant.getAllowed_actions()).contains("kibana:saved_objects/*/write") + ) + ); + } } } - } - return new Tuple>>(securityRole.getKey(), tuples); + return new Tuple>>(securityRole.getKey(), tuples); + } } - }); + ); futures.add(future); @@ -1090,7 +1142,9 @@ public Tuple>> call() throws Exception { } try { - final SetMultimap> tenantsMM_ = SetMultimapBuilder.hashKeys(futures.size()).hashSetValues(16).build(); + final SetMultimap> tenantsMM_ = SetMultimapBuilder.hashKeys(futures.size()) + .hashSetValues(16) + .build(); for (Future>>> future : futures) { Tuple>> result = future.get(); @@ -1118,32 +1172,33 @@ public Map mapTenants(final User user, Set roles) { final Map result = new HashMap<>(roles.size()); result.put(user.getName(), true); - tenantsMM.entries().stream().filter(e -> roles.contains(e.getKey())).filter(e -> !user.getName().equals(e.getValue().v1())).forEach(e -> { - - // replaceProperties for tenant name because - // at this point e.getValue().v1() can be in this form : "${attr.[internal|jwt|proxy|ldap].*}" - // let's substitute it with the eventual value of the user's attribute - final String tenant = replaceProperties(e.getValue().v1(),user); - final boolean rw = e.getValue().v2(); - - if (rw || !result.containsKey(tenant)) { //RW outperforms RO - - // We want to make sure that we add a tenant that exists - // Indeed, because we don't have control over what will be - // passed on as values of users' attributes, we have to make - // sure that we don't allow them to select tenants that do not exist. - if(ConfigModelV7.this.tenants.getCEntries().containsKey(tenant)) { - result.put(tenant, rw); + tenantsMM.entries() + .stream() + .filter(e -> roles.contains(e.getKey())) + .filter(e -> !user.getName().equals(e.getValue().v1())) + .forEach(e -> { + + // replaceProperties for tenant name because + // at this point e.getValue().v1() can be in this form : "${attr.[internal|jwt|proxy|ldap].*}" + // let's substitute it with the eventual value of the user's attribute + final String tenant = replaceProperties(e.getValue().v1(), user); + final boolean rw = e.getValue().v2(); + + if (rw || !result.containsKey(tenant)) { // RW outperforms RO + + // We want to make sure that we add a tenant that exists + // Indeed, because we don't have control over what will be + // passed on as values of users' attributes, we have to make + // sure that we don't allow them to select tenants that do not exist. + if (ConfigModelV7.this.tenants.getCEntries().containsKey(tenant)) { + result.put(tenant, rw); + } } - } - }); + }); Set _roles = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); _roles.addAll(roles); - if(!result.containsKey("global_tenant") && ( - _roles.contains("kibana_user") - || _roles.contains("all_access") - )) { + if (!result.containsKey("global_tenant") && (_roles.contains("kibana_user") || _roles.contains("all_access"))) { result.put("global_tenant", true); } @@ -1212,7 +1267,7 @@ private Set map(final User user, final TransportAddress caller) { final Set securityRoles = new HashSet<>(user.getSecurityRoles()); if (rolesMappingResolution == ConfigConstants.RolesMappingResolution.BOTH - || rolesMappingResolution == ConfigConstants.RolesMappingResolution.BACKENDROLES_ONLY) { + || rolesMappingResolution == ConfigConstants.RolesMappingResolution.BACKENDROLES_ONLY) { if (log.isDebugEnabled()) { log.debug("Pass backendroles from {}", user); } @@ -1220,7 +1275,7 @@ private Set map(final User user, final TransportAddress caller) { } if (((rolesMappingResolution == ConfigConstants.RolesMappingResolution.BOTH - || rolesMappingResolution == ConfigConstants.RolesMappingResolution.MAPPING_ONLY))) { + || rolesMappingResolution == ConfigConstants.RolesMappingResolution.MAPPING_ONLY))) { for (String p : WildcardMatcher.getAllMatchingPatterns(userMatchers, user.getName())) { securityRoles.addAll(users.get(p)); @@ -1236,7 +1291,7 @@ private Set map(final User user, final TransportAddress caller) { } if (caller != null) { - //IPV4 or IPv6 (compressed and without scope identifiers) + // IPV4 or IPv6 (compressed and without scope identifiers) final String ipAddress = caller.getAddress(); for (String p : WildcardMatcher.getAllMatchingPatterns(hostMatchers, ipAddress)) { @@ -1244,7 +1299,7 @@ private Set map(final User user, final TransportAddress caller) { } if (caller.address() != null - && (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) { + && (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) { final String hostName = caller.address().getHostString(); for (String p : WildcardMatcher.getAllMatchingPatterns(hostMatchers, hostName)) { @@ -1268,10 +1323,6 @@ private Set map(final User user, final TransportAddress caller) { } } - - - - public Map mapTenants(User user, Set roles) { return tenantHolder.mapTenants(user, roles); } diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java index 9d8c36576c..65be27d64f 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java @@ -89,29 +89,32 @@ static void resetStatics() { } private void loadStaticConfig() throws IOException { - JsonNode staticRolesJsonNode = DefaultObjectMapper.YAML_MAPPER - .readTree(DynamicConfigFactory.class.getResourceAsStream("/static_config/static_roles.yml")); + JsonNode staticRolesJsonNode = DefaultObjectMapper.YAML_MAPPER.readTree( + DynamicConfigFactory.class.getResourceAsStream("/static_config/static_roles.yml") + ); staticRoles = SecurityDynamicConfiguration.fromNode(staticRolesJsonNode, CType.ROLES, 2, 0, 0); - JsonNode staticActionGroupsJsonNode = DefaultObjectMapper.YAML_MAPPER - .readTree(DynamicConfigFactory.class.getResourceAsStream("/static_config/static_action_groups.yml")); + JsonNode staticActionGroupsJsonNode = DefaultObjectMapper.YAML_MAPPER.readTree( + DynamicConfigFactory.class.getResourceAsStream("/static_config/static_action_groups.yml") + ); staticActionGroups = SecurityDynamicConfiguration.fromNode(staticActionGroupsJsonNode, CType.ACTIONGROUPS, 2, 0, 0); - JsonNode staticTenantsJsonNode = DefaultObjectMapper.YAML_MAPPER - .readTree(DynamicConfigFactory.class.getResourceAsStream("/static_config/static_tenants.yml")); + JsonNode staticTenantsJsonNode = DefaultObjectMapper.YAML_MAPPER.readTree( + DynamicConfigFactory.class.getResourceAsStream("/static_config/static_tenants.yml") + ); staticTenants = SecurityDynamicConfiguration.fromNode(staticTenantsJsonNode, CType.TENANTS, 2, 0, 0); } public final static SecurityDynamicConfiguration addStatics(SecurityDynamicConfiguration original) { - if(original.getCType() == CType.ACTIONGROUPS && !staticActionGroups.getCEntries().isEmpty()) { + if (original.getCType() == CType.ACTIONGROUPS && !staticActionGroups.getCEntries().isEmpty()) { original.add(staticActionGroups.deepClone()); } - if(original.getCType() == CType.ROLES && !staticRoles.getCEntries().isEmpty()) { + if (original.getCType() == CType.ROLES && !staticRoles.getCEntries().isEmpty()) { original.add(staticRoles.deepClone()); } - if(original.getCType() == CType.TENANTS && !staticTenants.getCEntries().isEmpty()) { + if (original.getCType() == CType.TENANTS && !staticTenants.getCEntries().isEmpty()) { original.add(staticTenants.deepClone()); } @@ -128,18 +131,24 @@ public final static SecurityDynamicConfiguration addStatics(SecurityDynamicCo SecurityDynamicConfiguration config; - public DynamicConfigFactory(ConfigurationRepository cr, final Settings opensearchSettings, - final Path configPath, Client client, ThreadPool threadPool, ClusterInfoHolder cih) { + public DynamicConfigFactory( + ConfigurationRepository cr, + final Settings opensearchSettings, + final Path configPath, + Client client, + ThreadPool threadPool, + ClusterInfoHolder cih + ) { super(); this.cr = cr; this.opensearchSettings = opensearchSettings; this.configPath = configPath; - if(opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES, true)) { + if (opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES, true)) { try { loadStaticConfig(); } catch (IOException e) { - throw new StaticResourceException("Unable to load static resources due to "+e, e); + throw new StaticResourceException("Unable to load static resources due to " + e, e); } } else { log.info("Static resources will not be loaded."); @@ -162,18 +171,55 @@ public void onChange(Map> typeToConfig) { SecurityDynamicConfiguration whitelistingSetting = cr.getConfiguration(CType.WHITELIST); SecurityDynamicConfiguration allowlistingSetting = cr.getConfiguration(CType.ALLOWLIST); - if (log.isDebugEnabled()) { - String logmsg = "current config (because of " + typeToConfig.keySet() + ")\n" + - " actionGroups: " + actionGroups.getImplementingClass() + " with " + actionGroups.getCEntries().size() + " entries\n" + - " config: " + config.getImplementingClass() + " with " + config.getCEntries().size() + " entries\n" + - " internalusers: " + internalusers.getImplementingClass() + " with " + internalusers.getCEntries().size() + " entries\n" + - " roles: " + roles.getImplementingClass() + " with " + roles.getCEntries().size() + " entries\n" + - " rolesmapping: " + rolesmapping.getImplementingClass() + " with " + rolesmapping.getCEntries().size() + " entries\n" + - " tenants: " + tenants.getImplementingClass() + " with " + tenants.getCEntries().size() + " entries\n" + - " nodesdn: " + nodesDn.getImplementingClass() + " with " + nodesDn.getCEntries().size() + " entries\n" + - " whitelist " + whitelistingSetting.getImplementingClass() + " with " + whitelistingSetting.getCEntries().size() + " entries\n" + - " allowlist " + allowlistingSetting.getImplementingClass() + " with " + allowlistingSetting.getCEntries().size() + " entries\n"; + String logmsg = "current config (because of " + + typeToConfig.keySet() + + ")\n" + + " actionGroups: " + + actionGroups.getImplementingClass() + + " with " + + actionGroups.getCEntries().size() + + " entries\n" + + " config: " + + config.getImplementingClass() + + " with " + + config.getCEntries().size() + + " entries\n" + + " internalusers: " + + internalusers.getImplementingClass() + + " with " + + internalusers.getCEntries().size() + + " entries\n" + + " roles: " + + roles.getImplementingClass() + + " with " + + roles.getCEntries().size() + + " entries\n" + + " rolesmapping: " + + rolesmapping.getImplementingClass() + + " with " + + rolesmapping.getCEntries().size() + + " entries\n" + + " tenants: " + + tenants.getImplementingClass() + + " with " + + tenants.getCEntries().size() + + " entries\n" + + " nodesdn: " + + nodesDn.getImplementingClass() + + " with " + + nodesDn.getCEntries().size() + + " entries\n" + + " whitelist " + + whitelistingSetting.getImplementingClass() + + " with " + + whitelistingSetting.getCEntries().size() + + " entries\n" + + " allowlist " + + allowlistingSetting.getImplementingClass() + + " with " + + allowlistingSetting.getCEntries().size() + + " entries\n"; log.debug(logmsg); } @@ -183,70 +229,85 @@ public void onChange(Map> typeToConfig) { final NodesDnModel nm = new NodesDnModelImpl(nodesDn); final WhitelistingSettings whitelist = (WhitelistingSettings) cr.getConfiguration(CType.WHITELIST).getCEntry("config"); final AllowlistingSettings allowlist = (AllowlistingSettings) cr.getConfiguration(CType.ALLOWLIST).getCEntry("config"); - final AuditConfig audit = (AuditConfig)cr.getConfiguration(CType.AUDIT).getCEntry("config"); - - if(config.getImplementingClass() == ConfigV7.class) { - //statics - - if(roles.containsAny(staticRoles)) { - throw new StaticResourceException("Cannot override static roles"); - } - if(!roles.add(staticRoles) && !staticRoles.getCEntries().isEmpty()) { - throw new StaticResourceException("Unable to load static roles"); - } - - log.debug("Static roles loaded ({})", staticRoles.getCEntries().size()); + final AuditConfig audit = (AuditConfig) cr.getConfiguration(CType.AUDIT).getCEntry("config"); - if(actionGroups.containsAny(staticActionGroups)) { - System.out.println("static: " + actionGroups.getCEntries()); - System.out.println("Static Action Groups:" + staticActionGroups.getCEntries()); - throw new StaticResourceException("Cannot override static action groups"); - } - if(!actionGroups.add(staticActionGroups) && !staticActionGroups.getCEntries().isEmpty()) { - throw new StaticResourceException("Unable to load static action groups"); - } + if (config.getImplementingClass() == ConfigV7.class) { + // statics + if (roles.containsAny(staticRoles)) { + throw new StaticResourceException("Cannot override static roles"); + } + if (!roles.add(staticRoles) && !staticRoles.getCEntries().isEmpty()) { + throw new StaticResourceException("Unable to load static roles"); + } - log.debug("Static action groups loaded ({})", staticActionGroups.getCEntries().size()); - - if(tenants.containsAny(staticTenants)) { - throw new StaticResourceException("Cannot override static tenants"); - } - if(!tenants.add(staticTenants) && !staticTenants.getCEntries().isEmpty()) { - throw new StaticResourceException("Unable to load static tenants"); - } + log.debug("Static roles loaded ({})", staticRoles.getCEntries().size()); + if (actionGroups.containsAny(staticActionGroups)) { + System.out.println("static: " + actionGroups.getCEntries()); + System.out.println("Static Action Groups:" + staticActionGroups.getCEntries()); + throw new StaticResourceException("Cannot override static action groups"); + } + if (!actionGroups.add(staticActionGroups) && !staticActionGroups.getCEntries().isEmpty()) { + throw new StaticResourceException("Unable to load static action groups"); + } - log.debug("Static tenants loaded ({})", staticTenants.getCEntries().size()); + log.debug("Static action groups loaded ({})", staticActionGroups.getCEntries().size()); - log.debug("Static configuration loaded (total roles: {}/total action groups: {}/total tenants: {})", - roles.getCEntries().size(), actionGroups.getCEntries().size(), tenants.getCEntries().size()); + if (tenants.containsAny(staticTenants)) { + throw new StaticResourceException("Cannot override static tenants"); + } + if (!tenants.add(staticTenants) && !staticTenants.getCEntries().isEmpty()) { + throw new StaticResourceException("Unable to load static tenants"); + } + log.debug("Static tenants loaded ({})", staticTenants.getCEntries().size()); + log.debug( + "Static configuration loaded (total roles: {}/total action groups: {}/total tenants: {})", + roles.getCEntries().size(), + actionGroups.getCEntries().size(), + tenants.getCEntries().size() + ); - //rebuild v7 Models + // rebuild v7 Models dcm = new DynamicConfigModelV7(getConfigV7(config), opensearchSettings, configPath, iab); - ium = new InternalUsersModelV7((SecurityDynamicConfiguration) internalusers, + ium = new InternalUsersModelV7( + (SecurityDynamicConfiguration) internalusers, + (SecurityDynamicConfiguration) roles, + (SecurityDynamicConfiguration) rolesmapping + ); + cm = new ConfigModelV7( (SecurityDynamicConfiguration) roles, - (SecurityDynamicConfiguration) rolesmapping); - cm = new ConfigModelV7((SecurityDynamicConfiguration) roles,(SecurityDynamicConfiguration)rolesmapping, (SecurityDynamicConfiguration)actionGroups, (SecurityDynamicConfiguration) tenants,dcm, opensearchSettings); + (SecurityDynamicConfiguration) rolesmapping, + (SecurityDynamicConfiguration) actionGroups, + (SecurityDynamicConfiguration) tenants, + dcm, + opensearchSettings + ); } else { - //rebuild v6 Models + // rebuild v6 Models dcm = new DynamicConfigModelV6(getConfigV6(config), opensearchSettings, configPath, iab); ium = new InternalUsersModelV6((SecurityDynamicConfiguration) internalusers); - cm = new ConfigModelV6((SecurityDynamicConfiguration) roles, (SecurityDynamicConfiguration)actionGroups, (SecurityDynamicConfiguration)rolesmapping, dcm, opensearchSettings); + cm = new ConfigModelV6( + (SecurityDynamicConfiguration) roles, + (SecurityDynamicConfiguration) actionGroups, + (SecurityDynamicConfiguration) rolesmapping, + dcm, + opensearchSettings + ); } - //notify subscribers + // notify subscribers eventBus.post(cm); eventBus.post(dcm); eventBus.post(ium); eventBus.post(nm); - eventBus.post(whitelist==null? defaultWhitelistingSettings: whitelist); - eventBus.post(allowlist==null? defaultAllowlistingSettings: allowlist); + eventBus.post(whitelist == null ? defaultWhitelistingSettings : whitelist); + eventBus.post(allowlist == null ? defaultAllowlistingSettings : allowlist); if (cr.isAuditHotReloadingEnabled()) { eventBus.post(audit); } @@ -288,9 +349,11 @@ private static class InternalUsersModelV7 extends InternalUsersModel { private final SecurityDynamicConfiguration rolesMappingsV7SecurityDynamicConfiguration; - public InternalUsersModelV7(SecurityDynamicConfiguration internalUserV7SecurityDynamicConfiguration, - SecurityDynamicConfiguration rolesV7SecurityDynamicConfiguration, - SecurityDynamicConfiguration rolesMappingsV7SecurityDynamicConfiguration) { + public InternalUsersModelV7( + SecurityDynamicConfiguration internalUserV7SecurityDynamicConfiguration, + SecurityDynamicConfiguration rolesV7SecurityDynamicConfiguration, + SecurityDynamicConfiguration rolesMappingsV7SecurityDynamicConfiguration + ) { super(); this.internalUserV7SecurityDynamicConfiguration = internalUserV7SecurityDynamicConfiguration; this.rolesV7SecurityDynamicConfiguration = rolesV7SecurityDynamicConfiguration; @@ -305,25 +368,25 @@ public boolean exists(String user) { @Override public List getBackenRoles(String user) { InternalUserV7 tmp = internalUserV7SecurityDynamicConfiguration.getCEntry(user); - return tmp==null?null:tmp.getBackend_roles(); + return tmp == null ? null : tmp.getBackend_roles(); } @Override public Map getAttributes(String user) { InternalUserV7 tmp = internalUserV7SecurityDynamicConfiguration.getCEntry(user); - return tmp==null?null:tmp.getAttributes(); + return tmp == null ? null : tmp.getAttributes(); } @Override public String getDescription(String user) { InternalUserV7 tmp = internalUserV7SecurityDynamicConfiguration.getCEntry(user); - return tmp==null?null:tmp.getDescription(); + return tmp == null ? null : tmp.getDescription(); } @Override public String getHash(String user) { InternalUserV7 tmp = internalUserV7SecurityDynamicConfiguration.getCEntry(user); - return tmp==null?null:tmp.getHash(); + return tmp == null ? null : tmp.getHash(); } public List getSecurityRoles(String user) { @@ -331,14 +394,18 @@ public List getSecurityRoles(String user) { // Security roles should only contain roles that exist in the roles dynamic config. // We should filter out any roles that have hidden rolesmapping. - return tmp == null ? ImmutableList.of() : - tmp.getOpendistro_security_roles().stream().filter(role -> !isRolesMappingHidden(role) && rolesV7SecurityDynamicConfiguration.exists(role)).collect(ImmutableList.toImmutableList()); + return tmp == null + ? ImmutableList.of() + : tmp.getOpendistro_security_roles() + .stream() + .filter(role -> !isRolesMappingHidden(role) && rolesV7SecurityDynamicConfiguration.exists(role)) + .collect(ImmutableList.toImmutableList()); } // Remove any hidden rolesmapping from the security roles private boolean isRolesMappingHidden(String rolename) { final RoleMappingsV7 roleMapping = rolesMappingsV7SecurityDynamicConfiguration.getCEntry(rolename); - return roleMapping!=null && roleMapping.isHidden(); + return roleMapping != null && roleMapping.isHidden(); } } @@ -346,7 +413,6 @@ private static class InternalUsersModelV6 extends InternalUsersModel { SecurityDynamicConfiguration configuration; - public InternalUsersModelV6(SecurityDynamicConfiguration configuration) { super(); this.configuration = configuration; @@ -360,13 +426,13 @@ public boolean exists(String user) { @Override public List getBackenRoles(String user) { InternalUserV6 tmp = configuration.getCEntry(user); - return tmp==null?null:tmp.getRoles(); + return tmp == null ? null : tmp.getRoles(); } @Override public Map getAttributes(String user) { InternalUserV6 tmp = configuration.getCEntry(user); - return tmp==null?null:tmp.getAttributes(); + return tmp == null ? null : tmp.getAttributes(); } @Override @@ -377,7 +443,7 @@ public String getDescription(String user) { @Override public String getHash(String user) { InternalUserV6 tmp = configuration.getCEntry(user); - return tmp==null?null:tmp.getHash(); + return tmp == null ? null : tmp.getHash(); } public List getSecurityRoles(String user) { @@ -391,14 +457,17 @@ private static class NodesDnModelImpl extends NodesDnModel { public NodesDnModelImpl(SecurityDynamicConfiguration configuration) { super(); - this.configuration = null == configuration.getCType() ? SecurityDynamicConfiguration.empty() : - (SecurityDynamicConfiguration)configuration; + this.configuration = null == configuration.getCType() + ? SecurityDynamicConfiguration.empty() + : (SecurityDynamicConfiguration) configuration; } @Override public Map getNodesDn() { - return this.configuration.getCEntries().entrySet().stream().collect( - ImmutableMap.toImmutableMap(Entry::getKey, entry -> WildcardMatcher.from(entry.getValue().getNodesDn(), false))); + return this.configuration.getCEntries() + .entrySet() + .stream() + .collect(ImmutableMap.toImmutableMap(Entry::getKey, entry -> WildcardMatcher.from(entry.getValue().getNodesDn(), false))); } } } diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java index 22121bca7f..08976f2013 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java @@ -55,30 +55,53 @@ public abstract class DynamicConfigModel { protected final Logger log = LogManager.getLogger(this.getClass()); + public abstract SortedSet getRestAuthDomains(); + public abstract Set getRestAuthorizers(); + public abstract boolean isAnonymousAuthenticationEnabled(); + public abstract boolean isXffEnabled(); + public abstract String getInternalProxies(); + public abstract String getRemoteIpHeader(); + public abstract boolean isRestAuthDisabled(); + public abstract boolean isInterTransportAuthDisabled(); + public abstract boolean isRespectRequestIndicesEnabled(); + public abstract String getDashboardsServerUsername(); + public abstract String getDashboardsOpenSearchRole(); + public abstract String getDashboardsIndexname(); + public abstract boolean isDashboardsMultitenancyEnabled(); + public abstract boolean isDashboardsPrivateTenantEnabled(); + public abstract String getDashboardsDefaultTenant(); + public abstract boolean isDnfofEnabled(); + public abstract boolean isMultiRolespanEnabled(); + public abstract String getFilteredAliasMode(); + public abstract String getHostsResolverMode(); + public abstract boolean isDnfofForEmptyResultsEnabled(); public abstract List getIpAuthFailureListeners(); + public abstract Multimap getAuthBackendFailureListeners(); + public abstract List> getIpClientBlockRegistries(); + public abstract Multimap> getAuthBackendClientBlockRegistries(); protected final Map authImplMap = new HashMap<>(); @@ -114,6 +137,4 @@ public DynamicConfigModel() { authImplMap.put("username_authFailureListener", UserNameBasedRateLimiter.class.getName()); } - - } diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java index 2dce89ba7c..994989416b 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java @@ -81,27 +81,32 @@ public class DynamicConfigModelV6 extends DynamicConfigModel { public DynamicConfigModelV6(ConfigV6 config, Settings opensearchSettings, Path configPath, InternalAuthenticationBackend iab) { super(); this.config = config; - this.opensearchSettings = opensearchSettings; + this.opensearchSettings = opensearchSettings; this.configPath = configPath; this.iab = iab; buildAAA(); } + @Override public SortedSet getRestAuthDomains() { return Collections.unmodifiableSortedSet(restAuthDomains); } + @Override public Set getRestAuthorizers() { return Collections.unmodifiableSet(restAuthorizers); } + @Override public boolean isAnonymousAuthenticationEnabled() { return config.dynamic.http.anonymous_auth_enabled; } + @Override public boolean isXffEnabled() { return config.dynamic.http.xff.enabled; } + @Override public String getInternalProxies() { return config.dynamic.http.xff.internalProxies; @@ -116,44 +121,57 @@ public String getRemoteIpHeader() { public boolean isRestAuthDisabled() { return config.dynamic.disable_rest_auth; } + @Override public boolean isInterTransportAuthDisabled() { return config.dynamic.disable_intertransport_auth; } + @Override public boolean isRespectRequestIndicesEnabled() { return config.dynamic.respect_request_indices_options; } + @Override public String getDashboardsServerUsername() { return config.dynamic.kibana.server_username; } + @Override public String getDashboardsOpenSearchRole() { return config.dynamic.kibana.opendistro_role; } + @Override public String getDashboardsIndexname() { return config.dynamic.kibana.index; } + @Override public boolean isDashboardsMultitenancyEnabled() { return config.dynamic.kibana.multitenancy_enabled; } + @Override public boolean isDashboardsPrivateTenantEnabled() { return config.dynamic.kibana.private_tenant_enabled; } + @Override - public String getDashboardsDefaultTenant() { return config.dynamic.kibana.default_tenant; } + public String getDashboardsDefaultTenant() { + return config.dynamic.kibana.default_tenant; + } + @Override public boolean isDnfofEnabled() { return config.dynamic.do_not_fail_on_forbidden || config.dynamic.kibana.do_not_fail_on_forbidden; } + @Override public boolean isMultiRolespanEnabled() { return config.dynamic.multi_rolespan_enabled; } + @Override public String getFilteredAliasMode() { return config.dynamic.filtered_alias_mode; @@ -208,26 +226,33 @@ private void buildAAA() { final boolean httpEnabled = enabled && ad.getValue().http_enabled; final boolean transportEnabled = enabled && ad.getValue().transport_enabled; - if (httpEnabled || transportEnabled) { try { final String authzBackendClazz = ad.getValue().authorization_backend.type; final AuthorizationBackend authorizationBackend; - if(authzBackendClazz.equals(InternalAuthenticationBackend.class.getName()) //NOSONAR - || authzBackendClazz.equals("internal") - || authzBackendClazz.equals("intern")) { + if (authzBackendClazz.equals(InternalAuthenticationBackend.class.getName()) // NOSONAR + || authzBackendClazz.equals("internal") + || authzBackendClazz.equals("intern")) { authorizationBackend = iab; ReflectionHelper.addLoadedModule(InternalAuthenticationBackend.class); } else { authorizationBackend = newInstance( - authzBackendClazz,"z", - Settings.builder() + authzBackendClazz, + "z", + Settings.builder() .put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("authorization_backend.config")), DynamicConfiguration.checkKeyFunction()).build(), configPath); - .put(Settings.builder().loadFromSource(ad.getValue().authorization_backend.configAsJson(), XContentType.JSON).build()).build() - , configPath); + // .putProperties(ads.getAsStringMap(DotPath.of("authorization_backend.config")), + // DynamicConfiguration.checkKeyFunction()).build(), configPath); + .put( + Settings.builder() + .loadFromSource(ad.getValue().authorization_backend.configAsJson(), XContentType.JSON) + .build() + ) + .build(), + configPath + ); } if (httpEnabled) { @@ -242,7 +267,7 @@ private void buildAAA() { destroyableComponents0.add((Destroyable) authorizationBackend); } } catch (final Exception e) { - log.error("Unable to initialize AuthorizationBackend {} due to {}", ad, e.toString(),e); + log.error("Unable to initialize AuthorizationBackend {} due to {}", ad, e.toString(), e); } } } @@ -258,31 +283,56 @@ private void buildAAA() { try { AuthenticationBackend authenticationBackend; final String authBackendClazz = ad.getValue().authentication_backend.type; - if(authBackendClazz.equals(InternalAuthenticationBackend.class.getName()) //NOSONAR - || authBackendClazz.equals("internal") - || authBackendClazz.equals("intern")) { + if (authBackendClazz.equals(InternalAuthenticationBackend.class.getName()) // NOSONAR + || authBackendClazz.equals("internal") + || authBackendClazz.equals("intern")) { authenticationBackend = iab; ReflectionHelper.addLoadedModule(InternalAuthenticationBackend.class); } else { authenticationBackend = newInstance( - authBackendClazz,"c", - Settings.builder() + authBackendClazz, + "c", + Settings.builder() .put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("authentication_backend.config")), DynamicConfiguration.checkKeyFunction()).build() - .put(Settings.builder().loadFromSource(ad.getValue().authentication_backend.configAsJson(), XContentType.JSON).build()).build() - , configPath); + // .putProperties(ads.getAsStringMap(DotPath.of("authentication_backend.config")), + // DynamicConfiguration.checkKeyFunction()).build() + .put( + Settings.builder() + .loadFromSource(ad.getValue().authentication_backend.configAsJson(), XContentType.JSON) + .build() + ) + .build(), + configPath + ); } - String httpAuthenticatorType = ad.getValue().http_authenticator.type; //no default - HTTPAuthenticator httpAuthenticator = httpAuthenticatorType==null?null: (HTTPAuthenticator) newInstance(httpAuthenticatorType,"h", - Settings.builder().put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("http_authenticator.config")), DynamicConfiguration.checkKeyFunction()).build(), - .put(Settings.builder().loadFromSource(ad.getValue().http_authenticator.configAsJson(), XContentType.JSON).build()).build() - - , configPath); - - final AuthDomain _ad = new AuthDomain(authenticationBackend, httpAuthenticator, - ad.getValue().http_authenticator.challenge, ad.getValue().order); + String httpAuthenticatorType = ad.getValue().http_authenticator.type; // no default + HTTPAuthenticator httpAuthenticator = httpAuthenticatorType == null + ? null + : (HTTPAuthenticator) newInstance( + httpAuthenticatorType, + "h", + Settings.builder() + .put(opensearchSettings) + // .putProperties(ads.getAsStringMap(DotPath.of("http_authenticator.config")), + // DynamicConfiguration.checkKeyFunction()).build(), + .put( + Settings.builder() + .loadFromSource(ad.getValue().http_authenticator.configAsJson(), XContentType.JSON) + .build() + ) + .build() + + , + configPath + ); + + final AuthDomain _ad = new AuthDomain( + authenticationBackend, + httpAuthenticator, + ad.getValue().http_authenticator.challenge, + ad.getValue().order + ); if (httpEnabled && _ad.getHttpAuthenticator() != null) { restAuthDomains0.add(_ad); @@ -316,14 +366,19 @@ private void buildAAA() { destroyableComponents = Collections.unmodifiableList(destroyableComponents0); - if(originalDestroyableComponents != null) { + if (originalDestroyableComponents != null) { destroyDestroyables(originalDestroyableComponents); } originalDestroyableComponents = null; - createAuthFailureListeners(ipAuthFailureListeners0, - authBackendFailureListeners0, ipClientBlockRegistries0, authBackendClientBlockRegistries0, destroyableComponents0); + createAuthFailureListeners( + ipAuthFailureListeners0, + authBackendFailureListeners0, + ipClientBlockRegistries0, + authBackendClientBlockRegistries0, + destroyableComponents0 + ); ipAuthFailureListeners = Collections.unmodifiableList(ipAuthFailureListeners0); ipClientBlockRegistries = Collections.unmodifiableList(ipClientBlockRegistries0); @@ -346,8 +401,8 @@ private T newInstance(final String clazzOrShortcut, String type, final Setti String clazz = clazzOrShortcut; - if(authImplMap.containsKey(clazz+"_"+type)) { - clazz = authImplMap.get(clazz+"_"+type); + if (authImplMap.containsKey(clazz + "_" + type)) { + clazz = authImplMap.get(clazz + "_" + type); } return ReflectionHelper.instantiateAAA(clazz, settings, configPath); @@ -362,15 +417,20 @@ private String translateShortcutToClassName(final String clazzOrShortcut, final } } - private void createAuthFailureListeners(List ipAuthFailureListeners, - Multimap authBackendFailureListeners, List> ipClientBlockRegistries, - Multimap> authBackendUserClientBlockRegistries, List destroyableComponents0) { + private void createAuthFailureListeners( + List ipAuthFailureListeners, + Multimap authBackendFailureListeners, + List> ipClientBlockRegistries, + Multimap> authBackendUserClientBlockRegistries, + List destroyableComponents0 + ) { for (Entry entry : config.dynamic.auth_failure_listeners.getListeners().entrySet()) { Settings entrySettings = Settings.builder() - .put(opensearchSettings) - .put(Settings.builder().loadFromSource(entry.getValue().asJson(), XContentType.JSON).build()).build(); + .put(opensearchSettings) + .put(Settings.builder().loadFromSource(entry.getValue().asJson(), XContentType.JSON).build()) + .build(); String type = entry.getValue().type; String authenticationBackend = entry.getValue().authentication_backend; @@ -387,8 +447,13 @@ private void createAuthFailureListeners(List ipAuthFailureL ipClientBlockRegistries.add(clientBlockRegistry); } else { - log.error("Illegal ClientIdType for AuthFailureListener" + entry.getKey() + ": " - + ((ClientBlockRegistry) authFailureListener).getClientIdType() + "; must be InetAddress."); + log.error( + "Illegal ClientIdType for AuthFailureListener" + + entry.getKey() + + ": " + + ((ClientBlockRegistry) authFailureListener).getClientIdType() + + "; must be InetAddress." + ); } } @@ -405,8 +470,13 @@ private void createAuthFailureListeners(List ipAuthFailureL authBackendUserClientBlockRegistries.put(authenticationBackend, clientBlockRegistry); } else { - log.error("Illegal ClientIdType for AuthFailureListener" + entry.getKey() + ": " - + ((ClientBlockRegistry) authFailureListener).getClientIdType() + "; must be InetAddress."); + log.error( + "Illegal ClientIdType for AuthFailureListener" + + entry.getKey() + + ": " + + ((ClientBlockRegistry) authFailureListener).getClientIdType() + + "; must be InetAddress." + ); } } } diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java index 8e92675dcc..f6bbcc2161 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java @@ -81,27 +81,32 @@ public class DynamicConfigModelV7 extends DynamicConfigModel { public DynamicConfigModelV7(ConfigV7 config, Settings opensearchSettings, Path configPath, InternalAuthenticationBackend iab) { super(); this.config = config; - this.opensearchSettings = opensearchSettings; + this.opensearchSettings = opensearchSettings; this.configPath = configPath; this.iab = iab; buildAAA(); } + @Override public SortedSet getRestAuthDomains() { return Collections.unmodifiableSortedSet(restAuthDomains); } + @Override public Set getRestAuthorizers() { return Collections.unmodifiableSet(restAuthorizers); } + @Override public boolean isAnonymousAuthenticationEnabled() { return config.dynamic.http.anonymous_auth_enabled; } + @Override public boolean isXffEnabled() { return config.dynamic.http.xff.enabled; } + @Override public String getInternalProxies() { return config.dynamic.http.xff.internalProxies; @@ -116,44 +121,57 @@ public String getRemoteIpHeader() { public boolean isRestAuthDisabled() { return config.dynamic.disable_rest_auth; } + @Override public boolean isInterTransportAuthDisabled() { return config.dynamic.disable_intertransport_auth; } + @Override public boolean isRespectRequestIndicesEnabled() { return config.dynamic.respect_request_indices_options; } + @Override public String getDashboardsServerUsername() { return config.dynamic.kibana.server_username; } + @Override public String getDashboardsOpenSearchRole() { return config.dynamic.kibana.opendistro_role; } + @Override public String getDashboardsIndexname() { return config.dynamic.kibana.index; } + @Override public boolean isDashboardsMultitenancyEnabled() { return config.dynamic.kibana.multitenancy_enabled; } + @Override public boolean isDashboardsPrivateTenantEnabled() { return config.dynamic.kibana.private_tenant_enabled; } + @Override - public String getDashboardsDefaultTenant() { return config.dynamic.kibana.default_tenant; } + public String getDashboardsDefaultTenant() { + return config.dynamic.kibana.default_tenant; + } + @Override public boolean isDnfofEnabled() { return config.dynamic.do_not_fail_on_forbidden; } + @Override public boolean isMultiRolespanEnabled() { return config.dynamic.multi_rolespan_enabled; } + @Override public String getFilteredAliasMode() { return config.dynamic.filtered_alias_mode; @@ -189,7 +207,6 @@ public Multimap> getAuthBackendClientBlockRe return Multimaps.unmodifiableMultimap(authBackendClientBlockRegistries); } - private void buildAAA() { final SortedSet restAuthDomains0 = new TreeSet<>(); @@ -208,26 +225,33 @@ private void buildAAA() { final boolean httpEnabled = ad.getValue().http_enabled; final boolean transportEnabled = ad.getValue().transport_enabled; - if (httpEnabled || transportEnabled) { try { final String authzBackendClazz = ad.getValue().authorization_backend.type; final AuthorizationBackend authorizationBackend; - if(authzBackendClazz.equals(InternalAuthenticationBackend.class.getName()) //NOSONAR - || authzBackendClazz.equals("internal") - || authzBackendClazz.equals("intern")) { + if (authzBackendClazz.equals(InternalAuthenticationBackend.class.getName()) // NOSONAR + || authzBackendClazz.equals("internal") + || authzBackendClazz.equals("intern")) { authorizationBackend = iab; ReflectionHelper.addLoadedModule(InternalAuthenticationBackend.class); } else { authorizationBackend = newInstance( - authzBackendClazz,"z", - Settings.builder() + authzBackendClazz, + "z", + Settings.builder() .put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("authorization_backend.config")), DynamicConfiguration.checkKeyFunction()).build(), configPath); - .put(Settings.builder().loadFromSource(ad.getValue().authorization_backend.configAsJson(), XContentType.JSON).build()).build() - , configPath); + // .putProperties(ads.getAsStringMap(DotPath.of("authorization_backend.config")), + // DynamicConfiguration.checkKeyFunction()).build(), configPath); + .put( + Settings.builder() + .loadFromSource(ad.getValue().authorization_backend.configAsJson(), XContentType.JSON) + .build() + ) + .build(), + configPath + ); } if (httpEnabled) { @@ -242,7 +266,7 @@ private void buildAAA() { destroyableComponents0.add((Destroyable) authorizationBackend); } } catch (final Exception e) { - log.error("Unable to initialize AuthorizationBackend {} due to {}", ad, e.toString(),e); + log.error("Unable to initialize AuthorizationBackend {} due to {}", ad, e.toString(), e); } } } @@ -257,31 +281,56 @@ private void buildAAA() { try { AuthenticationBackend authenticationBackend; final String authBackendClazz = ad.getValue().authentication_backend.type; - if(authBackendClazz.equals(InternalAuthenticationBackend.class.getName()) //NOSONAR - || authBackendClazz.equals("internal") - || authBackendClazz.equals("intern")) { + if (authBackendClazz.equals(InternalAuthenticationBackend.class.getName()) // NOSONAR + || authBackendClazz.equals("internal") + || authBackendClazz.equals("intern")) { authenticationBackend = iab; ReflectionHelper.addLoadedModule(InternalAuthenticationBackend.class); } else { authenticationBackend = newInstance( - authBackendClazz,"c", - Settings.builder() + authBackendClazz, + "c", + Settings.builder() .put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("authentication_backend.config")), DynamicConfiguration.checkKeyFunction()).build() - .put(Settings.builder().loadFromSource(ad.getValue().authentication_backend.configAsJson(), XContentType.JSON).build()).build() - , configPath); + // .putProperties(ads.getAsStringMap(DotPath.of("authentication_backend.config")), + // DynamicConfiguration.checkKeyFunction()).build() + .put( + Settings.builder() + .loadFromSource(ad.getValue().authentication_backend.configAsJson(), XContentType.JSON) + .build() + ) + .build(), + configPath + ); } - String httpAuthenticatorType = ad.getValue().http_authenticator.type; //no default - HTTPAuthenticator httpAuthenticator = httpAuthenticatorType==null?null: (HTTPAuthenticator) newInstance(httpAuthenticatorType,"h", - Settings.builder().put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("http_authenticator.config")), DynamicConfiguration.checkKeyFunction()).build(), - .put(Settings.builder().loadFromSource(ad.getValue().http_authenticator.configAsJson(), XContentType.JSON).build()).build() - - , configPath); - - final AuthDomain _ad = new AuthDomain(authenticationBackend, httpAuthenticator, - ad.getValue().http_authenticator.challenge, ad.getValue().order); + String httpAuthenticatorType = ad.getValue().http_authenticator.type; // no default + HTTPAuthenticator httpAuthenticator = httpAuthenticatorType == null + ? null + : (HTTPAuthenticator) newInstance( + httpAuthenticatorType, + "h", + Settings.builder() + .put(opensearchSettings) + // .putProperties(ads.getAsStringMap(DotPath.of("http_authenticator.config")), + // DynamicConfiguration.checkKeyFunction()).build(), + .put( + Settings.builder() + .loadFromSource(ad.getValue().http_authenticator.configAsJson(), XContentType.JSON) + .build() + ) + .build() + + , + configPath + ); + + final AuthDomain _ad = new AuthDomain( + authenticationBackend, + httpAuthenticator, + ad.getValue().http_authenticator.challenge, + ad.getValue().order + ); if (httpEnabled && _ad.getHttpAuthenticator() != null) { restAuthDomains0.add(_ad); @@ -315,14 +364,19 @@ private void buildAAA() { destroyableComponents = Collections.unmodifiableList(destroyableComponents0); - if(originalDestroyableComponents != null) { + if (originalDestroyableComponents != null) { destroyDestroyables(originalDestroyableComponents); } originalDestroyableComponents = null; - createAuthFailureListeners(ipAuthFailureListeners0, - authBackendFailureListeners0, ipClientBlockRegistries0, authBackendClientBlockRegistries0, destroyableComponents0); + createAuthFailureListeners( + ipAuthFailureListeners0, + authBackendFailureListeners0, + ipClientBlockRegistries0, + authBackendClientBlockRegistries0, + destroyableComponents0 + ); ipAuthFailureListeners = Collections.unmodifiableList(ipAuthFailureListeners0); ipClientBlockRegistries = Collections.unmodifiableList(ipClientBlockRegistries0); @@ -345,8 +399,8 @@ private T newInstance(final String clazzOrShortcut, String type, final Setti String clazz = clazzOrShortcut; - if(authImplMap.containsKey(clazz+"_"+type)) { - clazz = authImplMap.get(clazz+"_"+type); + if (authImplMap.containsKey(clazz + "_" + type)) { + clazz = authImplMap.get(clazz + "_" + type); } return ReflectionHelper.instantiateAAA(clazz, settings, configPath); @@ -361,15 +415,20 @@ private String translateShortcutToClassName(final String clazzOrShortcut, final } } - private void createAuthFailureListeners(List ipAuthFailureListeners, - Multimap authBackendFailureListeners, List> ipClientBlockRegistries, - Multimap> authBackendUserClientBlockRegistries, List destroyableComponents0) { + private void createAuthFailureListeners( + List ipAuthFailureListeners, + Multimap authBackendFailureListeners, + List> ipClientBlockRegistries, + Multimap> authBackendUserClientBlockRegistries, + List destroyableComponents0 + ) { for (Entry entry : config.dynamic.auth_failure_listeners.getListeners().entrySet()) { Settings entrySettings = Settings.builder() - .put(opensearchSettings) - .put(Settings.builder().loadFromSource(entry.getValue().asJson(), XContentType.JSON).build()).build(); + .put(opensearchSettings) + .put(Settings.builder().loadFromSource(entry.getValue().asJson(), XContentType.JSON).build()) + .build(); String type = entry.getValue().type; String authenticationBackend = entry.getValue().authentication_backend; @@ -386,8 +445,13 @@ private void createAuthFailureListeners(List ipAuthFailureL ipClientBlockRegistries.add(clientBlockRegistry); } else { - log.error("Illegal ClientIdType for AuthFailureListener" + entry.getKey() + ": " - + ((ClientBlockRegistry) authFailureListener).getClientIdType() + "; must be InetAddress."); + log.error( + "Illegal ClientIdType for AuthFailureListener" + + entry.getKey() + + ": " + + ((ClientBlockRegistry) authFailureListener).getClientIdType() + + "; must be InetAddress." + ); } } @@ -404,8 +468,13 @@ private void createAuthFailureListeners(List ipAuthFailureL authBackendUserClientBlockRegistries.put(authenticationBackend, clientBlockRegistry); } else { - log.error("Illegal ClientIdType for AuthFailureListener" + entry.getKey() + ": " - + ((ClientBlockRegistry) authFailureListener).getClientIdType() + "; must be InetAddress."); + log.error( + "Illegal ClientIdType for AuthFailureListener" + + entry.getKey() + + ": " + + ((ClientBlockRegistry) authFailureListener).getClientIdType() + + "; must be InetAddress." + ); } } } diff --git a/src/main/java/org/opensearch/security/securityconf/EvaluatedDlsFlsConfig.java b/src/main/java/org/opensearch/security/securityconf/EvaluatedDlsFlsConfig.java index 8870cb3aad..aa22e8729f 100644 --- a/src/main/java/org/opensearch/security/securityconf/EvaluatedDlsFlsConfig.java +++ b/src/main/java/org/opensearch/security/securityconf/EvaluatedDlsFlsConfig.java @@ -21,14 +21,21 @@ import org.opensearch.security.support.WildcardMatcher; public class EvaluatedDlsFlsConfig { - public static EvaluatedDlsFlsConfig EMPTY = new EvaluatedDlsFlsConfig(Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap()); + public static EvaluatedDlsFlsConfig EMPTY = new EvaluatedDlsFlsConfig( + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyMap() + ); private final Map> dlsQueriesByIndex; private final Map> flsByIndex; private final Map> fieldMaskingByIndex; - public EvaluatedDlsFlsConfig(Map> dlsQueriesByIndex, Map> flsByIndex, - Map> fieldMaskingByIndex) { + public EvaluatedDlsFlsConfig( + Map> dlsQueriesByIndex, + Map> flsByIndex, + Map> fieldMaskingByIndex + ) { this.dlsQueriesByIndex = Collections.unmodifiableMap(dlsQueriesByIndex); this.flsByIndex = Collections.unmodifiableMap(flsByIndex); this.fieldMaskingByIndex = Collections.unmodifiableMap(fieldMaskingByIndex); @@ -88,8 +95,11 @@ public EvaluatedDlsFlsConfig filter(Resolved indices) { } else { Set allIndices = indices.getAllIndices(); - return new EvaluatedDlsFlsConfig(filter(dlsQueriesByIndex, allIndices), filter(flsByIndex, allIndices), - filter(fieldMaskingByIndex, allIndices)); + return new EvaluatedDlsFlsConfig( + filter(dlsQueriesByIndex, allIndices), + filter(flsByIndex, allIndices), + filter(fieldMaskingByIndex, allIndices) + ); } } @@ -119,8 +129,13 @@ private Map> filter(Map> map, Set getBackenRoles(String user); + public abstract Map getAttributes(String user); + public abstract String getDescription(String user); + public abstract String getHash(String user); + public abstract List getSecurityRoles(String user); } diff --git a/src/main/java/org/opensearch/security/securityconf/Migration.java b/src/main/java/org/opensearch/security/securityconf/Migration.java index ec6a5525b9..4eeaba5c68 100644 --- a/src/main/java/org/opensearch/security/securityconf/Migration.java +++ b/src/main/java/org/opensearch/security/securityconf/Migration.java @@ -53,10 +53,12 @@ import org.opensearch.security.securityconf.impl.v7.RoleV7; import org.opensearch.security.securityconf.impl.v7.TenantV7; - public class Migration { - public static Tuple,SecurityDynamicConfiguration> migrateRoles(SecurityDynamicConfiguration r6cs, SecurityDynamicConfiguration rms6) throws MigrationException { + public static Tuple, SecurityDynamicConfiguration> migrateRoles( + SecurityDynamicConfiguration r6cs, + SecurityDynamicConfiguration rms6 + ) throws MigrationException { final SecurityDynamicConfiguration r7 = SecurityDynamicConfiguration.empty(); r7.setCType(r6cs.getCType()); @@ -72,11 +74,11 @@ public static Tuple,SecurityDynamicConfigur Set dedupTenants = new HashSet<>(); - for(final Entry r6e: r6cs.getCEntries().entrySet()) { - final String roleName = r6e.getKey(); + for (final Entry r6e : r6cs.getCEntries().entrySet()) { + final String roleName = r6e.getKey(); final RoleV6 r6 = r6e.getValue(); - if(r6 == null) { + if (r6 == null) { RoleV7 noPermRole = new RoleV7(); noPermRole.setDescription("Migrated from v6, was empty"); r7.putCEntry(roleName, noPermRole); @@ -85,18 +87,18 @@ public static Tuple,SecurityDynamicConfigur r7.putCEntry(roleName, new RoleV7(r6)); - for(Entry tenant: r6.getTenants().entrySet()) { + for (Entry tenant : r6.getTenants().entrySet()) { dedupTenants.add(tenant.getKey()); } } - if(rms6 != null) { - for(final Entry r6m: rms6.getCEntries().entrySet()) { - final String roleName = r6m.getKey(); - //final RoleMappingsV6 r6 = r6m.getValue(); + if (rms6 != null) { + for (final Entry r6m : rms6.getCEntries().entrySet()) { + final String roleName = r6m.getKey(); + // final RoleMappingsV6 r6 = r6m.getValue(); - if(!r7.exists(roleName)) { - //rolemapping but role does not exists + if (!r7.exists(roleName)) { + // rolemapping but role does not exists RoleV7 noPermRole = new RoleV7(); noPermRole.setDescription("Migrated from v6, was in rolemappings but no role existed"); r7.putCEntry(roleName, noPermRole); @@ -105,7 +107,7 @@ public static Tuple,SecurityDynamicConfigur } } - for(String tenantName: dedupTenants) { + for (String tenantName : dedupTenants) { TenantV7 entry = new TenantV7(); entry.setDescription("Migrated from v6"); t7.putCEntry(tenantName, entry); @@ -115,22 +117,25 @@ public static Tuple,SecurityDynamicConfigur } - public static SecurityDynamicConfiguration migrateConfig(SecurityDynamicConfiguration r6cs) throws MigrationException { + public static SecurityDynamicConfiguration migrateConfig(SecurityDynamicConfiguration r6cs) + throws MigrationException { final SecurityDynamicConfiguration c7 = SecurityDynamicConfiguration.empty(); c7.setCType(r6cs.getCType()); c7.set_meta(new Meta()); c7.get_meta().setConfig_version(2); c7.get_meta().setType("config"); - if(r6cs.getCEntries().size() != 1) { - throw new MigrationException("Unable to migrate config because expected size was 1 but actual size is "+r6cs.getCEntries().size()); + if (r6cs.getCEntries().size() != 1) { + throw new MigrationException( + "Unable to migrate config because expected size was 1 but actual size is " + r6cs.getCEntries().size() + ); } - if(r6cs.getCEntries().get("opendistro_security") == null) { + if (r6cs.getCEntries().get("opendistro_security") == null) { throw new MigrationException("Unable to migrate config because 'opendistro_security' key not found"); } - for(final Entry r6c: r6cs.getCEntries().entrySet()) { + for (final Entry r6c : r6cs.getCEntries().entrySet()) { c7.putCEntry("config", new ConfigV7(r6c.getValue())); } return c7; @@ -143,54 +148,60 @@ public static SecurityDynamicConfiguration migrateNodesDn(SecurityDynam migrated.get_meta().setConfig_version(2); migrated.get_meta().setType("nodesdn"); - for(final Entry entry: nodesDn.getCEntries().entrySet()) { + for (final Entry entry : nodesDn.getCEntries().entrySet()) { migrated.putCEntry(entry.getKey(), new NodesDn(entry.getValue())); } return migrated; } - public static SecurityDynamicConfiguration migrateWhitelistingSetting(SecurityDynamicConfiguration whitelistingSetting) { + public static SecurityDynamicConfiguration migrateWhitelistingSetting( + SecurityDynamicConfiguration whitelistingSetting + ) { final SecurityDynamicConfiguration migrated = SecurityDynamicConfiguration.empty(); migrated.setCType(whitelistingSetting.getCType()); migrated.set_meta(new Meta()); migrated.get_meta().setConfig_version(2); migrated.get_meta().setType("whitelist"); - for(final Entry entry: whitelistingSetting.getCEntries().entrySet()) { + for (final Entry entry : whitelistingSetting.getCEntries().entrySet()) { migrated.putCEntry(entry.getKey(), new WhitelistingSettings(entry.getValue())); } return migrated; } - public static SecurityDynamicConfiguration migrateAllowlistingSetting(SecurityDynamicConfiguration allowlistingSetting) { + public static SecurityDynamicConfiguration migrateAllowlistingSetting( + SecurityDynamicConfiguration allowlistingSetting + ) { final SecurityDynamicConfiguration migrated = SecurityDynamicConfiguration.empty(); migrated.setCType(allowlistingSetting.getCType()); migrated.set_meta(new Meta()); migrated.get_meta().setConfig_version(2); migrated.get_meta().setType("whitelist"); - for(final Entry entry: allowlistingSetting.getCEntries().entrySet()) { + for (final Entry entry : allowlistingSetting.getCEntries().entrySet()) { migrated.putCEntry(entry.getKey(), new AllowlistingSettings(entry.getValue())); } return migrated; } - public static SecurityDynamicConfiguration migrateInternalUsers(SecurityDynamicConfiguration r6is) throws MigrationException { + public static SecurityDynamicConfiguration migrateInternalUsers(SecurityDynamicConfiguration r6is) + throws MigrationException { final SecurityDynamicConfiguration i7 = SecurityDynamicConfiguration.empty(); i7.setCType(r6is.getCType()); i7.set_meta(new Meta()); i7.get_meta().setConfig_version(2); i7.get_meta().setType("internalusers"); - for(final Entry r6i: r6is.getCEntries().entrySet()) { - final String username = !Strings.isNullOrEmpty(r6i.getValue().getUsername())?r6i.getValue().getUsername():r6i.getKey(); + for (final Entry r6i : r6is.getCEntries().entrySet()) { + final String username = !Strings.isNullOrEmpty(r6i.getValue().getUsername()) ? r6i.getValue().getUsername() : r6i.getKey(); i7.putCEntry(username, new InternalUserV7(r6i.getValue())); } return i7; } - public static SecurityDynamicConfiguration migrateActionGroups(SecurityDynamicConfiguration r6as) throws MigrationException { + public static SecurityDynamicConfiguration migrateActionGroups(SecurityDynamicConfiguration r6as) + throws MigrationException { final SecurityDynamicConfiguration a7 = SecurityDynamicConfiguration.empty(); a7.setCType(r6as.getCType()); @@ -198,27 +209,28 @@ public static SecurityDynamicConfiguration migrateActionGroups( a7.get_meta().setConfig_version(2); a7.get_meta().setType("actiongroups"); - if(r6as.getImplementingClass().isAssignableFrom(List.class)) { - for(final Entry r6a: r6as.getCEntries().entrySet()) { + if (r6as.getImplementingClass().isAssignableFrom(List.class)) { + for (final Entry r6a : r6as.getCEntries().entrySet()) { a7.putCEntry(r6a.getKey(), new ActionGroupsV7(r6a.getKey(), (List) r6a.getValue())); } } else { - for(final Entry r6a: r6as.getCEntries().entrySet()) { - a7.putCEntry(r6a.getKey(), new ActionGroupsV7(r6a.getKey(), (ActionGroupsV6)r6a.getValue())); + for (final Entry r6a : r6as.getCEntries().entrySet()) { + a7.putCEntry(r6a.getKey(), new ActionGroupsV7(r6a.getKey(), (ActionGroupsV6) r6a.getValue())); } } return a7; } - public static SecurityDynamicConfiguration migrateRoleMappings(SecurityDynamicConfiguration r6rms) throws MigrationException { + public static SecurityDynamicConfiguration migrateRoleMappings(SecurityDynamicConfiguration r6rms) + throws MigrationException { final SecurityDynamicConfiguration rms7 = SecurityDynamicConfiguration.empty(); rms7.setCType(r6rms.getCType()); rms7.set_meta(new Meta()); rms7.get_meta().setConfig_version(2); rms7.get_meta().setType("rolesmapping"); - for(final Entry r6m: r6rms.getCEntries().entrySet()) { + for (final Entry r6m : r6rms.getCEntries().entrySet()) { rms7.putCEntry(r6m.getKey(), new RoleMappingsV7(r6m.getValue())); } @@ -232,7 +244,7 @@ public static SecurityDynamicConfiguration migrateAudit(SecurityDyn migrated.get_meta().setConfig_version(2); migrated.get_meta().setType("audit"); - for(final Entry entry: audit.getCEntries().entrySet()) { + for (final Entry entry : audit.getCEntries().entrySet()) { migrated.putCEntry(entry.getKey(), entry.getValue()); } return migrated; diff --git a/src/main/java/org/opensearch/security/securityconf/RoleMappings.java b/src/main/java/org/opensearch/security/securityconf/RoleMappings.java index 01d686733e..ff4153898b 100644 --- a/src/main/java/org/opensearch/security/securityconf/RoleMappings.java +++ b/src/main/java/org/opensearch/security/securityconf/RoleMappings.java @@ -16,8 +16,8 @@ public class RoleMappings { - private List hosts= Collections.emptyList(); - private List users= Collections.emptyList(); + private List hosts = Collections.emptyList(); + private List users = Collections.emptyList(); public void setHosts(List hosts) { this.hosts = hosts; diff --git a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java index de7afbc27b..c52a3c1bad 100644 --- a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java +++ b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java @@ -43,15 +43,45 @@ public interface SecurityRoles { Set getRoleNames(); - Set reduce(Resolved requestedResolved, User user, String[] strings, IndexNameExpressionResolver resolver, ClusterService clusterService); + Set reduce( + Resolved requestedResolved, + User user, + String[] strings, + IndexNameExpressionResolver resolver, + ClusterService clusterService + ); - boolean impliesTypePermGlobal(Resolved requestedResolved, User user, String[] allIndexPermsRequiredA, IndexNameExpressionResolver resolver, ClusterService clusterService); + boolean impliesTypePermGlobal( + Resolved requestedResolved, + User user, + String[] allIndexPermsRequiredA, + IndexNameExpressionResolver resolver, + ClusterService clusterService + ); - boolean get(Resolved requestedResolved, User user, String[] allIndexPermsRequiredA, IndexNameExpressionResolver resolver, ClusterService clusterService); + boolean get( + Resolved requestedResolved, + User user, + String[] allIndexPermsRequiredA, + IndexNameExpressionResolver resolver, + ClusterService clusterService + ); - EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, IndexNameExpressionResolver resolver, ClusterService clusterService, NamedXContentRegistry namedXContentRegistry); + EvaluatedDlsFlsConfig getDlsFls( + User user, + boolean dfmEmptyOverwritesAll, + IndexNameExpressionResolver resolver, + ClusterService clusterService, + NamedXContentRegistry namedXContentRegistry + ); - Set getAllPermittedIndicesForDashboards(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs); + Set getAllPermittedIndicesForDashboards( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ); SecurityRoles filter(Set roles); diff --git a/src/main/java/org/opensearch/security/securityconf/impl/AllowlistingSettings.java b/src/main/java/org/opensearch/security/securityconf/impl/AllowlistingSettings.java index 131c43b50b..e2c86009d1 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/AllowlistingSettings.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/AllowlistingSettings.java @@ -48,7 +48,7 @@ public void setEnabled(boolean enabled) { } public Map> getRequests() { - return this.requests == null ? Collections.emptyMap(): this.requests; + return this.requests == null ? Collections.emptyMap() : this.requests; } public void setRequests(Map> requests) { @@ -60,7 +60,6 @@ public String toString() { return "AllowlistingSetting [enabled=" + enabled + ", requests=" + requests + ']'; } - /** * Helper function to check if a rest request is allowlisted, by checking if the path is allowlisted, * and then if the Http method is allowlisted. @@ -76,26 +75,26 @@ public String toString() { * GET /_cluster/settings - OK * GET /_cluster/settings/ - OK */ - private boolean requestIsAllowlisted(RestRequest request){ + private boolean requestIsAllowlisted(RestRequest request) { - //ALSO ALLOWS REQUEST TO HAVE TRAILING '/' - //pathWithoutTrailingSlash stores the endpoint path without extra '/'. eg: /_cat/nodes - //pathWithTrailingSlash stores the endpoint path with extra '/'. eg: /_cat/nodes/ + // ALSO ALLOWS REQUEST TO HAVE TRAILING '/' + // pathWithoutTrailingSlash stores the endpoint path without extra '/'. eg: /_cat/nodes + // pathWithTrailingSlash stores the endpoint path with extra '/'. eg: /_cat/nodes/ String path = request.path(); String pathWithoutTrailingSlash; String pathWithTrailingSlash; - //first obtain pathWithoutTrailingSlash, then add a '/' to it to get pathWithTrailingSlash + // first obtain pathWithoutTrailingSlash, then add a '/' to it to get pathWithTrailingSlash pathWithoutTrailingSlash = path.endsWith("/") ? path.substring(0, path.length() - 1) : path; pathWithTrailingSlash = pathWithoutTrailingSlash + '/'; - //check if pathWithoutTrailingSlash is allowlisted - if(requests.containsKey(pathWithoutTrailingSlash) && requests.get(pathWithoutTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) - return true; + // check if pathWithoutTrailingSlash is allowlisted + if (requests.containsKey(pathWithoutTrailingSlash) + && requests.get(pathWithoutTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) return true; - //check if pathWithTrailingSlash is allowlisted - if(requests.containsKey(pathWithTrailingSlash) && requests.get(pathWithTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) - return true; + // check if pathWithTrailingSlash is allowlisted + if (requests.containsKey(pathWithTrailingSlash) + && requests.get(pathWithTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) return true; return false; } @@ -108,15 +107,19 @@ private boolean requestIsAllowlisted(RestRequest request){ * then all PUT /_opendistro/_security/api/rolesmapping/{resource_name} work. * Currently, each resource_name has to be allowlisted separately */ - public boolean checkRequestIsAllowed(RestRequest request, RestChannel channel, - NodeClient client) throws IOException { + public boolean checkRequestIsAllowed(RestRequest request, RestChannel channel, NodeClient client) throws IOException { // if allowlisting is enabled but the request is not allowlisted, then return false, otherwise true. - if (this.enabled && !requestIsAllowlisted(request)){ - channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, channel.newErrorBuilder().startObject() - .field("error", request.method() + " " + request.path() + " API not allowlisted") - .field("status", RestStatus.FORBIDDEN) - .endObject() - )); + if (this.enabled && !requestIsAllowlisted(request)) { + channel.sendResponse( + new BytesRestResponse( + RestStatus.FORBIDDEN, + channel.newErrorBuilder() + .startObject() + .field("error", request.method() + " " + request.path() + " API not allowlisted") + .field("status", RestStatus.FORBIDDEN) + .endObject() + ) + ); return false; } return true; diff --git a/src/main/java/org/opensearch/security/securityconf/impl/CType.java b/src/main/java/org/opensearch/security/securityconf/impl/CType.java index 5d9f1f307b..4e5e2de496 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/CType.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/CType.java @@ -50,10 +50,8 @@ public enum CType { - INTERNALUSERS(toMap(1, InternalUserV6.class, 2, - InternalUserV7.class)), - ACTIONGROUPS(toMap(0, List.class, 1, ActionGroupsV6.class, 2, - ActionGroupsV7.class)), + INTERNALUSERS(toMap(1, InternalUserV6.class, 2, InternalUserV7.class)), + ACTIONGROUPS(toMap(0, List.class, 1, ActionGroupsV6.class, 2, ActionGroupsV7.class)), CONFIG(toMap(1, ConfigV6.class, 2, ConfigV7.class)), ROLES(toMap(1, RoleV6.class, 2, RoleV7.class)), ROLESMAPPING(toMap(1, RoleMappingsV6.class, 2, RoleMappingsV7.class)), diff --git a/src/main/java/org/opensearch/security/securityconf/impl/Meta.java b/src/main/java/org/opensearch/security/securityconf/impl/Meta.java index 1e9060efa1..2fa4ced06a 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/Meta.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/Meta.java @@ -31,7 +31,6 @@ public class Meta { - private String type; private int config_version; @@ -45,9 +44,11 @@ public void setType(String type) { this.type = type; cType = CType.fromString(type); } + public int getConfig_version() { return config_version; } + public void setConfig_version(int config_version) { this.config_version = config_version; } @@ -62,5 +63,4 @@ public String toString() { return "Meta [type=" + type + ", config_version=" + config_version + ", cType=" + cType + "]"; } - } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java b/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java index c282f439e8..285dec5d10 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java @@ -53,12 +53,13 @@ public class SecurityDynamicConfiguration implements ToXContent { - private static final TypeReference> typeRefMSO = new TypeReference>() {}; + private static final TypeReference> typeRefMSO = new TypeReference>() { + }; @JsonIgnore private final Map centries = new HashMap<>(); - private long seqNo= -1; - private long primaryTerm= -1; + private long seqNo = -1; + private long primaryTerm = -1; private CType ctype; private int version = -1; @@ -66,21 +67,36 @@ public static SecurityDynamicConfiguration empty() { return new SecurityDynamicConfiguration(); } - public static SecurityDynamicConfiguration fromJson(String json, CType ctype, int version, long seqNo, long primaryTerm) throws IOException { + public static SecurityDynamicConfiguration fromJson(String json, CType ctype, int version, long seqNo, long primaryTerm) + throws IOException { return fromJson(json, ctype, version, seqNo, primaryTerm, false); } - public static SecurityDynamicConfiguration fromJson(String json, CType ctype, int version, long seqNo, long primaryTerm, boolean acceptInvalid) throws IOException { + public static SecurityDynamicConfiguration fromJson( + String json, + CType ctype, + int version, + long seqNo, + long primaryTerm, + boolean acceptInvalid + ) throws IOException { SecurityDynamicConfiguration sdc = null; - if(ctype != null) { + if (ctype != null) { final Class implementationClass = ctype.getImplementationClass().get(version); - if(implementationClass == null) { - throw new IllegalArgumentException("No implementation class found for "+ctype+" and config version "+version); + if (implementationClass == null) { + throw new IllegalArgumentException("No implementation class found for " + ctype + " and config version " + version); } - if(acceptInvalid && version < 2) { - sdc = NonValidatingObjectMapper.readValue(json, NonValidatingObjectMapper.getTypeFactory().constructParametricType(SecurityDynamicConfiguration.class, implementationClass)); + if (acceptInvalid && version < 2) { + sdc = NonValidatingObjectMapper.readValue( + json, + NonValidatingObjectMapper.getTypeFactory() + .constructParametricType(SecurityDynamicConfiguration.class, implementationClass) + ); } else { - sdc = DefaultObjectMapper.readValue(json, DefaultObjectMapper.getTypeFactory().constructParametricType(SecurityDynamicConfiguration.class, implementationClass)); + sdc = DefaultObjectMapper.readValue( + json, + DefaultObjectMapper.getTypeFactory().constructParametricType(SecurityDynamicConfiguration.class, implementationClass) + ); } validate(sdc, version, ctype); @@ -97,29 +113,32 @@ public static SecurityDynamicConfiguration fromJson(String json, CType ct } public static void validate(SecurityDynamicConfiguration sdc, int version, CType ctype) throws IOException { - if(version < 2 && sdc.get_meta() != null) { - throw new IOException("A version of "+version+" can not have a _meta key for "+ctype); + if (version < 2 && sdc.get_meta() != null) { + throw new IOException("A version of " + version + " can not have a _meta key for " + ctype); } - if(version >= 2 && sdc.get_meta() == null) { - throw new IOException("A version of "+version+" must have a _meta key for "+ctype); + if (version >= 2 && sdc.get_meta() == null) { + throw new IOException("A version of " + version + " must have a _meta key for " + ctype); } - if(version < 2 && ctype == CType.CONFIG && (sdc.getCEntries().size() != 1 || !sdc.getCEntries().keySet().contains("opendistro_security"))) { - throw new IOException("A version of "+version+" must have a single toplevel key named 'opendistro_security' for "+ctype); + if (version < 2 + && ctype == CType.CONFIG + && (sdc.getCEntries().size() != 1 || !sdc.getCEntries().keySet().contains("opendistro_security"))) { + throw new IOException("A version of " + version + " must have a single toplevel key named 'opendistro_security' for " + ctype); } - if(version >= 2 && ctype == CType.CONFIG && (sdc.getCEntries().size() != 1 || !sdc.getCEntries().keySet().contains("config"))) { - throw new IOException("A version of "+version+" must have a single toplevel key named 'config' for "+ctype); + if (version >= 2 && ctype == CType.CONFIG && (sdc.getCEntries().size() != 1 || !sdc.getCEntries().keySet().contains("config"))) { + throw new IOException("A version of " + version + " must have a single toplevel key named 'config' for " + ctype); } } - public static SecurityDynamicConfiguration fromNode(JsonNode json, CType ctype, int version, long seqNo, long primaryTerm) throws IOException { + public static SecurityDynamicConfiguration fromNode(JsonNode json, CType ctype, int version, long seqNo, long primaryTerm) + throws IOException { return fromJson(DefaultObjectMapper.writeValueAsString(json, false), ctype, version, seqNo, primaryTerm); } - //for Jackson + // for Jackson private SecurityDynamicConfiguration() { super(); } @@ -134,7 +153,6 @@ public void set_meta(Meta _meta) { this._meta = _meta; } - @JsonAnySetter void setCEntries(String key, T value) { putCEntry(key, value); @@ -147,8 +165,8 @@ public Map getCEntries() { @JsonIgnore public void removeHidden() { - for(Entry entry: new HashMap(centries).entrySet()) { - if(entry.getValue() instanceof Hideable && ((Hideable) entry.getValue()).isHidden()) { + for (Entry entry : new HashMap(centries).entrySet()) { + if (entry.getValue() instanceof Hideable && ((Hideable) entry.getValue()).isHidden()) { centries.remove(entry.getKey()); } } @@ -156,8 +174,8 @@ public void removeHidden() { @JsonIgnore public void removeStatic() { - for(Entry entry: new HashMap(centries).entrySet()) { - if(entry.getValue() instanceof StaticDefinable && ((StaticDefinable) entry.getValue()).isStatic()) { + for (Entry entry : new HashMap(centries).entrySet()) { + if (entry.getValue() instanceof StaticDefinable && ((StaticDefinable) entry.getValue()).isStatic()) { centries.remove(entry.getKey()); } } @@ -165,14 +183,13 @@ public void removeStatic() { @JsonIgnore public void clearHashes() { - for(Entry entry: centries.entrySet()) { - if(entry.getValue() instanceof Hashed) { - ((Hashed) entry.getValue()).clearHash(); + for (Entry entry : centries.entrySet()) { + if (entry.getValue() instanceof Hashed) { + ((Hashed) entry.getValue()).clearHash(); } } } - public void removeOthers(String key) { T tmp = this.centries.get(key); this.centries.clear(); @@ -206,8 +223,19 @@ public BytesReference toBytesReference() throws IOException { @Override public String toString() { - return "SecurityDynamicConfiguration [seqNo=" + seqNo + ", primaryTerm=" + primaryTerm + ", ctype=" + ctype + ", version=" + version + ", centries=" - + centries + ", getImplementingClass()=" + getImplementingClass() + "]"; + return "SecurityDynamicConfiguration [seqNo=" + + seqNo + + ", primaryTerm=" + + primaryTerm + + ", ctype=" + + ctype + + ", version=" + + version + + ", centries=" + + centries + + ", getImplementingClass()=" + + getImplementingClass() + + "]"; } @Override @@ -250,7 +278,7 @@ public int getVersion() { @JsonIgnore public Class getImplementingClass() { - return ctype==null?null:ctype.getImplementationClass().get(getVersion()); + return ctype == null ? null : ctype.getImplementationClass().get(getVersion()); } @JsonIgnore @@ -264,21 +292,21 @@ public SecurityDynamicConfiguration deepClone() { @JsonIgnore public void remove(String key) { - centries.remove(key); + centries.remove(key); } @SuppressWarnings({ "rawtypes", "unchecked" }) public boolean add(SecurityDynamicConfiguration other) { - if(other.ctype == null || !other.ctype.equals(this.ctype)) { + if (other.ctype == null || !other.ctype.equals(this.ctype)) { return false; } - if(other.getImplementingClass() == null || !other.getImplementingClass().equals(this.getImplementingClass())) { + if (other.getImplementingClass() == null || !other.getImplementingClass().equals(this.getImplementingClass())) { return false; } - if(other.version != this.version) { + if (other.version != this.version) { return false; } @@ -292,7 +320,7 @@ public boolean containsAny(SecurityDynamicConfiguration other) { return !Collections.disjoint(this.centries.keySet(), other.centries.keySet()); } - public boolean isHidden(String resourceName){ + public boolean isHidden(String resourceName) { final Object o = centries.get(resourceName); return o != null && o instanceof Hideable && ((Hideable) o).isHidden(); } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/WhitelistingSettings.java b/src/main/java/org/opensearch/security/securityconf/impl/WhitelistingSettings.java index b6dfbaae4e..57405b24fe 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/WhitelistingSettings.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/WhitelistingSettings.java @@ -48,7 +48,7 @@ public void setEnabled(boolean enabled) { } public Map> getRequests() { - return this.requests == null ? Collections.emptyMap(): this.requests; + return this.requests == null ? Collections.emptyMap() : this.requests; } public void setRequests(Map> requests) { @@ -60,7 +60,6 @@ public String toString() { return "WhitelistingSetting [enabled=" + enabled + ", requests=" + requests + ']'; } - /** * Helper function to check if a rest request is whitelisted, by checking if the path is whitelisted, * and then if the Http method is whitelisted. @@ -76,26 +75,26 @@ public String toString() { * GET /_cluster/settings - OK * GET /_cluster/settings/ - OK */ - private boolean requestIsWhitelisted(RestRequest request){ + private boolean requestIsWhitelisted(RestRequest request) { - //ALSO ALLOWS REQUEST TO HAVE TRAILING '/' - //pathWithoutTrailingSlash stores the endpoint path without extra '/'. eg: /_cat/nodes - //pathWithTrailingSlash stores the endpoint path with extra '/'. eg: /_cat/nodes/ + // ALSO ALLOWS REQUEST TO HAVE TRAILING '/' + // pathWithoutTrailingSlash stores the endpoint path without extra '/'. eg: /_cat/nodes + // pathWithTrailingSlash stores the endpoint path with extra '/'. eg: /_cat/nodes/ String path = request.path(); String pathWithoutTrailingSlash; String pathWithTrailingSlash; - //first obtain pathWithoutTrailingSlash, then add a '/' to it to get pathWithTrailingSlash + // first obtain pathWithoutTrailingSlash, then add a '/' to it to get pathWithTrailingSlash pathWithoutTrailingSlash = path.endsWith("/") ? path.substring(0, path.length() - 1) : path; pathWithTrailingSlash = pathWithoutTrailingSlash + '/'; - //check if pathWithoutTrailingSlash is whitelisted - if(requests.containsKey(pathWithoutTrailingSlash) && requests.get(pathWithoutTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) - return true; + // check if pathWithoutTrailingSlash is whitelisted + if (requests.containsKey(pathWithoutTrailingSlash) + && requests.get(pathWithoutTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) return true; - //check if pathWithTrailingSlash is whitelisted - if(requests.containsKey(pathWithTrailingSlash) && requests.get(pathWithTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) - return true; + // check if pathWithTrailingSlash is whitelisted + if (requests.containsKey(pathWithTrailingSlash) + && requests.get(pathWithTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) return true; return false; } @@ -109,15 +108,19 @@ private boolean requestIsWhitelisted(RestRequest request){ * Currently, each resource_name has to be whitelisted separately */ @Override - public boolean checkRequestIsAllowed(RestRequest request, RestChannel channel, - NodeClient client) throws IOException { + public boolean checkRequestIsAllowed(RestRequest request, RestChannel channel, NodeClient client) throws IOException { // if whitelisting is enabled but the request is not whitelisted, then return false, otherwise true. - if (this.enabled && !requestIsWhitelisted(request)){ - channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, channel.newErrorBuilder().startObject() - .field("error", request.method() + " " + request.path() + " API not whitelisted") - .field("status", RestStatus.FORBIDDEN) - .endObject() - )); + if (this.enabled && !requestIsWhitelisted(request)) { + channel.sendResponse( + new BytesRestResponse( + RestStatus.FORBIDDEN, + channel.newErrorBuilder() + .startObject() + .field("error", request.method() + " " + request.path() + " API not whitelisted") + .field("status", RestStatus.FORBIDDEN) + .endObject() + ) + ); return false; } return true; diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/ActionGroupsV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/ActionGroupsV6.java index 99bef31505..45fced168c 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/ActionGroupsV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/ActionGroupsV6.java @@ -35,7 +35,6 @@ public class ActionGroupsV6 implements Hideable { - private boolean readonly; private boolean hidden; private List permissions = Collections.emptyList(); @@ -48,28 +47,34 @@ public ActionGroupsV6() { public boolean isReserved() { return readonly; } + public boolean isReadonly() { return readonly; } + public void setReadonly(boolean readonly) { this.readonly = readonly; } + public boolean isHidden() { return hidden; } + public void setHidden(boolean hidden) { this.hidden = hidden; } + public List getPermissions() { return permissions; } + public void setPermissions(List permissions) { this.permissions = permissions; } + @Override public String toString() { return "ActionGroups [readonly=" + readonly + ", hidden=" + hidden + ", permissions=" + permissions + "]"; } - } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java index 7f90e386a8..c85e69fb0d 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java @@ -25,7 +25,6 @@ * GitHub history for details. */ - package org.opensearch.security.securityconf.impl.v6; import java.util.Collections; @@ -46,8 +45,6 @@ public class ConfigV6 { public Dynamic dynamic; - - @Override public String toString() { return "Config [dynamic=" + dynamic + "]"; @@ -55,7 +52,6 @@ public String toString() { public static class Dynamic { - public String filtered_alias_mode = "warn"; public boolean disable_rest_auth; public boolean disable_intertransport_auth; @@ -74,8 +70,17 @@ public static class Dynamic { @Override public String toString() { - return "Dynamic [filtered_alias_mode=" + filtered_alias_mode + ", kibana=" + kibana + ", http=" + http + ", authc=" + authc + ", authz=" - + authz + "]"; + return "Dynamic [filtered_alias_mode=" + + filtered_alias_mode + + ", kibana=" + + kibana + + ", http=" + + http + + ", authc=" + + authc + + ", authz=" + + authz + + "]"; } } @@ -91,25 +96,33 @@ public static class Kibana { public String opendistro_role = null; public String index = ".kibana"; public boolean do_not_fail_on_forbidden; + @Override public String toString() { - return "Kibana [multitenancy_enabled=" + multitenancy_enabled + ", server_username=" + server_username + ", opendistro_role=" + opendistro_role - + ", index=" + index + ", do_not_fail_on_forbidden=" + do_not_fail_on_forbidden + "]"; + return "Kibana [multitenancy_enabled=" + + multitenancy_enabled + + ", server_username=" + + server_username + + ", opendistro_role=" + + opendistro_role + + ", index=" + + index + + ", do_not_fail_on_forbidden=" + + do_not_fail_on_forbidden + + "]"; } - - } public static class Http { public boolean anonymous_auth_enabled = false; public Xff xff = new Xff(); + @Override public String toString() { return "Http [anonymous_auth_enabled=" + anonymous_auth_enabled + ", xff=" + xff + "]"; } - } public static class AuthFailureListeners { @@ -126,7 +139,6 @@ public Map getListeners() { return listeners; } - } public static class AuthFailureListener { @@ -156,23 +168,33 @@ public static class Xff { @JsonInclude(JsonInclude.Include.NON_NULL) public boolean enabled = true; public String internalProxies = Pattern.compile( - "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + - "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + - "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + - "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}").toString(); - public String remoteIpHeader="X-Forwarded-For"; - public String proxiesHeader="X-Forwarded-By"; + "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}" + ).toString(); + public String remoteIpHeader = "X-Forwarded-For"; + public String proxiesHeader = "X-Forwarded-By"; public String trustedProxies; + @Override public String toString() { - return "Xff [enabled=" + enabled + ", internalProxies=" + internalProxies + ", remoteIpHeader=" + remoteIpHeader + ", proxiesHeader=" - + proxiesHeader + ", trustedProxies=" + trustedProxies + "]"; + return "Xff [enabled=" + + enabled + + ", internalProxies=" + + internalProxies + + ", remoteIpHeader=" + + remoteIpHeader + + ", proxiesHeader=" + + proxiesHeader + + ", trustedProxies=" + + trustedProxies + + "]"; } - } public static class Authc { @@ -195,26 +217,36 @@ public String toString() { return "Authc [domains=" + domains + "]"; } - } public static class AuthcDomain { @JsonInclude(JsonInclude.Include.NON_NULL) - public boolean http_enabled= true; + public boolean http_enabled = true; @JsonInclude(JsonInclude.Include.NON_NULL) - public boolean transport_enabled= true; + public boolean transport_enabled = true; @JsonInclude(JsonInclude.Include.NON_NULL) - public boolean enabled= true; + public boolean enabled = true; public int order = 0; public HttpAuthenticator http_authenticator = new HttpAuthenticator(); public AuthcBackend authentication_backend = new AuthcBackend(); + @Override public String toString() { - return "AuthcDomain [http_enabled=" + http_enabled + ", transport_enabled=" + transport_enabled + ", enabled=" + enabled + ", order=" - + order + ", http_authenticator=" + http_authenticator + ", authentication_backend=" + authentication_backend + "]"; + return "AuthcDomain [http_enabled=" + + http_enabled + + ", transport_enabled=" + + transport_enabled + + ", enabled=" + + enabled + + ", order=" + + order + + ", http_authenticator=" + + http_authenticator + + ", authentication_backend=" + + authentication_backend + + "]"; } - } public static class HttpAuthenticator { @@ -237,7 +269,6 @@ public String toString() { return "HttpAuthenticator [challenge=" + challenge + ", type=" + type + ", config=" + config + "]"; } - } public static class AuthzBackend { @@ -258,7 +289,6 @@ public String toString() { return "AuthzBackend [type=" + type + ", config=" + config + "]"; } - } public static class AuthcBackend { @@ -279,7 +309,6 @@ public String toString() { return "AuthcBackend [type=" + type + ", config=" + config + "]"; } - } public static class Authz { @@ -301,7 +330,6 @@ public String toString() { return "Authz [domains=" + domains + "]"; } - } public static class AuthzDomain { @@ -312,12 +340,20 @@ public static class AuthzDomain { @JsonInclude(JsonInclude.Include.NON_NULL) public boolean enabled = true; public AuthzBackend authorization_backend = new AuthzBackend(); + @Override public String toString() { - return "AuthzDomain [http_enabled=" + http_enabled + ", transport_enabled=" + transport_enabled + ", enabled=" + enabled + ", authorization_backend=" + authorization_backend + "]"; + return "AuthzDomain [http_enabled=" + + http_enabled + + ", transport_enabled=" + + transport_enabled + + ", enabled=" + + enabled + + ", authorization_backend=" + + authorization_backend + + "]"; } - } } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/InternalUserV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/InternalUserV6.java index f650101b64..0db727ad99 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/InternalUserV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/InternalUserV6.java @@ -38,91 +38,111 @@ public class InternalUserV6 implements Hideable, Hashed { - private String hash; - private boolean readonly; - private boolean hidden; - private List roles = Collections.emptyList(); - private Map attributes = Collections.emptyMap(); - private String username; - - - - public InternalUserV6(String hash, boolean readonly, boolean hidden, List roles, Map attributes, String username) { - super(); - this.hash = hash; - this.readonly = readonly; - this.hidden = hidden; - this.roles = roles; - this.attributes = attributes; - this.username = username; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public InternalUserV6() { - super(); - //default constructor - } - - public String getHash() { - return hash; - } - public void setHash(String hash) { - this.hash = hash; - } - - public void setPassword(String password){ - // no-op setter. Due to a bug in 6.x, empty "password" may be saved to the internalusers doc. Ignore it. - } - - public boolean isReadonly() { - return readonly; - } - public void setReadonly(boolean readonly) { - this.readonly = readonly; - } - public boolean isHidden() { - return hidden; - } - public void setHidden(boolean hidden) { - this.hidden = hidden; - } - public List getRoles() { - return roles; - } - public void setRoles(List roles) { - this.roles = roles; - } - public Map getAttributes() { - return attributes; - } - public void setAttributes(Map attributes) { - this.attributes = attributes; - } - - @Override - public String toString() { - return "SgInternalUser [hash=" + hash + ", readonly=" + readonly + ", hidden=" + hidden + ", roles=" + roles + ", attributes=" - + attributes + "]"; - } - - @JsonIgnore - public boolean isReserved() { - return readonly; - } - - @Override - @JsonIgnore - public void clearHash() { - hash = ""; - } + private String hash; + private boolean readonly; + private boolean hidden; + private List roles = Collections.emptyList(); + private Map attributes = Collections.emptyMap(); + private String username; + + public InternalUserV6( + String hash, + boolean readonly, + boolean hidden, + List roles, + Map attributes, + String username + ) { + super(); + this.hash = hash; + this.readonly = readonly; + this.hidden = hidden; + this.roles = roles; + this.attributes = attributes; + this.username = username; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public InternalUserV6() { + super(); + // default constructor + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public void setPassword(String password) { + // no-op setter. Due to a bug in 6.x, empty "password" may be saved to the internalusers doc. Ignore it. + } + + public boolean isReadonly() { + return readonly; + } + + public void setReadonly(boolean readonly) { + this.readonly = readonly; + } + + public boolean isHidden() { + return hidden; + } + + public void setHidden(boolean hidden) { + this.hidden = hidden; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + @Override + public String toString() { + return "SgInternalUser [hash=" + + hash + + ", readonly=" + + readonly + + ", hidden=" + + hidden + + ", roles=" + + roles + + ", attributes=" + + attributes + + "]"; + } + @JsonIgnore + public boolean isReserved() { + return readonly; + } + @Override + @JsonIgnore + public void clearHash() { + hash = ""; } + +} diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleMappingsV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleMappingsV6.java index bb10fd8812..f78c2f4305 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleMappingsV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleMappingsV6.java @@ -41,10 +41,7 @@ public class RoleMappingsV6 extends RoleMappings implements Hideable { private boolean readonly; private boolean hidden; private List backendroles = Collections.emptyList(); - private List andBackendroles= Collections.emptyList(); - - - + private List andBackendroles = Collections.emptyList(); public RoleMappingsV6() { super(); @@ -53,34 +50,51 @@ public RoleMappingsV6() { public boolean isReadonly() { return readonly; } + public void setReadonly(boolean readonly) { this.readonly = readonly; } + public boolean isHidden() { return hidden; } + public void setHidden(boolean hidden) { this.hidden = hidden; } + public List getBackendroles() { return backendroles; } + public void setBackendroles(List backendroles) { this.backendroles = backendroles; } - @JsonProperty(value="and_backendroles") + @JsonProperty(value = "and_backendroles") public List getAndBackendroles() { return andBackendroles; } + public void setAndBackendroles(List andBackendroles) { this.andBackendroles = andBackendroles; } @Override public String toString() { - return "RoleMappings [readonly=" + readonly + ", hidden=" + hidden + ", backendroles=" + backendroles + ", hosts=" + getHosts() + ", users=" - + getUsers() + ", andBackendroles=" + andBackendroles + "]"; + return "RoleMappings [readonly=" + + readonly + + ", hidden=" + + hidden + + ", backendroles=" + + backendroles + + ", hosts=" + + getHosts() + + ", users=" + + getUsers() + + ", andBackendroles=" + + andBackendroles + + "]"; } @JsonIgnore diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleV6.java index e8254d4530..8bc8b46249 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleV6.java @@ -65,8 +65,6 @@ public Map> getTypes() { private List _fls_; private List _masked_fields_; - - public String get_dls_() { return _dls_; } @@ -84,8 +82,6 @@ public String toString() { return "Index [types=" + types + ", _dls_=" + _dls_ + ", _fls_=" + _fls_ + ", _masked_fields_=" + _masked_fields_ + "]"; } - - } public boolean isReadonly() { @@ -130,7 +126,17 @@ public void setIndices(Map indices) { @Override public String toString() { - return "Role [readonly=" + readonly + ", hidden=" + hidden + ", cluster=" + cluster + ", tenants=" + tenants + ", indices=" + indices + "]"; + return "Role [readonly=" + + readonly + + ", hidden=" + + hidden + + ", cluster=" + + cluster + + ", tenants=" + + tenants + + ", indices=" + + indices + + "]"; } @JsonIgnore diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/ActionGroupsV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/ActionGroupsV7.java index f38978eec2..9ec9c25e5d 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/ActionGroupsV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/ActionGroupsV7.java @@ -38,8 +38,6 @@ public class ActionGroupsV7 implements Hideable, StaticDefinable { - - private boolean reserved; private boolean hidden; @JsonProperty(value = "static") @@ -51,11 +49,12 @@ public class ActionGroupsV7 implements Hideable, StaticDefinable { public ActionGroupsV7() { super(); } + public ActionGroupsV7(String agName, ActionGroupsV6 ag6) { reserved = ag6.isReserved(); hidden = ag6.isHidden(); allowed_actions = ag6.getPermissions(); - type = agName.toLowerCase().contains("cluster")?"cluster":"index"; + type = agName.toLowerCase().contains("cluster") ? "cluster" : "index"; description = "Migrated from v6"; } @@ -64,53 +63,72 @@ public ActionGroupsV7(String key, List allowed_actions) { type = "unknown"; description = "Migrated from v6 (legacy)"; } + public String getType() { return type; } + public void setType(String type) { this.type = type; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } - public boolean isReserved() { return reserved; } + public void setReserved(boolean reserved) { this.reserved = reserved; } + public boolean isHidden() { return hidden; } + public void setHidden(boolean hidden) { this.hidden = hidden; } + public List getAllowed_actions() { return allowed_actions; } + public void setAllowed_actions(List allowed_actions) { this.allowed_actions = allowed_actions; } + @JsonProperty(value = "static") public boolean isStatic() { return _static; } + @JsonProperty(value = "static") public void setStatic(boolean _static) { this._static = _static; } + @Override public String toString() { - return "ActionGroupsV7 [reserved=" + reserved + ", hidden=" + hidden + ", _static=" + _static + ", allowed_actions=" + allowed_actions - + ", type=" + type + ", description=" + description + "]"; + return "ActionGroupsV7 [reserved=" + + reserved + + ", hidden=" + + hidden + + ", _static=" + + _static + + ", allowed_actions=" + + allowed_actions + + ", type=" + + type + + ", description=" + + description + + "]"; } - - - } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java index 337dec8e31..87de6a31b0 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java @@ -85,20 +85,29 @@ public ConfigV7(ConfigV6 c6) { dynamic.authc = new Authc(); - dynamic.authc.domains.putAll(c6.dynamic.authc.getDomains().entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey(), - entry -> new AuthcDomain(entry.getValue())))); + dynamic.authc.domains.putAll( + c6.dynamic.authc.getDomains() + .entrySet() + .stream() + .collect(Collectors.toMap(entry -> entry.getKey(), entry -> new AuthcDomain(entry.getValue()))) + ); dynamic.authz = new Authz(); - dynamic.authz.domains.putAll(c6.dynamic.authz.getDomains().entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey(), - entry -> new AuthzDomain(entry.getValue())))); + dynamic.authz.domains.putAll( + c6.dynamic.authz.getDomains() + .entrySet() + .stream() + .collect(Collectors.toMap(entry -> entry.getKey(), entry -> new AuthzDomain(entry.getValue()))) + ); dynamic.auth_failure_listeners = new AuthFailureListeners(); - dynamic.auth_failure_listeners.listeners.putAll(c6.dynamic.auth_failure_listeners.getListeners().entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey(), - entry -> new AuthFailureListener(entry.getValue())))); + dynamic.auth_failure_listeners.listeners.putAll( + c6.dynamic.auth_failure_listeners.getListeners() + .entrySet() + .stream() + .collect(Collectors.toMap(entry -> entry.getKey(), entry -> new AuthFailureListener(entry.getValue()))) + ); } @Override @@ -108,7 +117,6 @@ public String toString() { public static class Dynamic { - public String filtered_alias_mode = "warn"; public boolean disable_rest_auth; public boolean disable_intertransport_auth; @@ -128,8 +136,17 @@ public static class Dynamic { @Override public String toString() { - return "Dynamic [filtered_alias_mode=" + filtered_alias_mode + ", kibana=" + kibana + ", http=" + http + ", authc=" + authc + ", authz=" - + authz + "]"; + return "Dynamic [filtered_alias_mode=" + + filtered_alias_mode + + ", kibana=" + + kibana + + ", http=" + + http + + ", authc=" + + authc + + ", authz=" + + authz + + "]"; } } @@ -144,27 +161,35 @@ public static class Kibana { public String server_username = "kibanaserver"; public String opendistro_role = null; public String index = ".kibana"; + @Override public String toString() { - return "Kibana [multitenancy_enabled=" + multitenancy_enabled + ", private_tenant_enabled=" + - private_tenant_enabled + ", default_tenant=" + default_tenant + ", server_username=" + - server_username + ", opendistro_role=" + opendistro_role - + ", index=" + index + "]"; + return "Kibana [multitenancy_enabled=" + + multitenancy_enabled + + ", private_tenant_enabled=" + + private_tenant_enabled + + ", default_tenant=" + + default_tenant + + ", server_username=" + + server_username + + ", opendistro_role=" + + opendistro_role + + ", index=" + + index + + "]"; } - - } public static class Http { public boolean anonymous_auth_enabled = false; public Xff xff = new Xff(); + @Override public String toString() { return "Http [anonymous_auth_enabled=" + anonymous_auth_enabled + ", xff=" + xff + "]"; } - } public static class AuthFailureListeners { @@ -181,7 +206,6 @@ public Map getListeners() { return listeners; } - } public static class AuthFailureListener { @@ -193,8 +217,6 @@ public static class AuthFailureListener { public int max_blocked_clients = 100_000; public int max_tracked_clients = 100_000; - - public AuthFailureListener() { super(); } @@ -223,20 +245,21 @@ public String asJson() { public static class Xff { public boolean enabled = false; public String internalProxies = Pattern.compile( - "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + - "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + - "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + - "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}").toString(); - public String remoteIpHeader="X-Forwarded-For"; + "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}" + ).toString(); + public String remoteIpHeader = "X-Forwarded-For"; + @Override public String toString() { - return "Xff [enabled=" + enabled + ", internalProxies=" + internalProxies + ", remoteIpHeader=" + remoteIpHeader+"]"; + return "Xff [enabled=" + enabled + ", internalProxies=" + internalProxies + ", remoteIpHeader=" + remoteIpHeader + "]"; } - } public static class Authc { @@ -259,17 +282,15 @@ public String toString() { return "Authc [domains=" + domains + "]"; } - - } public static class AuthcDomain { @JsonInclude(JsonInclude.Include.NON_NULL) - public boolean http_enabled= true; + public boolean http_enabled = true; @JsonInclude(JsonInclude.Include.NON_NULL) - public boolean transport_enabled= true; - //public boolean enabled= true; + public boolean transport_enabled = true; + // public boolean enabled= true; public int order = 0; public HttpAuthenticator http_authenticator = new HttpAuthenticator(); public AuthcBackend authentication_backend = new AuthcBackend(); @@ -283,10 +304,10 @@ public AuthcDomain(ConfigV6.AuthcDomain v6) { super(); http_enabled = v6.http_enabled && v6.enabled; transport_enabled = v6.transport_enabled && v6.enabled; -// if(v6.enabled)vv { -// http_enabled = true; -// transport_enabled = true; -// } + // if(v6.enabled)vv { + // http_enabled = true; + // transport_enabled = true; + // } order = v6.order; http_authenticator = new HttpAuthenticator(v6.http_authenticator); authentication_backend = new AuthcBackend(v6.authentication_backend); @@ -295,12 +316,21 @@ public AuthcDomain(ConfigV6.AuthcDomain v6) { @Override public String toString() { - return "AuthcDomain [http_enabled=" + http_enabled + ", transport_enabled=" + transport_enabled + ", order=" + order - + ", http_authenticator=" + http_authenticator + ", authentication_backend=" + authentication_backend + ", description=" - + description + "]"; + return "AuthcDomain [http_enabled=" + + http_enabled + + ", transport_enabled=" + + transport_enabled + + ", order=" + + order + + ", http_authenticator=" + + http_authenticator + + ", authentication_backend=" + + authentication_backend + + ", description=" + + description + + "]"; } - } public static class HttpAuthenticator { @@ -313,14 +343,12 @@ public HttpAuthenticator() { super(); } - public HttpAuthenticator(ConfigV6.HttpAuthenticator v6) { this.challenge = v6.challenge; this.type = v6.type; this.config = v6.config; } - @JsonIgnore public String configAsJson() { try { @@ -330,34 +358,26 @@ public String configAsJson() { } } - @Override public String toString() { return "HttpAuthenticator [challenge=" + challenge + ", type=" + type + ", config=" + config + "]"; } - } public static class AuthzBackend { public String type = "noop"; public Map config = Collections.emptyMap(); - - public AuthzBackend() { super(); } - - public AuthzBackend(ConfigV6.AuthzBackend v6) { this.type = v6.type; this.config = v6.config; } - - @JsonIgnore public String configAsJson() { try { @@ -367,35 +387,26 @@ public String configAsJson() { } } - - @Override public String toString() { return "AuthzBackend [type=" + type + ", config=" + config + "]"; } - } public static class AuthcBackend { public String type = InternalAuthenticationBackend.class.getName(); public Map config = Collections.emptyMap(); - - public AuthcBackend() { super(); } - - public AuthcBackend(ConfigV6.AuthcBackend v6) { this.type = v6.type; this.config = v6.config; } - - @JsonIgnore public String configAsJson() { try { @@ -405,14 +416,11 @@ public String configAsJson() { } } - - @Override public String toString() { return "AuthcBackend [type=" + type + ", config=" + config + "]"; } - } public static class Authz { @@ -434,7 +442,6 @@ public String toString() { return "Authz [domains=" + domains + "]"; } - } public static class AuthzDomain { @@ -458,11 +465,17 @@ public AuthzDomain(ConfigV6.AuthzDomain v6) { @Override public String toString() { - return "AuthzDomain [http_enabled=" + http_enabled + ", transport_enabled=" + transport_enabled - + ", authorization_backend=" + authorization_backend + ", description=" + description + "]"; + return "AuthzDomain [http_enabled=" + + http_enabled + + ", transport_enabled=" + + transport_enabled + + ", authorization_backend=" + + authorization_backend + + ", description=" + + description + + "]"; } - } } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java index a7aed05cc1..8f10df04a4 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java @@ -41,30 +41,38 @@ public class InternalUserV7 implements Hideable, Hashed, StaticDefinable { - private String hash; - private boolean reserved; - private boolean hidden; - private boolean service; - private boolean enabled; - @JsonProperty(value = "static") - private boolean _static; - private List backend_roles = Collections.emptyList(); - private Map attributes = Collections.emptyMap(); - private String description; - private List opendistro_security_roles = Collections.emptyList(); - - private InternalUserV7(String hash, boolean reserved, boolean hidden, List backend_roles, Map attributes) { - super(); - this.hash = hash; - this.reserved = reserved; - this.hidden = hidden; - this.backend_roles = backend_roles; - this.attributes = attributes; - this.enabled = true; - this.service = false; - } - - private InternalUserV7(String hash, boolean reserved, boolean hidden, List backend_roles, Map attributes, Boolean enabled, Boolean service) { + private String hash; + private boolean reserved; + private boolean hidden; + private boolean service; + private boolean enabled; + @JsonProperty(value = "static") + private boolean _static; + private List backend_roles = Collections.emptyList(); + private Map attributes = Collections.emptyMap(); + private String description; + private List opendistro_security_roles = Collections.emptyList(); + + private InternalUserV7(String hash, boolean reserved, boolean hidden, List backend_roles, Map attributes) { + super(); + this.hash = hash; + this.reserved = reserved; + this.hidden = hidden; + this.backend_roles = backend_roles; + this.attributes = attributes; + this.enabled = true; + this.service = false; + } + + private InternalUserV7( + String hash, + boolean reserved, + boolean hidden, + List backend_roles, + Map attributes, + Boolean enabled, + Boolean service + ) { super(); this.hash = hash; this.reserved = reserved; @@ -75,111 +83,129 @@ private InternalUserV7(String hash, boolean reserved, boolean hidden, List